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
});
Related
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.
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).
Just wondering how people deal stopping multiple external server calls? I'm doing everything in the .complete of the fetch because otherwise when I try to call anything the fetch hasn't completed and nothing is populated in the collection.
I'm new to backbone so I'm probably missing a trick.. but is there a way to do a fetch and store that information somewhere so that you never have to fetch again, you just work off the collection as a variable? All of my information comes from an external site, so I don't want to be making lots of unnecessary external calls if I can. I'm not updating the server or anything, its all just read-only.
What do other people do for a similar set up? Am I missing something silly? Or am I set up badly for this? Here's what I have so far (work in progress)
Oh also: I'm doing the fetch in the router.. is that a bad idea?
http://jsfiddle.net/leapin_leprechaun/b8y6L0rf/
.complete(
//after the fetch has been completed
function(){
//create the initial buttons
//pull the unique leagues out
var uniqueLeagues = _.uniq(matches.pluck("league"));
//pull the unique leagues out
var uniqueDates = _.uniq(matches.pluck("matchDate"));
//pass to info to the relative functions to create buttons
getLeagues(uniqueLeagues);
getMatchDates(uniqueDates);
homeBtn();
fetched = true;
}
); //end complete
Thanks for your time!
This is an often recurring question but the answer is rather simple.
Perhaps I'll make some drawings today, if it helps.
I never took the time to learn UML properly, so forgive me for that.
1. The problem
What you currently have is this:
The problem however is that this isn't very dynamic.
If these 3 functions at the right would require to be executed from different ajax callback functions, they need to be added to any of these callbacks.
Imagine that you want to change the name of any of these 3 functions, it means that your code would break instantly, and you would need to update each of these callbacks.
Your question indicates that you feel that you want to avoid every function to perform the async call separately, which is indeed the case because this creates unnecessary overhead.
2. Event aggregation
The solutions is to implement an event driven approach, which works like this:
This pattern is also called pub/sub (or observer pattern) because there are objects that publish events (in this case on the left) and objects that subscribe (on the right).
With this pattern, you don't need to call every function explicitly after the ajax callback is finished; rather, the objects subscribe to certain events, and execute methods when the event gets triggered. This way you are always certain that the methods will be executed.
Note that when triggering an event, parameters can be passed as well, which allows you to access the collection from the subscribing objects.
3. Backbone implementation
Backbone promotes an event driven approach.
Setting up an event aggregator is simple and can be done as follows:
window.APP = {};
APP.vent = _.extend({}, Backbone.Events);
From the ajax callback, you just trigger an event (you give it any name you want, but by convention, a semi colon is used as a separator):
APP.vent.trigger("some:event", collection);
The three receiving objects subscribe to the event as follows:
APP.vent.on("some:event", function(collection){
console.log(collection.toJSON());
});
And that's basically all.
One thing to take into account is to make sure that when you subscribe to events using "on", you also need to un-subscribe by calling "off", if you no longer need the object.
How to handle that is all up to you in Backbone.js but here is one of options you can take
Creating a View which has body as its el and handle everything.(I usually use Coffee so This might has some syntax errors)
$( document ).ready(function() {
mainView = new MainView({el: "body"});
});
MainView = Backbone.View.extend({
initialize : function(){
this.prepareCollection();
},
prepareCollection : function(collection){
_checker = function(){
if (collection.length === _done) {
this.render();
}
};
_.bind(_checker,this);
collection.each(function(item){
item.fetch(
success : function(){
//you can also initialize router here.
_checker();
}
);
});
},
rener : function(){
//make instance of View whichever you want and you can use colleciton just like variable
}
})
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>
My first experiment with angular.js.
I've a few columns, each of them includes some template:
<div class="col-md-5 js-column" ng-repeat="orm in orms" repeat-done="equalHeight">
<h2>{{ orm.name }}</h2>
<ng-include src="'/inc/_compiled/'+orm.id+'.html'"></ng-include>
</div>
Each included template contains the same elements as other templates, but they've different height. Example element:
<pre data-task="model" class="task-model">
from django.db import models
class Teacher(models.Model):
first_name = models.CharField(max_length=30)
last_name = models.CharField(max_length=30)
</pre>
The thing I want to achieve is to set equal height for specific element in all the columns. That means all pre.task-model will have the same height.
I've created some directive and the idea was to trigger it after ngRepeat loop is finished (scope.$last). But when I try to access the included nodes via jQuery/DOM selectors, I get nothing. I know each template is available in element variable, but I need to get also the other columns.
var ormApp = angular
.module('ormApp', [])
.directive('repeatDone', function () {
return function (scope, element, attrs) {
if (scope.$last) {
$('.js-column pre.task-model'); //<- got nothing
}
};
})
As mentioned in the comments, using $timeout solves the problem.
But why ?
The issue here is that several operations involved in the process, such as creating a new element (by ngRepeat), fetching the template (by ngInclude) using $http.get() (even if it comes from the $templateCache), resolving the promises returned etc, are asynchronously and are handled using $evalAsync(), which means that they are going to "happen" when everything else currently on Angular's async queue is processed.
Since there are several levels of nested $evalAsyncs (in this particular case 7), you can achieve what you want by iteratively calling $evalAsync() until the async operations required for fetching and lining the template are completed.
This is of course not a robust (or recommended) way to solve the problem, it is just meant to explain what is going on.
Using $timeout puts the operation in the browser's event queue, so the command will be processed after Angular's current $digest loop is completed (i.e. after all tasks on Angular's async queue have been processed and all taks added to the queue by those async taks etc).
(Another side-effect here is that, since the rendering engine's "render" command is already on the browser event queue, using $timeout will execute the command after the next DOM rendering has taken place. This has no relevance for this particular case, but might be important in other situations.)
See, also, this short demo illustrating both aproaches (open the DevTools console).