I have created (please excuse the mess - just hacking it together at the moment!) the following binding handler in knockout
The desired functionality is that if a field is numeric then it will be formatted to 'X' decimal places (default 2) when displaying but the underlying value is whatever is entered.
All works fine, but I want to add the functionality that when an input is focused then it shows the actual value and I just have no idea how to do this.
i.e.
user enters 1.1121 into an input
when they leave the input it formats to 1.11
if they go back into the input (focus) then it displays 1.1121 for editing
It is point 3 that I have no idea how to achieve as at the moment it shows 1.11 and then over-writes on blur??
Can anyone point me in the right direction - basically where do I access the underlying value on focus and replace the displayed text with the underlying value??
for brevity I have removed some other 'decoration' code that wraps the inputs as they are not relelvant.
Thanks in advance.
ko.bindingHandlers.specialInput = {
init: function (element, valueAccessor, allBindingsAccessor) {
var value = valueAccessor();
var decimals = allBindingsAccessor().decimals || 2;
var formatString = "";
var interceptor = ko.computed({
read: function () {
if(isNumeric(ko.unwrap(value))){
//to do if time - replace this with a function that will accept any number of decimals
if(decimals == 0){
formatString = "0,0";
}
if(decimals == 1){
formatString = "0,0.0";
}
if(decimals == 2){
formatString = "0,0.00";
}
if(decimals == 3){
formatString = "0,0.000";
}
if(decimals == 4){
formatString = "0,0.0000";
}
if(decimals == 5){
formatString = "0,0.00000";
}
return numeral(ko.unwrap(value)).format(formatString);
}else{
return ko.unwrap(value);
}
},
write: function (newValue) {
if ($.trim(newValue) == '')
value("");
else
if(isNumeric(newValue)){
value(numeral().unformat(newValue));
value.valueHasMutated();
}else{
value(newValue);
value.valueHasMutated();
}
}
}).extend({notify: 'always'});
if (element.tagName.toLowerCase() == 'input') {
ko.applyBindingsToNode(element, {
value: interceptor
});
} else {
ko.applyBindingsToNode(element, {
text: interceptor
});
}
},
update: function (element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
var value = ko.unwrap(valueAccessor());
return ko.bindingHandlers.value.update(element, valueAccessor, allBindingsAccessor, viewModel, bindingContext);
}
};
function isNumeric(n) {
return !isNaN(parseFloat(n)) && isFinite(n);
}
I wouldn't implement this with an interceptor computed.
I'd rather do the following:
In the init register event handlers for focus and blur in the init:
on the focus handler, you have to show the underlying value in the input
on the blur handler, you have to store the number in the textbox, and show the rounded value in the input
In the update you have to store the real value, and show it in the textbox. Most probably the textbox won't have the focus when you update the value, so you should be safe showing the rounded value. However, if you think that in some cases it can be focused, you can do something like this to test it to decide how to show the value: Using jQuery to test if an input has focus
pseudocode
ko.bindingHandlers.yourBindingName = {
init: function(element, valueAccessor, allBindings, viewModel, bindingContext) {
var $element = $(element);
$element.on('focus', function() {
// Show raw value:
$element.val(/* raw value */);
});
$element.on('blur', function() {
// Update the observable with the value in the input
ko.unwrap(valueAccessor())( /* get raw value from input */);
// Show the rounded value
$element.val(/* rounded value */);
});
// Update will be called after init when the binding is first applied,
// so you don't have to worry about what it's initially shown
},
update: function(element, valueAccessor, allBindings, viewModel, bindingContext) {
// When the value changes, show it in the input
$.element.val(/* if focused show raw, if not, show roundede*/);
}
};
If you are sure that you are only going to use it with input and writable observables, this code is safe. If you have doubts about it, you should add many checks like cheking the kind of element to use jQuery .val() or .text(), check if the binding expression is a writeable observable to update its value, and so on.
NOTE: thereis something which is overseen many times: disposing of objects that we no longer need when the element is destroyed (that can happen for example with 'if', 'whit' or 'template'). In this case I think you don't need to do that, but, please, see this: Custom disposal logic if you think you need to destroy something.
There's nothing wrong with using jQuery in a binding handler, but you could also do this pretty simply with existing binding handlers. It's the sort of thing that could be rolled into a component to be re-used (although I didn't do that in my example).
You just need a backing store for your value, and the computed returns either the backing store or the formatted backing store, depending on whether the input has focus (using the hasFocus binding).
vm = (function() {
var editing = ko.observable(false),
backingStore = 0,
value = ko.computed({
read: function() {
return editing() ? backingStore : (+backingStore).toFixed(2);
},
write: function(newValue) {
backingStore = newValue;
}
});
return {
editing: editing,
value: value
}
}());
ko.applyBindings(vm);
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
<input data-bind="hasFocus: editing, value: value" />
<input />
Related
Let's say I have this:
ko.bindingHandlers.test= {
update: function (element, valueAccessor) {
alert("Test");
}
};
The alert fires every time an observable is changed, but also initally when the binding is first evaluated. How can I make the alert fire on every change except initially?
Here's one way: keep an array of elements that the update populates with its element if it's not there (which is the first time it runs) and otherwise does whatever action. Since you've got a custom binding handler, you can just hang the array off of that.
ko.bindingHandlers.test = {
update: function(element, valueAccessor) {
var seenElements = ko.bindingHandlers.test.seen,
val = valueAccessor()();
if (seenElements.indexOf(element) >= 0) {
alert("Test");
} else {
seenElements.push(element);
}
}
};
ko.bindingHandlers.test.seen = [];
var vm = {
alertOn: ko.observable(0),
raiseAlert: function() {
vm.alertOn.notifySubscribers();
}
};
ko.applyBindings(vm);
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
<div data-bind="test:alertOn"></div>
<button data-bind="click:raiseAlert">Update</button>
How can I make the alert fire on every change except initially?
Knockout will call the update callback initially when the binding is applied to an element and track any dependencies (observables/computeds) that you access. When any of these dependencies change, the update callback will be called once again.
I don't think it is possible to fire only by changes and not in initially.
You can create a workaround to fit with your scenario by adding change event tied up with your element inside init.
init: function(element, valueAccessor) {
// element should be the element you want to attach change event.
$(element).on('change', function (value)
{
// do your stuff.
});
}
I'm using knockout.js for binding values to view.
When modal is shown i initialize formatter. Here is sample:
<input type="text" id="propertyName" class="form-control" name="name" required="" data-bind="value: Name">
$("#exampleFormModal").on("shown.bs.modal", function () {
self.InitFormatter();
});
self.InitFormatter = function () {
$('#propertyName').formatter({
'pattern': '{{aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa}}',
'persistent': true
});
}
The problem is that there is empty values in value: Name
Using knockout with a library that does any kind of DOM manipulation - including value updates on elements - requires a custom binding handler, so that knockout can a) initialize that library properly and b) pass any updates between viewmodel and view.
Writing a custom binding handler for formatter.js is tricky, because formatter.js takes very tight control of all value-related events (keyboard, paste) that happen on an input element - without exposing any events of its own.
In other words, it's easy to set up, but it's hard to be notified when a value changes. But that is exactly what's necessary to keep the viewmodel up-to-date.
To be able to do it anyway, we must hook into one of the internal functions of formatter - the _processKey method. This method is called whenever the value of an input changes, so it's the perfect spot to set up a little "snitch" that tells knockout when the value changes.
Disclaimer This is a hack. It will break whenever the formatter.js internals change. With the current version 0.1.5 however, it seems to work rather well.
This way we can bind our view like this:
<input data-bind="formatter: {
value: someObservable,
pattern: '{{9999}}-{{9999}},
persistent: true
}">
and knockout can fill in the input value whenever someObservable changes, and thanks to the hook into _processKey it also can update someObservable whenever the input value changes.
The full implementation of the binding handler follows (it has no jQuery dependency):
// ko-formatter.js
/* global ko, Formatter */
ko.bindingHandlers.formatter = {
init: function (element, valueAccessor) {
var options = ko.unwrap(valueAccessor()) || {},
instance = new Formatter(element, ko.toJS(options)),
_processKey = Formatter.prototype._processKey,
valueSubs, patternSubs, patternsSubs;
if (ko.isWritableObservable(options.value)) {
// capture initial element value
options.value(element.value);
// shadow the internal _processKey method so we see value changes
instance._processKey = function () {
_processKey.apply(this, arguments);
options.value(element.value);
};
// catch the 'cut' event that formatter.js originally ignores
ko.utils.registerEventHandler(element, 'input', function () {
options.value(element.value);
});
// subscribe to options.value to achieve two-way binding
valueSubs = options.value.subscribe(function (newValue) {
// back out if observable and element values are equal
if (newValue === element.value) return;
// otherwise reset element and "type in" new observable value
element.value = '';
_processKey.call(instance, newValue, false, true);
// write formatted value back into observable
if (element.value !== newValue) options.value(element.value);
});
}
// support updating "pattern" option through knockout
if (ko.isObservable(options.pattern)) {
patternSubs = options.pattern.subscribe(function (newPattern) {
instance.resetPattern(newPattern);
});
}
// support updating "patterns" option through knockout
if (ko.isObservable(options.patterns)) {
patternsSubs = options.patterns.subscribe(function (newPatterns) {
instance.opts.patterns = newPatterns;
instance.resetPattern();
});
}
// clean up after ourselves
ko.utils.domNodeDisposal.addDisposeCallback(element, function() {
if (valueSubs) valueSubs.dispose();
if (patternSubs) patternSubs.dispose();
if (patternsSubs) patternsSubs.dispose();
});
}
// this binding has no "update" part, it's not necessary
};
This also supports making the pattern observable, so you can change the pattern for an input field dynamically.
Live demo (expand to run):
// ko-formatter.js
/* global ko, Formatter */
ko.bindingHandlers.formatter = {
init: function (element, valueAccessor) {
var options = ko.unwrap(valueAccessor()) || {},
instance = new Formatter(element, ko.toJS(options)),
_processKey = Formatter.prototype._processKey,
valueSubs, patternSubs, patternsSubs;
if (ko.isWritableObservable(options.value)) {
// capture initial element value
options.value(element.value);
// shadow the internal _processKey method so we see value changes
instance._processKey = function () {
_processKey.apply(this, arguments);
options.value(element.value);
};
// catch the 'cut' event that formatter.js originally ignores
ko.utils.registerEventHandler(element, 'input', function () {
options.value(element.value);
});
// subscribe to options.value to achieve two-way binding
valueSubs = options.value.subscribe(function (newValue) {
// back out if observable and element values are equal
if (newValue === element.value) return;
// otherwise reset element and "type" new observable value
element.value = '';
_processKey.call(instance, newValue, false, true);
// write formatted value back into observable
if (element.value !== newValue) options.value(element.value);
});
}
// support updating "pattern" option through knockout
if (ko.isObservable(options.pattern)) {
patternSubs = options.pattern.subscribe(function (newPattern) {
instance.resetPattern(newPattern);
});
}
// support updating "patterns" option through knockout
if (ko.isObservable(options.patterns)) {
patternsSubs = options.patterns.subscribe(function (newPatterns) {
instance.opts.patterns = newPatterns;
instance.resetPattern();
});
}
// clean up after ourselves
ko.utils.domNodeDisposal.addDisposeCallback(element, function() {
if (valueSubs) valueSubs.dispose();
if (patternSubs) patternSubs.dispose();
if (patternsSubs) patternsSubs.dispose();
});
}
// this binding has no "update" part, it's not necessary
};
// viewmodel implementation
ko.applyBindings({
inputPattern: ko.observable('{{9999}}-{{9999}}-{{9999}}-{{9999}}'),
inputValue: ko.observable(),
setValidValue: function () {
var dummy = this.inputPattern().replace(/\{\{([a9*]+)\}\}/g, function ($0, $1) {
return $1.replace(/\*/g, "x");
});
this.inputValue(dummy);
},
setInvalidValue: function () {
this.inputValue('invalid value');
}
});
input {
width: 20em;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.0/knockout-min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/formatter.js/0.1.5/formatter.min.js"></script>
View:<br>
<input data-bind="formatter: {
value: inputValue,
pattern: inputPattern,
persistent: true
}">
<input data-bind="value: inputPattern"><br>
<button data-bind="click: setValidValue">Set valid value</button>
<button data-bind="click: setInvalidValue">Set invalid value</button>
<hr>
Viewmodel:<br>
<pre data-bind="text: ko.toJSON($root, null ,2)"></pre>
I have two identical Binding Handlers and one of them is not firing, because it has no ko.unwrap(valueAccessor()); in the "update:" part of it.
Best way to see what is going on, is to look at my jsfiddle. By hitting the button the first span toggles the background, but the second not.
When you uncomment line 26 "ko.unwrap(valueAccessor());" the 2nd update function is called and the other span toggles gray/white.
HTML:
<button id='ChangeValue'>item++</button>
<hr/>
<span>Update fired viewModel.item = </span>
<span data-bind='text: $data.item(), bind1: $data.item'></span>
<br/>
<span>Update not fired viewModel.item = </span>
<span data-bind='text: $data.item(), bind2: $data.item'></span>
JS/jQuery/knockoutjs:
$(document).ready(function() {
$('#ChangeValue').click(function() {
viewModel.item(viewModel.item() + 1);
});
var viewModel = {
item: ko.observable(1)
};
ko.bindingHandlers.bind1 = {
init: function(element, valueAccessor) {
},
update: function(element, valueAccessor) {
ko.unwrap(valueAccessor());
if (element.style.background == "gray") {
element.style.background = "white";
} else {
element.style.background = "gray";
}
}
};
ko.bindingHandlers.bind2 = {
init: function(element, valueAccessor) {
},
update: function(element, valueAccessor) {
// if uncomment following line "update" is called
//ko.unwrap(valueAccessor());
if (element.style.background == "gray") {
element.style.background = "white";
} else {
element.style.background = "gray";
}
}
};
ko.applyBindings(viewModel);
});
Is this the expected behavior? I have a complex update functionality in my real code and was wondering why some of my binding handlers are not firing and after a long time of research I did extract this example.
I think it has something to do with the garbage collector in the browser (tested in chrome/IE 11) or knockout.
So my questions:
Is it enought to add "ko.unwrap(valueAccessor());" in my update part of the custom binding to be sure it will be processed?
Is there any documentation about that? Because I did not find something at knockout.js
Yes, this is the expected behavior. The update method is called whenever the binding is applied, and while executing, knockout registers/tracks the observables that are accessed in update method. So in your case, the bind2 binding is not accessing the valueAccessor value, therefore not triggering any update. So if you have
var value = valueAccessor();
var valueUnwrapped = ko.unwrap(value);
This enables knockout to trigger update on any change in valueAccessor() observable.
If you access the value of any observable in update function, the change in that observable will trigger the update function.
It is documented well here.
Using: http://knockoutjs.com/downloads/knockout-2.1.0.js
http://jsfiddle.net/fgav467w/1/
ko.bindingHandlers.bind1 = {
init: function(element, valueAccessor) {
},
update: function(element, valueAccessor) {
if (element.style.background == "gray") {
element.style.background = "white";
} else {
element.style.background = "gray";
}
}
};
The 3.0.0 releases removed a lot of extra "work" which Dandy describes. The 2.0.0 releases like the one I use in the fiddle did not. They will work as you intend but the overall performance will not be as good. But you have options at least.
I use select2 and knockoutJs with this simple binding:
ko.bindingHandlers.select2 = {
init: function (element, valueAccessor, allBindings, viewModel, bindingContext) {
var options = ko.toJS(valueAccessor()) || {};
setTimeout(function () {
$(element).select2(options);
}, 0);
}
};
Markup:
<select class="select2" style="width:100%" data-bind="optionsCaption: '',options: $root.items,optionsText: 'description',optionsValue: 'id', value: $root.selectedItem,select2: { placeholder: 'Select an item...',allowClear: true }"></select>
it works! Now I enabled the allowClear option in Select2 to clear dropdown to a placeholder value like Select an item....
If I click the x icon dropdown properly sets the placeholder but knockout don't update observable binded value!
I think I've to change custombinding adding something like this:
setTimeout(function () {
$(element).select2(options).on("select2-removed", function (e) {
ko.bindingHandlers.value.update(element, function () { return ''; });
});
...
but it won't work!
There are couple of issues.
1) the update in bindinghandler is to update DOM based on value change, you should never define an update callback with the ability to mutate your model.
The right way is, when define a new bindinghandler, use init callback to hook up all change events with the model, the update callback is merely a DOM drawing routine.
If your init provided DOM drawing (such as provided by select2), you don't need to define an update callback.
Hence the line ko.bindingHandlers.value.update(element, function () { return ''; }); only redraw the DOM, it doesn't do what you want.
2) the select2 binding you created has some holes.
first, value binding doesn't know the existence of select2 binding, that's where you struggled.
second, your select2 binding has to wait for other binding (the options binding) to finish DOM creation, what's why you use setTimeout. But ko provided a way to define sequence of the bindings, just look the source code of ko value binding, it's defined as 'after': ['options', 'foreach']
third, your select2 doesn't respond to outside change. For instance, if you have another UI to update $root.selectedItem (a normal select list), the change raised by that UI would not sync back to your select2.
The solution
Define select2 binding based on existing value binding (just found out it's not needed), and hook up all change events.
we don't need "select2-removed" event, it's all about "change" event.
select2 provided all drawing, we don't need update callback.
use a flag shouldIgnore to break the loop between value subscriber and select2 change event handler.
http://jsfiddle.net/huocp/8N3zX/6/
http://jsfiddle.net/huocp/8N3zX/9/
ko.bindingHandlers.valueSelect2 = {
'after': ['options'],
'init': function(element, valueAccessor, allBindings) {
// kind of extend value binding
// ko.bindingHandlers.value.init(element, valueAccessor, allBindings);
var options = allBindings.get('select2Options') || {};
$(element).select2(options);
var value = valueAccessor();
// init val
$(element).val(ko.unwrap(value)).trigger("change");
var changeListener;
if (ko.isObservable(value)) {
var shouldIgnore = false;
changeListener = value.subscribe(function(newVal) {
if (! shouldIgnore) {
shouldIgnore = true;
// this is just select2 syntax
$(element).val(newVal).trigger("change");
shouldIgnore = false;
}
});
// this demo only works on single select.
$(element).on("change", function(e) {
if (! shouldIgnore) {
shouldIgnore = true;
if (e.val == '') {
// select2 use empty string for unselected value
// it could cause problem when you really want '' as a valid option
value(undefined);
} else {
value(e.val);
}
shouldIgnore = false;
}
});
}
ko.utils.domNodeDisposal.addDisposeCallback(element, function() {
if (changeListener) changeListener.dispose();
$(element).select2("destory");
});
}
};
I've found a couple other questions and resources here on using Bootstrap tooltips with a custom knockout binding handler. However, I haven't found is a cohesive solution that 1) involves using a dynamic knockout template 2) one where the tooltip can change when data it is bound to changes.
I'm also away of knockout-bootstrap on GitHub, but the tooltip title in that is rendered only once,
I've created a NEW JSFiddle with the following new dynamicTooltip that's based on the prior JSFiddle.
The new DynamicTooltip data binder looks like:
ko.renderTemplateHtml = function (templateId, data) {
var node = $("<div />")[0];
ko.renderTemplate(templateId, data, {}, node);
return $(node).html();
};
ko.bindingHandlers.tooltip = {
init: function (element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
var local = ko.utils.unwrapObservable(valueAccessor()),
options = {};
ko.utils.extend(options, ko.bindingHandlers.tooltip.options);
ko.utils.extend(options, local);
var tmplId = options.kotemplate;
ko.utils.extend(options, {
title: ko.renderTemplateHtml(tmplId, viewModel)
});
$(element).tooltip(options);
ko.utils.domNodeDisposal.addDisposeCallback(element, function () {
$(element).tooltip("destroy");
});
},
update: function (element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
var local = ko.utils.unwrapObservable(valueAccessor()),
options = {};
ko.utils.extend(options, ko.bindingHandlers.tooltip.options);
ko.utils.extend(options, local);
var tmplId = options.kotemplate;
var forceRefresh = options.forceRefresh;
var newdata = ko.renderTemplateHtml(tmplId, viewModel);
$(element).data('bs.tooltip').options.title = newdata
},
options: {
placement: "top",
trigger: "hover",
html: true
}};
It's not complete, as I'm manually triggering the update call manually by passing in a dummy databinding property on the view Model, in this case, it's called renderTooltip():
<a data-bind="tooltip: { title: firstName, placement: 'bottom', kotemplate: 'tile-tooltip-template', forceRefresh: renderTooltip() }">Hover on me</a>
I'd like to be able to trigger the tooltip to refresh itself when data changes.
I'm thinking I should be using createChildContext() and maybe controlsDescendantBindings, but I'm not really sure.
Any thoughts? I'll continue to update this, because it seems like dynamic bootstrap tooltips would be a common idea.
The root of the problem is that the update binding isn't firing, because it doesn't have a dependency on the properties that you are trying to update (i.e. firstName and address);
Normally you can delegate these properties to a new binding and let knockout automatically handle the dependency tracking. However, in this case, you're actually returning a string, so the element's automatic binding can't be used. A string is necessary, because that's how the tooltip works. If it could dynamically read from a DOM element, then the automatic bindings would work, but because it requires a HTML string, there's no way for the bindings to affect that.
Couple of options that I see:
1. Automatically create a dependency on the properties used by the template. This can be done by isolating the template view model (data) as seen in this fiddle: http://jsfiddle.net/tMbs5/13/
//create a dependency for each observable property in the data object
for(var prop in templateData)
if( templateData.hasOwnProperty(prop) && ko.isObservable(templateData[prop]))
templateData[prop]();
2. Instead of using an DOM-based template, use ko.computed to generate the template inside your view model. This will automatically create the dependencies as needed.
See this fiddle for an example of that: http://jsfiddle.net/tMbs5/12/
var vm = {
firstName: ko.observable('Initial Name'),
address: ko.observable('55 Walnut Street, #3'),
changeTooltip: function () {
this.firstName('New Name');
}
};
vm.tooltipHtml = ko.computed(function () {
return "<h2>" + vm.firstName() + "</h2>" +
"<span>" + vm.address() + "</span>";
});
ko.applyBindings(vm);
note: In both fiddles, I have refactored things a tiny bit - mostly for simplification