This is a follow-on post to a recent series where I provided administrators with an option to add a despatch date in Admin, present this in the order grid and include it in an order update email. In this post, we want to ‘ajaxify’ the user experience. We’ll update the database with the new date without having to refresh the order view page.
We created most of the following code in earlier posts. See here and here. This code provides a field in the order view page, and posts the user’s entry to the controller we set up in this post.
We created the code shown in blue and green in app/design/adminhtml/default/default/template/despatchdate/fields.phtml. In this post, we’ll retain the blue and delete the green. We created the orange code originally here in js/jquery/data_validation.js. In this post, we’re adding the jquery in data_validation.js to the fields.phtml file. The code in red is new code we’re adding to fields.phtml in this post.
1<div>
2<div class=”entry-edit-head”>
3<h4><?php echo $this->__(‘Estimated Dispatch Date (d/m/yy)’) ?></h4>
4</div>
5<div class=”fieldset”>
6<form id=”dateform” action=”<?php echo $this->getUrl(‘despatchdate/form/post’)?>” method=”POST”>
7<input type=”hidden” name=”form_key” value=”<?php echo $this->getFormKey(); ?>” />
8<!– <input type=”hidden” name=”url” value=”<?php echo Mage::getUrl(“adminhtml/sales_order/view”,array(‘_current’ => true)); ?>” /> –>
9<input type=”hidden” name=”record_id” value=”<?php echo $this->getRequest()->getParam(‘order_id’); ?>” />
10<input type=”text” class=”input-text” id=”estDestDate” name=”estDate” value=”<?php echo $this->getDestDate(); ?>” />
11<img title=”Select date” id=”estDestDate_trig” src=”<?php echo Mage::getBaseUrl(Mage_Core_Model_Store::URL_TYPE_SKIN) . ‘adminhtml/default/default/images/grid-cal.gif’;?>” class=”v-middle”>
12<input type=”submit” class=”form-button” id=”submit” name=”submit” value=”Submit Date”/>
13</form>
14</div>
15<div id=”errorwarning” style=”color:#eb5e00; font-weight:bold;”></div>
16</div>
17<script type=”text/javascript”>
18Calendar.setup({
19inputField : ‘estDestDate’,
20ifFormat : ‘%e/%m/%y’,
21button : ‘estDestDate_trig’,
22align : ‘Bl’,
23singleClick : true
24});
25</script>
26<script type=”text/javascript”>
27//<![CDATA[
28var $j=jQuery.noConflict();
29$j(“document”).ready (function(){
30$j(‘#dateform’).bind(‘submit’,function(){
31var dateentered = $j(‘#estDestDate’).val();
32var components = dateentered.split(“/”);
33if(components.length !== 3){
34$j(‘#errorwarning’).html(‘<p>Please enter a valid date as d/m/yy</p>’);
35return false;
36}
37if(components[2].length > 4 || components[2].length < 2 || components[2].length == 3){
38$j(‘#errorwarning’).html(‘<p>Please enter a valid date as d/m/yy</p>’);
39return false;
40}
41if(components[2].length == 4){
42if(parseInt(components[2]) > 2030 || parseInt(components[2]) < 2012){
43$j(‘#errorwarning’).html(‘<p>Please enter a valid date as d/m/yy</p>’);
44return false;
45}
46}
47if(components[2].length == 2){
48if(parseInt(components[2]) > 30 || parseInt(components[2]) < 12){
49$j(‘#errorwarning’).html(‘<p>Please enter a valid date as d/m/yy</p>’);
50return false;
51}
52}
53if (! /^[0-9]+$/.test(components[1])) {
54$j(‘#errorwarning’).html(‘<p>Please enter a valid date as d/m/yy</p>’);
55return false;
56}
57if((parseInt(components[1]) > 12 || parseInt(components[1]) < 1)){
58$j(‘#errorwarning’).html(‘<p>Please enter a valid date as d/m/yy</p>’);
59return false;
60}
61if (! /^[0-9]+$/.test(components[0])) {
62$j(‘#errorwarning’).html(‘<p>Please enter a valid date as d/m/yy</p>’);
63return false;
64}
65var Months = [0,31,29,31,30,31,30,31,31,30,31,30,31];
66if (parseInt(components[0]) < 1 || parseInt(components[0]) > Months[parseInt(components[1])]) {
67$j(‘#errorwarning’).html(‘<p>Please enter a valid date as d/m/yy</p>’);
68return false;
69}
70if (parseInt(components[2]) % 4 !== 0 && parseInt(components[0]) == 29) {
71$j(‘#errorwarning’).html(‘<p>Please enter a valid date as d/m/yy</p>’);
72return false;
73}
74$j(‘#errorwarning’).empty();
75$j.post(‘<?php echo $this->getUrl(“despatchdate/form/post”)?>’,$j(“#dateform”).serialize(),function(dataReceived){
76$j(‘#errorwarning’).html(dataReceived);
77});
78return false;
79});
80});
81//]]>
82</script>
We delete line 8 because we no longer need it. Previously, we posted the url of the order view page for processing by our controller, because we wanted to redirect back to the order view page after the database had been updated with a new despatch date. We won’t need to redirect back now, because with AJAX we’re not leaving the page.
We’ll retain line 7. With or without AJAX, we’ll need to send a form key in order to be able to access the new controller file within Admin. You’ll recall, if you followed the earlier series here, that omitting the form key directs us to the Admin Dashboard – not what we wanted. Well, omitting the form key when we send data to the controller with jQuery’s AJAX function returns the dashboard into the current page, which renders the order view page as a bit of a mess. So we send the form key in line 7.
Why have we chosen to add the contents of the jquery file to the template file? Answer, we want to embed some PHP in the jquery code. Our Webserver won’t interpret the PHP unless it’s in a file such as .phtml or .php where the server expects to find PHP. The jQuery code is somewhat repetitive and we could clean it up a bit, reducing the number of times we use ‘Return False’ for example, but we’ll copy and paste as is. We mentioned in the earlier series that as we’re manipulating text here, there’s quite a bit of verification, and we could provide a different error message for each type of error if we chose to.
AJAX with jQuery
The key part of this post is line 75 of fields.phtml above. Here we’re adding the jquery.post function. If you’re wondering about the ‘$j’ notation, check out the earlier post on how this avoids conflicts with Prototype, Magento’s out-of-the-box javascript library of choice.
Jquery provides a number of options when it comes to loading data from the server: jQuery.ajax(), load(), jQuery.get(), jQuery.getJSON() and jQuery.post(). More information on these functions and other related can be found in http://api.jquery.com/category/ajax/.
The jquery.post() function is succinct and ideal for our purposes in this post. It comprises 3 arguments: the url of the page to which we are sending the data, the data we want to send, and the callback function for processing the response.
We want to embed Magento’s getUrl function as the first argument within jquery.post. (This is why we’ve added the jquery code to our phtml template.) We discussed getUrl earlier here. It allows us to specify the target URL using the module name, the controller and the action method.
The second argument is optional. We’ll clearly need it any time we’ve data to send. It can be expressed as a query string, such as ‘estDate=’ + estDestDate, or as an object literal (also known as JSON), such as: –
{
estDate: estDestDate,
recordId: recordId
}
Where we have a form, and this is the approach we’ve taken, we can use jQuery’s serialize() function to create a query string comprising all the name-value pairs making up our form (the names of the input tags and their respective values). We just need to specify the ID of the form (the ID we’ve specified in the form tag) and then the serialize function thus: – $j(“#dateform”).serialize().
The third argument is also optional. This is the function that will handle the response if we are expecting one. The first argument returned into our call-back function is the data we return from the controller. A second argument can also be captured which shows the status of the response as either ‘success’ or ‘error’, if for example the controller can’t be found. In the call-back function we’ve created here, we only capture the data. We display it in the same location as any error messages returned from validation.
In a moment we’ll take a look at the controller to see what modifications we’ve made to the original to ajaxify the user experience. Before we do that though, there’s one bit of housekeeping. As our jquery code is now part of our template file, there’s no need to add it in the head of the order view page. We’ll delete the action method we added previously to app/design/adminhtml/default/default/layout/despatchdate.xml – shown in green and commented out in the following code.
1<?xml version=”1.0″?>
2<layout>
3<adminhtml_sales_order_view>
4<reference name=”order_tab_info”>
5<action method=”setTemplate”>
6<template>despatchdate/sales/order/view/tab/info.phtml</template>
7</action>
8<block type=”despatchdate/fields” name=”despatchdate” template=”despatchdate/fields.phtml” />
9</reference>
10<!– <reference name=”head”> –>
11<!– <action method=”addJs”><script>jquery/date_validation.js</script></action> –>
12<!– </reference> –>
13</adminhtml_sales_order_view>
14</layout>
Amending our Controller
The following code is the controller file we created in an earlier post here. In green and commented out is code we no longer need. In red is code we want to add to the original file we created.
1<?php
2class Blessthemoon_Despatchdate_FormController extends Mage_Adminhtml_Controller_Action
3{
4public function postAction()
5{
6try {
7$estdate = $this->getRequest()->getParam(‘estDate’);
8if (empty($estdate)) {
9Mage::throwException($this->__(‘Invalid date. Please check & resubmit.’));
10}
11$components = explode(“/”, $estdate);
12if (strlen($components[0])<2)
13{
14$components[0]= “0”.$components[0];
15}
16if (strlen($components[1])<2)
17{
18$components[1]= “0”.$components[1];
19}
20if (strlen($components[2])<4)
21{
22$components[2]= “20”.$components[2];
23}
24$estdate = $components[2].”/”.$components[1].”/”.$components[0];
25$recordId = $this->getRequest()->getParam(‘record_id’);
26$orderModel = Mage::getModel(‘sales/order’)->load($recordId);
27$orderModel->setEstdate($estdate)->save();
28$message = $this->__(‘The estimated despatch date has been submitted successfully.’);
29// Mage::getSingleton(‘adminhtml/session’)->addSuccess($message);
30// $url = $this->getRequest()->getParam(‘url’);
31// Mage::app()->getFrontController()->getResponse()->setRedirect($url);
32} catch (Exception $e) {
33// Mage::getSingleton(‘adminhtml/session’)->addError($e->getMessage());
34// $url = $this->getRequest()->getParam(‘url’);
35// Mage::app()->getFrontController()->getResponse()->setRedirect($url);
36$message = $this->__(‘Submission unsuccessful. Please try again.’);
37}
38echo $message;
39}
40}
We’ll take out the lines 29 and 33 in green which added values for the session. These were displayed in order view when we refreshed the page. Our approach now using AJAX is to display the same message in the location where we display validation errors as noted already.
We’re also removing redirection to the order view URL in lines 30, 31, 34 and 35, as AJAX doesn’t require it.
We simply add a message in case of an error in line 36, and echo the message (either successful or unsuccessful) to the callback function we created earlier.
That’s it. Now we’re updating the database and returning a message without a page refresh!
It’s worth noting as a footnote, that if the data we want to return is more extensive, we can encode it as JSON using the Magento helper method jsonEncode($myarray), where $myarray is an array of key value pairs comprising the data we want to return. Typically we’d form the data to be returned using something like: – $this->getResponse()->setBody(Mage::helper(‘core’)->jsonEncode($myarray));
Data returned as JSON allows us to use simple jQuery chaining to access the object’s contents, e.g. dataReceived.message or dataReceived.lastName.