Categories


Archives


Recent Posts


Categories


UI Component Checkout Step Visibility

astorm

Frustrated by Magento? Then you’ll love Commerce Bug, the must have debugging extension for anyone using Magento. Whether you’re just starting out or you’re a seasoned pro, Commerce Bug will save you and your team hours everyday. Grab a copy and start working with Magento instead of against it.

Updated for Magento 2! No Frills Magento Layout is the only Magento front end book you'll ever need. Get your copy today!

Writing this one down to remember it – hopefully this makes sense to someone else.

The following method is the one responsible for moving the Checkout from one step to another.

#File: vendor/magento/module-checkout/view/frontend/web/js/model/step-navigator.js
next: function() {
    var activeIndex = 0;
    steps.sort(this.sortItems).forEach(function(element, index) {
        if (element.isVisible()) {
            element.isVisible(false);
            activeIndex = index;
        }
    });
    if (steps().length > activeIndex + 1) {
        var code = steps()[activeIndex + 1].code;
        steps()[activeIndex + 1].isVisible(true);
        window.location = window.checkoutConfig.checkoutUrl + "#" + code;
        document.body.scrollTop = document.documentElement.scrollTop = 0;
    }
}    

The first loop hides all the steps, and marks the most recent visible step as active.

var activeIndex = 0;
steps.sort(this.sortItems).forEach(function(element, index) {
    if (element.isVisible()) {
        element.isVisible(false);
        activeIndex = index;
    }
});  

The second makes the next step visible

if (steps().length > activeIndex + 1) {
    var code = steps()[activeIndex + 1].code;
    steps()[activeIndex + 1].isVisible(true);
    window.location = window.checkoutConfig.checkoutUrl + "#" + code;
    document.body.scrollTop = document.documentElement.scrollTop = 0;
}    

The isVisisble method is tricky. Its set when you register a step

#File: vendor/magento/module-checkout/view/frontend/web/js/model/step-navigator.js
//look at the fourth parameter
registerStep: function(code, alias, title, isVisible, navigate, sortOrder) {    
    //...
}

If you look at step registration

#File: vendor/magento/module-checkout/view/frontend/web/js/view/shipping.js

//...

visible: ko.observable(!quote.isVirtual()),

//...

stepNavigator.registerStep(
    'shipping',
    '',
    $t('Shipping'),
    this.visible, _.bind(this.navigate, this),
    10
);    

The this.visible refers to the visible: ko.observable(!quote.isVirtual()) earlier in the file. This means isVisisble in the step is a Knockout Observable. Observables are objects that let you set or retrieve a value – a one value getter/setter. These objects also allow others to listen in for when a value is set, or get. They’re important in KnockoutJS.

So the last part of understanding how isVisisble works? If you look at a Magento Remote Knockout Template for a step object/module (Require JS object/module: Magento_Checkout/shipping, Knockout template: Magento_Checkout/shipping)

#File: vendor/magento//module-checkout/view/frontend/web/template/shipping.html
<li id="shipping" class="checkout-shipping-address" data-bind="fadeVisible: visible()">    

You’ll see the fadeVisisble Knockout binding. This is a custom KnockoutJS binding from Magento, borrowed from the KnockoutJS docs

#File: vendor/magento/module-ui/view/base/web/js/lib/knockout/bindings/fadeVisible.js
/**
 * Copyright © 2016 Magento. All rights reserved.
 * See COPYING.txt for license details.
 */
define([
    'jquery',
    'ko',
], function($, ko) {
    ko.bindingHandlers.fadeVisible = {
        init: function (element, valueAccessor) {
            // Initially set the element to be instantly visible/hidden 
            //depending on the value
            var value = valueAccessor();
            $(element).toggle(ko.unwrap(value)); // Use "unwrapObservable" so we can handle values that may or may not be observable
        },
        update: function (element, valueAccessor) {
            // Whenever the value subsequently changes, slowly fade the 
            // element in or out
            var value = valueAccessor();
            ko.unwrap(value) ? $(element).fadeIn() : $(element).fadeOut();
        }
    };
});

Whenever the observable’s value is updated (i.e. when the step changing code calls .isVisisble(true) or .isVisisble(false)), the update method above gets called, making the element visible. Also, for reasons that aren’t 100% clear to me right now, is a step like payment is initially invisible

#File: vendor/magento/module-checkout/view/frontend/web/template/payment.html       
<li id="payment" role="presentation" class="checkout-payment-method" data-bind="fadeVisible: isVisible">    

    <!-- ... -->

    <!-- ko foreach: getRegion('beforeMethods') -->
        <!-- ko template: getTemplate() --><!-- /ko -->
    <!-- /ko -->

    <!-- ... -->

    <!-- ko foreach: getRegion('payment-methods-list') -->
        <!-- ko template: getTemplate() --><!-- /ko -->
    <!-- /ko -->        

    <!-- ko foreach: getRegion('afterMethods') -->
        <!-- ko template: getTemplate() --><!-- /ko -->
    <!-- /ko -->        

</li>    

its templates won’t be rendered (i.e. fetched) until isVisisble gets called. It’s not clear if this happens because of other subscribers to the observable, or if Knockout doesn’t render something until its actually DOM visible. One thing to maybe do here is grep though the js source for .subscribe calls.

Also, some weird Magento inconsistencies. On an individual step object, the method is always isVisible.

<li ... data-bind="fadeVisible: visible()">  
<li ... data-bind="fadeVisible: isVisible"> 

However, on the RequireJs components that register the two Magento steps, which are the view models of these templates, Magento was inconsistent naming one method visible, the other isVisible. Also, the () on visible seem extraneous. I’m not sure if Knockout removes them and knows its an observable, or if that actually fetches the observable’s value and is equivalent to something like fadeVisible: true/fadeVisible: false.

Copyright © Alana Storm 1975 – 2023 All Rights Reserved

Originally Posted: 12th November 2016

email hidden; JavaScript is required