11 Dec

Quick Tip: Activate CakePHP console in one go

CakePHP

Let me share a quickie which will save you a few seconds if you’re using CakePHP under Windows environment. To activate CakePHP shell, you must be doing it ugly way which involves copying up full path of ‘app’ directory or cake’s console directory. To overcome this:

  1. Create a new separate directory somewhere on your machine (say 3rdpartytools)
  2. Create a batch file here called cake.bat with following contents:

    @echo off

    ..\cake\console\cake %*

  3. Now, add this directory to your ‘Path’ variable (environment variable) from control panel.
  4. Restart windows and you’re done.

Now to activate CakePHP shell, just open ‘cmd’ from working app directory and type your favorite command like ‘cake schema generate –f’ or anything. What happens behind the scene? Our own cake.bat is called after we issue a command, which then passes execution to console’s cake.bat.

Hope it saves you some time.

21 Feb

Turn your normal CakePHP forms to AJAX forms in a second

Best Practices, CakePHP, jQuery

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.

Abhimanyu Grover

11 Feb

Thickbox Helper for CakePHP

CakePHP, Latest Developments

It's quite difficult to copy-paste JavaScript for same thing again and again. That's why I've come up with Thickbox helper for CakePHP – as a result of a project which involved lot of thickboxes implementations. For those who don't know what it is be sure to check Thickbox jQuery Plugin.

To use it, just include this helper in your controller and Its implementation is very simple:

1. For inline content:

PHP:
  1. <?
  2. $thickbox->setProperties(array('id'=>'domId', 'height'=>'300', 'width'=>'334')); // set height, width and DOM ID
  3. $thickbox->setPreviewContent('click me'); // the link which will trigger thickbox on click
  4. $thickbox->setMainContent('<div>see it??</div>'); // the content which will be shown in thickbox
  5. echo $thickbox->output();
  6. ?>

 

2. For AJAX:

PHP:
  1. $thickbox->setProperties(array('id'=>'domId','type'=>'ajax','ajaxUrl'=>'/controller/action'));
  2. $thickbox->setPreviewContent("Click me to see thickbox");
  3. echo $thickbox->output();

Here's the helper:

PHP:
  1. <?php
  2. class ThickboxHelper extends AppHelper {
  3.  
  4.     var $helpers = array('Javascript', 'Html');
  5.    
  6.     /**
  7.      * Set properties - DOM ID, Height and Width, Type of thickbox window - inline or ajax
  8.      *
  9.      * @param array $options
  10.      */
  11.     function setProperties($options = array())
  12.     {
  13.         if(!isset($options['type']))
  14.         {
  15.             $options['type'] = 'inline';
  16.         }
  17.         $this->options = $options;
  18.     }
  19.    
  20.     function setPreviewContent($content)
  21.     {
  22.         $this->options['previewContent'] = $content;
  23.     }
  24.  
  25.     function setMainContent($content)
  26.     {
  27.         $this->options['mainContent'] = $content;
  28.     }
  29.    
  30.     function reset()
  31.     {
  32.         $this->options = array();
  33.     }
  34.    
  35.     function output()
  36.     {
  37.         extract($this->options);
  38.         if($type=='inline')
  39.         {
  40.             $href = '#TB_inline?';
  41.             $href .= '&inlineId='.$id;
  42.         }
  43.         elseif($type=='ajax')
  44.         {
  45.             $ajaxUrl = $this->Html->url($ajaxUrl);
  46.             $href = $ajaxUrl.'?';
  47.         }
  48.                
  49.         if(isset($height))
  50.         {
  51.             $href .= '&height='.$height;
  52.         }
  53.         if(isset($width))
  54.         {
  55.             $href .= '&width='.$width;
  56.         }
  57.        
  58.        
  59.         $output = '<a class="thickbox" href="'.$href.'">'.$previewContent.'</a>';
  60.        
  61.         if($type=='inline')
  62.         {
  63.             $output .= '<div id="'.$id.'" style="display:none;">'.$mainContent.'</div>';
  64.         }
  65.        
  66.         unset($this->options);
  67.        
  68.         return $output;
  69.     }
  70.    
  71.     function beforeRender()
  72.     {
  73.         $out = $this->Html->css('/effects/css/thickbox.css').'<script src="'.$this->Html->url('/effects/js/thickbox-compressed.js').'"></script>';
  74.         $view =& ClassRegistry::getObject('view');
  75.         $view->addScript($out);
  76.     }
  77.  
  78. }
  79. ?>

 

You'll need to copy the thickbox files to /app/webroot/effects. You can keep it in any folder for that matter, but as our team is following plugins – it's made that way.

- Abhimanyu Grover

Hire us

Contact us to get a free quote on your project.