📜 ⬆️ ⬇️

Asynchronous patterns in Knockout.JS

Knockout.JS is a good library for creating complex web applications. For a long time I lacked an asynchronous template mechanism. I could not implement it until I found out that window.setTimeout calls its callback no earlier than the end of the current context. Those. in code
setTimeout("console.log(window.Value)",0),(function (){while (Math.random() < 0.9999999);window.Value = 1;})() 
Conclusion to the console will occur only after the completion of a long function of a random search for a number very close to one.
An article for those who understand knockout.js and who can write customBindings.

So, knowing the behavior of setTimeout (callback, 0), the implementation is very simple.
Custom-binding code:

 ko.bindingHandlers['asynctemplate'] = { update: function (element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) { $(element).empty(); var template = ko.utils.unwrapObservable(valueAccessor()); if (!template) return; setTimeout(function() { $.ajax({ url: template }).done(function(result){ var view = $(result).appendTo(element)[0]; ko.applyBindings(bindingContext.$data, view); }); }, 0); } } 


Application:
 //     <div data-bind="asynctemplate: '/Templates/Controls/Components/Modal.html'"></div> //     <div data-bind="asynctemplate: templatePath"></div> //    with  foreach <div data-bind="asynctemplate: templatePath, with: templateViewModel">!!</div> 

')
Without a null timeout, problems would arise: if getting the pattern takes a little time, then inserting the pattern into the DOM tree can occur before the knockout binding mechanism gets to the corresponding element. In this case, binding will happen twice, which does not end with anything good.

This problem could have been solved in another way: create an observable template field, into which a plug like “loading ...” is first put, and the template itself is loaded upon receipt of a response from the server. But this provokes blinking in the interface: a loading indication appears for half a second.

If someone is interested, I can add template caching and support for asynctemplate binding at the same time as with and foreach.

UPD: as explained in the xdenser comments , everything is much simpler. The Knockout.js documentation presents a simpler way: add init to custom-binding:

 ko.bindingHandlers['asynctemplate'] = { init: function () { return { controlsDescendantBindings: true }; }, update: function (element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) { $(element).empty(); var template = ko.utils.unwrapObservable(valueAccessor()); if (!template) return; $.ajax({ url: template }).done(function (result) { $(result).appendTo(element); ko.applyBindingsToDescendants(bindingContext, element); }); } } 

+ Fixed a bug when binding the template: in the original version, binding only occurred on the first element.

Source: https://habr.com/ru/post/176143/


All Articles