I’m still digging into the new Magento 1.9 CE responsive web design (RWD) theme, but one thing I noticed immediately (since it conflicted with my new Custom Checkout Step extension) is how the theme needed to replace a method in the Prototype JS Checkout
class.
#File: skin/frontend/rwd/default/js/opcheckout_rwd.js
Checkout.prototype.gotoSection = function (section, reloadProgressBlock) {
// Adds class so that the page can be styled to only show the "Checkout Method" step
if ((this.currentStep == 'login' || this.currentStep == 'billing') && section == 'billing') {
$j('body').addClass('opc-has-progressed-from-login');
}
if (reloadProgressBlock) {
this.reloadProgressBlock(this.currentStep);
}
this.currentStep = section;
var sectionElement = $('opc-' + section);
sectionElement.addClassName('allow');
this.accordion.openSection('opc-' + section);
// Scroll viewport to top of checkout steps for smaller viewports
if (Modernizr.mq('(max-width: ' + bp.xsmall + 'px)')) {
$j('html,body').animate({scrollTop: $j('#checkoutSteps').offset().top}, 800);
}
if (!reloadProgressBlock) {
this.resetPreviousSteps();
}
}
This little bit of javascript changed the definition of the gotoSection
method of the Prototype JS Checkout
class. It looks like the Llamas, Falkowski, and/or anyone else involved wanted to add some animation effects as you progressed through each step. Normally this method looks like this
#File: skin/frontend/base/default/js/opcheckout.js
gotoSection: function (section, reloadProgressBlock) {
if (reloadProgressBlock) {
this.reloadProgressBlock(this.currentStep);
}
this.currentStep = section;
var sectionElement = $('opc-' + section);
sectionElement.addClassName('allow');
this.accordion.openSection('opc-' + section);
if(!reloadProgressBlock) {
this.resetPreviousSteps();
}
},
This is a completely valid technique — the Prototype JS class system was designed to allow you to do things like this. There are, however, two potential problems here.
The first is, the original gotoSection
method never gets called when you use this technique. This means repeating the original code in your new method. This is similar to creating a Magento rewrite without calling parent::methodName
. When I needed to do something similar for Custom Checkout Step, I used a technique like this
Checkout.prototype.namespaceOriginalGotoSection = Checkout.prototype.gotoSection;
Checkout.prototype.gotoSection = function(section,reloadProgressBlock)
{
//... custom code here ...
this.namespaceOriginalGotoSection(section,reloadProgressBlock);
};
Here we’ve used javascript’s flexible object system/first class functions to store a copy of the gotoSection
method, replace it, and then call the original method after our new method has done its stuff. This technique is a bit more bulletproof, and ensures that any changes made to the gotoSection
method in the future will be picked up by our modification.
In fact, because I used this technique in Custom Checkout Step, my extension was mostly compatible with the new responsive theme in Magento 1.9. Where it wasn’t compatible brings me to the second problem with the new RWD theme’s gotoStep
method: hard coded assumptions. Specifically, this conditional
#File: skin/frontend/rwd/default/js/opcheckout_rwd.js
if ((this.currentStep == 'login' || this.currentStep == 'billing') && section == 'billing') {
$j('body').addClass('opc-has-progressed-from-login');
}
This bit of code adds the class opc-has-progressed-from-login
to the document’s <body/>
tag, and is meant to signal when/if the user has progressed on from the Checkout Method step. It’s a hook for CSS animation effects. The problem here is the conditional
if (this.currentStep == 'login' || this.currentStep == 'billing') && section == 'billing'
In english this says “If the current step is login
or billing
, AND the section we’re moving to is billing
”. The problem with this is it assumes no one has added any steps to the checkout. When a Custom Checkout Step extension user has their step immediately after the “Checkout Method” step, it means the new $j('body').addClass('opc-has-progressed-from-login');
never gets called. Or, it never got called until I released the 1.0.1
version of the extension.
While this is a valid critique of the RWD reference template, it’s pretty understandable how/why it happened. The Magento One Page Checkout really wasn’t designed to be modified like this — and ironically the checkout is one of the most modified systems in Magento. There’s always going to be conflicts. A big part of my planning for the Custom Checkout Step extension was budgeting enough time for the inevitable support requests.
So, with all deference given to different programming styles and tradeoffs, my approach here would have been slightly different. First, with the conditional, I would have tried one of the following
if (section != 'login')
...
if ((this.currentStep == 'login' || this.currentStep == 'billing') && section != 'login')
Namely, if you’re going somewhere that’s not the login, my gut says it’s safe to assume that the one page checkout has proceeded from the login. Instead of checking the specific cases that exists now, we’re checking for all cases that might exist in the future.
Second, I’d have broken the new animation functionality lines out into their own methods
Checkout.prototype.gotoSection = function (section, reloadProgressBlock) {
// Adds class so that the page can be styled to only show the "Checkout Method" step
this.flagHasProgressedFromLogin();
if (reloadProgressBlock) {
this.reloadProgressBlock(this.currentStep);
}
this.currentStep = section;
var sectionElement = $('opc-' + section);
sectionElement.addClassName('allow');
this.accordion.openSection('opc-' + section);
// Scroll viewport to top of checkout steps for smaller viewports
this.fixViewportScroll();
if (!reloadProgressBlock) {
this.resetPreviousSteps();
}
};
Checkout.prototype.flagHasProgressedFromLogin = function() {
if ((this.currentStep == 'login' || this.currentStep == 'billing') && section == 'billing') {
$j('body').addClass('opc-has-progressed-from-login');
}
};
Checkout.prototype.fixViewportScroll = function() {
if (Modernizr.mq('(max-width: ' + bp.xsmall + 'px)')) {
$j('html,body').animate({scrollTop: $j('#checkoutSteps').offset().top}, 800);
}
};
By adding new flagHasProgressedFromLogin
and fixViewportScroll
methods to the Checkout
class, we’ve made it much easier for a third party developer to change any of the logic they need to without interfering with the core behavior of the gotoSection
method.
All in all though, regardless of these small points of high nerdery, it’s going to be nice working in a modern frontend framework in an officially supported way.