Knockout templates: afterRender triggered twice because name/data are coupled observables - javascript

I'm having trouble getting afterRender to trigger exactly once. I'm seeing that afterRender is triggered twice because I have two observables in my template binding that update at the same time:
<div data-bind="template: { name: template.name(), data: template.data, if: template.data, afterRender: doSomeStuff }">
</div>
This is because an update to name triggers an update to data since it is a computed observable whose value depends on name:
this.data = ko.computed({
var viewName = this.name();
switch (viewName) {
case "a":
return this.modelA();
case "b":
return this.modelB();
case "c":
return this.modelC();
default:
return null;
}
});
What I'm trying to do is log the time the template is rendered. I'm also concerned about potential performance implications of rendering twice - are those DOM elements within the template being created twice every single time name changes?
It is possible for data to change (e.g. the modelA, modelB or modelC object changes) without name changing and I do want data changes to trigger afterRender if it is changed by itself. Ideally, I would be able to couple the name-data update to trigger afterRender once but I'm not sure if this is possible.
Is there an elegant way to accomplish this?
I looked into throttle extenders but those wouldn't prevent the
template binding from being triggered twice
deferEvaluation on the
data computed observable does not appear to cause the template
binding to pick up the new object when template.name changes (not
sure if this is because the template binding is not actually
reevaluating data when template.name changes), and should still trigger the template binding twice.
I'm thinking about getting rid of the data object completely and in each HTML template, attaching a with binding directly to modelA, modelB, and modelC, but then I wouldn't get those afterRender updates if the data changes.
Edit: I am using KO 2.2.1, and realized that the template name can't be observable (until 2.3.0), hence the parentheses after template.name. So my original assumption that this afterRender is happening twice because name is an observable too must be incorrect?

Bindings are updated using a computed observable that tracks changes to any dependencies. Your situation is like this: computed C (template binding) is dependent on computed B (data) and observable A (name), and computed B is dependent on observable A. When A changes, it updates B and C. And when B updates, it also updates C. So that's why C gets two updates.
For reference, here's your example in jsFiddle showing two updates: http://jsfiddle.net/mbest/DnY9V/
Throttle extender
Knockout's throttle extender is the only built-in way to prevent multiple updates. You can create a custom binding that wraps the template binding to run the update in a throttled computed observable.
ko.bindingHandlers.throttledTemplate = {
init: function(element) {
var args = arguments, self = this,
result = ko.bindingHandlers.template.init.apply(this, arguments);
ko.computed(function() {
ko.bindingHandlers.template.update.apply(self, args);
}, null, {disposeWhenNodeIsRemoved: element}).extend({throttle:1});
return result;
}
};
http://jsfiddle.net/mbest/j5D6p/
Deferred Updates plugin
Alternatively, you can use my Deferred Updates plugin that prevents all duplicate updates to computed observables: https://github.com/mbest/knockout-deferred-updates
http://jsfiddle.net/mbest/UXYJx/

Related

Knockout "with" binding removes my jquery DOM events

I have several jquery dom events that are created on DOM load or document ready. These are mostly default behaviors that should be applied to all forms in my application. Example:
$('input:text').focus(function ()
{
$(this).select();
});
Right before applying knockout binding, I can check my dom elements and all events are there:
But when I run the applyBindings method to bind the viewmodel to my DOM, the "with" binding removes all events that are not related to knockout:
I have tried overwriting the cleanExternalData as explained on the documentation and on this answer. But that did not help with this, the function is replaced, but the events are still removed from the DOM when the templating is applied on the binding process.
For the record, this is not an exclusive behavior of the with function, but all anonymous templating functions also do that, foreach, if, ifnot. Using template, as expected, also behaves the same way. The DOM element is completely destroyed, stored as a template, then added again on my document when the condition is satisfied, but now without any jquery event handlers.
How to avoid that knockout removes the events from my DOM elements?
Instead of binding elements to a specific node, you can use a databinding to use the jquery on() functionality to handle events. Here's a binding I use:
define(['knockout'], function (ko) {
ko.bindingHandlers.eventListener = {
init: function (element, valueAccessor, allBindings, viewModel, bindingContext) {
var params = ko.utils.unwrapObservable(valueAccessor());
if (!(params instanceof Array)) {
params = [params];
}
params.forEach(function (param) {
$(element).on(param.event, param.selector, function (event) {
param.callback(ko.dataFor(this), ko.contextFor(this), event);
});
});
}
}
});
Usage:
<div data-bind="eventListener: [
{ event: 'click', selector: '.copyInclusionRule', callback: copyInclusionRule},
{ event: 'click', selector: '.deleteInclusionRule', callback: deleteInclusionRule}]">
... other knockout template stuff here ...
</div>
The above will listen for click events on either an element with the specified class and perform the callback when the event is received for anything within the div's 'scope'. The value of 'event' param can be anything that on() uses.
I think the reason why you can't leverage the cleanNode overrides is that your dom is being completely destroyed and re-created..at least that's my theory, if there was a way to get some kind of memory ID of the pre-applyBindings() dom elements and then after the applyBindings is called, are those new nodes? If they are new nodes, it's not something you can't fix with cleaning, those nodes are gone.
Alright, here is how I fixed my problem and I hope this can clarify things to others that don't want to destroy their DOM as well. If you don't want that knockout to destroy your DOM, that is possible since version 2.2. And thus, destroying the DOM when that is not necessary is not intended behavior and can be avoided.
I had tried several bidings created by Michael Best before, like his using binding that will come in knockout 3.5, and let or withLight (which became using now). None really worked. These simplified bidings would load the initial object, but not update the dom when this object properties had changed.
But this helped me to figure out what I am doing wrong. When I wanted to update my observable object, I was using myViewModel.observableObject(NewObject), like the documentation told me to do:
To write a new value to the observable, call the observable and pass the new value as a parameter. For example, calling myViewModel.personName('Mary') will change the name value to 'Mary'.
But I wasn't passing a single property's value, I was passing a new object that had the same structure (same properties). And this triggered knockout that the old object was destroyed (and thus, falsy for a second) and a new object took its place, even though all properties are there, they just got different values. Unlike the documentation told me, it didn't simply changed the value, but changed the entire object.
To go around this, instead of doing that, First, I had to initiate my viewModel with this object already created, using dummy data, this makes knockout not destroy the DOM when applyBindings is called. Then, when I want my object to update, I replaced the value of each property of the observable object to have the value of the new object. This didn't destroy the object and knockout updated my binding properly.
myViewModel.setSelectedItem = function setSelectedItem (newObject)
{
for (var prop in myViewModel.myObservableObject())
myViewModel.myObservableObject()[prop](newObject[prop]);
}
The with binding still killed some of my events (my angular ng-change for one of my components, for instance), but it kept all jquery events in there (which is great). And the using binding didn't kill any of my events at all (which is even better).

How to ignore Angular $watch event when I myself have caused it?

I have a custom UI element with link to ngModel:
scope:
{
ngModel : "="
}
There are two ways how the attached model might change:
it is changed from outside - in this case I want to update UI of my custom element
it is changed from inside - in this case I want to ignore the changes because my UI is already up-to-date
I have a watch:
$scope.$watch("ngModel", function(newValue){
// here I have complex logic to traverse newValue
// to see if it matches my current UI state
// if it matches, then I return
// if it does not match, then I sync my UI to the newValue
});
and I have a function which pushes current UI state to the model:
function pushSelectionToModel() {
// some code
$scope.ngModel = newState;
// some code
}
Everything works fine, but in cases when user is scrolling through my UI directive fast, ngModel watch is being triggered each time. My code to detect if newValue matches my current UI state is fairly complex, thus causing performance issues.
A natural solution seems to be somehow to ignore the $watch in case when I have just called pushSelectionToModel. I cannot just disable the watch in pushSelectionToModel before update and enable after that because the watch is executed later, after pushSelectionToModel has exited.
How do I tell Angular that for some particular model assignment operation I don't want to trigger some particular $watch?
Here is the relevant simplified Plunkr example
Essentially I want to prevent updateUi() from being called twice when I click Apply button. But the example is simplified, in reality I can't directly assign or compare innerName and ngModel values because in my actual code the values differ because of some transformations.
I've sovled a similar problem, by adding the following statement:
$scope.$watch('ngModel', function(newValue){
if($scope.innerName !== newValue){
// now execute code
http://plnkr.co/edit/r9sQax4VNqBraimQi9pz
but its more of an workaround...

Working with current values of properties of the ViewModel with Knockout-Kendo.js

I recently found this great component -> Knockout-Kendo.js.
I use it to handle some behaviors with kendoComboBox.
The synchronization with the viewmodel works perfectly.
I want to listen changes of the control to execute some actions based on the current selected value.
I don't see any property that I can bind in the 'data-bind' attribute to listen changes but I know that internally, the knockout-kendo component listen changes and this is how the viewmodel is able to by sync with the control.
If I try to listen the valueChange event of the control, the problem is my eventhandler is catched before the viewmodel and while running in my eventhandler, I just have the previous value of the control using the viewmodel.
Look at this binding configuration in the component. What I understand is I'm able to use 'enabled', 'search', 'data', 'value' and any other exposed properties of the telerik control. What would be nice would be to define in data-bind attribute a property 'change' with an eventhandler linked in my viewmodel and be sure my eventhandler would be called after the internal eventhandler of the knockout-kendo component.
createBinding({
name: "kendoAutoComplete",
events: {
change: VALUE,
open: {
writeTo: ISOPEN,
value: true
},
close: {
writeTo: ISOPEN,
value: false
}
},
watch: {
enabled: ENABLE,
search: [SEARCH, CLOSE],
data: function(value) {
ko.kendo.setDataSource(this, value);
},
value: VALUE
}
});
I know I can try to modify the order of bind of events to be sure my eventhandler must be called after the synchronization of the viewmodel but I think it's a very bad practice.
Anybody have an idea how I can solve this problem with elegance?
You haven't mentioned why you want to do this. I can imagine two reasons:
To trigger some UI behavior/logic directly;
To trigger business logic (which may in turn trigger UI changes of course);
For people landing at this question with the latter case, here's an alternative solution. (This answer may not be a straight up answer to the OP's question, but seems useful enough to post it here.)
Suppose you have this basic view model:
var ViewModel = function() {
var self = this;
self.kendoObservable = ko.observable("Some text")
};
There are two ways you can indirectly respond to changes by Kendo. First, for simple cases, there's computed observables:
// Option 1, add this to ViewModel
self.dependentObservable = ko.computed(function() {
return self.kendoObservable() === "" ? "Empty" : "Not empty"; // example
});
This dependentObservable will be modified each time the kendoObservable changes. Basic stuff.
If you want to do something more complex when kendoObservable changes, e.g. do an AJAX call or whatnot, you may need a manual subscription:
// Option 2, add this to ViewModel
self.kendoObservable.subscribe(function(newValue) {
// Possibly do an AJAX call here or whatnot. Example:
alert("The new value is: " + newValue);
});
This will allow you to fire some complex logic each time the kendoObservable changes. AFAIK you need to check yourself whether the newValue is actually a changed value, at least in some versions of KO.

Async Knockout Bindings

What are the implications/potential problems of completing a binding asynchronously?
Consider the following binding:
ko.bindingHandlers.widget = {
init: function (element, valueAccessor, allBindingsAccessor, viewModel)
{
var values = valueAccessor();
//depending on type, delegate binding work to another function
factory.getWidget(values.type)
.done(function (widget) {
widget.bind(element, values.settings, allBindingsAccessor, viewModel);
});
//widget.bind is responsible for all children
return { controlsDescendantBindings: true };
}
};
What are the potential problems that may occur as a result of the init function returning before the binding "work", which includes DOM manipulation, has actually been completed?
I can't clearly see any specific scenario that would cause a problem, but I want to see if anyone else knows something in the way knockout works that may cause problems later.
I was recently working on a similar problem. I have created a custom binding for jsTree that I'm using in my web application. So the UI is -> root -> children -> children's children and so on (on the left)... And for each entity, there is a table that shows some related data (on the right) and a form field showing the fields of the entity. Also, I'm loading both the tree and the data table asynchronously hitting the server with an AJAX call each time the user clicks on a tree node. However, I do have both the init and update methods filled out in my binding handlers.
To help you understand the context a little better, lets take USA as the root node in my tree, USA has many states (root's children) and each state has many counties and so on. Many of the fields inside the root object (USA) will most likely be different than the fields inside its children entities (states, in this case). I'm sorry I can't think of fields that would be there in USA and not in a State, but I'm hoping you get the point.
Also, I have an observable called 'currentSelectedObject' in my VM that gets updated each time the user clicks on a different node in the tree, I make an AJAX call to get the object, and I have a method called replace current object that handles changing the current selected object.. And as mentioned before, parent and child objects have (mostly) different fields. Here's where I ran into a lot of problems. In my html I bind the values of the element like this:
data-bind: value: currentSelectedObject().xyz
Now lets say the user clicks on a different object that doesn't have the field 'xyz,' I used to get this error:
can't read property xyz of undefined.
I got that error because for an extremely small time, the currenySelectedObject has changed and the view (the HTML template) hasn't. I thought the change would be atomic but apparently it isn't. The way I fixed this was by actually updating the current object in the success callback of my AJAX call.
self.replaceCurrentObject = function(type, id, callback){
server.getObject(type, id, // server.js is an abstraction over the data layer
function(returnObject){ // corresponds to: success: function(data){...}
self.currentSelectedObject(returnObject);
}
if(callback) callback(); // to handle other table related stuff
}

Why does my ko computed observable not update bound UI elements when its value changes?

I'm trying to wrap a cookie in a computed observable (which I'll later turn into a protectedObservable) and I'm having some problems with the computed observable. I was under the opinion that changes to the computed observable would be broadcast to any UI elements that have been bound to it.
I've created the following fiddle
JavaScript:
var viewModel = {};
// simulating a cookie store, this part isnt as important
var cookie = function () {
// simulating a value stored in cookies
var privateZipcode = "12345";
return {
'write' : function (val) { privateZipcode = val; },
'read': function () { return privateZipcode; }
}
}();
viewModel.zipcode = ko.computed({
read: function () {
return cookie.read();
},
write: function (value) {
cookie.write(value);
},
owner: viewModel
});
ko.applyBindings(viewModel);?
HTML:
zipcode:
<input type='text' data-bind="value: zipcode"> <br />
zipcode:
<span data-bind="text: zipcode"></span>?
I'm not using an observable to store privateZipcode since that's really just going to be in a cookie. I'm hoping that the ko.computed will provide the notifications and binding functionality that I need, though most of the examples I've seen with ko.computed end up using a ko.observable underneath the covers.
Shouldn't the act of writing the value to my computed observable signal the UI elements that are bound to its value? Shouldn't these just update?
Workaround
I've got a simple workaround where I just use a ko.observable along side of my cookie store and using that will trigger the required updates to my DOM elements but this seems completely unnecessary, unless ko.computed lacks the signaling / dependency type functionality that ko.observable has.
My workaround fiddle, you'll notice that the only thing that changes is that I added a seperateObservable that isn't used as a store, its only purpose is to signal to the UI that the underlying data has changed.
// simulating a cookie store, this part isnt as important
var cookie = function () {
// simulating a value stored in cookies
var privateZipcode = "12345";
// extra observable that isnt really used as a store, just to trigger updates to the UI
var seperateObservable = ko.observable(privateZipcode);
return {
'write' : function (val) {
privateZipcode = val;
seperateObservable(val);
},
'read': function () {
seperateObservable();
return privateZipcode;
}
}
}();
This makes sense and works as I'd expect because viewModel.zipcode depends on seperateObservable and updates to that should (and does) signal the UI to update. What I don't understand, is why doesn't a call to the write function on my ko.computed signal the UI to update, since that element is bound to that ko.computed?
I suspected that I might have to use something in knockout to manually signal that my ko.computed has been updated, and I'm fine with that, that makes sense. I just haven't been able to find a way to accomplish that.
sigh, I found someone with my exact same problem
If dependentObservables don't notifySubscribers on write, why do they
even bother to do it on read? They get added to the observables list
and subscribed to, but then they never trigger on updates. So what is
the point of subscribing to them at all?
Ryan Niemeyer answers:
I think that for your scenario, dependentObservables may not be the
right tool for the job. dependentObservables are set up to detect
dependencies in the read function and re-evaluate/notify whenever any
of those dependencies change. In a writeable dependentObservable, the
write function is really just a place to intercept the write and allow
you to set any observables necessary, such that your read function
would return the proper value (write is typically the reverse of read
in most cases, unless you are transforming the value).
For your case, I would personally use an observable to represent the
value and then a manual subscription to that observable to update the
original value (the one that you may not have control over).
It would be like: http://jsfiddle.net/rniemeyer/Nn5TH/
So it looks like this fiddle would be a solution
var viewModel = {};
// simulating a cookie store, this part isnt as important
var cookie = function () {
// simulating a value stored in cookies
var privateZipcode = "12345";
return {
'write' : function (val) {
console.log("updated cookie value with: " + val);
privateZipcode = val;
},
'read': function () {
return privateZipcode;
}
}
}();
viewModel.zipcode = ko.observable(cookie.read());
// manually update the cookie when the observable changes
viewModel.zipcode.subscribe(function(newValue) {
cookie.write(newValue);
});
ko.applyBindings(viewModel);​
That makes sense and its somewhat simpler to use. Overall I'm not sure how great of an idea it is to treat a cookie as an observable since the server could edit it in an ajax request, etc.
Try making your internal privatezipcode an observable. See here: http://jsfiddle.net/KodeKreachor/fAGes/9/

Categories