binding
binding copied to clipboard
Input bindings don't update when changes are reverted within a setter or changeHandler
I think these examples will show the issue clearly enough to void more explanation. The interpolated text shows the real value but the inputs don't revert to the underlying value.
getter/setter example: https://gist.run/?id=8c56fe352fd849462d39311ede2c8b5d computed getter/setter example: https://gist.run/?id=14ce505a70736ca3c76943ac762fe836 changeHandler example: https://gist.run/?id=45e93cac4e74d8ffb702325d56a7192a
I believe the same can be said for textboxes too. I have alarm limits whose ranges are validated (which is related to this). If the limit is at 100, it cannot exceed 100 and 101 is entered it is reverted to a value of 100 but the input itself doesn't reflect this change.
Any ideas? Is this something that can be fixed in the binding system?
I implemented a work around by using a binding signaler on the input - I don't consider this an acceptable 'fix' however but I'm posting in case someone comes across the same issue.
Below is code for the numeric limit input. I only signal when the value has been clamped back to the same old value (clamped && limit === oldLimit
).
<input value.bind="limitConfig.limit & signal:limitConfig.limitSignal & updateTrigger:'blur'">
limitChanged(limit, oldLimit) {
if (typeof limit !== 'number') {
limit = Number(limit);
this.limit = limit;
}
let clamped = false;
if (this.enabled) {
if (this.lowerLimitConfig !== null && this.lowerLimitConfig.upperBound > limit) {
limit = this.lowerLimitConfig.upperBound;
clamped = true;
}
if (this.upperLimitConfig !== null && this.upperLimitConfig.lowerBound < limit) {
limit = this.upperLimitConfig.lowerBound;
clamped = true;
}
}
if (clamped) {
this.limit = limit;
}
if (clamped && limit === oldLimit) {
this.bindingSignaler.signal(this.limitSignal);
}
}
A beforeUpdate(newValue,oldValue, cancel) would be nice here I've opened an issue some while ago with a very similar request: https://github.com/aurelia/templating-resources/issues/137 The answer is once more: a custom bindingbehaviour :)
It turns out that with Aurelia you can easely generate demo apps and prototypes. But when it comes down to some very usual use cases like converting values (http://stackoverflow.com/questions/37882659/force-view-to-be-updated-from-within-a-valueconverter), or rejecting changes like mentioned here we often need to take a deep plunge and use much more advanced concepts, possibly scaring away people from using this nice framework after they have mastered the demo apps and tutorial phase.
So what can we imo do. Either those missing pieces in the default concepts in valueConverter and propertyChanged gets added or a set of default bindingbehaviours should be added to the product.
Thanks for the link to that issue @fopsdev - those advanced techniques are quite well... advanced! I agree this should be a little easier to accomplish as a standard operation that aurelia directly supports.
I think to fix this issue in particular it needn't be something the developer has to handle themselves. I expect the input (value) binding should recheck the value after setting it and then reset the input/checked value appropriately.
I'm with @AdamWillden on this. I was quite surprised to find that value converters don't support this behavior out of the box. It's a common use case to do some normalisation like trim
on an input, and I would like to be able to simply specify this as:
<input value.bind="myProp | trim & validate" />
If I use something like the above now, the normalised value is passed to the model and used for validation - which is exactly what we want. However, some changes applied by trim
are reflected back to the screen and others aren't, which results in a very confusing user experience.
For example: if I type some spaces in the input with the trim
converter, the first space is ignored (if the model value started with undefined) and the next ones are entered. If I type a character after that, all the preceding spaces suddenly disappear. And if I type any spaces afterwards, they aren't removed even though the value that will eventually be used won't include these spaces.
Right now if I want to normalise and keep my screen and model value in sync, my options are to
-
create a binding behavior to wrap the binding's
updateSource
method and if necessary callupdateTarget
with the normalised value - build a custom attribute or element to do the normalisation and keep the view value in sync
- add a
blur
/input
trigger and do the normalisation and validation myself
These solutions are all doable, but IMO overkill for what should be a simple and common use case.
For now I've built this TypeScript base class for binding behaviors that do normalisation. Maybe it will be of use to others.
export abstract class NormalisationBindingBehavior {
protected abstract normalise(value: any): any;
private get scopeName() {
return this.constructor.name;
}
bind(binding: any) {
const normalise = this.normalise.bind(this);
const updateSource = binding.updateSource.bind(binding);
binding[`${this.scopeName}UpdateSourceOriginal`] = binding.updateSource;
binding.updateSource = function(value: any) {
const normal = normalise(value);
updateSource(normal);
if (normal !== value) {
this.updateTarget(normal);
}
};
}
unbind(binding: any) {
binding.updateSource = binding[`${this.scopeName}UpdateSourceOriginal`];
delete binding[`${this.scopeName}UpdateSourceOriginal`];
}
}
Example use:
import {NormalisationBindingBehavior} from "./normalisation-binding-behavior";
export class TrimBindingBehavior extends NormalisationBindingBehavior {
protected normalise(value: string) {
return value.trim();
}
}
<template>
<require from="bindingbehaviors/trim"></require>
<input value.bind="myVal & updateTrigger:'blur' & trim"/>
</template>
(Note that I add updateTrigger:'blur'
so that the user can still add spaces between words - if the input is trimmed after every change it wouldn't be possible to type spaces at all)
EDIT: this doesn't play nicely with the validate
binding behavior: see https://github.com/aurelia/validation/issues/423. I've created a hacky workaround for this case.