You can create a custom binding that calls the value
binding internally, or you can overwrite the value
binding to auto-trim before it actually binds (not-recommended).
The basic idea:
- Intercept the
value
binding
- Wrap the passed observable in a
computed
- Make the binding
read
and write
from the computed instead of from the original observable
- When new input arrives, trim it before we write it
- When the model value changes, trim it and update both model & UI if needed
ko.bindingHandlers.trimmedValue = {
init: function(element, valueAccessor, allBindings) {
const ogValue = valueAccessor();
let newVa = valueAccessor;
// If this is a type="text" element and the data-bound value is observable,
// we create a new value accessor that returns an in-between layer to do
// our trimming
if (element.type === "text" && ko.isObservable(ogValue)) {
const trimmedValue = ko.observable().extend({"trim": true});
// Write to the model whenever we change
trimmedValue.subscribe(ogValue);
// Update when the model changes
ogValue.subscribe(trimmedValue);
// Initialize with model value
trimmedValue(ogValue());
// From now on, work with the trimmedValue
newVa = () => trimmedValue;
}
// Note: you can also use `ko.applyBindingsToNode`
return ko.bindingHandlers.value.init(element, newVa, allBindings)
}
}
// Our observable to check our results with
var myObs = ko.observable("test ");
myObs.subscribe(function(newValue) {
console.log("Change: \"" + newValue + "\"");
});
// The extender that does the actual trim
ko.extenders.trim = function(target, option) {
return ko.computed({
read: target,
write: function(val) {
target(
val && typeof val.trim === "function"
? val.trim()
: val
);
// This makes sure the trimming always resets the input UI
if (val !== target.peek()) {
target.valueHasMutated();
}
}
}).extend({notify: "always"});
};
ko.applyBindings({
myObs: myObs
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>
<h4><code>type="text" trimmedValue</code></h4>
<input type="text" data-bind="trimmedValue: myObs">
If you don't care about some unneeded valueHasMutated
s in your model
The tricky part is to determine what updates you want to receive in your model... The example below will not trigger valueHasMutated
nor mutate your model's observable. However, if you change your model value to an untrimmed string, the binding handler will reset it instantly. E.g.: myObs(" test ")
will trigger
Change: " test "
, and
Change: "test"
If you only need trimming from the UI to the model, and don't mind some extra updates, you can use:
ko.bindingHandlers.value.init = function(element, valueAccessor, allBindings) {
const ogValue = valueAccessor();
const newVa = (element.type === "text" && ko.isObservable(ogValue))
? () => ogValue.extend({"trim": true})
: valueAccessor;
return ogValueInit(element, newVa, allBindings)
};
Overwriting the default value
binding
To use this behaviour as standard behaviour (again, not recommended), you can do:
const ogValueInit = ko.bindingHandlers.value.init;
ko.bindingHandlers.value.init = function( /*... */ ) {
// ...
return ogValueInit( /* ... */);
};
const ogValueInit = ko.bindingHandlers.value.init;
ko.bindingHandlers.value.init = function(element, valueAccessor, allBindings) {
const ogValue = valueAccessor();
let newVa = valueAccessor;
// If this is a type="text" element and the data-bound value is observable,
// we create a new value accessor that returns an in-between layer to do
// our trimming
if (element.type === "text" && ko.isObservable(ogValue)) {
const trimmedValue = ko.observable().extend({"trim": true});
// Write to the model whenever we change
trimmedValue.subscribe(ogValue);
// Update when the model changes
ogValue.subscribe(trimmedValue);
// Initialize with model value
trimmedValue(ogValue());
// From now on, work with the trimmedValue
newVa = () => trimmedValue;
}
return ogValueInit(element, newVa, allBindings)
};
// Our observable to check our results with
var myObs = ko.observable("test ");
myObs.subscribe(function(newValue) {
console.log("Change: \"" + newValue + "\"");
});
// The extender that does the actual trim
ko.extenders.trim = function(target, option) {
return ko.computed({
read: target,
write: function(val) {
target(
val && typeof val.trim === "function"
? val.trim()
: val
);
// This makes sure the trimming always resets the input UI
if (val !== target.peek()) {
target.valueHasMutated();
}
}
}).extend({notify: "always"});
};
ko.applyBindings({
myObs: myObs
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>
<h4><code>type="text" value</code></h4>
<input type="text" data-bind="value: myObs">