This is the second part of the Magento OneStep Checkout tutorial. In the first part, I’ve discussed the objective and challenges of the tutorial and laid the foundation for the new checkout extension. Next, I will describe the JavaScript component of the OneStep checkout and give more details on the controller functionality.
If you are impatient, you can download the finished extension from GitHub Solvingmagento_OneStepCheckout now. A demo shop with a working OneStep checkout can be found here.
Other parts of this tutorial are: Part 1: Introducing OneStep Checkout, Part3: Deeper into the Checkout Sections, Part4: Order Review.
Step 5: Starting with the JavaScript
Similarly to the OnePage checkout, I place the JavaScript files of my new extension under the /skin folder (see Figure 3). This allows a more orderly customization of the OneStep checkout should you need to implement it for a custom design package and theme.
The JavaScript component of the new checkout consists of a bootstrap file onestep.js and a set of classes representing the checkout and its sections. The JavaScript files containing these classes are included into the OneStep checkout’s main template onestep.phtml.
The code in the onestep.js file instantiates section objects and the main checkout object, which will control the interactions between the customer and the checkout elements (Listing 18).
var Checkout, Login, Billing, Shipping, ShippingMethod, Payment, Review, switchToPaymentMethod, currentPaymentMethod, login = new Login('login'), billing = new Billing('billing'), shipping = new Shipping('shipping'), shippingMethod = new ShippingMethod('shipping_method'), payment = new Payment('payment'), review = new Review('review'), checkout = new Checkout( { 'login': login, 'billing': billing, 'shipping': shipping, 'shipping_method': shippingMethod, 'payment': payment, 'review': review } ); if (currentPaymentMethod) { payment.currentMethod = currentPaymentMethod; } payment.init(); if (switchToPaymentMethod) { payment.switchMethod(switchToPaymentMethod); } review.updateReview(this, true);
Listing 18. The bootsrap JavaScript file of the OneStep checkout, /skin/base/default/js/solvingmagento/onestepcheckout/onestep.js, line 1.
In Listing 18, I first declare variables Checkout, Login, Billing, Shipping, ShippingMethod, Payment, Review which all refer to the classes that represent the checkout and its sections. The actual values for these variables are set in the respective JavaScript files. By declaring these variables now I avoid referring to non-declared variables later in the code – a problem that JSLint doesn’t hesitate to point out every time I check my JavaScript code with this code quality tool. For the same reason, I must declare variables switchToPaymentMethod and currentPaymentMethod whose values are set globally in the Payment Information section templates (here and here).
Next in Listing 18, I instantiate section objects and pass them to the checkout object that initializes the JavaScript control mechanics of the OneStep checkout. After that, I set the current payment method to the payment object. The currentPaymentMethod is a string referring to a previously selected payment method, if such is available to the current quote. Some payment methods can have a child form (e.g., the credit card form); and setting the currentPaymentMethod variable instructs the Payment Information section to display the child form, if one is present.
It may happen that the payment method list contains only one option. In this case, the methods.phtml template of the Payment Information section will set the switchToPaymentMethod variable. At the end of the Listing 18, onestep.js checks this variable and instructs the payment instance to automatically switch to the only available payment method.
Finally, in Listing 18, the onestep.js file issues a call to the updateReview method of the review instance. This will load the initial HTML for the Order Review section.
Step 6: JavaScript class Checkout
I implement the general JavaScript functionality of the OneStep checkout in class Checkout in file skin/base/default/js/solvingmagento/onestepcheckout/classes/Checkout.js. For this class, as well as for other section classes, I use the Prototype.js Class.create method that creates a class and returns a constructor function. When I call this constructor function (like in Listing 18), Prototype.js executes the initialize method of my class, which means this method must be defined in each of my JavaScript classes’ prototypes. Listing 19 shows the declaration of the Checkout class prototype and its initialize method.
var failureUrl = '/checkout/cart/', /** * variable declarations omitted for brevity */ Checkout = Class.create(); Checkout.prototype = { columnLeft: null, columnCenter: null, columnRight: null, columnUp: null, columnBottom: null, buttonUpdateText: '', buttonWaitText: '', steps: {}, initialize: function (steps) { 'use strict'; this.steps = steps || {}; this.columnLeft = $('osc-column-left'); this.columnCenter = $('osc-column-center'); this.columnRight = $('osc-column-right'); this.columnUp = $('osc-column-up'); this.columnBottom = $('osc-column-bottom'); //localized values can be set in the review/button.phtml template this.buttonUpdateText = buttonUpdateText || 'Update order before placing'; this.buttonSaveText = buttonSaveText || 'Place order'; this.buttonWaitText = buttonWaitText || 'Please wait...'; this.moveTo(this.steps.login, 'up'); this.moveTo(this.steps.billing, 'left'); this.moveTo(this.steps.review, 'bottom'); if (this.steps.shipping && this.steps.shipping.stepContainer && this.steps.shipping.stepContainer.visible() ) { this.moveTo(this.steps.shipping, 'center'); this.moveTo(this.steps.shipping_method, 'right'); this.moveTo(this.steps.payment, 'right'); } else { this.moveTo(this.steps.shipping_method, 'center'); this.moveTo(this.steps.payment, 'center'); } this.observeChanges(); }, /** * code omitted for brevity */ }
Listing 19. Checkout class initialization, /skin/base/default/js/solvingmagento/onestepcheckout/classes/Checkout.js, line 18.
In Listing 19, the Checkout prototype declares properties columnLeft, columnRight, columnCenter, columnUp, and columnBottom that represent the HTML containers making up the checkout layout which shown in Figure 6. The initialize method links these properties to the HTML elements whose IDs are prefixed with “osc-column-”, e.g., “osc-column-left”. The steps property will house checkout section objects that are passed to the initialize method when an instance of Checkout is created in the onestep.js file in Listing 18.
The initialize method is responsible for assigning the HTML elements of individual sections to their respective containers. When the OneStep controller generates the indexAction output, it first places all the section elements into an invisible container “checkoutStepsPreload” in the main checkout template onestep.phtml (Listing 20).
<div class="osc-column-wrapper"> <div id="osc-column-up" class="osc-column up"></div> <div id="osc-column-left" class="osc-column left"></div> <div id="osc-column-center" class="osc-column center"></div> <div id="osc-column-right" class="osc-column right"></div> <div id="osc-column-bottom" class="osc-column bottom"></div> </div> <div id="checkoutStepsPreload"> <?php $i = 0; foreach ($this->getSteps() as $stepId => $stepInfo): ?> <?php if (!$this->getChild($stepId) || !$this->getChild($stepId)->isShow()): continue; endif; $i++ ?> <div id="osc-<?php echo $stepId ?>" class="section<?php echo !empty($stepInfo['complete'])?' saved':'' ?>"> <div id="checkout-step-<?php echo $stepId ?>" class="step a-item"> <div class="step-title"> <span class="number"><?php echo $i ?></span> <span class="step-title"><?php echo $stepInfo['label'] ?></span> </div> <?php echo $this->getChildHtml($stepId) ?> </div> </div> <?php endforeach ?> </div>
Listing 20. OneStep checkout section containers, /app/design/frontend/base/default/template/solvingmagento/onestepcheckout/onestep.phtml, line 22.
When a Checkout instance is created, it invokes its method initialize that moves section elements from the checkoutStepsPreload wrapper into the respective column elements under the “osc-column-wrapper” container. In Listing 19, the initialize method checks, if the Shipping Information step is available. If yes, the respective section is moved to the center column, and the elements of the Shipping Method and Payment Information sections are moved to the right container. If no shipping is necessary, the Payment Information section is moved to the center. The moveTo method, which the checkout object uses, is shown in Listing 21.
moveTo: function (element, id) { 'use strict'; var destination = this['column' + (id.charAt(0).toUpperCase() + id.slice(1))], parent; if (element && element.stepContainer && destination) { parent = element.stepContainer.up(); if (destination !== parent) { destination.insert(element.stepContainer); parent.remove(element.stepContainer); } } },
Listing 21. The moveTo method of the Checkout prototype, /skin/base/default/js/solvingmagento/onestepcheckout/classes/Checkout.js, line 85.
Besides the initialize and moveTo methods, the Checkout class contains functions responsible for validating the checkout data, enabling and disabling loading animations, and updating the interface with the HTML received from the controller. I will explain these routines in more details when discussing the checkout sections.
Step 7: Checkout Method section
The first such section is the Checkout Method. As I’ve already mentioned, the OneStep checkout shows this section to non-authenticated customers allowing them to either login, create an account, or to proceed as a guest. The decision whether to hide or display this section is made in the main OneStep block class method getSteps. My class Solvingmagento_OneStepCheckout_Block_Onestep inherits this method from its OnePage counterpart Mage_Checkout_Block_Onepage (Listing 22).
public function getSteps() { $steps = array(); $stepCodes = $this->_getStepCodes(); if ($this->isCustomerLoggedIn()) { $stepCodes = array_diff($stepCodes, array('login')); } foreach ($stepCodes as $step) { $steps[$step] = $this->getCheckout()->getStepData($step); } return $steps; }
Listing 22. Generating a list of steps for the checkout, /app/code/core/Mage/Checkout/Block/Onepage.php, line 41.
In method getSteps, Magento checks if the customer is logged in and, if yes, excludes the “login” step from the list of the checkout sections. In this case, the loop that outputs the sections’ HTML in the onestep.phtml template will skip the Checkout Method step.
Even if the Checkout Method is shown, the system must decide to display the guest option or not (see the login.phtml template file in Listing 23).
<div class="onestep-step"> <div class="login-method"> <?php echo $this->getMessagesBlock()->getGroupedHtml() ?> <h3><?php if ($this->getQuote()->isAllowedGuestCheckout()) : ?> <?php echo $this->__('Checkout as a Guest or Register') ?> <?php else: ?><?php echo $this->__('Register to Create an Account') ?> <?php endif; ?></h3> <ul class="form-list"> <?php if ($this->getQuote()->isAllowedGuestCheckout()) : ?> <li class="control"> <input type="radio" name="checkout_method" id="login:guest" value="guest" <?php if ($this->getQuote()->getCheckoutMethod() == Mage_Checkout_Model_Type_Onepage::METHOD_GUEST) : ?> checked="checked" <?php endif; ?> class="radio" /> <label for="login:guest"><?php echo $this->__('Checkout as Guest') ?></label> <div class="validation-advice advice-required-entry-checkout_method" style="display:none"> <?php echo Mage::helper('checkout')->__('This is a required field.');?> </div> </li> <?php endif; ?> <!-- radio button input for the "register" checkout method --> </ul> </div> <div class="login-method"> <!-- control elements for the "customer" method --> </div> <div class="checkout-clear-both"></div> </div>
Listing 23. Checkout Information template: hide or show the “guest” method, /app/design/frontend/base/default/template/solvingmagento/onestepcheckout/login.phtml, line 27.
Listing 23 shows a fragment of the Checkout Method template that outputs the available checkout methods (“guest”, “register”, and “customer”). Calling $this->getQuote()->isAllowedGuestCheckout() allows Magento to get the configuration setting of the guest checkout display. The back-end configuration entry under Sales > Checkout isn’t the only thing that can enable or disable the guest checkout. Module Mage_Downloadable also adds a configuration option that allows you to disable the guest checkout for download orders.
When a customer selects one of the checkout methods, the OneStep JavaScript sends an Ajax request which saves the choice to the quote. Before this request is sent, the checkout must validate the section form by checking if one method is selected and displaying validation errors if not. While waiting for the request to be processed, the user interface of the checkout will be showing a “loading” animation next to the section’s title.
The above functionality is provided by the JavaScript class Login (see the classes/Login.js file). This class implements methods which:
- Display the validation result
- Start and stop the “loading” animated GIF image
- Post the form data to the OneStep controller
- Update the interface after the section data is saved.
Two more checkout section classes (Shipping Method and Payment Information) require a similar set of functions, which justifies creation of a common class from which these function can be inherited. I declare this class in file classes/MethodStep.js.
A necessary detour: object MethodStep and the inheritance
MethodStep is a regular JavaScript object that has a number of properties common to three of the section classes of the OneStep checkout. The first such property is stepId, which is used to identify HTML elements belonging to the section. Section templates contain elements with IDs whose suffixes correspond to the section stepId s, e.g., a <div> ID advice-required-entry-payment_method has a suffix payment_method, which is the stepId of the Payment Information section. The value of the stepId property of the MethodStep object is null; and every section class prototype sets its own stepId.
The next property is a function – addValidationAdvice. This function is used to add a hidden <div> container with a validation error message to every radio button element of the section form. Radio button lists in the OneStep checkout are populated by third party extensions, such as payment or shipping modules. I can’t alter their “phtml” template files to include the validation elements but I can modify the DOM structure dynamically using the addValidationAdvice function (Listing 24).
addValidationAdvice: function () { 'use strict'; var advice, clone; //destroy already existing elements $$('li div.advice-required-entry-' + this.stepId).each( function (element) { Element.remove(element); } ); if ($(this.stepId + '-advice-source')) { advice = $(this.stepId + '-advice-source').firstDescendant(); if (advice) { $$('input[name="' + this.stepId + '"]').each( function (element) { clone = Element.clone(advice, true); $(element).up().appendChild(clone); } ); } } },
Listing 24. Adding validation advice containers to the input elements of a checkout section, /skin/frontend/base/default/js/solvingmagento/onestepcheckout/classes/MethodStep.js, line 22.
n Listing 24, I must first destroy validation advice elements which already exist. Everytime a section content is updated by an Ajax request, the checkout JavaScript calls the addValidationAdvice method. Because of this, cleaning up the validation advice remaining from a previous call is necessary to prevent a possible duplicate display of validation errors.
Next in Listing 24, I clone a <div> element containing the validation error from a source container that is declared in the respective section template, like in Listing 25 which shows the source container of the Payment Information template. The cloned <div>s are then added next to the input elements.
<div id="payment_method-advice-source" style="display: none"> <div class="validation-advice advice-required-entry-payment_method" style="display:none"> <?php echo Mage::helper('checkout')->__('This is a required field.');?> </div> </div>
Listing 25. Validation advice elements in the payment Information section template, /app/design/frontend/base/default/template/solvingmagento/onestepcheckout/payment.phtml, line 75.
You can see in Listing 24 and 25 how stepId property is used to identify DOM elements.
Methods startLoader and stopLoader declared in the MethodStep class (Listing 26) issue calls to a checkout instance that toggles the display of the “loading” GIF animations of the checkout sections. Here too, stepId is used to identify the right container element.
stopLoader: function () { 'use strict'; if (checkout) { checkout.toggleLoading(this.stepId + '-please-wait', false); } }, startLoader: function () { 'use strict'; if (checkout) { checkout.toggleLoading(this.stepId + '-please-wait', true); } },
Listing 26. Starting and stopping the step loading animation, /skin/frontend/base/default/js/solvingmagento/onestepcheckout/classes/MethodStep.js. line 50.
Method postData is responsible for sending an Ajax POST request to the OneStep controller. Its code is shown in Listing 27.
postData: function (postUrl, parameters, validatonAdvice) { 'use strict'; $$(validatonAdvice).each( function (element) { $(element).hide(); } ); this.startLoader(); var request = new Ajax.Request( postUrl, { method: 'post', onComplete: this.stopLoader.bind(this), onFailure: checkout.ajaxFailure.bind(checkout), onSuccess: this.onSave, parameters: parameters } ); }
Listing 27. Method postData, /skin/frontend/base/default/js/solvingmagento/onestepcheckout/classes/MethodStep.js, line 76.
In Listing 27, the postData method receives a postUrl parameter pointing to a relevant checkout controller action, a parameters variable containing the serialized input data of the section form as POST parameters, and a validationAdvice string the refers to a class of HTML elements containing the validation error messages (e.g. “div.advice-required-entry-checkout_method”). The posting of data happens only after a successful validation, which is why the postData method hides any possible validation error messages first.
Next in Listing 27, the postData method starts the “loading” animation and issues an Ajax request. The Ajax request binds handlers to its following events:
- “onComplete” – After the request is completed, successfully or not, the stopLoader method will hide the “loading” animation
- “onFailure” – If the request is completed, but the returned HTTP status code is not 2xx (like 404 or 500), the JavaScript will trigger the ajaxFailure Method of the checkout instance that will redirect the customer to the shopping cart page
- “onSuccess” – A successfully completed request will trigger the onSave handler, which every section class defines in its initialize method
The onSave handler is the methodSaved function, which is also declared in the MethodStep object. Here, in object MethodStep, it basically does nothing (Listing28).
methodSaved: function () { 'use strict'; if (this.nothing === undefined) { this.nothing = 0; } },
Listing 28. Method methodSaved, /skin/frontend/base/default/js/solvingmagento/onestepcheckout/classes/MethodStep.js, line 106.
Doing nothing is fine as a response to saving of the Checkout Method selection. Classes ShippingMethod and Payment, however, implement their own methodSaved functions, where the controller response is used to update the checkout interface to reflect the changes caused by saving of the section data (updating a section requires a nearly identical routine shown in Listing 29).
The final method defined in the methodStep class is updateMethods. In Figure 7, which shows the checkout interface, in sections Shipping Method and Payment Information, you can see links “Update shipping options” and “Update payment options”. These links allow customers to update the available shipping and payment options, which may be necessary if the address information has been changed. If the customer clicks one of these links, the checkout will submit the new address data to the OneStep controller, which will return an updated HTML contents of the Shipping Method or Payment Information sections. The returned JSON data are processed by the updateMethods function as shown in Listing 29.
updateMethods: function (transport) { 'use strict'; var response = {}; if (transport && transport.responseText) { response = JSON.parse(transport.responseText); } //the response is expected to contain the update HTML for the payment step if (checkout) { checkout.setResponse(response); } }
Listing 29. Processing the controller response containing a section update, /skin/frontend/base/default/js/solvingmagento/onestepcheckout/classes/MethodStep.js, line 120.
In Listing 29, the controller response is passed to the updateMethods function, which parses the responseText property into a JSON object. The JSON response is then passed to the checkout instance which updates the DOM accordingly (Listing 30).
setResponse: function (response) { 'use strict'; var step; if (response.error) { if ((typeof response.message) === 'string') { alert(response.message); } else { if (window.billingRegionUpdater) { billingRegionUpdater.update(); } alert(response.message.join("\n")); } } else if (response.redirect) { location.href = response.redirect; return true; } for (step in response.update_step) { if (response.update_step.hasOwnProperty(step)) { if ($('checkout-load-' + step)) { $('checkout-load-' + step).update(response.update_step[step]); } /** * Add validation advice and register click handlers on the newly loaded elements */ if (step === 'shipping_method' && this.steps.shipping_method) { this.steps.shipping_method.methodsUpdated(); } if (step === 'payment_method' && this.steps.payment) { this.steps.payment.addValidationAdvice(); if (this.steps.payment.currentMethod) { this.steps.payment.switchMethod(this.steps.payment.currentMethod); } } } } this.observeChanges(); }
Listing 30. Method setResponse of the Checkout class, /skin/frontend/base/default/js/solvingmagento/onestepcheckout/classes/Checkout.js, line 306.
In Listing 30, I return briefly to the Checkout class whose method setResponse is tasked with updating the checkout HTML contents with the JSON response received from the OneStep controller. The JSON response object can contain the following elements:
- error – If this element is set then the setResponse method will display an alert containing an error message. After the customer closes the alert window, the setResponse method resumes the response processing including a possible update of the checkout interface
- message – A string or an array of messages to be displayed in the alert box
- redirect – When set, this element contains a URL to which the customer will be redirected. In this case, the setResponse method will ignore any HTML checkout update passed with the response
- update_step – An object whose element names correspond to the checkout section IDs (e.g., shipping_method). The contents of the update_step object’s elements are the HTML updates rendered by the controller. The setResponse will update the respective checkout section containers with these data.
The last line in the setResponse method is a call to the Checkout object’s method observeChanges. I will tell about its role later when discussing the JavaScript related to the last checkout section – Order Review.
Let’s return to the MethodStep class. This class defines a functionality commonly used by classes Login, ShippingMethod, and Payment. The JavaScript approach to inheritance doesn’t allow me to explicitly declare these classes as children to MethodStep. Instead, I simply copy the properties of the MethodStep class into the prototype objects (Listing 31).
for (property in MethodStep) { if (MethodStep.hasOwnProperty(property)) { if (!Login.prototype[property]) { Login.prototype[property] = MethodStep[property]; } } }
Listing 31. Extending the Login prototype with the properties of the MethodStep object, /skin/frontend/base/default/js/solvingmagento/onestepcheckout/classes/Login.js, line 63.
In Listing 31, I iterate through the properties of the MethodStep object, check if the Login prototype already has a property with the same name, and, if not, copy the property into the prototype object. By making a property check on the prototype, I allow my classes to implement their own methods instead of using the functionality inherited from the MethodStep object.
Classes ShippingMethod and Payment extend their prototypes in the same way as in Listing 31.
Back to the Checkout Method section
Customer interaction in the Checkout Method section is controlled by the Login class implemented in the classes/Login.js file. When a customer selects a checkout method, the Login class posts this selection to the OneStep controller’s saveMethod action (Listing 32).
public function saveMethodAction() { if ($this->_expireAjax()) { return; } if ($this->getRequest()->isPost()) { $method = $this->getRequest()->getPost('checkout_method'); $result = $this->getOnestep()->saveCheckoutMethod($method); $this->getResponse()->setBody(Mage::helper('core')->jsonEncode($result)); } }
Listing 32. The saveMethodAction function of the OneStep controller, controllers/OnestepController.php, line 281.
In Listing 32, the controller receives a POST parameter checkout_method (with a value register or guest) and saves it to the quote. If the saving is successful, the $result variable will be an empty array, otherwise it will contain two elements: error and message. When creating an order in the last step, Magento will use the saved checkout method value to decide whether to create a new account for the customer or not. The saveMethodAction function returns a JSON formatted $result array which is then processed by the checkout JavaScript class instance.
In this part of the tutorial I’ve started describing the functionality of the individual sections of the OneStep checkout starting with “Checkout Method”. In the next part, this discussion will continue: Part3: Deeper into the Checkout Sections.
when i use one step checkout plugin couldn’t able to checkout with Paypal , it shows #10410 invalid token error.Please provide me a solution quickly.