Wicket TextField which by default show

2019-07-20 03:10发布

问题:

We need to support high precision for certain numbers (interest rates) in an application, but we'd still like to show them rounded to 4 decimal places by default.

So, in a table, some cells contain rates in a TextField<BigDecimal> where user can enter a value with any precision, but which shows the value with 4 decimal points. This is done with a custom BigDecimal converter (as documented in this answer).

We also have a simple tooltip (title attribute) in place, showing the exact value (when I say "exact", there's of course some upper limit to the scale, currently 16):

These fields have "onblur" AjaxFormComponentUpdatingBehavior, which causes changed value to be immediately saved and the rest of the table updated.

Now, there are two problems:

  1. If user clicks the field, then clicks somewhere else, the rounded (4 decimal) value gets updated in domain object & persisted without the user even realising she changed anything. I.e., the exact value gets lost.
  2. Somewhat trickier: I'd like to make editing the exact value easy. I.e., when the textfield is clicked (focused), it should change to show the exact value so user can easily edit that (and not the rounded value).

As a hacky fix to #1 I changed the setter of the underlying DTO so that it doesn't actually change the value if the incoming new value is the same but with smaller scale. But I'm looking for something that solves #2 (which would automatically take care of #1 too).

final TextField<BigDecimal> field;
BigDecimal interestRate = ...; // current exact value from the model object

field.add(new AjaxFormComponentUpdatingBehavior("onfocus") {
    @Override
    protected void onUpdate(AjaxRequestTarget target) {
        // something I could do here? using e.g. 
        // field.setModelObject(), field.setConvertedInput() 
        // or field.getConverter(BigDecimal.class);
      }
 }

Having the field, the domain object & the exact BigDecimal value at hand as in above code (executed on every update of the table), what could I do to replace the value visible (editable) in UI?

Could I somehow swap the converter when the field is focused? There isn't a setter for converter, but perhaps create a custom TextField subclass that allows you to change the converter on the fly...?

As last resort we could change the converter (increase setMaximumFractionDigits) so that the exact values would always be displayed (but that isn't a solution to this specific problem, it's merely sidestepping it).

回答1:

Well, I got it working eventually. Not using pure Wicket, but with a small JavaScript hack.

As mentioned in the question, I already had the exact value in the title attribute of the field. So I can just copy the attribute value as the input field's value.

Java:

For these kinds of TextFields, add a Behavior which calls a JS function. Essential method here is getMarkupId() which returns the (arbitrary, dynamic) <input> element ID that I can use in JS.

private void addPreservePrecisionBehavior(final TextField<BigDecimal> field) {
    field.add(new AjaxFormComponentUpdatingBehavior("onclick") {
        @Override
        protected void onUpdate(AjaxRequestTarget target) {
            String js = String.format("copyExactValueFromTitle('%s')", field.getMarkupId());
            target.appendJavascript(js);
        }
    });
}

JavaScript (in a .js file which gets loaded on the relevant Wicket page):

/**
 * Takes the specified input field's title and sets it as the field's value.
 *
 * @param elementId ID of an input element
 */
function copyExactValueFromTitle(elementId) {
    if (!elementId) {
        return;
    }
    var input = $('#' + elementId);
    var exactValue = input.attr("title");
    if (exactValue) {
        input.val(exactValue);
    }
}

(Note that jQuery is used above; you could of course do this without it too.)

The only problem with this is, at least in our case, a slight delay (maybe 200-500 ms) before the onUpdate() method gets called, which makes editing the field a little less smooth. (If user changes the value quickly, that change gets overridden by the JS code.)

To minimise that annoyance, I only add the Behavior when it is really needed:

 if (interestRate != null && interestRate.stripTrailingZeros().scale() > 4) {
     addPreservePrecisionBehavior(field);
 }

Feel free to post better solutions!



回答2:

I know it's too late now but why not use two components?

Component one displays the rounded value and the tooltip with the exact value. When you click it, it hides itself and shows the other component, which is a standard text field with an onblur behaviour that hides component 2 and shows component 1 again.



标签: java wicket