0

I'm trying to add a back button to payment.html in my module.

I added the button in the html in my module like this:

<!--
 <li id="payment" role="presentation" class="checkout-payment-method" data-bind="fadeVisible: isVisible">
<div id="checkout-step-payment"
     class="step-content"
     data-role="content"
     role="tabpanel"
     aria-hidden="false">
    <!-- ko if: (quoteIsVirtual) -->
        <!-- ko foreach: getRegion('customer-email') -->
            <!-- ko template: getTemplate() --><!-- /ko -->
        <!--/ko-->
    <!--/ko-->
    <form id="co-payment-form" class="form payments" novalidate="novalidate">
        <input data-bind='attr: {value: getFormKey()}' type="hidden" name="form_key"/>
        <fieldset class="fieldset">
            <legend class="legend">
                <span data-bind="i18n: 'Payment Information'"></span>
            </legend><br />
            <!-- ko foreach: getRegion('beforeMethods') -->
                <!-- ko template: getTemplate() --><!-- /ko -->
            <!-- /ko -->
            <div id="checkout-payment-method-load" class="opc-payment" data-bind="visible: isPaymentMethodsAvailable">
                <!-- ko foreach: getRegion('payment-methods-list') -->
                    <!-- ko template: getTemplate() --><!-- /ko -->
                <!-- /ko -->
            </div>
            <button class="button action continue primary" data-bind="goToPrevStep()">
                <span data-bind="i18n: 'Back'"></span>
            </button>

            <div class="no-quotes-block" data-bind="visible: isPaymentMethodsAvailable() == false">
                <!-- ko i18n: 'No Payment method available.'--><!-- /ko -->
            </div>
            <!-- ko foreach: getRegion('afterMethods') -->
                <!-- ko template: getTemplate() --><!-- /ko -->
            <!-- /ko -->
        </fieldset>
    </form>
</div>

But i'm unsure on the best way to add the js. I tried overwriting the payment.js but ofcourse then i also need to copy over the all the dependancies.

Is there any better way to do this.

I know I need something like this

        goToPrevStep:function(){
        stepNavigator.next();
    },

That is dependent on stepNavigator. But i'm unsure where to exactly put it.

Or is this the best solution? I feel like there should be a prettier solution, right?

2

The complete working code for Magento version 2.2.2 is:

checkout_index_index.xml

    <?xml version="1.0"?>
       <page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" layout="1column" xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd">
         <body>
           <referenceBlock name="base-footer-container" remove="false" />
           <move element="secure-shop-logo" destination="micro-header-container" />
            <referenceContainer name="content">
            <referenceBlock name="checkout.root">
              <arguments>
                <argument name="jsLayout" xsi:type="array">
                    <item name="components" xsi:type="array">
                        <item name="checkout" xsi:type="array">
                            <item name="children" xsi:type="array">
                                <item name="steps" xsi:type="array">
                                    <item name="children" xsi:type="array">
                                        <item name="billing-step" xsi:type="array">
                                            <item name="children" xsi:type="array">
                                                <item name="payment" xsi:type="array">
                                                    <item name="children" xsi:type="array">
                                                        <item name="afterMethods" xsi:type="array">

                                                            <item name="children" xsi:type="array">
                                                                <item name="back-button" xsi:type="array">

                                                                 <item name="component"  xsi:type="string">Magento_Checkout/js/view/back-button</item>

                                                                   <item name="displayArea" xsi:type="string">afterMethods</item>
                                                                </item>
                                                            </item>
                                                        </item>
                                                    </item>
                                                </item>
                                            </item>
                                        </item>

                                    </item>
                                </item>
                            </item>
                        </item>
                    </item>
                </argument>
            </arguments>
        </referenceBlock>
    </referenceContainer>
</body>
</page>

app/design/frontend/Vendor/yourTheme/Magento_Checkout/web/template/back-button.html

<div class="actions-toolbar" id="shipping-method-buttons-container">
<div class="primary">
   <div class="back">
      <a data-bind="click: goToPrevStep"
   aria-describedby="checkout-back"
   class="action back">
       <span data-bind="i18n: '&lt; Back'"></span>
     </a>
  </div>
 </div></div>

app/design/frontend/Vendor/yourTheme/Magento_Checkout/web/js/view/back-button.js

 define(
[
    'ko',
    'uiComponent',
    'Magento_Checkout/js/model/step-navigator'
],
function (ko, Component, stepNavigator) {
    "use strict";

return Component.extend({

    defaults: {
        template: 'Magento_Checkout/back-button'
    },

    goToPrevStep: function () {
        stepNavigator.navigateTo('shipping');
    }
})
});

This is the working code for adding a back button to checkout payment step to go back to shipping step.

2

UPDATE: Since I first asked this question I found a better way to do it:

NEW ANSWER:

  1. I extended the LayoutProcessor by making a plugin. Then I added the following code to add the new back button:

    public function aroundProcess(
        \Magento\Checkout\Block\Checkout\LayoutProcessor $subject,
        \Closure $proceed,
        array $jsLayout
    )
    {
        $jsLayoutResult = $proceed($jsLayout);
    
        if ($this->getQuote()->isVirtual()) {
            return $jsLayoutResult;
        }
    
       $jsLayoutResult['components']['checkout']['children']['steps']['children']['billing-step']['children']['payment']['children']['afterMethods']['children']['back-button']['component'] = 'BB_Checkout/js/view/back-button';
    
       return $jsLayoutResult;
    

    }

  2. I have overwritten the step-navigator.js to add a back function:

step-navigator-overwrite.js

define([
'jquery',
'ko'
], function ($, ko) {
'use strict';

var steps = ko.observableArray();

return {
    steps: steps,
    stepCodes: [],
    validCodes: [],

    /**
     * @return {Boolean}
     */
    handleHash: function () {
        var hashString = window.location.hash.replace('#', ''),
            isRequestedStepVisible;

        if (hashString === '') {
            return false;
        }

        if ($.inArray(hashString, this.validCodes) === -1) {
            window.location.href = window.checkoutConfig.pageNotFoundUrl;

            return false;
        }

        isRequestedStepVisible = steps.sort(this.sortItems).some(function (element) {
            return (element.code == hashString || element.alias == hashString) && element.isVisible(); //eslint-disable-line
        });

        //if requested step is visible, then we don't need to load step data from server
        if (isRequestedStepVisible) {
            return false;
        }

        steps.sort(this.sortItems).forEach(function (element) {
            if (element.code == hashString || element.alias == hashString) { //eslint-disable-line eqeqeq
                element.navigate(element);
            } else {
                element.isVisible(false);
            }

        });

        return false;
    },

    /**
     * @param {String} code
     * @param {*} alias
     * @param {*} title
     * @param {Function} isVisible
     * @param {*} navigate
     * @param {*} sortOrder
     */
    registerStep: function (code, alias, title, isVisible, navigate, sortOrder) {
        var hash;

        if ($.inArray(code, this.validCodes) !== -1) {
            throw new DOMException('Step code [' + code + '] already registered in step navigator');
        }

        if (alias != null) {
            if ($.inArray(alias, this.validCodes) !== -1) {
                throw new DOMException('Step code [' + alias + '] already registered in step navigator');
            }
            this.validCodes.push(alias);
        }
        this.validCodes.push(code);
        steps.push({
            code: code,
            alias: alias != null ? alias : code,
            title: title,
            isVisible: isVisible,
            navigate: navigate,
            sortOrder: sortOrder
        });
        this.stepCodes.push(code);
        hash = window.location.hash.replace('#', '');

        if (hash != '' && hash != code) { //eslint-disable-line eqeqeq
            //Force hiding of not active step
            isVisible(false);
        }
    },

    /**
     * @param {Object} itemOne
     * @param {Object} itemTwo
     * @return {Number}
     */
    sortItems: function (itemOne, itemTwo) {
        return itemOne.sortOrder > itemTwo.sortOrder ? 1 : -1;
    },

    /**
     * @return {Number}
     */
    getActiveItemIndex: function () {
        var activeIndex = 0;

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

        return activeIndex;
    },

    /**
     * @param {*} code
     * @return {Boolean}
     */
    isProcessed: function (code) {
        var activeItemIndex = this.getActiveItemIndex(),
            sortedItems = steps.sort(this.sortItems),
            requestedItemIndex = -1;

        sortedItems.forEach(function (element, index) {
            if (element.code == code) { //eslint-disable-line eqeqeq
                requestedItemIndex = index;
            }
        });

        return activeItemIndex > requestedItemIndex;
    },

    /**
     * @param {*} code
     * @param {*} scrollToElementId
     */
    navigateTo: function (code, scrollToElementId) {
        var sortedItems = steps.sort(this.sortItems),
            bodyElem = $.browser.safari || $.browser.chrome ? $('body') : $('html');

        scrollToElementId = scrollToElementId || null;

        if (!this.isProcessed(code)) {
            return;
        }
        sortedItems.forEach(function (element) {
            if (element.code == code) { //eslint-disable-line eqeqeq
                element.isVisible(true);
                bodyElem.animate({
                    scrollTop: $('#' + code).offset().top
                }, 0, function () {
                    window.location = window.checkoutConfig.checkoutUrl + '#' + code;
                });

                if (scrollToElementId && $('#' + scrollToElementId).length) {
                    bodyElem.animate({
                        scrollTop: $('#' + scrollToElementId).offset().top
                    }, 0);
                }
            } else {
                element.isVisible(false);
            }

        });
    },

    /**
     * Next step.
     */
    next: function () {
        var activeIndex = 0,
            code;

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

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

    back: function () {
        var activeIndex = 0,
            code;

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

        if (steps().length > activeIndex - 1) {
            code = steps()[activeIndex - 1].code;
            steps()[activeIndex - 1].isVisible(true);
            window.location = window.checkoutConfig.checkoutUrl + '#' + code;
            document.body.scrollTop = document.documentElement.scrollTop = 0;
        }
    }
};
});
  1. Added the overwrite to

requirejs-config.js

var config = {
'map': {
    '*': {
        'Magento_Checkout/js/model/step-navigator': 'BB_Checkout/js/model/step-navigator-overwrite'
    }
}
 };
  1. created back-button.js

     define([
    'uiComponent',
    'Magento_Checkout/js/model/step-navigator'
       ], function (Component, stepNavigator) {
    'use strict';
    
    return Component.extend({
        defaults: {
            template: 'BB_Checkout/back-button'
        },
    
        /**
         * Back step.
         */
        back: function () {
            stepNavigator.back();
        }
    });
    });
    
  • It's a shame, that if we want to add a back button to each payment-method, we need to override each payment method's html-template. – Daan van den Bergh Jun 18 '18 at 14:49
  • 1
    @DaanvandenBergh That's why i added it to 'afterMethods' this way it shows it below the payment methods ;) – Sanne Jul 16 '18 at 13:05

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.