Knockout JS not clearing components - javascript

So here's a weird KnockoutJS problem I've never actually come across before.
I'm working on an application that uses Knockout Components very heavily.
In one part of the app, I have an editor page that's built dynamically from a JSON driven backend, and which populates a front end page with a number of widgets, depending on what it's told from the back end data.
Example
The back end might send
[{"widget": "textBox"},{"widget": "textBox"},{"widget": "comboBox"},{"widget": "checkBox"}]
Which would cause the front end to build up a page containing
<html>
....
<textbox></textbox>
<textbox></textbox>
<combobox></combobox>
<checkbox></checkbox>
....
</html>
Each of the custom tags is an individual KnockoutJS component, compiled as an AMD module and loaded using RequireJS, each component is based on the same boiler plate:
/// <amd-dependency path="text!application/components/pagecontrols/template.html" />
define(["require", "exports", "knockout", 'knockout.postbox', "text!application/components/pagecontrols/template.html"], function (require, exports, ko, postbox) {
var Template = require("text!application/components/pagecontrols/template.html");
var ViewModel = (function () {
function ViewModel(params) {
var _this = this;
this.someDataBoundVar = ko.observable("");
}
ViewModel.prototype.somePublicFunction = function () {
postbox.publish("SomeMessage", { data: "some data" });
};
return ViewModel;
})();
return { viewModel: ViewModel, template: Template };
});
The components communicate with each other and with the page using "Knockout Postbox" in a pub sub fashion.
And when I put them into the page I do so in the following manor:
<div data-bind="foreach: pageComponentsToDisplay">
<!-- ko if: widget == "textBox" -->
<textBox params="details: $data"></textBox>
<!-- /ko -->
<!-- ko if: widget == "comboBox" -->
<comboBox params="details: $data"></comboBox>
<!-- /ko -->
<!-- ko if: widget == "checkBox" -->
<checkBox params="details: $data"></checkBox>
<!-- /ko -->
</div>
and where pageComponentsToDisplay is a simple knockout observable array that I just push the objects received from the backend onto:
pageComponentsToDisplay = ko.observableArray([]);
pageComponentsToDisplay(data);
Where 'data' is as shown in JSON above
Now all of this works great, but here-in now lies the ODD part.
If I have to do a "reload" of the page, I simply
pageComponentsToDisplay = ko.observableArray([]);
to clear the array, and consequently, all my components also disappear from the page, as expected, however when I load the new data in, again using:
pageComponentsToDisplay(data);
I get my new components on screen as expected, BUT the old ones appear to be still present and active in memory, even though there not visible.
The reason I know the controls are still there, because when I issue one of my PubSub messages to ask the controls for some state info, ALL of them reply.
It seems to me that when I clear the array, and when KO clears the view model, it actually does not seem to be destroying the old copies.
Further more, if I refresh again, I then get 3 sets of components responding, refresh again and it's 4, and this keeps increasing as expected.
This is the first time I've encountered this behaviour with knockout, and I've used this kind of pattern for years without an issue.
If you want a good overview of how the entire project is set up, I have a sample skeleton layout on my github page:
https://github.com/shawty/dotnetnotts15
If anyone has any ideas on what might be happening here I'd love to hear them.
As a final note, I'm actually developing all this using Typescript, but since this is a runtime problem, I'm documenting it from a JS point of view.
Regards
Shawty
Update 1
So after digging further (and with a little 'new thinking' thanks to cl3m's answer) I'm a little bit further forward.
In my initial post, I did mention that I was using Ryan Niemeyer's excelent PubSub extension for Knockout 'ko postbox'.
It turn's out, that my 'Components' are being disposed of and torn down BUT the subscription handlers that are being created to respond to postbox are not.
The result is, that the VM (or more specifically the values that the subscription uses in the VM) are being kept in memory, along with the postbox subscription handler.
This means when my master broadcasts a message asking for component values, the held reference responds, followed by the visibly active component.
What I need to now do is figure out a way to dispose these subscriptions, which because I'm using postbox directly, and NOT assigning them to an observable in my model, means I don't actually have a var or object reference to target them with.
The quest continues.
Update 2
See my self answer to the question below.

I'm not sure this will help but, as per my comment, here is how I use the ko.utils.domNodeDisposal.addDisposeCallback() in my custom bindings. Perhaps there is a way to use it in knockout components:
ko.bindingHandlers.tooltip = {
init: function(element, valueAccessor) {
$(element).tooltip(options);
ko.utils.domNodeDisposal.addDisposeCallback(element, function() {
$(element).tooltip('destroy');
});
}
}
More reading on Ryan Niemeyer's website

The problem it seems was due to Knockout hanging onto subscriptions set up by postbox when the actual components where active.
In my case, I use postbox purely as a messaging platform, so all i'm doing is
ko.postbox.subscribe("foo", function(payload) { ... });
all the time, since I was only ever using single shot subscriptions in this fashion, I was never paying ANY Attention to the values returned by the postbox subscription call.
I did things this way, simply because in many of the components I create there is a common API that they all use, but to which they all respond in different ways, so all I ever needed was a simple this is what to do when your called handler that was component specific, but not application specific.
It turns out however that when you use postbox in this manner, there is no observable for you to target, and as such there is nothing to dispose. (Your not saving the return, so you have nothing to work with)
What the Knockout and Postbox documentation does not mention, is that the return value from postbox.subscribe is a general Knockout subscription function, and by assigning the return from it to a property within your model, you then have a means to call the functionality available on it, one of those functions provides the ability to "dispose" the instance, which NOT ONLY removes the physical manifestation of the component from it's collection, BUT ALSO ensures that any subscriptions or event handlers connected to it are also correctly torn down.
Couple with that, the fact that you can pass a dispose handler to your VM when you register it, the final solution is to make sure you do the following
/// <amd-dependency path="text!application/components/pagecontrols/template.html" />
define(["require", "exports", "knockout", 'knockout.postbox', "text!application/components/pagecontrols/template.html"], function (require, exports, ko, postbox) {
var Template = require("text!application/components/pagecontrols/template.html");
var ViewModel = (function () {
function ViewModel(params) {
var _this = this;
this.someDataBoundVar = ko.observable("");
this.mySubscriptionHandler = ko.postbox.subscribe("foo", function(){
// do something here to handle subscription message
});
}
ViewModel.prototype.somePublicFunction = function () {
postbox.publish("SomeMessage", { data: "some data" });
};
return ViewModel;
ViewModel.prototype.dispose = function () {
this.mySubscriptionHandler.dispose();
};
return ViewModel;
})();
return { viewModel: ViewModel, template: Template, dispose: this.dispose };
});
You'll notice that the resulting class has a "dispose" function too, this is something that KnockoutJS provides on component classes, and if your class is managed as a component by the main KO library, KO will look for and execute if found, that function when your component class goes out of scope.
As you can see in my example, Iv'e saved the return from the subscription handler as previously mentioned, then in this hook point that we know will get called, used that to ensure that I also call dispose on each subscription.
Of course this ONLY shows one subscription, if you have multiple subscriptions, then you need multiple saves, and multiple calls at the end. An easy way of achieving this, especially if your using Typescript as I am, is to use Typescripts generics functionality and save all your subscriptions into a typed array, meaning at the end all you need to do is loop over that array and call dispose on every entry in it.

Related

Backbone views which don't know about their container, models to be fetched via AJAX, no UI/UX trade-offs and maintainable code

Since I'm not totally sure on which level my issue actually is to be solved best, I'd like to summarise the path I went and the things I tried first:
It's more or less about $el (I think).
As most basic backbone examples state, I started with having the $el defined within its view, like
Invoice.InvoiceView = Backbone.View.extend({
el: $('#container'),
template: ..,
..
});
It didn't feel right, that the view is supposed to know about its parent (=container). The paragraph 'Decouple Views from other DOM elements' written on http://coenraets.org/blog/2012/01/backbone-js-lessons-learned-and-improved-sample-app/) perfectly puts it into words.
Following this article's advice, I switched to passing $el over to the view while calling the render()-method. Example:
$('#container').html( new WineListView({model: app.wineList}).render().el );
So far so good - but now render() gets called, while it maybe shouldn't (yet).
For example the View asynchronously fetches a model in its initialize()-routine. Adding a binding to reset or sync (e.g. like this.model.bind('sync', this.render, this)) makes sure, render() gets definitely called once the model is fetched, however above stated way, render() still might get called while the model isn't fetched yet.
Not nice, but working(TM), I solved that by checking for the model's existence of its primary key:
render: function() {
if(this.model.get('id')) {
...
}
However, what I didn't expect - and if it really isn't documented (at least I didn't find anything about it) I think it really should be - the fetch operation doesn't seem to be atomic. While the primary key ('id') might be already part of the model, the rest might not, yet. So there's no guarantee the model is fetched completely that way. But that whole checking seemed wrong anyway, so I did some research and got pointed to the deferred.done-callback which sounded exactly what I was looking for, so my code morphed into this:
render: render() {
var self = this;
this.model.deferred.done(function() {
self.model.get('..')
};
return this;
}
..
$('#container').html( new WineListView({model: app.wineList}).render().el);
It works! Nice, hu? Ehrm.. not really. It might be nice from the runtime-flow's point of view, but that code is quite cumbersome (to put it mildly..). But I'd even bite that bullet, if there wouldn't be that little, tiny detail, that this code sets (=replaces) the view instantly, but populates it later (due to the deferred).
Imagine you have two (full-page) views, a show and an edit one - and you'd like to instantly switch between the two (e.g. after hitting save in the edit-view it morphs into the show-view. But using above code it sets (=resets) the view immediately and then renders its content, once the deferred fires (as in, once fetching the model is completed).
This could be a short flickering or a long blank transition page. Either way, not cool.
So, I guess my question is: How to implement views, which don't know about their container, involve models which need to be fetched, views which should be rendered on demand (but only once the model is fetched completely), having no need to accept UI/UX trade-offs and - the cherry on the cake - having maintainable code in the end.
First of all, the first method you found is terrible (hard coding selector in view's constructor)
The second: new WineListView({model: app.wineList}).render().el is very common and ok. This requires you to return the reference to view from render method, and everyone seems to follow this, which is unnecessary.
The best method (imo) is to simply attach the views element to the container, like this
$('#container').html(new WineListView({model: app.wineList}).el);
The WineListView doesn't need to know about where it's going to be used, and whatever is initializing WineListView doesn't need to worry about when to render the WineListView view instance:
because the el is a live reference to an HTML Element, the view instance can modify it anytime it wants to, and the changes will reflect wherever it is attached in DOM/ when it gets attached in DOM.
For example,
WineListView = Backbone.View.extend({
initialize: function(){
this.render(); // maybe call it here
this.model.fetch({
success: _.bind(this,function(){
this.render(); // or maybe here
})
});
}
});
Regarding flickering: this hardly has to do anything with rendering or backbone, it's just that you're replacing one element with another and there will be an emptiness for a tiny bit of time even if your new view renders instantly. You should handle this using general techniques like transitions, loaders etc, or avoid having to switch elements (For example convert labels into inputs in the same view, without switching view)
First off, the linked example is outdated. It's using version 0.9.2,
whereas the current version (2016-04-19) is 1.3.3. I recommend
you have look at the change log and note the differences, there are many.
Using the el property is fine. Like everything though, there's a time and place.
It didn't feel right, that the view is supposed to know about its parent (=container). The paragraph 'Decouple Views from other DOM elements' written on http://coenraets.org/blog/2012/01/backbone-js-lessons-learned-and-improved-sample-app/) perfectly puts it into words.
I wouldn't define an el property on every view, but sometimes it makes sense, such as your example. Which is why, I'm assuming, Backbone allows the use of the el property. If you know container is already in the DOM, why not use it?
You have a few options:
The approach outlined in my original answer, a work-around.
fetch the model, and in the success callback, insert the view element into the DOM:
model.fetch({
success:function() {
$('#container').html(new View({model:model}).render().el);
}
});
Another work-around.
Define an el property on the view and fetch the model in the view initialize function. The new content will be rendered in the container element (also the view), when the content/model data is ready, by ready, I mean when the model has finished fetching from the server.
In short,
If you don't want to define an el property, go with number 1.
If you don't want to let the view fetch the model, go with number 2.
If you want to use the el property, go with number 3.
So, I guess my question is: How to implement views, which don't know about their container
In your example, I would use the el property, it's simple a solution with the least amount of code. Not using the el property here, turns into hacky work-arounds that involve more code (complexity) without adding any value (power).
Here's what the code looks like using el:
var Model = Backbone.Model.extend({url:'/model_url'});
var model = new Model();
// set-up a view
var View = Backbone.View.extend({
el:'#container',
template:'model_template',
initialize:function() {
this.model.fetch();
this.listenTo(this.model,'sync',this.render);
},
render:function() {
this.$el.html(this.template(this.model.toJSON()));
return this;
}
});
var view = new View({model:model});
Check out the documentation for el.
Here is an updated working example.
If there is an obvious flicker because, your model takes a noticeable amount of time
to be fetched from the server...maybe you should think about displaying a loading bar/variation thereof
while fetching the model. Otherwise instead of seeing the flicker, it will appear the
application is slow, delayed, or hanging..but in reality - it's waiting to render the next view,
waiting for the model to finish fetching from the server. Sitting on old content, just waiting for
the model to load new data to show new content.

Synchronous component registration

I have a knockout component which looks something like this:
define(['knockout', 'text!./my-component.html', 'pubsub'], function(ko, htmlString, pubsub) {
function viewModel(params) { }
return {
viewModel: {
createViewModel: function(params, componentInfo) {
var vm = new viewModel(params);
pubsub('updateViewModel').subscribe(function(){
// update vm
});
return vm;
}
},
template: htmlString
};
});
I use the createViewModel function to subscribe to an update event, which I use later on to trigger an update of the components viewmodel from other components.
I include the compnent on my page like this:
<!-- ko component: "my-component" -->
<!-- /ko -->
What I would like some verification on is the load order of things. I want to be sure that the createViewModel has been invoked before I might trigger the event.
This is my current order of calls:
// register my-component here
ko.applyBindings(myMainViewModel);
// code that might trigger the component update event here
I've read that ko.applyBindings is sychronous. But does that also include an implicit applybindings to all registered components, like my-component above? Do I need to set the synchronous property to true on the component in order to achieve this? If I understand it correctly, that flag is only related to rendering.
What I want to avoid is a race condition where I trigger the update event before it has been subscribed.
ko.applyBindings can act synchronously if the following conditions are satisfied BEFORE calling:
All components are registered
ALL component viewmodels are loaded in memory (fetched from network..)
ALL component templates are loaded in memory
Its when the component viewmodel and templates are not in memory that applyBindings becomes async (event if you set the synchronous=true).
This synchronous flag comes in to play in applybindings from the component binding. Notice that component binding does a ko.components.get call and passes a call back which will render the component on the DOM.
knockout/src/components/loaderRegistry.js has the definition of ko.components.get. The synchronous flag says that if the component is already cached (in memory) DON'T relinquish control of the thread. Its only when you release control of the thread (setTimeout, DOM insert/wait, ..) that applyBindings will return.
The only thing I'm not too sure about is how RequireJS will interact in here. There is code in knockout which will try to resolve the component using require first.
In any case the following steps will bring you closer (NOT PERFECT. See notes bellow)
//Load component vm, template and register it with synchronous=true
ko.appplyBinding(....)
ko.components.get("my-component" , function() {
//trigger component update event
})
There are few problems with this, and there are solutions to all of them.
Need to wait for multiple components to finish loading
[to solve this you can create a promise array for each component and resolve each of them via ko.components.get. Finally you can $.when(mypromiseArray, myCallback) to synch up all the promises]
ko.component.get does NOT tell you when the component is finally rendered on the DOM.
This is a much more challenging problem. I will share the solution if you need this level of precision (you need to know with in 50ms of when the component is loaded, and rendered on the UI).

KnockoutJS afterRender callback when all nested Components have been rendered?

I have a hierarchy of nested KnockoutJS Components using 3.2.0. It's working very well but I'm looking to execute some code once my entire hierarchy of components has been loaded and rendered. It's a rough equivalent of afterRender(), needed for the same common uses cases as afterRender.
I've tried a few approaches but no luck so far:
Added the following to the root template but it gets called before the nested components are loaded, so too early.
<!--ko template: {afterRender: onLoad.bind($data)} -->
Using the latest 3.3.0-alpha and specifying synchronous:true on all components. But I believe since I'm using AMD, the components are still 'loaded' asynchronously which mean that just because my root applyBindings() returns, doesn't mean that all components have been loaded and rendered.
Even tried building a collection of deferred objects that get resolved only when their corresponding components are loaded. This got overly complicated and still didn't work for reasons I won't go into.
Is there a way to get a callback called once a complete hierarchy of knockoutjs components have been loaded and rendered? Thanks!
I just came across these two threads so it seems others are looking for this as well. The key differentiator from the existing workarounds are they don't work with nested components.
https://github.com/knockout/knockout/issues/1533
https://github.com/knockout/knockout/issues/1475
I've written a knockout library that triggers an event when all components have been loaded and bound. It uses reference counting, similar to referencing counting used for garbage collection. I extensively use components in my project(s), including nesting many levels deep, and I can't live without knowing when everything is "ready to go". I haven't spend much time on documentation of usage, but the basics are there.
Git Hub wiki:
https://github.com/ericraider33/ko.component.loader/wiki
Fiddle:
https://jsfiddle.net/ericeschenbach/487hp5zf/embedded/result/
Usage HTML:
<div id="ko-div">
Status: <span data-bind="text: loading() ? 'Loading' : 'Done'"></span>
<br><br>
<test-panel></test-panel>
</div>
Usage JS:
var pageModel = {
loading: ko.observable(true),
completedCallback: function (childRef) {
pageModel.loading(false);
childRef.testValue(childRef.testValue()+1);
}
};
var tpRef = ko.componentLoader.ref.child({ completedCallback: pageModel.completedCallback});
var tpModel = {
attached: function(element) { return tpRef; },
testValue: ko.observable(5)
};
ko.components.register('test-panel', {
viewModel: function() { return tpModel; },
template: '<div data-bind="attached: true">Test Panel<br>From Code <span data-bind="text: testValue"></span></div>'
});
ko.componentLoader.setOptions({ verbose: true });
ko.applyBindings(pageModel, $('#ko-div')[0]);
Here is what worked for me. I did not try it in all possible variations such as mixing sync and async components, or using custom component loaders.
There is a method in KO 3.3.0 that all components loading goes through:
ko.components = { get: function(componentName, callback) { ...
the get method is invoked with a desired componentName and when component has been loaded - a callback is invoked.
So all you need to do is wrap ko.components.get and callback and increment pendingComponentsCount on each call, and decrement it after callback is executed. When count reaches zero it means that all components were loaded.
25 lines of JS code (using underscorejs).
You also need to handle a special case where ko.applyBindings did not encounter any components, in which it also means that all components (all zero of them) were loaded.
Again, not sure if this works in every situation, but it seems to be working in my case. I can think of few scenarios where this can easily break (for example if somebody would cache a reference to ko.components.get before you get to wrap it).
If you'r working with ko.components this might be of use:
1) Create a deferred object to keep track of each component loading
var statusX = $.Deferred()
var statusY = $.Deferred()
2) Inform knockout to tell you when the component is loaded and ready
ko.components.get('x-component', statusX.resolve) //Note: not calling resolve, but passing the function
ko.components.get('y-component', statusY.resolve)
3) Synch up both status deferreds
$.when(statusX.promise(), statusY.promise())
.done( function allComponentsLoaded(componentX, componentY){
//Both components are ready here
//Note the arguments from the function comes via
//ko->jquery deferred resolve
});

MV* in Polymer, models and services as polymer-elements?

Say I want two views (polymer-elements) to share a model for example.
In Angular the model would live in a singleton service that gets injected into the views, both views read from the same source.
I tried emulating this approach with Polymer so I can do something like:
<polymer-element name="view1">
<template>
<my-model></my-model>
...
</template>
...
</polymer-element>
<polymer-element name="view2">
<template>
<my-model></my-model>
...
</template>
...
</polymer-element>
I like this approach because it's a declarative way of defining dependencies, and it basically works the same as <core-ajax> and other "out of the box" Polymer elements.
With this way I need to wait for the domReady lifecycle callback before I can interface with any element declared in the template, so this is where I'm holding my initialisation logic at the minute. The problem is that this callback gets called once for each <my-model> element declared (so <my-model> would be initialised twice in this example because it's present both in <view1> and <view2>). To make sure that my model follows the singleton pattern I have to move state outside of the element instance, something like this:
<polymer-element name="my-model">
<script>
(function(){
// private shared state
var instances = [], registered; // pattern variables
var foo; // state, model, whatever
// element init logic
Polymer('my-model', {
// Polymer callbacks
domReady: function(){
if (registered === (registered=true)) return;
// singleton init logic
foo = 'something';
// event handlers
this.addEventListener('foo', function(){ foo += 'baz'; });
},
attached: function() { instances.push(this); },
detached: function(){
instances = instances.filter(function(instance){
return instance !== this;
}.bind(this));
},
// element API
update: doSomething,
get state() { return foo; }
});
// private functions
function doSomething(){ foo += 'bar' }
})();
</script>
</polymer-element>
So it works but it looks wrong to me. Is using <polymer-element> generally incompatible with the singleton pattern? Should I move away from Polymer for models and services? How do Polymer core-elements get away with it?
[EDIT] I added some event listeners to the initialising code above. They're only registered in one instance to avoid the listeners triggering multiple times across multiple instances. What would happen if the instance where the event handlers are declared gets removed? Will that not break the asynchronous logic?
I'd go like this:
Define your model on the main page and call it from your views.
if it gets removed you could:
1 - listen for the "detached" lifecycle callback and inside it register it imperatively or
2 - store stuff on a prototype build in a higher level object and access it the way you fancy the most.
3 - if all fails, (i'm not sure it will but i guess so as i've yet to use this kind of implementation, as of now i talk to php and pass around objects i need persistent) you could use a "prepareForRemoval" knowing you will leave the instance, local storage your stuff and do number 1 then "recoverFromRemoval" if you know what i mean by camel casing prototype suggestions.
Anyways i'm not very fond of singletons. Polymer is powerful front-end stuff but i'm not sure it's the best way to go about it.
in the API docs they do not mention the possibility of getting it cut off (as you can see)
but i honestly think you're right and you would lose your stuff.
That's just my 2 cents actually just a inellegant sollution i came up for at this very moment, maybe #ebidel, #DocDude or #dodson can help us in that matter but you can't really tag em here on SO i'll tag em on G+ for us, you sir got me intrigued.
BTW why would you move away from your main page? there's no point for it in polymer you should change the content dynamically not get away from it. what would be the usage scenario?
ps.: sorry, i hate capitalizing proper nouns.Get over it
EDIT (wouldn't fit on the comments):
I expressed myself wrong. Anyways i strongly think i wasn't understanding what you wanted.
Well, if i got it right this time yes it will fire multiple times (they are supposed to), but it shouldn't cut others out once a particular view gets removed.
As for your initialisation logic i would go about adding a listener to the window or document (i think window is more advisable) itself waiting for the 'polymer-ready' event.
"To make sure that my model follows the singleton pattern I have to
move state outside of the element instance"
Yes thats right. but don't wait for the domready in it's prototype, instead use a construct or contruct-like and call it it as the callback of the aforementioned event listener. i'll edit my answer to make it clearer (if it's not, let me know) when i get back home. i hope you got i meant.
if you don't i'll be back soon.
In browsers, window == singleton object by definition.
Simple use:
var window.instances = [];
var window.registered;
var window.foo;
instead.
Another way is to use Polymer core-meta element:
<core-meta id="x-foo" label="foo"></core-meta>
<core-meta id="x-bar" label="bar"></core-meta>
<core-meta id="x-zot" label="zot"></core-meta>
<core-meta id="apple" label="apple" type="fruit"></core-meta>
<core-meta id="orange" label="orange" type="fruit"></core-meta>
<core-meta id="grape" label="grape" type="fruit"></core-meta>
<h2>meta-data</h2>
<template id="default" repeat="{{metadata}}">
<div>{{label}}</div>
</template>
<h2>meta-data (type: fruit)</h2>
<template id="fruit" repeat="{{metadata}}">
<div>{{label}}</div>
</template>
<script>
document.addEventListener('polymer-ready', function() {
var meta = document.createElement('core-meta');
document.querySelector('template#default').model = {
metadata: meta.list
};
var fruitMeta = document.createElement('core-meta');
fruitMeta.type = 'fruit';
document.querySelector('template#fruit').model = {
metadata: fruitMeta.list
};
});
</script>

KnockoutJS data update writes internal function to UI

I'm having this odd issue when I update my viewmodel...basically with every update, there appears to be a random chance that each observable will contain this data:
function observable() {
if (arguments.length > 0) {
// Write
// Ignore writes if the value hasn't changed
if ((!observable['equalityComparer']) || !observable['equalityComparer'](_latestValue, arguments[0])) {
observable.valueWillMutate();
_latestValue = arguments[0];
observable.valueHasMutated();
}
return this; // Permits chained assignments
} else {
// Read
ko.dependencyDetection.registerDependency(observable); // The caller only needs to be notified of changes if they did a "read" operation
return _latestValue;
}
}
I've been using KnockoutJS for a while, and I've never seen anything like this. My guess is that it has something to do with my template binding, but I'm really not sure. I'm going to dig into it, but I figured I'd post it here in case anyone else is having this issue, or has a solution. Like I said, it doesn't happen consistently, only on occasion.
//// More Information ////
So Matt below referenced this (http://stackoverflow.com/questions/9763211/option-text-becomes-a-function-string-after-updated-with-fromjs), which is roughly the same issue. The only difference is that I'm using the native template binding in a style like this:
<div data-bind="template: {name: 'issueTemplate', data: incidents}"></div>
<script id="dashboardIssueTemplate" type="text/html">
<!--ko foreach: $data-->
<div data-bind="text: title"></div>
</script>
It was my assumption that KnockoutJS handled the unwrapping by itself when you pass the observableArray into the template binder. I know I can't say "title()" in this example, because that doesn't exist. Am I supposed to be binding with a command like $root.title()?
//// Even More Information ////
It appears that this problem occurs as a result of having two "applyBindings" on one page. My application contains an external widget which adds it's DOM to the host page DOM at runtime. That widget is using the ko.applyBindings(vm, ROOTNODE) syntax which should allow for the host page to run it's own ko.applyBindings(hostVm).
In fact, it does, and it works correctly every refresh. The problem however is when the host page does a viewModel update with no refresh. Somehow, the UI rendering spits out this internal function on EVERY data-bound node. I've debugged through KnockoutJS and actually confirmed that the viewModel and rootNode are correct...something outside of the actual binding is taking over.
This has something to do with the "()" appended onto the data object in the template. What I've found is that during the first render (page load) writing the template like this:
<div data-bind="template: {name: 'issueTemplate', data: incidents}"></div>
<script id="dashboardIssueTemplate" type="text/html">
<div data-bind="text: title"></div>
</script>
works just fine. However, once you run the update on the observableArray my "title" object becomes that function. If I write the template using this style:
<div data-bind="text: title()"></div>
It seems to work on every update.
I am not certain why this is the solution. From the looks of it, the data object being passed to the Knockout binder is the exact same on both page load and update. I'll post this as an answer, but I'm not marking it as an answer until I understand why this is happening.

Categories