The days of jQuery are gone for modern Javascript development. Yet I see many of the practices that came with that all-powerful library live on. And some, for good reason. Much of the interfaces that jQuery used are now native in the browser. We can continue to utilize them. Take element.remove() for example. It is now a native method that can be called on DOM elements. No more element.parentNode.removeChild(element) oddities. On the other hand, some of jQuery’s practices are best buried in the graveyard of old technology next to HTML tables and full page layouts with only floats*. Magento 2 ships with a full framework built on top of the Knockout Javascript framework. If you haven’t worked with Knockout, I highly recommend you check out the tutorials and documentation onKnockout’s website. Here is the fundamental difference between a jQuery-first approach and the framework Magento 2 includes: with jQuery, we find an element and then act on it; with Knockout, we associate the element directly with its actions and data.
With jQuery, we find an element and then act on it; with Knockout, we associate the element directly with its actions and data.
With jQuery, or rather its methodology, we first go out and find an element with a particular selector. We then take some actions on that. This usually includes something of the following, for example: adding an event listener, updating some of the attributes on that element, or changing the content. With Knockout, we use the data-bind attribute to describe what actions will occur with that element and what data will be inside it, or just one of them. For a practical example, let’s do an Ajax call when the user clicks a button to load a customized list of products:
<button class="js-load-products">Show similar products</button> <div class="js-product-results"></div>
Note: keep in mind these examples are over-simplified to show concepts. Customization and error checking for your use case is important.
document.querySelector('.js-load-products').addEventListener('click', loadProducts); function loadProducts() { fetch('/user/products/list').then(response => { response.text().then(results => { document.querySelector('.js-product-results').innerHTML = results }) }); }
With Knockout, the following concept would be used:
<button data-bind="click: loadProducts">Show similar products</button> <div data-bind="foreach: products"> <div class="product"> <h4 data-bind="text: name"></h4> <p data-bind="text: shortDescription"></p> </div> </div>
class CustomizedProductList { constructor() { this.products = ko.observableArray([]); } loadProducts() { fetch('/user/products/data').then(response => { response.json().then(results => { this.products(results) }) }); } }
Notice how, in the second example, the Javascript class has no particular knowledge of the DOM, or how it is structured. It only handles data and actions made on that data set. Also note how the server’s response is different in the two examples: in the first example, the server rendered the HTML first and it was then injected into the container. While it would be possible to manage a single-product template within the Javascript, that can be a little cumbersome, especially if it is complex. In the Knockout example, only product data is returned. It then uses the same template to render every product.
There is another important part of the Knockout JS example: the data can be updated, and the DOM will continue to reflect the latest change without any effort. We first add the products to the DOM by changing the data, but we could also add a function that allows for items to be manipulated or removed.
Consider a situation where we were showing users a list of recommended products as options. Each product had buttons on it, to either show more like that product or remove it as a good option. When they choose to remove the item, they click the button which would have a data-bind: click: removeProduct attribute on it. That would call a removeProduct function which would remove that item from the array, and call the server to update their profile. The Javascript is still only handling data.
If you come to Magento 2 and only use jQuery methodologies, you will limit yourself. We have many tools available to us when building an interface for a custom Magento module, but the key is choosing the right tool. Of course, there are not always going to be clear delineations, but understanding the benefits of each will allow you to use the proper one.
One important thing to understand is that Knockout is rendered client-side. This means that when the page first loads, no real data is in the part of the page rendered by Knockout JS. Since many search engine crawlers and similar user agents do not run Javascript, they cannot tell what is on the page. As a result, building components with Knockout is not appropriate for anything that would be beneficial for search engines. Blocks that are unique to a customer or in the admin panel are good places to use Knockout JS. Examples of places it is already used is the checkout, admin product grid, and even the minicart (although, it’s not a great example).
So the next time you reach for $() or, better document.querySelectorAll(), consider if the user interface you are working on would be better built with Knockout JS. If you are extending code that was built with a modern, templated approach, it’s quite likely that you could achieve the desired functionality by adding parameters to the data-bind attribute instead of manually selecting the element first.
This article is the first in a series about using modern Javascript approaches when developing with Magento 2.
Jesse Maxwell
COO / Senior Developer at SwiftOtter - @bassplayer_7