Tag: knockoutjs

KnockoutJs custom bindings in Magento 2

Today I will discuss KnockoutJs two important custom bindings in Magento 2. This is the series of Magento 2 Certified Professional Javascript Developer exam.

Two important knockoutjs bindings are:

1. Knockout Fast Foreach
2. Repeat

How these bindings are the map in magento 2?
vendor/magento/module-ui/view/base/web/js/lib/knockout/bindings/bootstrap.js

1. Knockout Fast Foreach
KnockoutJs default foreach is slow for large data. For overcome this slowness magento use Knockout Fast Foreach out of box. Location lib/web/knockoutjs/knockout-fast-foreach.js. Ex.

<div data-bind="scope: 'outerfasteach-example'">
    <table>
        <thead>
            <tr><th>First name</th><th>Last name</th></tr>
        </thead>
        <tbody data-bind='fastForEach: {data: getCustomerData()}'>
            <tr>
                <td data-bind="text: firstName"></td>
                <td data-bind="text: lastName"></td>
            </tr>
        </tbody>
    </table>
</div>
<script type="text/x-magento-init">
{
    "*": {
        "Magento_Ui/js/core/app": {
            "components": {
                "outerfasteach-example": {
                    "component": "VendorName_ModuleName/js/fast-foreach"
                }
            }
        }
    }
}
</script>

app/code/VendorName/ModuleName/view/frontend/web/js/fast-foreach.js

define([
    'ko',
    'uiComponent',
    'underscore'
], function (ko, Component, _) {
    'use strict';

    return Component.extend({
        customerNameList: ko.observableArray([]),
        /** @inheritdoc */
        initialize: function () {
            this._super();
            var self = this;
            for (var i = 0; i < 5000; i++) {
                self.customerNameList.push({'firstName': 'firstName_'+i, 'lastName': 'lastName_'+i});
            }
        },
        getCustomerData: function () {
            return this.customerNameList;
        }
    });
});

You can use as a node, so attribute name should be outerfasteach. Check here how magento uses in the grid listing:
vendor/magento/module-ui/view/base/web/templates/grid/listing.html

<div class="admin__data-grid-wrap" data-role="grid-wrapper">
    <table class="data-grid" data-role="grid">
       <thead>
            <tr each="data: getVisible(), as: '$col'" render="getHeader()"/>
        </thead>
        <tbody>
            <tr class="data-row" repeat="foreach: rows, item: '$row'" css="'_odd-row': $index % 2">
                <td outerfasteach="data: getVisible(), as: '$col'"
                    css="getFieldClass($row())" click="getFieldHandler($row())" template="getBody()"/>
            </tr>
            <tr ifnot="hasData()" class="data-grid-tr-no-data">
                <td attr="colspan: countVisible()" translate="'We couldn\'t find any records.'"/>
            </tr>
        </tbody>
    </table>
</div>

2. Repeat

You can use repeat binding alternative of foreach. This is also faster than foreach binding.

Here’s a comparison between foreach and repeat for a data table:

<table>
    <tbody data-bind="foreach: data">
        <tr data-bind="foreach: $parent.columns">
            <td data-bind="text: $parent[$data.propertyName]"></td>
        </tr>
    </tbody>
</table>

<table>
    <tbody>
        <tr data-bind="repeat: { foreach: data, item: '$row' }">
            <td data-bind="repeat: { foreach: columns, item: '$col' }"
                data-repeat-bind="text: $row()[$col().propertyName]"></td>
        </tr>
    </tbody>
</table>

For more details, you can check here

Magento uses this in timeline:
vendor/magento/module-ui/view/base/web/templates/timeline/timeline.html

How to create knockoutjs custom binding in Magento 2

Magento 2 uses knockoutjs for rendering UI component. Today I will discuss how you can use existing knockoutjs binding and how you can create a new binding.

What is binding?
Binding or a data binding is a way to link your ViewModels to your Views and vice versa.

What custom bindings are available in knockoutjs?
Magento 2 uses all default knockoutjs bindings and uses some custom bindings too. Most important binding is scope binding. You can read all custom bindings from devdocs. All custom bindings under /vendor/magento/module-ui/view/base/web/js/lib/knockout/bindings

How to use?

Check the scope binding example:

<div class="greet welcome" data-bind="scope: 'customer'">
    <!-- ko if: customer().fullname  -->
    <span class="logged-in" data-bind="text: customer().fullname"></span>
    <!-- /ko -->
</div>
<script type="text/x-magento-init">
{
    "*": {
        "Magento_Ui/js/core/app": {
            "components": {
                "customer": {
                    "component": "Magento_Customer/js/view/customer"
                }
            }
        }
    }
}
</script>

Here scope is customer. Where a component is important and this must me extend uiComponent(vendor/magento/module-ui/view/base/web/js/lib/core/element/element.js).

How to create custom bindings?
1. Create app/code/VendorName/ModuleName/view/frontend/web/js/custom-binding.js

define([
    'ko',
    'Magento_Ui/js/lib/knockout/template/renderer'
], function (ko, renderer) {
    'use strict';

    ko.bindingHandlers.customBinding = {

        init: function(element, valueAccessor, allBindings, viewModel, bindingContext) {
            // This will be called when the binding is first applied to an element
            // Set up any initial state, event handlers, etc. here
            element.innerHTML = 'Custom Binding';
        },
        update: function(element, valueAccessor, allBindings, viewModel, bindingContext) {
            // This will be called once when the binding is first applied to an element,
            // and again whenever any observables/computeds that are accessed change
            // Update the DOM element based on the supplied values here.
        }
    };

    renderer
        .addNode('customBinding')
        .addAttribute('customBinding');
});

2. Now you need to register. Create app/code/VendorName/ModuleName/view/frontend/requirejs-config.js

var config = {
    config: {
        mixins: {
            'Magento_Ui/js/lib/knockout/bindings/bootstrap': {
                'VendorName_ModuleName/js/lib/knockout/bindings/bootstrap': true
            }
        }
    }
};

3. Create app/code/VendorName/ModuleName/view/frontend/web/js/lib/knockout/bindings/bootstrap.js

define(function (require) {
        'use strict';
        return function (target) {
            target.customBinding = require('VendorName_ModuleName/js/custom-binding');
            return target;
        }
    }
);

Now you can able to use this custom binding. Ex:

<div class="greet welcome" data-bind="scope: 'custom-binding-example'">
    <span data-bind="customBinding:'true'"></span>
</div>
<script type="text/x-magento-init">
{
    "*": {
        "Magento_Ui/js/core/app": {
            "components": {
                "custom-binding-example": {
                    "component": "uiComponent"
                }
            }
        }
    }
}
</script>