Magento 2 – Initialize Block Loader on Element Loaded by Knockout.js

knockoutjsmagento2

I'm trying to implement a "block loader" animation on the cart summary element on the cart page while the cart totals are loading.

The template file which loads this is Magento_Checkout/templates/cart/totals.phtml.
It loads the totals with knockout.js and reads the html templates from checkout_cart_index.xml:

<!-- ko template: getTemplate() --><!-- /ko -->
<script type="text/x-magento-init">
        {
            "#cart-totals": {
                "Magento_Ui/js/core/app": <?= /* @escapeNotVerified */ $block->getJsLayout() ?>
            }
        }
</script>

EDIT

Magento has a default loader on the cart totals when the customer clicks on the estimated shipping country, but I find this functionality useless, so I've disabled it.

I'm trying to show the loader animation when the cart page is loading. There is always a delay before the totals show up, so the my goal is to show a loader here before they are fully loaded.

I've made several attempts in Magento_Checkout::templates/cart/totals.phtml

Attempt 1 – through x-magento-init script

<div id="cart-totals" class="cart-totals" data-bind="scope:'block-totals'">
    <!-- ko template: getTemplate() --><!-- /ko -->
    <script type="text/x-magento-init">
        {
            "#cart-totals": {
                "Magento_Ui/js/core/app": <?= /* @escapeNotVerified */ $block->getJsLayout() ?>
            },
            "*": { // I tried with #cart-totals here as well
                "Magento_Ui/js/block-loader": "<?= /* @escapeNotVerified */ $block->getViewFileUrl('images/loader-2.gif') ?>"
            }
        }
    </script>
</div>

Attempt 2 – by triggering processStart on the element

<div id="cart-totals" class="cart-totals" data-bind="scope:'block-totals'" data-mage-init='{"loader": {}}'>
    <!-- ko template: getTemplate() --><!-- /ko -->
    <script type="text/x-magento-init">
        {
            "#cart-totals": {
                "Magento_Ui/js/core/app": <?= /* @escapeNotVerified */ $block->getJsLayout() ?>
            }
        }
    </script>
</div>
<script>
    require([
        'jquery',
        'domReady!'
    ], function ($) {

        $('#cart-totals').trigger('processStart');

    })
</script>

Attempt 3 – data-mage-init

<div id="cart-totals" class="cart-totals" data-bind="scope:'block-totals'" data-mage-init='{"loader": {}}'>
    <!-- ko template: getTemplate() --><!-- /ko -->
    <script type="text/x-magento-init">
        {
            "#cart-totals": {
                "Magento_Ui/js/core/app": <?= /* @escapeNotVerified */ $block->getJsLayout() ?>
            }
        }
    </script>
</div>

Each attempt failed. It just doesn't show the loader.

What am I missing here?

Best Answer

Did you want to add loader into templates/cart/totals.phtml These some ways to add loader spinner into block or page.

1 Use: <div data-bind="blockLoader: isLoadding"> /** Block Content HTML */ </div>,

with this option you should add isLoadding : ko.observable(false) into your Component

Example: Block XML Define

<block class="Magento\Framework\View\Element\Template" name="block-name"
                       template="Namespace_ModuleName::templates.phtml">
    <arguments>
        <argument name="jsLayout" xsi:type="array">
            <item name="components" xsi:type="array">
                <item name="scope_name" xsi:type="array">
                    <item name="component" xsi:type="string">Namespace_ModuleName/js/componentjsfile</item>
                </item>
            </item>
        </argument>
    </arguments>
 </block>

Block templates.phtml CODE

<div id="abc-def" data-bind="scope: 'scope_name'">
    <div class="product-wizard-selections" data-bind="blockLoader: isLoading">
        <div class="cart table-wrapper">
               HTML CONTENT
        </div>
    </div>
</div>

Script

<script type="text/x-magento-init">
    {
        "#abc-def": {
              "Magento_Ui/js/core/app":  <?php /* @escapeNotVerified */ echo $block->getJsLayout(); ?>
        }
    }
</script>

Javascript code: Namespace_ModuleName/js/componentjsfile

define([
    'uiComponent',
    'ko'
], function (Component, ko){
    return Component.extend({
        defaults: {
            addToCartTitle: 'Add To Cart'
        },
        isLoading: ko.observable(false),
        initObservable: function () {
              this._super();
              this.isLoading(true);
        }
    });
});

2: With <div class="loader"> using rjsResolver

js file named "Namespace_ModuleName/js/abc"

define([
    'rjsResolver',
], function (resolver) {
    'use strict';

    /**
     * Removes provided loader element from DOM.
     *
     * @param {HTMLElement} $loader - Loader DOM element.
     */
    function hideLoader($loader) {
        let defaultValue = this;
        $loader.parentNode.removeChild($loader);
    }

    /**
     * Initializes assets loading process listener.
     *
     * @param {Object} config - Optional configuration default data after loaded component
     * @param {HTMLElement} $loader - Loader DOM element.
     */
    function init(config, $loader) {
        resolver(hideLoader.bind(config, $loader));
    }

    return init;
});

PHTML file

<div id="block-loader-id" data-role="abc-loader" class="loading-mask" data-mage-init='{"Namespace_ModuleName/js/abc": {}}'>
    <div class="loader">
        <img src="<?= /* @escapeNotVerified */ $block->getViewFileUrl('images/loader-1.gif') ?>"
             alt="<?= /* @escapeNotVerified */__('Loading...') ?>" style="position: absolute;">
    </div>
</div>
<script>
    require([
        'mage/url',
        'Magento_Ui/js/block-loader'
    ], function (url, blockLoader) {
        blockLoader("<?= /* @escapeNotVerified */ $block->getViewFileUrl('images/loader-1.gif') ?>");
        return url.setBaseUrl('<?= /* @escapeNotVerified */ $block->getBaseUrl() ?>');
    });
</script>