Knockoutjs valueHasMutated not working correctly - javascript

Hopefully this will be a quick one for a knockout guru....
I'm writing a couple of custom bindings to help me translate a UI using a custom translation engine in the project I'm working on.
One is to translate text, the other is to translate the 'placeholder' attribute on HTML5 input elements.
Both bindings are identical apart from the last statement, where one updates the text in the element and the other updates an attribute value.
The text one works perfectly, but the place holder one does not, and I'm stuck for an answer as to why.
The binding code is as follows:
Translated Text Binding
ko.bindingHandlers.translatedText = {
init: (element: HTMLElement, valueAccessor: Function, allBindings: KnockoutAllBindingsAccessor, viewModel: any, bindingContext: KnockoutBindingContext) => {
// Get our custom binding values
var value = valueAccessor();
var associatedObservable = value.observable;
var translationToken = value.translationToken;
// Set up an event handler that will respond to events telling it when our translations have finished loading
// the custom binding will instantly update when a key matching it's translation ID is loaded into the
// local session store
window.addEventListener("TranslationsLoaded", (e) => {
//associatedObservable(" "); // Force an update on our observable, so that the update routine below is triggered
associatedObservable.valueHasMutated();
}, false);
},
update: (element: HTMLElement, valueAccessor: Function, allBindings: KnockoutAllBindingsAccessor, viewModel: any, bindingContext: KnockoutBindingContext) => {
// Get our custom binding values
var value = valueAccessor();
var associatedObservable = value.observable;
var translationToken = value.translationToken;
// Ask local storage if we have a token by that name
var translatedText = utilityLib.getTranslatedString(translationToken);
// Check if our translated text is defined, if it's not then substitute it for a fixed string that will
// be seen in the UI (Whatever you put into the 'associatedObservable' at this point WILL appear in the element
if (undefined === translatedText || translatedText === "" || translatedText === null) {
if (sessionStorage["translations"] === undefined) {
// No translations have loaded yet, so we blank the text
translatedText = "";
} else {
// Translations have loaded, and the token is still not found
translatedText = "No Translation ID";
}
}
associatedObservable(translatedText);
ko.utils.setTextContent(element, associatedObservable());
}
} // End of translatedText binding
Translated Placeholder Binding
ko.bindingHandlers.translatedPlaceholder = {
// This one works pretty much the same way as the translated text binding, except for the final part where
// the translated text is inserted into the element.
init: (element: HTMLElement, valueAccessor: Function, allBindings: KnockoutAllBindingsAccessor, viewModel: any, bindingContext: KnockoutBindingContext) => {
var value = valueAccessor();
var associatedObservable = value.observable;
var translationToken = value.translationToken;
window.addEventListener("TranslationsLoaded", (e) => {
debugger;
associatedObservable.valueHasMutated();
}, false);
},
update: (element: HTMLElement, valueAccessor: Function, allBindings: KnockoutAllBindingsAccessor, viewModel: any, bindingContext: KnockoutBindingContext) => {
var value = valueAccessor();
var associatedObservable = value.observable;
var translationToken = value.translationToken;
var translatedText = utilityLib.getTranslatedString(translationToken);
debugger;
if (undefined === translatedText || translatedText === "" || translatedText === null) {
if (sessionStorage["translations"] === undefined) {
translatedText = "";
} else {
translatedText = "No Translation ID";
}
}
associatedObservable(translatedText);
element.setAttribute("placeholder", translatedText);
}
} // End of translatedPlaceholder binding
The idea is a simple one, if the binding runs and the translations are already present in sessionStorage, then we pick up the translated string and plug it in to the observable associated with the element.
If the translations have loaded, but the translation is not found "No Translation ID" is plugged into the observable bound to the element.
If the translations have NOT yet loaded, plug an empty string into the observable, then wait for the event 'TranslationsLoaded' to fire. When this event is raised, the observable bound to the element is mutated, causing an update to happen, which in turn re-checks the translations, which it then finds have loaded and so acts accordingly.
However.....
It doesn't matter how hard I try, the translated placeholder binding just will not fire it's update.
I can clearly see in the debugger that the event is recieved on both bindings, and the mutate function IS called.
On the translated text binding, I get the following sequence...
'init' -> 'update' -> 'event' -> 'mutate' -> 'update'
Which is exactly what I expect, and it occurs on every element+observable bound to that binding.
On the translated placeholder i get
'init' -> 'update' -> 'event' -> 'mutate'
but the final update never occurs.
As a result, the translated string for the placeholder is never looked up correctly, the text one with identical code works perfectly!!
For those who'll ask, i'm using the bindings like this:
<input type="text" class="form-control" data-bind="value: userName, translatedPlaceholder: { observable: namePlaceHolderText, translationToken: 'loginBoxNamePlaceholderText'}">
<span class="help-block" data-bind="translatedText: {observable: nameErrorText, translationToken: 'loginBoxUserNameEmptyValidationText'}"></span>
and inside the view model, the 'observable' parameters are just normal ko.observable variables holding strings.
Cheers
Shawty

I believe you have event bubbling issues... try putting a 'return true' after your call to valueHasMutated like this:
init: (element: HTMLElement, valueAccessor: Function, allBindings: KnockoutAllBindingsAccessor, viewModel: any, bindingContext: KnockoutBindingContext) => {
var value = valueAccessor();
var associatedObservable = value.observable;
var translationToken = value.translationToken;
window.addEventListener("TranslationsLoaded", (e) => {
associatedObservable.valueHasMutated();
return true; // allow event to bubble
}, false);
},
That being said, I am thinking all this manual eventing is against-the-grain for what knockout can do for you. You should be observing your data, binding to it, and letting knockout do all the internal eventing for you... that's what it does.
An example building on user3297291's fiddle:
ko.bindingHandlers.translatedPlaceholder = {
init: function(element, valueAccessor) {
var va = valueAccessor();
var obs = ko.utils.unwrapObservable(va.obs);
var placeholderStr = obs[va.key];
console.log(placeholderStr);
element.setAttribute("placeholder", placeholderStr);
},
update: function(element, valueAccessor) {
var va = valueAccessor();
var obs = ko.utils.unwrapObservable(va.obs);
var placeholderStr = obs[va.key];
console.log(placeholderStr);
element.setAttribute("placeholder", placeholderStr);
}
};
var vm = function() {
var self = this;
self.dictionary = ko.observable({
"placeholder": "Initial State"
});
self.switchTranslations = function() {
// Set the 'new' dictionary data:
self.dictionary({
"placeholder": "My Translated Placeholder"
});
};
}
ko.applyBindings(new vm());
Fiddle: https://jsfiddle.net/brettwgreen/5pmmd0va/

Trigger the update handler
From the Knockout documentation:
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.
The key is that you have to access the observable within the update function to get updates. In your translatedText binding you do so:
ko.utils.setTextContent(element, associatedObservable());
But there is no such access of associatedObservable in the update function of translatedPlaceholder. You'll need to add it like this:
associatedObservable(translatedText);
associatedObservable(); // get notified of updates to associatedObservable
element.setAttribute("placeholder", translatedText);
A better approach
For your case, there really isn't a need for an update handler because you don't need to update the view based on changes to the viewmodel. Instead your updates just come from events, which can be set up in the init handler.
ko.bindingHandlers.translatedPlaceholder = {
init: function (element, valueAccessor, allBindings, viewModel, bindingContext) {
function loadTranslation() {
var translationToken = valueAccessor(),
translatedText = utilityLib.getTranslatedString(translationToken);
element.setAttribute("placeholder", translatedText || "No Translation ID");
window.removeEventListener("TranslationsLoaded", loadTranslation);
}
if (sessionStorage["translations"] === undefined)
window.addEventListener("TranslationsLoaded", loadTranslation, false);
} else {
loadTranslation();
}
}
}
Usage:
data-bind="value: userName, translatedPlaceholder: 'loginBoxNamePlaceholderText'"

Related

Knockout bindingHandler for comma separated numbers

I'm building a very number-heavy app in KnockoutJS and I want to have the ability to format large numbers so that they're comma-seperated and nice on the eye (xxx,xxx).
As you'll see from the fiddle below, I do have this working by wrapping the binded value inside of a formatting function with a simple RegEx but the problem with this is that this overwrites the value inside the input and inserts ',' into the underlying value.
The large numbers are used further down the app and so to prevent NaN errors I've had to assign a data attribute to the input value containing the value with no ',' and this is the value that gets stored in sessionStorage.
I feel that I have unncessarily bloated my HTML markup and believe that what I want to achieve is possible with a bindingHandler but my binding handler isn't quite there.
Fiddle: http://jsfiddle.net/36sD9/2
formatLargeNumber = function (number) {
if (typeof (number) === 'function') {
return number().toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',');
}
}
ko.bindingHandlers.largeNumber = {
init: function(element, valueAccessor) {
var value = ko.unwrap(valueAccessor());
var interceptor = ko.computed({
read: function() {
return formatLargeNumber(value);
},
write: function(newValue) {
value(reverseFormat(newValue));
}
});
if(element.tagName == 'input' )
ko.applyBindingsToNode(element, {
value: interceptor
});
else
ko.applyBindingsToNode(element, {
text: interceptor
});
}
}
Any ideas?
You have multiple problems with your current approach:
element.tagName returns INPUT, etc so you need to take care of the casing when doing the comparing.
var value = ko.unwrap(valueAccessor()); you are unwrapping your observable so in your computed you are using its value and not the function itself. So you just need var value = valueAccessor(); and you need to call ko.unwrap in your computed read method.
You don't just need to format but you need to "unformat" in the write method, but your formatLargeNumber only do the format direction.
You have applied value and your largeNumber on the same input which make the two bindings interfering with each other
Don't write the formatting code yourself just use a library which already does this like: http://numeraljs.com/
So here is the corrected version of your binding using numeraljs:
ko.bindingHandlers.largeNumber = {
init: function(element, valueAccessor) {
var value = valueAccessor();
var interceptor = ko.computed({
read: function() {
return numeral(ko.unwrap(value)).format('0,0');
},
write: function(newValue) {
value(numeral().unformat(newValue));
value.valueHasMutated();
}
}).extend({notify: 'always'});
if(element.tagName.toLowerCase() == 'input' )
ko.applyBindingsToNode(element, {
value: interceptor
});
else
ko.applyBindingsToNode(element, {
text: interceptor
});
}
}
And use it like this:
<input data-bind="largeNumber: testVal">
Demo JSFiddle.

In Javascript, is there a way to evaluate a variable that may or may not be a function

I have a complex javascript variable that has numerous nested variables inside of it, which each can contain their own nested variables... When I do my knockout mapping sometimes the nested variables are set as objects and other times those exact same variables are functions. So when my data bindings to those nested variables is expecting an object the next time around when the variable is now a function the binding does not update.
The below fiddle gives an idea of what I am seeing:
http://jsfiddle.net/Eves/AUcGM/2/
html:
<p> <span>Name:</span>
<span data-bind="text: TempData().objectA.Name"></span>
<button id="update" data-bind="click: Update">Update!</button>
</p>
Javascript:
var ViewModel = function (data) {
var me = this;
me.TempData = ko.observable(data);
me.Update = function () {
ko.mapping.fromJS(temp2, {}, me);
};
return me;
};
var temp1 = {
objectA: {
Name: 'temp1.objectA.Name'
}
};
var temp2 = {
objectA: function () {
this.Name = 'temp2.objectA.Name';
return this;
}
};
window.viewModel = ko.mapping.fromJS(new ViewModel(temp1));
ko.applyBindings(window.viewModel);
Initially the span text shows "temp1.objectA.Name". However, if you click the update button and the binding switches from the temp1 object to the temp2 object because "objectA" is now a function the data binding never updates. If I were to change the span's data binding to be "TempData().objectA().Name" then temp2 will work fine...but then that breaks temp1.
So the question is:
Is there a way to always evaluate a variable as either an object or function?
I suspect I could use ko.computed to always get the appropriate value regardless of function or object. But that would get really messy considering the complexity of the object I am really dealing with.
I didn't read your entire question but there is a quick answer -
var returnedValue = ko.unwrap(possibleFunction);
(In older versions of ko this is ko.utils.unwrapObservable)
A good way that you could use this would be in a custom binding handler to unwrap a value, regardless of whether it is a function or not.
ko.bindingHandlers.returnAction = {
init: function (element, valueAccessor, allBindingsAccessor, viewModel) {
var value = ko.unwrap(valueAccessor());
// now value is a value no matter what the element's value was set as
$(element).keydown(function (e) {
if (e.which === 13) {
value(viewModel);
}
});
}
};
Edit
Here is a working fiddle example of passing in various objects - http://jsfiddle.net/5udhU/3/
That demonstrates passing a string, an observable, a computed, and a function that all evaluate properly.

Knockout.js Extending value binding with interceptor

This seems to be a common approach to sanitizing/validating/formatting data with knockout when binding to an input field, it creates a reusable custom binding that uses a computed observable. It basically extends the default value binding to include an interceptor that will format/sanitize/validate input before written/read.
ko.bindingHandlers.amountValue = {
init: function (element, valueAccessor, allBindingsAccessor) {
var underlyingObservable = valueAccessor();
var interceptor = ko.computed({
read: function () {
// this function does get called, but it's return value is not used as the value of the textbox.
// the raw value from the underlyingObservable is still used, no dollar sign added. It seems like
// this read function is completely useless, and isn't used at all
return "$" + underlyingObservable();
},
write: function (newValue) {
var current = underlyingObservable(),
valueToWrite = Math.round(parseFloat(newValue.replace("$", "")) * 100) / 100;
if (valueToWrite !== current) {
// for some reason, if a user enters 20.00000 for example, the value written to the observable
// is 20, but the original value they entered (20.00000) is still shown in the text box.
underlyingObservable(valueToWrite);
} else {
if (newValue !== current.toString())
underlyingObservable.valueHasMutated();
}
}
});
ko.bindingHandlers.value.init(element, function () { return interceptor }, allBindingsAccessor);
},
update: ko.bindingHandlers.value.update
};
jsFiddle example: http://jsfiddle.net/6wxb5/1/
Am i missing something? I've seen this method used everywhere, but it doesn't seem to fully work. The read function seems completely useless as it doesn't get used at all.., and in the write function, entering "23.0000" changes the written value to 23, but the textbox values do not update.
The issue comes from the update portion of your custom binding. This part will update the field based on the original model value. So, the event handler attached in the init will send the new value through your writeable computed, but the updating of the field actually happens in the update.
One option is to apply the value binding from your init function and skip the update function like:
ko.bindingHandlers.amountValue = {
init: function (element, valueAccessor, allBindingsAccessor) {
var underlyingObservable = valueAccessor();
var interceptor = ko.computed({
read: function () {
// this function does get called, but it's return value is not used as the value of the textbox.
// the raw value from the underlyingObservable, or the actual value the user entered is used instead, no
// dollar sign added. It seems like this read function is completely useless, and isn't used at all
return "$" + underlyingObservable();
},
write: function (newValue) {
var current = underlyingObservable(),
valueToWrite = Math.round(parseFloat(newValue.replace("$", "")) * 100) / 100;
if (valueToWrite !== current) {
// for some reason, if a user enters 20.00000 for example, the value written to the observable
// is 20, but the original value they entered (20.00000) is still shown in the text box.
underlyingObservable(valueToWrite);
} else {
if (newValue !== current.toString())
underlyingObservable.valueHasMutated();
}
}
});
ko.applyBindingsToNode(element, { value: interceptor });
}
};
Updated fiddle: http://jsfiddle.net/rniemeyer/Sr8Ev/

Detecting change to Knockout view model

Sure this is a very easy question to answer but is there an easy way to determine if any property of a knockout view model has changed?
Use extenders:
ko.extenders.trackChange = function (target, track) {
if (track) {
target.isDirty = ko.observable(false);
target.originalValue = target();
target.setOriginalValue = function(startingValue) {
target.originalValue = startingValue;
};
target.subscribe(function (newValue) {
// use != not !== so numbers will equate naturally
target.isDirty(newValue != target.originalValue);
});
}
return target;
};
Then:
self.MyProperty= ko.observable("Property Value").extend({ trackChange: true });
Now you can inspect like this:
self.MyProperty.isDirty()
You can also write some generic viewModel traversing to see if anything's changed:
self.isDirty = ko.computed(function () {
for (key in self) {
if (self.hasOwnProperty(key) && ko.isObservable(self[key]) && typeof self[key].isDirty === 'function' && self[key].isDirty()) {
return true;
}
}
});
... and then just check at the viewModel level
self.isDirty()
You can subscribe to the properties that you want to monitor:
myViewModel.personName.subscribe(function(newValue) {
alert("The person's new name is " + newValue);
});
This will alert when personName changes.
Ok, so you want to know when anything changes in your model...
var viewModel = … // define your viewModel
var changeLog = new Array();
function catchChanges(property, value){
changeLog.push({property: property, value: value});
viewModel.isDirty = true;
}
function initialiseViewModel()
{
// loop through all the properties in the model
for (var property in viewModel) {
if (viewModel.hasOwnProperty(property)) {
// if they're observable
if(viewModel[property].subscribe){
// subscribe to changes
viewModel[property].subscribe(function(value) {
catchChanges(property, value);
});
}
}
}
viewModel.isDirty = false;
}
function resetViewModel() {
changeLog = new Array();
viewModel.isDirty = false;
}
(haven't tested it - but you should get the idea)
Consider using Knockout-Validation plug-in
It implements the following:
yourProperty.isModified() - Checks if the user modified the value.
yourProperty.originalValue - So you can check if the value really changed.
Along with other validation stuff which comes in handy!
Cheers
You might use the plugin below for this:
https://github.com/ZiadJ/knockoutjs-reactor
The code for example will allow you to keep track of all changes within any viewModel:
ko.watch(someViewModel, { depth: -1 }, function(parents, child) {
alert('New value is: ' + child());
});
PS: As of now this will not work with subscribables nested within an array but a new version that supports it is on the way.
Update: The sample code was upgraded to work with v1.2b which adds support for array items and subscribable-in-subscribable properties.
I had the same problem, i needed to observe any change on the viewModel, in order to send the data back to the server,
If anyone still intersted, i did some research and this is the best solution iv'e managed to assemble:
function GlobalObserver(viewModel, callback) {
var self = this;
viewModel.allChangesObserver = ko.computed(function() {
self.viewModelRaw = ko.mapping.toJS(viewModel);
});
viewModel.allChangesObserver.subscribe(function() {
callback(self.viewModelRaw);
});
self.dispose = function() {
if (viewModel.allChangesObserver)
viewModel.allChangesObserver.dispose();
delete viewModel.allChangesObserver;
};
};
in order to use this 'global observer':
function updateEntireViewModel() {
var rawViewModel = Ajax_GetItemEntity(); //fetch the json object..
//enter validation code here, to ensure entity is correct.
if (koGlobalObserver)
koGlobalObserver.dispose(); //If already observing the older ViewModel, stop doing that!
var viewModel = ko.mapping.fromJS(rawViewModel);
koGlobalObserver = new GlobalObserver(viewModel, Ajax_Submit);
ko.applyBindings(viewModel [ ,optional dom element]);
}
Note that the callback given (in this case 'Ajax_Submit') will be fired on ANY change that occurs on the view model, so i think it's really recommended to make some sort of delay mechanism to send the entity only when the user finished to edit the properties:
var _entitiesUpdateTimers = {};
function Ajax_Submit(entity) {
var key = entity.ID; //or whatever uniquely related to the current view model..
if (typeof _entitiesUpdateTimers[key] !== 'undefined')
clearTimeout(_entitiesUpdateTimers[key]);
_entitiesUpdateTimers[key] =
setTimeout(function() { SendEntityFunction(entity); }, 500);
}
I'm new to JavaScript and the knockout framework, (only yestarday i started to work with this wonderfull framework), so don't get mad at me if i did something wrong.. (-:
Hope this helps!
I've adapted #Brett Green code and extended it so that we can have AcceptChanges, marking the model as not dirty plus having a nicer way of marking models as trackables. Here is the code:
var viewModel = {
name: ko.observable()
};
ko.track(viewModel);
http://jsfiddle.net/david_freire/3HZEu/2/
I did this by taking a snapshot of the view model when the page loads, and then later comparing that snapshot to the current view model. I didn't care what properties changed, only if any changed.
Take a snapshot:
var originalViewModel = JSON.stringify(ko.toJS(viewModel));
Compare later:
if(originalViewModel != JSON.stringify(ko.toJS(viewModel))){
// Something has changed, but we don't know what
}
Consider a view model as follows
function myViewModel(){
var that = this;
that.Name = ko.observable();
that.OldState = ko.observable();
that.NewState = ko.observable();
that.dirtyCalcultions - ko.computed(function(){
// Code to execute when state of an observable changes.
});
}
After you Bind your Data you can store the state using ko.toJS(myViewModel) function.
myViewModel.Name("test");
myViewModel.OldState(ko.toJS(myViewModel));
You can declare a variable inside your view model as a computed observable like
that.dirtyCalculations = ko.computed(function () {});
This computed function will be entered when there is change to any of the other observables inside the view model.
Then you can compare the two view model states as:
that.dirtyCalculations = ko.computed(function () {
that.NewState(that);
//Compare old state to new state
if(that.OldState().Name == that.NewState().Name()){
// View model states are same.
}
else{
// View model states are different.
}
});
**Note: This computed observable function is also executed the first time when the view model is initialized. **
Hope this helps !
Cheers!!
I like Brett Green's solution. As someone pointed out, the isDirty comparison doesn't work with Date objects. I solved it by extending the subscribe method like this:
observable.subscribe(function (newValue) {
observable.isDirty(newValue != observable.originalValue);
if (newValue instanceof Date) {
observable.isDirty(newValue.getTime() != observable.originalValue.getTime());
}
});

Knockout.JS Observable Array via AJAX bound to DropDownList

I am trying to fill my view model with some drop down options from the database. I want to keep track of the selected object because it has properties on it that I am using in a custom binding elsewhere.
I initialize the observable with a "blank" value so that it's properly set when the binding occurs and my custom binding works. Once the server responds, I morph the data to an observable array and the drop down list displays the options.
Here is the JavaScript code:
ko.bindingHandlers.jq_fade = {
init: function (element, valueAccessor) {
// Initially set the element to be instantly visible/hidden depending on the value
var value = valueAccessor();
$(element).toggle(ko.utils.unwrapObservable(value)); // Use "unwrapObservable" so we can handle values that may or may not be observable
},
update: function (element, valueAccessor) {
// Whenever the value subsequently changes, slowly fade the element in or out
var value = valueAccessor();
ko.utils.unwrapObservable(value) ? $(element).fadeIn() : $(element).fadeOut();
}
};
function Firm(id, name, outsideCounsel) {
this.name = name;
this.id = id;
this.outsideCounsel = outsideCounsel;
}
function RequestViewModel() {
var self = this,
ONE_DAY = 1000 * 60 * 60 * 24;
self.firm = ko.observable(new Firm(-1, "", false));
$.post(ajaxAddress + 'LoadFirms', function (data) {
var mappedFirms = $.map(data.d, function (item) {
return new Firm(item.OrganizationLookupID, item.OrganizationLookupName, item.IsExternalCounselFirm);
});
self.availableFirms(mappedFirms);
self.firm(self.availableFirms()[0]);
});
}
$(document).ready(function () {
model = new RequestViewModel();
ko.applyBindings(model);
});
Here is the relevant HTML
<span data-bind="jq_fade: firm().outsideCounsel">
Outside Counsel
<input type="text" id="outsideCounsel" data-bind="value: outsideCounsel" />
</span>
I want that DIV to show only if the selected firm is an outside counsel. If remove the line data-bind="jq_fade: firm().outsideCounsel, everything works fine. If I make the $.post() calls synchronously, it works. I'm thinking it's something wrong with my init function in jq_fade.
The error I receive is:
Uncaught Error: Unable to parse bindings.
Message: TypeError: Cannot call method 'outsideCounsel' of undefined;
Bindings value: jq_fade: firm().outsideCounsel()
I understand what Knockout is telling me, I'm just not sure how firm() can ever be undefined because I set up an initial value.
If you're binding availableFirms() to a dropdownlist, I'm assuming you've also bound firm() to the same list so that when another is selected from the list, firm() gets automatically updated and all your bindings update automatically.
If this is the case, you do not need to set firm() at all as it will be set to the first element in the dropdownlist anyway.
See example 3 here:
http://knockoutjs.com/documentation/options-binding.html
var viewModel = {
availableCountries : ko.observableArray([
new country("UK", 65000000),
new country("USA", 320000000),
new country("Sweden", 29000000)
]),
selectedCountry : ko.observable() // Nothing selected by default
};
Try it like above without specifically setting firm() and see if it errors again

Categories