Using AJAX with CakePHP Forms

CakePHP helpers are so powerful that when you begin to hack helpers here and there you discover a lot of possibilities which can save you a lot of time. Same thing happened when I had an idea to AJAX’ify the forms using some shortcut.

Let’s try hacking the FormHelper and enable AJAX. We’ll use jQuery Form plugin to submit forms via AJAX request (I don’t know any other good plugins for forms).

What we’ll be doing:

Converting a normal form like below one, to make it call AJAX(using jQuery lib) and alert us when form posted:

PHP:
  1. <div class=“jerks form”>
  2. <?php echo $form->create(‘Jerk’);?>
  3.     <fieldset>
  4.        <legend><?php __(‘Add Jerk’);?></legend>
  5.     <?php
  6.         echo $form->input(‘name’);
  7.     ?>
  8.     </fieldset>
  9. <?php echo $form->end(‘Submit’);?>
  10. </div>

 

1. Extend your FormHelper to something like AjaxFormHelper (Now normally I don’t extend core helpers like this, but use a similar method similar to this (comment by grigri): http://cakebaker.42dh.com/2008/10/18/dont-abuse-the-apphelper-to-extend-the-core-helpers/#comment-110708 ) and overwrite create() method like this:

PHP:
  1. function create($model = null, $options = array())
  2. {
  3.     $output = “”;
  4.     if(isset($options[‘ajax’]) && $options[‘ajax’]==‘true’)
  5.     {
  6.         if(!isset($options[‘id’]))
  7.         {
  8.             $options[‘id’] = ‘form’ . intval(mt_rand());
  9.         }
  10.        
  11.         $this->ajaxForm = $options[‘id’];
  12.         $url = “$(‘#”.$options[‘id’].“‘).attr(‘action’)+’?ajax=1&flash_only=1”;
  13.        
  14.         if(@$options[‘response’]==‘inline’)
  15.         {
  16.             $datatype = ‘text’;
  17.             $success = “$(‘#”.$options[‘id’].“_status’).hide().html(responseText).fadeIn();
  18.                         setTimeout(function(){
  19.                             $(‘#”.$options[‘id’].“_status’).fadeOut();
  20.                         }, 5000);”;
  21.             $url .= “&js=false'”;
  22.         }
  23.         else {
  24.             $datatype = ‘script’;
  25.             $success = ;
  26.             $url .= “‘”;
  27.         }
  28.        
  29.         // to-do: avoid multiple inclusion of this script
  30.         $output .= “<div id='”.$options[‘id’].“_status’ style=’display:none;’></div>”;
  31.         $output .= “<script src='”.$this->Html->url(‘/effects/js/jquery.form.js’).“‘></script>”;
  32.         $output .= “<script>
  33.         $(document).ready(function() {
  34.         $(‘#”.$options[‘id’].“‘).ajaxForm({dataType: ‘”.$datatype.“‘, url:  “.$url.“,
  35.             beforeSubmit: function(){
  36.             $(‘#”.$options[‘id’].” .submit input’).attr(‘disabled’, true);
  37.             $(‘#progressIndicator’).show();
  38.             $(‘#”.$options[‘id’].” .form_progress’).show();
  39.             },
  40.             success: function(responseText, statusText){
  41.             $(‘#”.$options[‘id’].” .submit input’).attr(‘disabled’, false);
  42.             $(‘#progressIndicator’).hide();
  43.             $(‘#”.$options[‘id’].” .form_progress’).hide();
  44.             “.$success.
  45.             },
  46.         });
  47.     });
  48.     </script>”;
  49.     }
  50.     // unset js options
  51.     $output .= parent::create($model, $options);
  52.    
  53.     return $this->output($output);
  54. }

I’m not that good in explaining code flow, but you might have noted 3 get variables appended to our form action URL above. These variables are: ‘ajax’,’flash_only’ ,’js’ and serves their own purpose that I’ll tell in next steps.

 

2. Now we enable the AJAX in our form (CakePHP’s $options array is so good that you can overwrite and modify almost many methods easily):

This will show a JS alert after form has been submitted successfully.

PHP:
  1. <div class=“jerks form”>
  2. <?php echo $ajaxForm->create(‘Jerk’, array(‘ajax’=>‘true’));?>
  3.     <fieldset>
  4.        <legend><?php __(‘Add Jerk’);?></legend>
  5.     <?php
  6.         echo $ajaxForm ->input(‘name’);
  7.     ?>
  8.     </fieldset>
  9. <?php echo $ajaxForm ->end(‘Submit’);?>
  10. </div>

This will show an inline message after form has been submitted successfully.

PHP:
  1. <div class=“jerks form”>
  2. <?php echo $ajaxForm->create(‘Jerk’, array(‘ajax’=>‘true’ , ‘response’=>‘inline’));?>
  3.     <fieldset>
  4.        <legend><?php __(‘Add Jerk’);?></legend>
  5.     <?php
  6.         echo $ajaxForm ->input(‘name’);
  7.     ?>
  8.     </fieldset>
  9. <?php echo $ajaxForm ->end(‘Submit’);?>
  10. </div>

Note that ajax=true parameter we sent in create() method. This will tell helper to load this form via AJAX.

 

3. Now your form will be AJAX ready (if you’ve included this AjaxFormHelper properly). But because our controller function was made to process normal POST function, and flash message on success – we’ll have to change that behavior. This is what a normal JerksController::add() method should look like:

PHP:
  1. function add() {
  2.         if (!empty($this->data)) {
  3.             $this->Jerk->create();
  4.             if ($this->Jerk->save($this->data)) {
  5.                 $this->Session->setFlash(‘Jerk saved.’);
  6.             } else {
  7.             }
  8.         }
  9.     }

We don’t need full action content from views/jerks/add.ctp to appear in response when AJAX is called. We can make this work traditional way by checking if it’s an AJAX request in controller method itself and do needful, but I wouldn’t want to modify all my controller functions to enable AJAX, so here’s what I’ve come up with.

Remember the 3 GET variables above?

‘ajax’ => This one will determine if a given HTTP request is an AJAX request.

‘flash_only’ => This will tell if rendering should happen or not. Flash only means, after controller function is executed, do not render, just show flash message.

‘js’ => This is used for alert type, if this is not set, show inline alert. If set true, helper must show JS alert() on form success.

Inside your AppController::beforeFilter(), add this code:

PHP:
  1. if(isset($_GET[‘ajax’]))
  2. {
  3.     Configure::write(‘debug’,0);
  4.     $this->layout = ‘ajax’;
  5.     $ this ->set(‘ajax’, true);
  6.     if($_GET[‘flash_only’])
  7.     {
  8.         $ this ->set(‘flash_only’, true);
  9.         //$ this ->autoRender = false;
  10.     }
  11.    
  12.     if($_GET[‘js’]==‘false’)
  13.     {
  14.         $ this ->set(‘js’, 0);
  15.     }
  16.     else {
  17.         $ this ->set(‘js’, 1);
  18.     }
  19. }

Do not blame me for using GET variables, I found many issues with ‘named’ so I’m relying on normal GET variables.

Now you’ll need to edit ajax.ctp layout file.

PHP:
  1. <?php
  2. if ($session->check(‘Message.flash’))
  3. {
  4.     $strMessage = ;
  5.     $message = $session->read(‘Message.flash’);
  6.     if(isset($message[‘params’][‘type’]))
  7.     {
  8.         $type = $message[‘params’][‘type’];
  9.         $strMessage = ucfirst($type).“: “.$message[‘message’];   
  10.     }
  11.     else {
  12.         $strMessage = $message[‘message’];     
  13.     }
  14.     $session->del(‘Message.flash’);
  15. }
  16. if(!empty($this->validationErrors))
  17. {
  18.     $strMessage = ;
  19.     foreach($this->validationErrors as $model=>$errors)
  20.     {
  21.         foreach($this->validationErrors[$model] as $field=>$error)
  22.         {
  23.             $strMessage .= $error;
  24.         }
  25.     }
  26. }
  27. ?>
  28. <?
  29. if($strMessage) {
  30.     if($js==1) {
  31. ?>
  32.     alert(‘<?=$strMessage;?>’);
  33. <? } elseif($js==0) {
  34.     echo $strMessage;
  35. } } ?>
  36. <?
  37. if(!isset($flash_only))
  38. {
  39.     echo $content_for_layout;
  40. }
  41. ?>

This should be self explanatory, even though this code definitely needs some refactoring. We’re basically manipulating those 3 GET variables according to our need. In process, we’re also checking validation errors occurred in form.

I have not used it under the production environment yet, so I’d really like to hear any pitfalls (if any) using this approach. Thanks for reading.