Sometime we want to run custom post-processing logic (after DOM ready) on the DOM elements generated by our templates.
For example,
If we're using a JavaScript widgets library such as jQuery UI and want to intercept templates output so that we can run jQuery UI commands on it to transform some of the rendered elements into date pickers, sliders, or anything else.
If we're using require(["jquery", "domReady!"], function($){} function or jQuery(window).load() function,
Using this way mostly our custom Javascript Code execute before the knockout element render.
By Default, If you have called any Javascript Code in the page, Javascript Code doesn't give guarantee for load after page content loading completed.
Magento uses KnockoutJS at many places in the code. For Example : Checkout Page data render done by KnockoutJS.
Generally, the best way to perform such LOGIC's on DOM elements is to write a custom binding, but if we really just want to access the raw DOM elements emitted by a template.
In Knockout js, there is one function called afterRender.
- afterRender function in KnockoutJS used to perform the JS action after knockout DOM loaded completed (or DOM is Ready).
- afterRender binding notifies its subscriber when an associated element is inserted into the DOM.
Pass a function reference (either a function literal, or give the name of a function on your view model), and Knockout will invoke it immediately after rendering or re-rendering our template.
How to prevent custom Javascript Code execute before Knockout Element render using afterRender?
Example - using afterRender
<div data-bind='afterRender: applyOurLogicAfterDOMReady '></div>
In our template(s), we have to define afterRender with data-bind.
Now create/update a function in the JS (component JS) file,
applyOurLogicAfterDOMReady: function(){ // create custom code in above function and they will execute after ko render completed }
If we're using foreach, Knockout will invoke afterRender callback for each item added to our observable array.
afterRender works with template binding only.
How to prevent custom Javascript Code execute before Knockout Element render using custom binding?
we can create custom binding handler that tracks html binding changes and fires callback when value is updated.
Example - 1 - using custom binding
<div data-bind="html: dynamicHtml, afterHtmlRender: customCode"></div>
ko.bindingHandlers.afterHtmlRender = { update: function(element, valueAccessor, allBindings){ // check if element has 'html' binding if (!allBindings().html) { return; } // get bound callback (don't care about context, it's ready-to-use ref to function) var callback = valueAccessor(); // fire callback with new value of an observable bound via 'html' binding callback(allBindings().html); } }
Example - 2 - using custom binding
Use a custom binding. You can either place your custom binding after foreach in the list of bindings in your data-bind or you could execute your code in a setTimeout to allow foreach to generate the content before your code is executed.
running code a single time and running code each time that your observableArray updates:
<table data-bind="foreach: items, updateTableOnce: true"> <tr> <td data-bind="text: id"></td> <td data-bind="text: name"></td> </tr> </table> <table data-bind="foreach: items, updateTableEachTimeItChanges: true"> <tr> <td data-bind="text: id"></td> <td data-bind="text: name"></td> </tr> </table> <button data-bind="click: addItem">Add Item</button>
var getRandomColor = function() { return 'rgb(' + (Math.floor(Math.random() * 256)) + ',' + (Math.floor(Math.random() * 256)) + ',' + (Math.floor(Math.random() * 256)) + ')'; }; ko.bindingHandlers.updateTableOnce = { init: function(element) { $(element).css("color", getRandomColor()); } }; //this binding currently takes advantage of the fact that all bindings in a data-bind will be triggered together, so it can use the "foreach" dependencies ko.bindingHandlers.updateTableEachTimeItChanges = { update: function(element) { $(element).css("color", getRandomColor()); } }; var viewModel = { items: ko.observableArray([ { id: 1, name: "one" }, { id: 1, name: "one" }, { id: 1, name: "one" } ]), addItem: function() { this.items.push({ id: 0, name: "new" }); }; ko.applyBindings(viewModel);