Question

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?

Was it helpful?

Solution 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();
        }
    });
    });
    

OTHER TIPS

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.

There is an easier way to do this, overriding in your theme just the templates file for the payment; for example, override vendor/magento/module-purchase-order/view/frontend/web/template/payment/purchaseorder-form.html in your theme and add where you want the button to appear:

<div data-bind="scope: 'checkout.progressBar'">
                <!-- ko foreach: { data: steps().sort(sortItems), as: 'item' } -->
                    <!-- ko if: item.code === 'shipping' -->
                        <div class="secondary">
                            <button class="primary action" data-bind="click: $parent.navigateTo">
                                <span translate="'Go Back'" />
                            </a>
                        </div>
                    <!-- /ko -->
                <!-- /ko -->
            </div>
Licensed under: CC-BY-SA with attribution
Not affiliated with magento.stackexchange
scroll top