Add a back button to payment.html in module
-
15-04-2021 - |
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?
Solution 2
UPDATE: Since I first asked this question I found a better way to do it:
NEW ANSWER:
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;
}
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;
}
}
};
});
- Added the overwrite to
requirejs-config.js
var config = {
'map': {
'*': {
'Magento_Checkout/js/model/step-navigator': 'BB_Checkout/js/model/step-navigator-overwrite'
}
}
};
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: '< 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>