Developing Check Out with AJAX – Part 1

Developing Check Out with AJAX – Part 1

This two-part series will look at the development of Magento checkout to meet the requirements of a client using Enterprise 1.13. We won’t deal with the basics of setting up modules or extending classes. I’ve an earlier series on that. Here, I want to focus on checkout’s end-to-end inner workings — a post I’ve wanted to do for some time.

This series will cover how one page checkout works, how its blocks and templates are organized, how AJAX invokes the one-page controller, instantiates block classes and refreshes page elements, how product totals are calculated and presented, and how the resultant parameters are passed to the order and Admin backend.

We’ll cover all of this in the context of a client’s requirement to add the ability to receive charitable donations in the review step of one-page checkout. You can see what was delivered in these pictures. They show the donations section before and after making a selection, in this example, a charitable donation was made of £10. You can see the live site here.

Before making a selection

review_before_selection

After making a selection

 

review_after_selectionAs an aside – in case you were wondering why ‘Order review’ follows the ‘Delivery option’ step in the above images, the ‘Payment Information’ step was removed from checkout to meet an earlier requirement of the client. You can also see in the second picture how selecting a donation amount reveals information regarding taxation for charitable donations in the United Kingdom.

OK that’s our final destination. In this introduction, part 1 of this series, let’s first take a look at the structure of one page checkout. If you just want the solution, then skip to part 2.

In part 1 let’s try to understand the following: how is the HTML for one page checkout selected, how are the block objects which support HTML templates selected and instantiated and how is AJAX used to provide checkout section processing and refresh? If you’re only reading this because you’re interested in AJAX, then skip to the section ‘Looping the checkout step HTML in onepage.phtml’. That section and the sections which follow, ‘The Prototype JavaScript Framework’ and ‘Deploying AJAX’, will guide you through AJAX’s role in one page checkout.

Let’s start where we put items in our cart and click on Proceed to Checkout. This routes us to checkout with a url of this format: http://mystore.com/checkout/onepage/. The one page controller gets invoked based on the url pattern http://mystore.com/frontName/actionControllerName/actionMethod/. Unless the controller is extended (and ours will need to be later), the frontname ‘checkout’ maps to module Mage_Checkout, and ‘onepage’ maps to controller OnepageController.php. We don’t have an action method, so the default is indexAction(). Lets take a look at this method in Mage_Checkout_OnepageContoller to get some clues about the way in which HTML assembly for one page checkout will be triggered.

Let’s start with the controller – Onepage.php

188public function indexAction()

189{

190if (!Mage::helper(‘checkout’)->canOnepageCheckout()) {

191Mage::getSingleton(‘checkout/session’)->addError($this->__(‘The onepage checkout is disabled.’));

192$this->_redirect(‘checkout/cart’);

193return;

194}

195$quote = $this->getOnepage()->getQuote();

196if (!$quote->hasItems() || $quote->getHasError()) {

197$this->_redirect(‘checkout/cart’);

198return;

199}

200if (!$quote->validateMinimumAmount()) {

201$error = Mage::getStoreConfig(‘sales/minimum_order/error_message’) ?

202Mage::getStoreConfig(‘sales/minimum_order/error_message’) :

203Mage::helper(‘checkout’)->__(‘Subtotal must exceed minimum order amount’);

204

205Mage::getSingleton(‘checkout/session’)->addError($error);

206$this->_redirect(‘checkout/cart’);

207return;

208}

209Mage::getSingleton(‘checkout/session’)->setCartWasUpdated(false);

210Mage::getSingleton(‘customer/session’)->setBeforeAuthUrl(Mage::getUrl(‘*/*/*’, array(‘_secure’ => true)));

211$this->getOnepage()->initCheckout();

212$this->loadLayout();

213$this->_initLayoutMessages(‘customer/session’);

214$this->getLayout()->getBlock(‘head’)->setTitle($this->__(‘Checkout’));

215$this->renderLayout();

216}

The code $this->loadLayout() is shown in red, and is responsible for getting one-page checkout layout blocks and instantiating them ready to be used by the corresponding HTML templates. The call $this->renderLayout() shown in orange is responsible for getting and including the HTML – more on that later.

Take a look at the loadLayout method called by $this->loadLayout() in app/code/core/Mage/Core/Controller/Varien/Action.php, and you’ll find a number of interesting lines of code – shown in red.

Loading layout with the loadLayout method

249public function loadLayout($handles = null, $generateBlocks = true, $generateXml = true)

250{

251// if handles were specified in arguments load them first

252if (false!==$handles && ”!==$handles) {

253$this->getLayout()->getUpdate()->addHandle($handles ? $handles : ‘default’);

254}

255

256// add default layout handles for this action

257$this->addActionLayoutHandles();

258

259$this->loadLayoutUpdates();

260

261if (!$generateXml) {

262return $this;

263}

264$this->generateLayoutXml();

265

266if (!$generateBlocks) {

267return $this;

268}

269$this->generateLayoutBlocks();

270$this->_isLayoutLoaded = true;

271

272return $this;

273}

These calls are responsible for getting onepage’s layout handles, so that Magento can get the blocks associated with those handles for the checkout page and then instantiate them. In my Magento instance these handles for onepage are:-

  • default
  • STORE_default
  • THEME_frontend_nhm_default
  • customer_logged_out or customer_logged_in
  • customer_form_template_Handle
  • checkout_onepage_index

We get the default handle on line 253. The Default handle is picked up for this and most other pages. It’s worth noting now though that if a handle name is supplied as the argument in the call to loadLayout(), then it will provide the first handle for our page rather than ‘default’. You can see this in the logic of line 253. More on this later when we use AJAX to call a controller action method that loads only blocks for a specific section of checkout.

Default is a sort of wrapper handle for all the HTML on a page. You can find the layout for this handle in app/design/frontend/enterprise/default/layout/page.xml. In case you are wondering why my instance of Magento chooses the Enterprise package for its page.xml file, rather than Base or anything else, I’ll come back to that.

Here’s the layout content of page.xml for the default handle stripped down to exclude a lot of the detail.

33<default translate=”label” module=”page”>

34<label>All Pages</label>

35<block type=”page/html” name=”root” output=”toHtml” template=”page/3columns.phtml”>

:

37<block type=”page/html_head” name=”head” as=”head”>

38<action method=”addJs”><script>prototype/prototype.js</script></action>

:

54<action method=”addCss”><stylesheet>css/styles.css</stylesheet></action>

:

63</block>

64

65<block type=”core/text_list” name=”after_body_start” as=”after_body_start” translate=”label”>

66<label>Page Top</label>

67</block>

68

69<block type=”page/html_notices” name=”global_notices” as=”global_notices” template=”page/html/notices.phtml” />

70

71<block type=”page/html_header” name=”header” as=”header”>

:

79<block type=”core/text_list” name=”top.menu” as=”topMenu” translate=”label”>

80<label>Navigation Bar</label>

81<block type=”page/html_topmenu” name=”catalog.topnav” template=”page/html/topmenu.phtml”/>

82</block>

:

88</block>

:

96<block type=”core/text_list” name=”left” as=”left” translate=”label”>

97<label>Left Column</label>

98</block>

:

103<block type=”core/text_list” name=”content” as=”content” translate=”label”>

104<label>Main Content Area</label>

105</block>

106

107<block type=”core/text_list” name=”right” as=”right” translate=”label”>

108<label>Right Column</label>

109</block>

:

116<block type=”page/html_footer” name=”footer” as=”footer” template=”page/html/footer.phtml”>

117<block type=”page/template_links” name=”footer_links” as=”footer_links” after=”footer.newsletter” template=”page/template/links.phtml”/>

118<block type=”page/switch” name=”store_switcher” as=”store_switcher” template=”page/switch/stores.phtml”/>

119</block>

:

125</block>

126

127<block type=”core/profiler” output=”toHtml” name=”core_profiler”/>

128</default>

Interesting here is the name root for the block defined on line 35 in red. As we’ll see later, the block with the name of ‘root’ is the first block to be rendered on a page. Also interesting is the block with name ‘content’ on line 92 in orange, which is the main block containing all checkout steps as we’ll see in a moment.

Carrying on with the loadLayout method, next we get three more handles with the call $this->addActionLayoutHandles() on line 257 of app/code/core/Mage/Core/Controller/Varien/Action.php. Here’s the method called:

275public function addActionLayoutHandles()

276{

277$update = $this->getLayout()->getUpdate();

278

279// load store handle

280$update->addHandle(‘STORE_’.Mage::app()->getStore()->getCode());

281

282// load theme handle

283$package = Mage::getSingleton(‘core/design_package’);

284$update->addHandle(

285‘THEME_’.$package->getArea().’_’.$package->getPackageName().’_’.$package->getTheme(‘layout’)

286);

287

288// load action handle

289$update->addHandle(strtolower($this->getFullActionName()));

290

291return $this;

292}

We get the handles STORE_default and THEME_frontend_nhm_default from lines 280 (red) and 285 (orange) respectively.

The handle checkout_onepage_index is the most interesting, and as we’ll see, this is the handle containing the blocks which will comprise the block named ‘content’ in page.xml. We get it next on line 289 in blue with the call $update->addHandle(strtolower($this->getFullActionName())). As we’ll note it mirrors the url route we took to get here. Note $this->getFullActionName(), which calls this method:

224public function getFullActionName($delimiter=’_’)

225{

226return $this->getRequest()->getRequestedRouteName().$delimiter.

227$this->getRequest()->getRequestedControllerName().$delimiter.

228$this->getRequest()->getRequestedActionName();

229}

The route name (or the module name), the controller name and the action name are stored in the Request object: Mage_Core_Controller_Request_Http(). This object was instantiated way back when, i.e. App::run – _initRequest(). The request object makes it easy to collect route information.

So take note, its here in this method that the request url determines the correct handle to define the content for our page. The url http://mystore.com/checkout/onepage/ determines the handle ‘checkout_onepage_index’ as the key handle for our page.

Before we look at getting the blocks and templates associated with two of the important handles we’ve identified – default and checkout_onepage_index – let’s make mention of the two other handles, we’ll get for this page: customer_logged_out or customer_logged_in and customer_form_template_Handle.

We want a handle for either customer_logged_out or customer_logged_in. This gets added when the event controller_action_layout_load_before is fired following the call $this->loadLayoutUpdates() on line 259 of the loadLayout() method in app/code/core/Mage/Core/Controller/Varien/Action.php (above). The beforeLoadLayout method in Mage_Customer_Model_Observer is run and this code selects the handle as either customer_logged_in or customer_logged_out: $observer->getEvent()->getLayout()->getUpdate()->addHandle(‘customer_logged_’ . ($loggedIn ? ‘in’ : ‘out’)). Finally, the handle customer_form_template_Handle is also obtained as part of the loadLayoutUpdates() method.

So that in a nutshell is how we get the layout handles for our page.

Getting layout blocks

OK. you’re still with me. Next comes getting the blocks associated with those handles. The call in red $this->loadLayoutUpdates() takes care of this within Mage_Core_Model_Layout_Update’s load method: foreach ($this->getHandles() as $handle) {public function merge($handle)}.

Selecting the relevant xlm file from a hierarchy of package and theme files is achieved by calls made to methods in Mage_Core_Model_Design_Package from methods in Mage_Core_Model_Layout_Update. I’m using a workaround which allows the Enterprise package to be easily included in Magento Enterprise 1.13’s package/theme hierarchy. In particular, its this piece of code $configuration = Mage::getStoreConfig(‘design/fallback/fallback’, $this->getStore()) in the module Aoe_DesignFallback (thanks to Fabrizio Branca). It collects the desired hierarchy from the database based on what the Administrator enters into Magento Admin – System-Configuration-Design-Fallback.

fallback_hierarchy

A foreach loop in the _fallback() method of Mage_Core_Model_Design_Package cycles through the array of packages / themes built from what the administrator entered. It does this in package / theme sequence until it finds a file that exists using $this->validateFile($file, $params), shown in red in the code next. If the file exists, then its handles and associated blocks are used. We’ll come back to the _fallback() method again later, because as well as being used to select the relevant layout file, it’s also used to select the relevant template (phtml) file.

392protected function _fallback($file, array &$params, array $fallbackScheme = array(array()))

393{

394if ($this->_shouldFallback) {

395foreach ($fallbackScheme as $try) {

396$params = array_merge($params, $try);

397$filename = $this->validateFile($file, $params);

398if ($filename) {

399return $filename;

400}

401}

402$params[‘_package’] = self::BASE_PACKAGE;

403$params[‘_theme’] = self::DEFAULT_THEME;

404}

405return $this->_renderFilename($file, $params);

406}

Checkout’s content block

We’ve seen the blocks associated with the ‘default’ handle. Here are the blocks associated with the layout handle checkout_onepage_index from app/design/frontend/enterprise/default/layout/checkout.xml.

341<checkout_onepage_index translate=”label”>

342<label>One Page Checkout</label>

343<!– Mage_Checkout –>

344<remove name=”left”/>

345<remove name=”right”/>

346

347<reference name=”root”>

348<action method=”setTemplate”><template>page/1column.phtml</template></action>

349</reference>

350<reference name=”content”>

351<block type=”checkout/onepage” name=”checkout.onepage” template=”checkout/onepage.phtml”>

:

361<block type=”checkout/onepage_login” name=”checkout.onepage.login” as=”login” template=”checkout/onepage/login.phtml”>

362<block type=”page/html_wrapper” name=”checkout.onepage.login.before” as=”login_before” translate=”label”>

363<label>Login/Registration Before</label>

364<action method=”setMayBeInvisible”><value>1</value></action>

365</block>

366</block>

367<block type=”checkout/onepage_billing” name=”checkout.onepage.billing” as=”billing” template=”checkout/onepage/billing.phtml”>

368<block type=”enterprise_customer/form” template=”customer/form/userattributes.phtml” name=”customer_form_customer_user_defined_attributes”>

369<action method=”setFormCode”><code>checkout_register</code></action>

370</block>

371<block type=”enterprise_customer/form” template=”customer/form/userattributes.phtml” name=”customer_form_billing_address_user_defined_attributes”>

372<action method=”setFormCode”><code>customer_register_address</code></action>

373</block>

374</block>

375<block type=”checkout/onepage_shipping” name=”checkout.onepage.shipping” as=”shipping” template=”checkout/onepage/shipping.phtml”>

376<block type=”enterprise_customer/form” template=”customer/form/userattributes.phtml” name=”customer_form_shipping_address_user_defined_attributes”>

377<action method=”setFormCode”><code>customer_register_address</code></action>

378</block>

379</block>

380<block type=”checkout/onepage_shipping_method” name=”checkout.onepage.shipping_method” as=”shipping_method” template=”checkout/onepage/shipping_method.phtml”>

381<block type=”checkout/onepage_shipping_method_available” name=”checkout.onepage.shipping_method.available” as=”available” template=”checkout/onepage/shipping_method/available.phtml”/>

382<block type=”checkout/onepage_shipping_method_additional” name=”checkout.onepage.shipping_method.additional” as=”additional” template=”checkout/onepage/shipping_method/additional.phtml”/>

383</block>

384<block type=”checkout/onepage_payment” name=”checkout.onepage.payment” as=”payment” template=”checkout/onepage/payment.phtml”>

385<block type=”checkout/onepage_payment_methods” name=”checkout.payment.methods” as=”methods” template=”checkout/onepage/payment/info.phtml”>

386<action method=”setMethodFormTemplate”><method>purchaseorder</method><template>payment/form/purchaseorder.phtml</template></action>

387</block>

388<block type=”core/template” name=”checkout.onepage.payment.additional” as=”additional” />

389<block type=”core/template” name=”checkout.onepage.payment.methods_additional” as=”methods_additional” />

390</block>

391<block type=”checkout/onepage_review” name=”checkout.onepage.review” as=”review” template=”checkout/onepage/review.phtml”/>

392</block>

393</reference>

394<update handle=”customer_form_template_handle”/>

395</checkout_onepage_index>

The layout in red shows the reference to the content block we already saw in the ‘default’ handle above. So the block named ‘checkout.onepage’ and all its child blocks make up the ‘content’ of the checkout page. The associated template for ‘checkout.onepage’ is onepage.phtml as we can see on line 351. Also worth noting is that the template for the root block of the default handle is changed in lines 347 to 349 (in blue) to 1column.phtml. The main sub-blocks of ‘content’ are shown in orange with their templates, which are ‘included’ to make up each of the steps in checkout:

  • login.phtml
  • billing.phtml
  • shipping.phtml
  • shipping_method.phtml
  • payment.phtml
  • review.phtml

Before we take a look at onepage.phtml to see how these step templates are included, let’s complete this overview of layout loading and rendering.

Layout blocks instantiation

Before we can render the page, Magento will instantiate the blocks selected for each handle. This is the call $this->generateLayoutBlocks() in the method loadLayout in Mage_Core_Controller_Varien_Action, the great grandparent of OnepageController. This is shown in blue on line 269 for the loadLayout method shown above. The method generateLayoutBlocks() is also found in Mage_Core_Controller_Varien_Action and this in turn calls methods within Mage_Core_Model_Layout which loop through all blocks and instantiate them. We can find $block = new $block($attributes) in the Layout object’s _getBlockInstance method.

Page Rendering

Rendering starts in the indexAction() method of OnepageController.php on line 215 with $this->renderLayout(). You may need to scroll up to the first code excerpt in this post to remind yourself that this was several lines of code after $this->loadLayout. Again, like instantiation, Magento uses methods in Mage_Core_Controller_Varien_Action and hands off to Mage_Core_Model_Layout.

The call $output = $this->getLayout()->getOutput() in the renderLayout() method of Mage_Core_Controller_Varien_Action calls $this->getBlock($callback[0])->$callback[1]() in the getOutput() method, which for its most important iteration equates to $this->getBlock(‘root’)->toHtml(). (The second and final iteration equates to $this->getBlock(‘core_profiler’)->toHtml().)

Magento returns the block Mage_Page_Block_Html with getBlock(‘root’) because ‘root’ is the name of that already instantiated block of block type “page/html” within the default handle of layout. Again, scroll up to see the default handle blocks in the excerpt of code for page.xml.

So in the second part of $this->getBlock(‘root’)->toHtml(), the call is to the toHtml() method in a parent of Mage_Page_Block_Html: Mage_Core_Block_Abstract. The toHtml() method is key and is used not only by ‘root’ blocks but by child blocks called by the getChildHtml() method within an HTML (phtml) file.

Then follows a sequence of calls within methods of Mage_Core_Block_Template (bear with me, the orange is an aide–mémoire) : $html = $this->_toHtml(), $html = $this->renderView() and $html = $this->fetchView($this->getTemplateFile()). The latter of these calls in the renderView() method – $html = $this->fetchView($this->getTemplateFile()) – first gets the filename and then with fetchView() includes the file path with ‘include $includeFilePath’, where in this case $includeFilePath is equal to app/design/frontend/base/default/template/page/1column.phtml. Remember that the associated template for ‘root’ was first set in app/design/frontend/enterprise/default/layout/page.xml as page/3columns.phtml and then changed in app/design/frontend/enterprise/default/layout/checkout.xml to page/1column.phtml.

The call $this->getTemplateFile() (in blue) is also worthy of more discussion, because ultimately it hands off to a method we saw earlier in Mage_Core_Model_Design_Package: _fallback() – see the code for _fallback() above. This method was responsible for selecting the relevant layout file from our package/theme hierarchy. Now it does the same for the template file.

Rendering with getChildHtml()

Before moving on to an in-depth look at the onepage template, let’s take a look at rendering a sub-block. This uses a similar code path to the one we just saw for the root block. Let’s take the example of rendering onepage.phtml when Magento encounters $this->getChildHtml(‘content’) in 1column.phtml – which we’ve just rendered.

Following this call $this->getChildHtml(‘content’), Magento calls the toHtml() method on the ‘content’ block type. (It does this in the _getChildHtml() method of Mage_Core_Block_Abstract). For ‘content’ we know the block type is Mage_Core_Block_Text_List. You can see this in page.xml, shown already in this post: <block type=”core/text_list” name=”content” as=”content” translate=”label”>.

So the call toHtml() on Mage_Core_Block_Text_List again invokes the toHtml() method in Mage_Core_Block_Text_List’s grandparent: Mage_Core_Block_Abstract — because neither Mage_Core_Block_Text_List or Mage_Core_Block_Text (the parent) have this method. However, when we invoked the line $html = $this->_toHtml() from the toHtml() method earlier — in orange — for ‘root’ with block Mage_Page_Block_Html, the _toHtml() method in Mage_Core_Block_Template was invoked and as noted this led to our getting the template file and ‘including’ the HTML.

But here when we invoke the line from $html = $this->_toHtml() from the toHtml() method for ‘content’ with block Mage_Core_Block_Text_List, the _toHtml() method in Mage_Core_Block_Text_List is invoked. This call doesn’t just go on to get the file and include it as we see here in the following code, app/code/core/Mage/Core/Block/Text/List.php.

35protected function _toHtml()

36{

37$this->setText(”);

38foreach ($this->getSortedChildren() as $name) {

39$block = $this->getLayout()->getBlock($name);

40if (!$block) {

41Mage::throwException(Mage::helper(‘core’)->__(‘Invalid block: %s’, $name));

42}

43$this->addText($block->toHtml());

44}

45return parent::_toHtml();

46}

Here we see an interesting distinction between the operation of the block types core/template and core/text_list or Mage_Core_Block_Template and Mage_Core_Block_Text_List.

Mage_Core_Block_Template requires getChildHtml() to get its children. In contrast, as can be seen in the code in line 38 in red, Mage_Core_Block_Text_List gets the children of ‘content’ one by one and calls toHtml() (again) on line 43 in blue to have each of them rendered.

One of the children found in this instance by getSortedChildren() is ‘checkout.onepage’. (There are others, e.g. ‘customer_form_template’.) The block with name ‘checkout.onepage’ has as its block type Mage_Checkout_Block_Onepage. Mage_Checkout_Block_Onepage can trace its ancestry back to Mage_Core_Block_Template, so — and here’s the thing — when the ‘checkout.onepage’ block calls toHtml(), and within toHtml() invokes $html = $this->_toHtml(), the call is made to Mage_Core_Block_Template and the file onepage.phtml is found and the HTML within it is ‘included’.

Now of course, if onepage.phtml also has getChildHtml() calls within it, then each of these blocks will also call toHtml(). If the call is made on a block with ancestry Mage_Core_Block_Template, then the block gets rendered, otherwise with Mage_Core_Block_Text_List in its lineage, it will go through the loop in app/code/core/Mage/Core/Block/Text/List.php _toHtml() again…

Looping the checkout step HTML in onepage.phtml

OK, we’re ready to dive into onepage.phtml. Find it here: app/design/frontend/base/default/template/checkout/onepage.phtml

27<div class=”page-title”>

28<h1><?php echo $this->__(‘Checkout’) ?></h1>

29</div>

30<script type=”text/javascript” src=”<?php echo $this->getJsUrl(‘varien/accordion.js’) ?>”></script>

31<script type=”text/javascript” src=”<?php echo $this->getSkinUrl(‘js/opcheckout.js’) ?>”></script>

32<ol class=”opc” id=”checkoutSteps”>

33<?php $i=0; foreach($this->getSteps() as $_stepId => $_stepInfo): ?>

34<?php if (!$this->getChild($_stepId) || !$this->getChild($_stepId)->isShow()): continue; endif; $i++ ?>

35<li id=”opc-<?php echo $_stepId ?>” class=”section<?php echo !empty($_stepInfo[‘allow’])?’ allow’:” ?><?php echo !empty($_stepInfo[‘complete’])?’ saved’:” ?>”>

36<div class=”step-title”>

37<span class=”number”><?php echo $i ?></span>

38<h2><?php echo $_stepInfo[‘label’] ?></h2>

39<a href=”#”><?php echo $this->__(‘Edit’) ?></a>

40</div>

41<div id=”checkout-step-<?php echo $_stepId ?>” class=”step a-item” style=”display:none;”>

42<?php echo $this->getChildHtml($_stepId) ?>

43</div>

44</li>

45<?php endforeach ?>

46</ol>

47<script type=”text/javascript”>

48//<![CDATA[

49var accordion = new Accordion(‘checkoutSteps’, ‘.step-title’, true);

50<?php if($this->getActiveStep()): ?>

51accordion.openSection(‘opc-<?php echo $this->getActiveStep() ?>’);

52<?php endif ?>

53var checkout = new Checkout(accordion,{

54progress: ‘<?php echo $this->getUrl(‘checkout/onepage/progress’) ?>’,

55review: ‘<?php echo $this->getUrl(‘checkout/onepage/review’) ?>’,

56saveMethod: ‘<?php echo $this->getUrl(‘checkout/onepage/saveMethod’) ?>’,

57failure: ‘<?php echo $this->getUrl(‘checkout/cart’) ?>’}

58);

59//]]>

60</script>

Much more succinct than expected perhaps? We include templates in our checkout page for each checkout step using the loop comprising lines 33 to 45. Templates are included in this loop based on step ID using the method getChildHtml() – see line 42 in red. Step ID is taken from an array $this->getSteps(), line 33, which ultimately accesses this array in the _getStepCodes method of Mage_Checkout_Block_Onepage_Abstract: array(‘login’, ‘billing’, ‘shipping’, ‘shipping_method’, ‘payment’, ‘review’). However, if the customer is logged in, then the getSteps method in Mage_Checkout_Block_Onepage removes ‘login’ from the array as we’d expect: $stepCodes = array_diff($stepCodes, array(‘login’)).

Note that all steps are hidden because of style=”display:none;” on line 41. We’re going to reveal the appropriate step HTML with some Javascript, which uses Magento’s native Prototype library (as of Enterprise 1.4). This little detour should help us later when we come to code our own AJAX functionality later on. If you can’t wait, you could always jump to part 2.

The Prototype JavaScript Framework

If you’ve not had much to do with Prototype in Magento, don’t worry, because the patterns will be similar from elsewhere. Essentially there are three steps:

Firstly, grab some files containing JavaScript code wrapped up in classes. You can see this in lines 30 and 31 in blue. Note the files are located in different places.

Secondly, instantiate the classes required to provide usable objects containing the methods we need. You can see this in line 49 and in lines 53 through 57 in blue, where we instantiate accordion and checkout objects respectively. In these lines, we’re passing parameters to a constructor, known as an ‘initialize’ function in Prototype. ‘Accordion’, as the name implies, deals with the display of checkout, whereas ‘checkout’ deals mainly with AJAX. To the accordion constructor we pass the ordered list ID, defined on line 32, and the div class defined on line 36. We’ll need these DOM markers when Magento starts playing around with what sections are displayed. To the checkout constructor we pass the accordion object – AJAX callbacks will need this when hiding and displaying sections – and some urls, needed later.

Thirdly, call methods in the new objects. You can see an example on line 51, in purple. Here you can see how prototype calls the openSection method in the accordion. Remember, all sections are closed before this method is encountered. We pass $this->getActiveStep(), with ‘opc-‘ prepended, to the method. The method selects either ‘billing’ or ‘login’ depending on whether the customer is logged in – see the getActiveStep method in Mage_Checkout_Block_Onepage which returns $this->isCustomerLoggedIn() ? ‘billing’ : ‘login’;.

Of course, we’ll call the openSection method again later when we move to another checkout step, prepending ‘opc-‘ to whatever checkout step name is targeted. In fact we call it from within the gotoSection method of the Prototype checkout class, which is part of the callback from a checkout object’s AJAX request.

Let’s take a look at the openSection method in the accordion class (js/varien/accordion.js).

27openSection: function(section) {

28var section = $(section);

29

30// Check allow

31if (this.checkAllow && !Element.hasClassName(section, ‘allow’)){

32return;

33}

34

35if(section.id != this.currentSection) {

36this.closeExistingSection();

37this.currentSection = section.id;

38$(this.currentSection).addClassName(‘active’);

39var contents = Element.select(section, ‘.a-item’);

40contents[0].show();

41//Effect.SlideDown(contents[0], {duration:.2});

42

43if (this.disallowAccessToNextSections) {

44var pastCurrentSection = false;

45for (var i=0; i<this.sections.length; i++) {

 

46if (pastCurrentSection) {

47Element.removeClassName(this.sections[i], ‘allow’)

48}

49if (this.sections[i].id==section.id) {

50pastCurrentSection = true;

51}

52}

53}

54}

55},

In red on line 39, you can see how the string passed to the method, in this case either ‘opc-login’ or ‘opc-billing’, is used along with the class ‘.a-item’ to target the div on line 41 on onepage.phtml (see above). The contents of the div are revealed on line 40 with the openSection method using ‘contents[0].show()’. We’re selecting the element that gets found first by using an array item number of 0 in ‘contents’, although in fact there is only one element there.

Note line 41 is commented out on this instance of Magento. This would provide animation for a vertical concertina checkout. I’ve used it, but only for wide incarnations of checkout on the desktop, and then it needs adjustments to facilitate checkout step height issues. For screen widths less than 1024 pixels, I use ‘contents[0].show()’ without the animation, which looks less confusing on tablets and phones.

Ok, so at this stage we know where the content of our checkout page comes from when we move from cart to checkout. The page is populated with templates for each of its steps, with either login.phtml or billing.phtml displayed, depending on whether the customer is logged in. In the review step, we’re going to be making some changes, adding the elements and methods to allow the processing of charitable donations. So let’s take a look at the review step in more depth.

Deploying AJAX

First, how is AJAX used to process what the customer has selected in previous steps and display the review step? Let’s look at moving from checkout payment to checkout review (in fact as mentioned, in my Magento instance, we move from checkout shipping method to checkout review, but that’s another story).

Take a look at review.phtml which populates checkout’s review step.

27<div class=”order-review” id=”checkout-review-load”>

28<!– Content loaded dynamically –>

29</div

Ok, so there’s not much to it. As is apparent, content gets loaded dynamically. As an aside, content gets loaded dynamically for the shipping method and payment steps of checkout as well. Loading content dynamically is what AJAX is good at.

So where do we find the content for the review step? Well there wasn’t much for review in checkout.xml for the checkout_onepage_index handle, only <block type=”checkout/onepage_review” name=”checkout.onepage.review” as=”review” template=”checkout/onepage/review.phtml”/>.

But if we take a look at another excerpt of xml from app/design/frontend/enterprise/default/layout/checkout.xml, we’ll see more. This is the excerpt for the handle <checkout_onepage_review>.

506<checkout_onepage_review translate=”label”>

507<label>One Page Checkout Overview</label>

508<!– Mage_Checkout –>

509<remove name=”right”/>

510<remove name=”left”/>

511

512<block type=”checkout/onepage_review_info” name=”root” output=”toHtml” template=”checkout/onepage/review/info.phtml”>

513<action method=”addItemRender”><type>default</type><block>checkout/cart_item_renderer</block><template>checkout/onepage/review/item.phtml</template></action>

514<action method=”addItemRender”><type>grouped</type><block>checkout/cart_item_renderer_grouped</block><template>checkout/onepage/review/item.phtml</template></action>

515<action method=”addItemRender”><type>configurable</type><block>checkout/cart_item_renderer_configurable</block><template>checkout/onepage/review/item.phtml</template></action>

516<block type=”checkout/cart_totals” name=”checkout.onepage.review.info.totals” as=”totals” template=”checkout/onepage/review/totals.phtml”/>

517<block type=”core/text_list” name=”checkout.onepage.review.info.items.before” as=”items_before” translate=”label”>

518<label>Items Before</label>

519</block>

520<block type=”core/text_list” name=”checkout.onepage.review.info.items.after” as=”items_after” translate=”label”>

521<label>Items After</label>

522</block>

523<block type=”checkout/agreements” name=”checkout.onepage.agreements” as=”agreements” template=”checkout/onepage/agreements.phtml”/>

524<block type=”core/template” name=”checkout.onepage.review.button” as=”button” template=”checkout/onepage/review/button.phtml”/>

525</block>

526<block type=”core/text_list” name=”additional.product.info” translate=”label”>

527<label>Additional Product Info</label>

528</block>

529</checkout_onepage_review>

We’ll need to load this handle and its associated blocks and templates whenever we move to the review step in checkout. These are the blocks that get loaded dynamically.

As we can see in line 512 in orange, the immediate child block is named root with template checkout/onepage/review/info.phtml. As it’s named root, we know that the template info.phtml of this block will be rendered first and then templates of other blocks within ‘root’ starting with each of the calls to getChildHtml(). Let’s take a look at info.phtml.

27<?php echo $this->getChildHtml(‘items_before’); ?>

28<div id=”checkout-review-table-wrapper”>

29<table class=”data-table” id=”checkout-review-table”>

30<?php if ($this->helper(‘tax’)->displayCartBothPrices()): $colspan = $rowspan = 2; else: $colspan = $rowspan = 1; endif; ?>

31<col />

32<col width=”1″ />

33<col width=”1″ />

34<col width=”1″ />

35<?php if ($this->helper(‘tax’)->displayCartBothPrices()): ?>

36<col width=”1″ />

37<col width=”1″ />

38<?php endif; ?>

39<thead>

40<tr>

41<th rowspan=”<?php echo $rowspan ?>”><?php echo $this->__(‘Product Name’) ?></th>

42<th colspan=”<?php echo $colspan ?>” class=”a-center”><?php echo $this->__(‘Price’) ?></th>

43<th rowspan=”<?php echo $rowspan ?>” class=”a-center”><?php echo $this->__(‘Qty’) ?></th>

44<th colspan=”<?php echo $colspan ?>” class=”a-center”><?php echo $this->__(‘Subtotal’) ?></th>

45</tr>

46<?php if ($this->helper(‘tax’)->displayCartBothPrices()): ?>

47<tr>

48<th class=”a-right”><?php echo $this->helper(‘tax’)->getIncExcTaxLabel(false) ?></th>

49<th><?php echo $this->helper(‘tax’)->getIncExcTaxLabel(true) ?></th>

50<th class=”a-right”><?php echo $this->helper(‘tax’)->getIncExcTaxLabel(false) ?></th>

51<th><?php echo $this->helper(‘tax’)->getIncExcTaxLabel(true) ?></th>

52</tr>

53<?php endif; ?>

54</thead>

55<?php echo $this->getChildHtml(‘totals’); ?>

56<tbody>

57<?php foreach($this->getItems() as $_item): ?>

58<?php echo $this->getItemHtml($_item)?>

59<?php endforeach ?>

60</tbody>

61</table>

62</div>

63<?php echo $this->getChildHtml(‘items_after’); ?>

64<script type=”text/javascript”>

65//<![CDATA[

66decorateTable(‘checkout-review-table’);

67truncateOptions();

68//]]>

69</script>

70<div id=”checkout-review-submit”>

71<?php echo $this->getChildHtml(‘agreements’) ?>

72<div class=”buttons-set” id=”review-buttons-container”>

73<p class=”f-left”><?php echo $this->__(‘Forgot an Item?’) ?> <a href=”<?php echo $this->getUrl(‘checkout/cart’) ?>”><?php echo $this->__(‘Edit Your Cart’) ?></a></p>

74<?php echo $this->getChildHtml(‘button’) ?>

75<span class=”please-wait” id=”review-please-wait” style=”display:none;”>

76<img src=”<?php echo $this->getSkinUrl(‘images/opc-ajax-loader.gif’) ?>” alt=”<?php echo $this->__(‘Submitting order information…’) ?>” title=”<?php echo $this->__(‘Submitting order information…’) ?>” class=”v-middle” /> <?php echo $this->__(‘Submitting order information…’) ?>

77</span>

78</div>

79<script type=”text/javascript”>

80//<![CDATA[

81review = new Review(‘<?php echo $this->getUrl(‘checkout/onepage/saveOrder’, array(‘form_key’ => Mage::getSingleton(‘core/session’)->getFormKey())) ?>’, ‘<?php echo $this->getUrl(‘checkout/onepage/success’) ?>’, $(‘checkout-agreements’));

82//]]>

83</script>

84</div>

In our development in part 2, we’ll revisit this to make changes and embed the HTML for the charitable donations section and some javascript too, which will enable us to trigger the refresh of review and recalculate totals based on the customer’s selection.

Let’s take a look at how the review step gets opened and processed. To achieve that, we’ll need to take a look at app/design/frontend/base/default/template/checkout/onepage/payment.phtml. This is the HTML displayed for the payment step in checkout, the step immediately before review.

27script type=”text/javascript”>

28//<![CDATA[

29var quoteBaseGrandTotal = <?php echo (float)$this->getQuoteBaseGrandTotal(); ?>;

30var checkQuoteBaseGrandTotal = quoteBaseGrandTotal;

31var payment = new Payment(‘co-payment-form’, ‘<?php echo $this->getUrl(‘checkout/onepage/savePayment’) ?>’);

32var lastPrice;

33//]]>

34</script>

35<form action=”” id=”co-payment-form”>

36<fieldset>

37<?php echo $this->getChildHtml(‘methods’) ?>

38</fieldset>

39</form>

40<div class=”tool-tip” id=”payment-tool-tip” style=”display:none;”>

41<div class=”btn-close”><a href=”#” id=”payment-tool-tip-close” title=”<?php echo $this->__(‘Close’) ?>”><?php echo $this->__(‘Close’) ?></a></div>

42<div class=”tool-tip-content”><img src=”<?php echo $this->getSkinUrl(‘images/cvv.gif’) ?>” alt=”<?php echo $this->__(‘Card Verification Number Visual Reference’) ?>” title=”<?php echo $this->__(‘Card Verification Number Visual Reference’) ?>” /></div>

43</div>

44<div class=”buttons-set” id=”payment-buttons-container”>

45<p class=”required”><?php echo $this->__(‘* Required Fields’) ?></p>

46<p class=”back-link”><a href=”#” onclick=”checkout.back(); return false;”><small>« </small><?php echo $this->__(‘Back’) ?></a></p>

47<button type=”button” class=”button” onclick=”payment.save()”><span><span><?php echo $this->__(‘Continue’) ?></span></span></button>

48<span class=”please-wait” id=”payment-please-wait” style=”display:none;”>

49<img src=”<?php echo $this->getSkinUrl(‘images/opc-ajax-loader.gif’) ?>” alt=”<?php echo $this->__(‘Loading next step…’) ?>” title=”<?php echo $this->__(‘Loading next step…’) ?>” class=”v-middle” /> <?php echo $this->__(‘Loading next step…’) ?>

50</span>

51</div>

52<script type=”text/javascript”>

53//<![CDATA[

54function toggleToolTip(event){

55if($(‘payment-tool-tip’)){

56$(‘payment-tool-tip’).setStyle({

57top: (Event.pointerY(event)-560)+’px’//,

58//left: (Event.pointerX(event)+100)+’px’

59})

60$(‘payment-tool-tip’).toggle();

61}

62Event.stop(event);

63}

64if($(‘payment-tool-tip-close’)){

65Event.observe($(‘payment-tool-tip-close’), ‘click’, toggleToolTip);

66}

67//]]>

68</script>

69<script type=”text/javascript”>

70//<![CDATA[

71payment.currentMethod = “<?php echo $this->getChild(‘methods’)->getSelectedMethodCode() ?>”;

72//]]>

73</script>

Ok, a lot of code here again, but the bits that we’re interested in are in red and blue. The code on line 31 in red instantiates the payment object. Note the payment class is already in the DOM. It’s in the file js/opcheckout.js that was opened on line 31 in onepage.phtml. Scroll up to see, if you need a reminder.

Take a look at the code on line 31. Prototype is passing two arguments to the payment class’s constructor, ‘initialize’: the name of this form as shown on line 35 and the url of the action method to be run on save.

Line 47 in blue specifies the payment method that will be run when the customer clicks on the Continue button: payment.save.

First, take a look at the initialize function of the payment class in skin/frontend/base/default/js/opcheckout.js.

683var Payment = Class.create();

684Payment.prototype = {

685beforeInitFunc:$H({}),

686afterInitFunc:$H({}),

687beforeValidateFunc:$H({}),

688afterValidateFunc:$H({}),

689initialize: function(form, saveUrl){

690this.form = form;

691this.saveUrl = saveUrl;

692this.onSave = this.nextStep.bindAsEventListener(this);

693this.onComplete = this.resetLoadWaiting.bindAsEventListener(this);

694},

In blue, we can see how the form ID and the url that were passed to this constructor are stored as this.form and this.saveUrl respectively. In red, we see how the method this.nextStep with this payment object as an argument is stored for use in this.onSave. In Prototype we use the bindAsEventListener function so that the ‘this’ reference will be available when the relevant event is triggered.

So that’s what happens when the payment object is instantiated. Now let’s take a look at what happens when the customer clicks on Continue and invokes the payment.save function — also in the payment class of skin/frontend/base/default/js/opcheckout.js:

829save: function(){

830if (checkout.loadWaiting!=false) return;

831var validator = new Validation(this.form);

832if (this.validate() && validator.validate()) {

833checkout.setLoadWaiting(‘payment’);

834var request = new Ajax.Request(

835this.saveUrl,

836{

837method:’post’,

838onComplete: this.onComplete,

839onSuccess: this.onSave,

840onFailure: checkout.ajaxFailure.bind(checkout),

841parameters: Form.serialize(this.form)

842}

843);

844}

845},

OK, here we finally see Prototype’s ‘Ajax.Request’ object. Like other javascript libraries such as JQuery, Prototype provides an Ajax object which uses the transport XmlHttpRequest with browser differences safely abstracted from the user. The request has two parameters: the first is the url of the request; the second is an array which can contain the HTTP method, callback methods and parameters.

The Ajax.Request object is instantiated on line 834 in blue. The url of the request is this.saveUrl, which, as we saw in payment.phtml, where the payment object was instantiated, is checkout/onepage/savePayment. We should also note the callback method ‘onSuccess’ on line 839 that invokes this.onSave, which is the nextStep method as we saw in the initialize function.

Finally, on line 841, we can see which parameters are passed for the request. The method Form.serialize sends the contents of this.form, which as we see from payment.phtml, where the payment object was instantiated, has the ID co-payment-form. The content of the form is just the payment method in this particular example.

JQuery users will relate to Form.serialize as the .serialize method is available in that library too. If you are interested, I looked at AJAX with JQuery in an earlier post here.

Where to now? Well the AJAX request invokes the savePaymentAction() method in OnepageController.php. Here’s the code:

463public function savePaymentAction()

464{

465if ($this->_expireAjax()) {

466return;

467}

468try {

469if (!$this->getRequest()->isPost()) {

470$this->_ajaxRedirectResponse();

471return;

472}

473

474$data = $this->getRequest()->getPost(‘payment’, array());

475$result = $this->getOnepage()->savePayment($data);

476

477// get section and redirect data

478$redirectUrl = $this->getOnepage()->getQuote()->getPayment()->getCheckoutRedirectUrl();

479if (empty($result[‘error’]) && !$redirectUrl) {

480$this->loadLayout(‘checkout_onepage_review’);

481$result[‘goto_section’] = ‘review’;

482$result[‘update_section’] = array(

483‘name’ => ‘review’,

484‘html’ => $this->_getReviewHtml()

485);

486}

487if ($redirectUrl) {

488$result[‘redirect’] = $redirectUrl;

489}

490} catch (Mage_Payment_Exception $e) {

491if ($e->getFields()) {

492$result[‘fields’] = $e->getFields();

493}

494$result[‘error’] = $e->getMessage();

495} catch (Mage_Core_Exception $e) {

496$result[‘error’] = $e->getMessage();

497} catch (Exception $e) {

498Mage::logException($e);

499$result[‘error’] = $this->__(‘Unable to set Payment Method.’);

500}

501$this->getResponse()->setBody(Mage::helper(‘core’)->jsonEncode($result));

502}

In purple we can see where the method gets the posted parameters on line 474 and saves them on line 475. Critical for us here is a method invoked a little further downstream as a result of the savePayment() call. It’s this this->getQuote()->collectTotals() in Mage_Sales_Model_Quote_Payment. We’ll want to remember this call as we’ll need to use it in our solution in part 2. The call is made throughout checkout: when adding items to cart, when moving to cart view, when proceeding to checkout, and when saving each of the steps of checkout with the exception of login/register.

In a nutshell this->getQuote()->collectTotals() cycles through each of the addresses used in our quote; unless we’re using multiple shipment addresses these just happen to be a billing address and one shipping address. Within each address, Magento cycles through total collector models, each of which is used to calculate a total making up the totaling of our order. Native examples of total collector models are subtotal, shipping total, discount total, tax total and grand total. Other custom examples that I’ve developed include drop shipper shipping total, drop shipper subtotal and, as we’ll see in part two, charitable donation total.

In red on line 480, we see loadLayout(), and as we’ve already alluded to in this post, by giving the call a handle as its argument, it means that this handle rather than the default handle will be loaded. Makes sense, we’re only replacing the review block. We don’t want all of the blocks comprising default loading too. So with loadLayout(), we’ve the block objects we need and their methods, which we can call from within the templates making up the review step.

In blue, on lines 481 through 484, we build the key elements of the array $result, which will be used by the AJAX call back methods that we saw in the payment.save function of the Prototype payment class. Specifically, JavaScript will need to know which section to reveal and the HTML that comprises it.

Note _getReviewHtml() on line 484 which gets the HTML for the review step. It’s essential that we get the HTML for this page at this point, after totals have been recalculated and blocks have been loaded and instantiated. The method _getReviewHtml() runs this code: return $this->getLayout()->getBlock(‘root’)->toHtml(). This is how we render the HTML when we’re using AJAX.

Finally in this code in orange on line 501, we see where a response object is created with a body that comprises a JSON object, which in this example looks like this {“goto_section”:”review”,”update_section”:{“name”:”review”,”html”:”HTML“} — where HTML is all the prepared HTML making up the review step section.

If you’re not familiar with the response object in Magento, it is the logical counterpart to the request object. It collects headers and content so that they may be returned with the sendResponse() method in Mage_Core_Controller_Response_Http. In this method the event http_response_send_before is fired, allowing developers to modify the output with an observer before it sent. Note sendResponse() ultimately fires sendResponse() and outputBody() in Zend_Controller_Response_Abstract — if you want to trace where ‘echo’ is ultimately called.

So what happens next? We’ve already noted that ‘onSuccess’ the javascript ‘nextStep’ function is invoked on return from the AJAX request. So take a look at the code for that in the payment class of skin/frontend/base/default/js/opcheckout.js

851nextStep: function(transport){

852if (transport && transport.responseText){

853try{

854response = eval(‘(‘ + transport.responseText + ‘)’);

855}

856catch (e) {

857response = {};

858}

859}

860/*

861* if there is an error in payment, need to show error message

862*/

863if (response.error) {

864if (response.fields) {

865var fields = response.fields.split(‘,’);

866for (var i=0;i<fields.length;i++) {

867var field = null;

868if (field = $(fields[i])) {

869Validation.ajaxError(field, response.error);

870}

871}

872return;

873}

874alert(response.error);

875return;

876}

877

878checkout.setStepResponse(response);

879

880//checkout.setPayment();

881},

On line 854 in orange, we use the responseText method to capture the string of name/value pairs from the returned object ‘transport’: {“goto_section”:”review”,”update_section”:{“name”:”review”,”html”:”HTML“}; then using the function eval this is converted to a JSON object for further interrogation.

On line 878 in red, we invoke the setStepResponse method on the checkout object. You may recall that the checkout object was instantiated in onepage.phtml above. See line 31 of app/design/frontend/base/default/template/checkout/onepage.phtml above.

Let’s take a look now at the setStepResponse method of the checkout class in skin/frontend/base/default/js/opcheckout.js.

261setStepResponse: function(response){

262if (response.update_section) {

263$(‘checkout-‘+response.update_section.name+’-load’).update(response.update_section.html);

264}

265if (response.allow_sections) {

266response.allow_sections.each(function(e){

267$(‘opc-‘+e).addClassName(‘allow’);

268});

269}

270

271if(response.duplicateBillingInfo)

272{

273this.syncBillingShipping = true;

274shipping.setSameAsBilling(true);

275}

276

277if (response.goto_section) {

278this.gotoSection(response.goto_section, true);

279return true;

280}

281if (response.redirect) {

282location.href = response.redirect;

283return true;

284}

285return false;

286}

In red on line 263, we replace the HTML for the element with the ID checkout-review-load with the html from the response object. The review HTML is still hidden on the page, but within the checkout object’s gotoSection method — which is called on line 278 in blue — there’s a hand off to the accordion object which takes care of revealing and hiding sections: this.accordion.openSection(‘opc-‘ + section), where section equals ‘review’. We saw the openSection method earlier in the accordion class of the file js/varien/accordion.js. Scroll up for a reminder. The method hides the payment section and displays the review section.

That’s all that we cover here. In this post, we’ve looked at checkout from end to end. We’ve seen how the checkout is loaded and rendered as we move from cart to checkout. Taking the review step as our example, we’ve seen how sections are refreshed, loaded and rendered, paying attention to how AJAX facilitates a smooth transition.

In the next post, we’ll take what we’ve learned here and apply it in the context of effectively creating a new section within ‘review’ for receiving donations input from the customer. We’ll create new blocks and templates, calculate and fetch new totals, and use AJAX to invoke custom controller methods and refresh the review section.


david.mann

Leave a Reply

Your email address will not be published. Required fields are marked *