February 6, 2012

CakePHP - AJAX observeField in JsHelper with Prototype

Creating an equivalent of $ajax->observeField with the new JsHelper is quite simple. With the help of JsHelper you can retrive the value of single field to your AJAX action, as well as retrive the values of all fields in the form.

I will present it on example of registration form in which we want to check if given field is valid after the field is updated (so, it's kind of registration form with validation of user data on the fly).

Let's assume that registration form is held by the "register" action in UsersController, and the action which validates the fields (and to which we make AJAX call) is "verify".
  1. Include JsHelper in your controller where you want to use it.
    You should also define which JavaScript library you want to use. Default is JQuery, I use Prototype.
    public $helpers = array('Js'=>array('Prototype'));
  2. If you use Security component in your controller, you should disable validatePost and csrfCheck in the action to which you make an AJAX call.
    In our example with validating registration form - we should turn off validatePost and csrfCheck for the "verify" action. This can be done in the beforeFilter() function inside our controller:
    public function beforeFilter() {
        parent::beforeFilter();
        if($this->params['action']=='verify') {
          $this->Security->validatePost=false;
          $this->Security->csrfCheck=false;
        }
    }
  3. Next, you should include a JavaScript library which you use, in the top of the view where the form is displayed (in our example - in "register" view).
    <?php echo $this->Html->script('prototype',false);?>
    (If you set the second parameter to false, the script will be put into the <head> section).
    Of course if the library has already been included in the layout for other purposes - you don't need to double it in the view.
  4. Next, in the layout which holds the registration form, you should add a writeBuffer method, which will output buffered scripts. The writeBuffer method should be placed before the </body> tag.
    <?php if($this->params['action']=='register'):?>
      <?php echo $this->Js->writeBuffer();?>
    <?php endif;?>
  5.  Now, let's create a simple registration form with empty message div's to update after field validation:
    <?php echo $this->Form->create('User',array('controller'=>'users','action'=>'register'))?>
    <div>
      Login: <?php echo $this->Form->text('login',array('id'=>'login'));?>
      <div id="message-login"></div>
    </div>
    <div>
      Password: <?php echo $this->Form->text('password',array('id'=>'password'));?>
      <div id="message-password"></div>
    </div>
    <?php echo $this->Form->submit('Register',array('div'=>false))?></div>
    <?php echo $this->Form->end();?>
  6. Next, add JsHelper event "onchange" for both fields (of course you can change the event from "change", which will update given element after you leave the input, to any other of the normal DOM events, such as "keyup" for "onkeyup", "click" for "onclick" etc.):
    <?php echo $this->Form->create('User',array('controller'=>'users','action'=>'register'))?>
    <div>
      Login: <?php echo $this->Form->text('login',array('id'=>'input_login'));?>
      <div id="message-login"></div>
      <?php $this->Js->get('#input_login');?>
      <?php
        echo $this->Js->event('change',
          $this->Js->request(
            '/users/verify/login',
            array(
              'method'=>'post',
              'async'=>true,
              'update'=>'#message-login',
              'dataExpression'=>true,
              'data'=>'$("input_login").serialize()',
            )
          )
        );
      ?>
    </div>
    <div>
      Password: <?php echo $this->Form->text('password',array('id'=>'input_password'));?>
      <div id="message-password"></div>
      <?php $this->Js->get('#input_password');?>
      <?php
        echo $this->Js->event('change',
          $this->Js->request(
            '/users/verify/password',  
            array(
              'method'=>'post',
              'async'=>true,
              'update'=>'#message-password',
              'dataExpression'=>true,
              'data'=>'$("input_password").serialize()',
            )
          )
        );
      ?>
    </div>
    <?php echo $this->Form->submit('Register',array('div'=>false))?></div>
    <?php echo $this->Form->end();?>
  7. Your "verify" function could look like this:
    public function verify($field) {
      $this->layout = 'blank';
      if($field!=NULL) {
        $fields=array('login','password');
        if(in_array($field,$fields)) {
          App::uses('Sanitize','Utility');
          Sanitize::clean($this->request->data['User'][$field]);
          $this->User->set($this->request->data);
          if(!$this->User->validates()) {
            $this->validateErrors($this->User);
            $errors=$this->validationErrors;
            $this->set('errors',$errors);
          }
          $this->set('field',$field));
        }
      }
    }
    Of course you should define the $validate variable in the model to verify if the given field validates.

  8. The AJAX view for "verify" could look like this:
    <?php if(isset($errors[$field]) && !empty($errors[$field])):?>
      <div class="error-message">
        <?php foreach($errors[$field] as $error):?>
          <?php echo $error?><br/>
        <?php endforeach;?>
      </div>
    <?php else:?>
      OK
    <?php endif;?>

Above solution allows to retrieve the value of only one field from the form.
If you need to retrive more than one field from a form (for example if you want to compare "password" and "repeat password" fields), you should set the 'data' option as follows:
<?php $this->Js->get('#id_of_field_to_observe');?>
<?php
  echo $this->Js->event('change',
    $this->Js->request(
      array('controller'=>'controller','action'=>'action', parameters),  
      array(
        'method'=>'post',
        'async'=>true,
        'update'=>'#id_of_element_to_update',
        'dataExpression'=>true,
        'data'=>$this->Js->serializeForm(array('isForm'=>false, 'inline'=>true))
      )
    )
  );
?>

This will allow you to retrieve data from whole form.

Have fun playing with JsHelper;-)

February 3, 2012

CakePHP 2.x & JS scripts in AJAX view

Sometimes, after making an AJAX request, we want to use JS scripts in the view of AJAX function.
I was having a trouble with that after switching to 2.0.5 version - a simple AJAX view, including only a text message and a bit of JavaScript code - didn't render and run the script at all.

The solution for this is an evalScripts option in JsHelper.
If you want your AJAX view to display and run <script>'s, simply add this option to your AJAX call.

For example:

<?php
   echo $this->Js->link('update',
      array('controller' => 'controller', 'action' -> 'action'),
      array(
         'update' => '#id_of_element_to_update',
         'evalScripts' => true,
      )
   );
?>