formatter.js overrides knockout value - javascript

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>

Related

Knockout custom binding, Identify Update is called by changes and not in initially [duplicate]

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.
});
}

show underlying value of input on focus knockout custom binding

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 />

Select2 allowclear and knockout.js

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");
});
}
};

YUI modifying and detecting changes of a <div>

I want to see an alert message when the value of a div changes. This value is being modified by modify_div. When I click the button this function modifies the div, but the alert "value changed" is not displayed. Am I missing something?
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" " http://www.w3.org/TR/html4/strict.dtd">
<html>
<head>
<script src="http://yui.yahooapis.com/3.5.1/build/yui/yui-min.js"></script>
<script>
YUI().use('node', function (Y) {
var demo = Y.one('#test');
demo.on('click', function (e) {
//alert('You clicked me');
});
});
YUI().use('node','event', function (Y) {
var demo = Y.one('#variable-name');
demo.on('change', function (e) {
alert('Value changed');
});
});
</script>
<script type="text/javascript">
function modify_div()
{
//var thevar = "This is a test";
var thevar = 7;
document.getElementById('variable-name').innerHTML = thevar;
}
</script>
</head>
<body>
<!-- Click me button -->
<input type="button" id="test" value="Click me" enabled="true" onclick="modify_div();"> </input>
</br>
<div id="variable-name" style="display:inline;">01010101</div>
</body>
</html>
based on http://www.quirksmode.org/dom/events/change.html,
change event only fires if its form field
e.g. input textarea and select
so change event will not fire when contents of div is changed.
It will work if you replace div with input and update its value.
other option is to manually fire event where ever you are changing the value your variable
http://tech.groups.yahoo.com/group/ydn-javascript/message/13216
following SO question has answers but it requires jQuery
Detect element content changes with jQuery
The correct answer was given by #N30: there is no change event for divs. He provides good alternatives but no YUI specific information, so I'd like to extend his answer with an example of a YUI Plugin.
Like he explained, the basic idea is to keep a value in JavaScript memory and fire an event when you change that value. You can do this by extending Y.EventTarget which provides you with custom events:
YUI().use('node', 'plugin', function (Y) {
function NodeValuePlugin(config) {
// Boilerplate
NodeValuePlugin.superclass.constructor.apply(this);
// config.host is the Y.Node instance
this._node = config.host;
// we keep the value in a private property
this._value = this._node.get('text');
// we publish a 'change' event and set a default
// function to run when the event is fired
// This function will change the private property
// and update the DOM
// This means you can call e.preventDefault() and
// stop the default behavior (the change of value)
this.publish('change', {
emitFacade: true,
defaultFn: this._defValueChangeFn
});
}
Y.extend(NodeValuePlugin, Y.EventTarget, {
set: function (newVal) {
// we want to do stuff only when the value changes
if (newVal != this._value) {
// instead of changing the DOM here,
// we fire an event and let the event
// do the work
// We pass it the new and old values
this.fire('change', {
newVal: newVal,
prevVal: this._value
});
}
// make this method chainable
return this;
},
get: function () {
return this._value;
},
_defValueChangeFn: function (e) {
// sync everything
this._value = e.newVal;
this._node.set('text', e.newVal);
},
// this is necessary boilerplate for plugins
destroy: function () {}
}, {
// we can access the plugin from node[namespace]
// in this case, node.value
NS: 'value'
});
var node = Y.one('#test').plug(NodeValuePlugin);
node.value.on('change', function (e) {
console.log('Here\'s the old value: ' + e.prevVal);
console.log('Here\'s the new value: ' + e.newVal);
});
// Freebie:
// since we're using node.set('text', foo)
// this prevents XSS vulnerabilities
node.value.set('qwer');
});​
You can learn more about plugins from the Plugin User Guide in the YUI website.

Overwriting a Backbone Models Change Event

I think what I want to do is pretty simple I just don't know how to do it. I would like to fire my own event when one of my models attributes changes for the purpose of passing some data to the event handler (whether the change was an increase or decrease in value).
Basically I want my handler to do this in the view
handler: function(increased) {
if(increased) {
alert("the value increased")
}
else {
alert("the value decreased")
}
}
// ...
this.model.on("change:attr", this.handler, this);
Here you go: You basically listen for change:myvar. When a change occurs you use your model's previous() to get the old value. Depending on whether it increased or decreased you fire the appropriate event. You can listen to these events as shown in the initialize().
(function($){
window.MyModel = Backbone.Model.extend({
initialize: function () {
this.on('change:myvar', this.onMyVarChange);
this.on('increased:myvar', function () {
console.log('Increased');
});
this.on('decreased:myvar', function () {
console.log('Decreased');
});
},
onMyVarChange: function () {
if (this.get('myvar') > this.previous('myvar')) {
this.trigger('increased:myvar');
} else {
this.trigger('decreased:myvar');
}
}
});
window.mymodel = new MyModel({myvar: 1});
mymodel.set({myvar: 2});
mymodel.set({myvar: 3});
mymodel.set({myvar: 1});
})(jQuery);​
Running the above will print "Increased", "Increased", "Decreased" to your console.
Just look at previousAttributes()
You can then compare:
If(this.get(attr) > this.previousAttributes()[attr]){
console.log('bigger');
} else {
console.log('smaller');
}
If you use that in your change event handler you're all set. No need for a custom trigger or a ton of code.
EDIT
This is from my Backbone.Validators project and how I obtain the list of all attributes which have changed during the validation step:
var get_changed_attributes = function(previous, current){
var changedAttributes = [];
_(current).each(function(val, key){
if(!_(previous).has(key)){
changedAttributes.push(key);
} else if (!_.isEqual(val, previous[key])){
changedAttributes.push(key);
}
});
return changedAttributes;
};
This requires Underscore 1.3.1 because it's using _.has. If you can't upgrade that's an easy thing to replace though. In your case you'd passing this.previousAttributes() and this.attributes
What if you fire your own custom event after listening to the change event?
handler: function(increased) {
this.model.trigger('my-custom-event', stuff, you, want);
},
myHandler: function(stuff, you, want){
// Do it...
}
// ...
this.model.on("change:attr", this.handler, this);
this.model.on('my-custom-event, this.myHandler, this);

Categories