Invoke custom method on value changed - javascript

Is there a way to invoke one time some method on value changed? I created wrapper for bindingHandlers.value that invoke this method:
var update = bindingHandlers.value.update;
bindingHandlers.value.update = function(element, valueAccessor, allBindingAccessor, viewModel) {
var newValue = ko.utils.unwrapObservable(valueAccessor());
var elementValue = ko.selectExtensions.readValue(element);
var valueHasChanged = (newValue != elementValue);
update(element, valueAccessor, allBindingAccessor, viewModel);
if (valueHasChanged) {
myMethod();
}
}
Unfortunatelly when I change some value myMethod is called two times becuase come dependencyObservable is also changed. Any ideas?

If you just want to subscribe to a value changed, you can subscribe to any observable:
var viewModel = { property: ko.observable() };
viewModel.property.subscribe(function(newValue) {
//do stuff
});
To subscribe to all properties of an object you could do something like:
function subscribeAll(viewModel) {
for(var propertyName in viewModel) {
if(viewModel[propertyName].subscribe === 'function') {
viewModel[propertyName].subscribe(function(newValue) {
//do stuff
}
}
}
}

Related

Knockout min/max validation not working

I am using a custom binding handler that I found at https://www.moonlightbytes.com/blog/useful-knockout-js-binding-handlers
It works very well to format input as currency. However, it also stops my Knockout min/max validation from working. I need a min of 1 and max of 200. Does anyone why this is occuring?
Custom Binding
function formatCurrency(symbol, value, precision) {
return (value < 0 ? "-" : "") + symbol + Math.abs(value).toFixed(precision).replace(/(\d)(?=(\d{3})+\.)/g, "$1,");
}
function rawNumber(val) {
return Number(val.replace(/[^\d\.\-]/g, ""));
}
ko.bindingHandlers.currency = {
symbol: ko.observable("$"),
init: function (element, valueAccessor, allBindingsAccessor) {
//only inputs need this, text values don't write back
if ($(element).is("input") === true) {
var underlyingObservable = valueAccessor(),
interceptor = ko.computed({
read: underlyingObservable,
write: function (value) {
if (value === "") {
underlyingObservable(null);
} else {
underlyingObservable(rawNumber(value));
}
}
});
ko.bindingHandlers.value.init(element, function () {
return interceptor;
}, allBindingsAccessor);
}
},
update: function (element, valueAccessor, allBindingsAccessor) {
var symbol = ko.unwrap(allBindingsAccessor().symbol !== undefined ? allBindingsAccessor().symbol : ko.bindingHandlers.currency.symbol),
value = ko.unwrap(valueAccessor());
if ($(element).is("input") === true) {
//leave the boxes empty by default
value = value !== null && value !== undefined && value !== "" ? formatCurrency(symbol, parseFloat(value), 2) : "";
$(element).val(value);
} else {
//text based bindings its nice to see a 0 in place of nothing
value = value || 0;
$(element).text(formatCurrency(symbol, parseFloat(value), 2));
}
}
};
ViewModel observable
self.PriceAdvanced = ko.observable("").extend({ required: true, min: 1, max: 200 });
Html
<input class="form-control max225" type="text" id="PriceAdvanced" name="PriceAdvanced" data-bind="currency: PriceAdvanced" size="23" placeholder="$0.00" />
Found the answer here, and it worked perfectly:
First, Create the custom binding, such as...
ko.bindingHandlers.yourBindingName = {
init: function(element, valueAccessor, allBindings, viewModel, bindingContext) {
// This will be called when the binding is first applied to an element
// Set up any initial state, event handlers, etc. here
},
update: function(element, valueAccessor, allBindings, viewModel, bindingContext) {
// This will be called once when the binding is first applied to an element,
// and again whenever any observables/computeds that are accessed change
// Update the DOM element based on the supplied values here.
}
};
... then register the custom binding with knockout validation:
ko.validation.makeBindingHandlerValidatable('yourBindingName');

NotifySubscribers is not refreshing UI (Writable Computed)

I have a writable computed inside a custom binding to format an observable. However, when the users remove the formatted mask, the read function wont trigger, since the observable is removing all non digits, ok, its working as it should be, so i added the notifySubcribers to call read everytime, even if the observable value didnt change and its working, but the ui element isnt refreshing the new value (read return)
Follow the code:
ko.bindingHandlers.conta = {
init : function(element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
var observable = valueAccessor(), formatted = ko.computed({
read : function() {
var val = ko.unwrap(valueAccessor());
if (!val) return val;
if (val.toString().length < 6) {
val = zeroPad(val, 6);
observable(val);
}
return val.toString().slice(0, val.toString().length - 1) + "-" + val.toString().slice(val.toString().length - 1);
},
write : function(value) {
if (value) {
value = zeroPad(value, 6);
}
observable(value.replace(/\D/g, ''));
observable.notifySubscribers();
}
});
if ($(element).is('input')) {
ko.applyBindingsToNode(element, {
numbersOnly : true,
maxLength : 10,
value : formatted
});
} else {
ko.applyBindingsToNode(element, {
numbersOnly : true,
text : formatted
});
}
return {
controlsDescendantBindings : true
};
}
};
Can someone give me some directions here?
Thanks :D
Extend the formatted computed obs with notify: always:
ko.bindingHandlers.conta = {
init : function(element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
var observable = valueAccessor(), formatted = ko.computed({
...
}).extend({ notify: 'always' });
...
This will force it to notify always even if there is no changes.

Knockout custom binding doesn't update the computed function

I have a custom binding for an html editable field..
I changed it to use another custom binding now (HtmlValue), because EditableText had an error when updating the values (both custom bindings are included in the jsfiddle).
Anyone knows how to fix this?
This is the code that doesn't update the value:
ko.bindingHandlers.htmlValue = {
init: function (element, valueAccessor, allBindingsAccessor) {
ko.utils.registerEventHandler(element, "keyup", function () {
var modelValue = valueAccessor();
var elementValue = element.innerHTML;
if (ko.isWriteableObservable(modelValue)) {
modelValue(elementValue);
}
else { //handle non-observable one-way binding
var allBindings = allBindingsAccessor();
if (allBindings['_ko_property_writers'] && allBindings['_ko_property_writers'].htmlValue) allBindings['_ko_property_writers'].htmlValue(elementValue);
}
}
)
},
update: function (element, valueAccessor) {
var value = ko.utils.unwrapObservable(valueAccessor()) || "";
if (element.innerHTML !== value) {
element.innerHTML = value;
}
}
};
You can try it out here: http://jsfiddle.net/DMf8r/
There is a bunch of problems with the way the view model is constructed and with the bindings themselves...
The tax_total computed should be declared after lines because it accesses lines and Knockout executes tax_total as soon as the computed is created.
this needs to be passed into the computed so that this inside the computed is the view model
elem needs to be defined in the $.each() call
To loop the underlying array in $.each(), you need to use this.lines() instead of this.lines
The values inside lines need to be observables, otherwise the computed would not be notified of changes.
The span is using a value binding, it should be text.
There might have been more problems but it's hard to keep track of what all the changes were...
this.lines = ko.observableArray([
{ unit_price: ko.observable(5.0), tax_rate: ko.observable(21.00) },
{ unit_price: ko.observable(5.0), tax_rate: ko.observable(21.00) }]);
this.add_line = function () {
this.lines.push({ unit_price: ko.observable(5.0), tax_rate: ko.observable(21.00) });
}.bind(this);
this.tax_total = ko.computed(function () {
var total = 0; //this.subtotal()
$.each(this.lines(), function (index, elem) {
total += (elem.unit_price() * (elem.tax_rate() / 100));
});
return total;
}, this);
<span data-bind="text: tax_total">1.02</span>
Fiddle: http://jsfiddle.net/DMf8r/1/

Knockout custom click binding, return true to prevent click hijack?

So we all know return true manually will allow default click action on element with a click binding, but what if I have custom binding as following:
ko.bindingHandlers.action = {
init: function(element, valueAccessor, allBindingsAccessor, context) {
var options = valueAccessor();
var params = options.slice(1);
//wrap it in function, with parameter binding
var newValueAccessor = function() {
return function() {
options[0].apply(context, params);
};
};
ko.bindingHandlers.click.init(element, newValueAccessor, allBindingsAccessor, context);
}
};
which takes N arguments from a binding:
action: [handle, 'open', $index()]
how does one allow click to go through? return true in handle does not work in this case.
Your actual click handler is defined here:
return function() {
options[0].apply(context, params);
};
Just change it so it returns the value of the provided sub-handler:
return function() {
return options[0].apply(context, params);
};

How to define a custom binding who use previous value to determine class in Knockout?

I need to bind a table with knockout, and I would like the table cell to get a different css class if the new value is higher or lower of the previous.
I have in mind different possibilities, such as storing the previous value in the bindingContext and have a function which returns the right class, but is it possible to add a custom binding handler which receives the previous value and the new value?
Although Jeff's and SÅ‚awomir's answers would work, I found an alternative that doesn't need any change to the view model nor relies on altering the DOM element object.
function subscribeToPreviousValue(observable, fn) {
observable.subscribe(fn, this, 'beforeChange');
}
ko.bindingHandlers['bindingWithPrevValue'] = {
init: function (element, valueAccessor) {
var observable = valueAccessor();
var current = observable();
console.log('initial value is', current);
subscribeToPreviousValue(observable, function (previous) {
console.log('value changed from', previous, 'to', current);
});
}
};
Naturally, that will only work if the bound property is an observable.
I looked into knockout source and I suppose that you can't access previous value inside update method of bindingHandler but you can store it inside element
ko.bindingHandlers['bindingWithPrevValue'] = {
update: function (element, valueAccessor) {
var prevValue = $(element).data('prevValue');
var currentValue = valueAccessor();
$(element).data('prevValue', currentValue());
// compare prevValue with currentValue and do what you want
}
};
What you could do is create an extender to extend the observables that you wish to track the previous values of. You could then inspect the previous value to do as you wish.
Just pass in the name of the property that will hold the previous value.
ko.extenders.previousValue = function (target, propertyName) {
var previousValue = ko.observable(null);
target[propertyName] = ko.computed(previousValue);
target.subscribe(function (oldValue) {
previousValue(oldValue);
}, target, 'beforeChange');
return target;
};
Then to use it:
function ViewModel() {
this.value = ko.observable('foo').extend({ previousValue: 'previousValue' });
}
var vm = new ViewModel();
console.log(vm.value()); // 'foo'
console.log(vm.value.previousValue()); // null
vm.value('bar');
console.log(vm.value()); // 'bar'
console.log(vm.value.previousValue()); // 'foo'
In your case, you could probably use something like this:
function TableCell(value) {
this.value = ko.observable(value).extend({ previousValue: 'previousValue' });
this.cssClass = ko.computed(function () {
// I'm assuming numbers
var current = Number(this.value()),
previous = Number(this.value.previousValue());
if (current < previous)
return 'lower';
else if (current > previous)
return 'higher';
}, this);
}

Categories

Resources