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,
      )
   );
?>

January 23, 2012

CKEditor + KCFinder integration with CakePHP - tutorial

Lately I have been developing an application based on the new version of CakePHP (I am currently switching from 1.3 to 2.0.5).
Together with the change of CakePHP version, I also wanted to implement the new version of WYSIWYG editor - so far I've been using FCKeditor, which is no longer developed and which was replaced with CKEditor. I decided to move on with CKEditor.
CKEditor is also free and open source like FCKeditor, but there is a small difference between these two editors. FCKeditor had a built-in option of file managment (for example browsing server or file upload in insert image file option), which CKEditor doesn't have in the free and open source version. The file manager which can be used along with CKEditor is CKFinder - but this software isn't free. You have to buy licence to use it.
But there is a workaround - we can use a free KCFinder file manager, which can be easily integrated into CKEditor and other editors (FCKeditor, TinyMCE).

For the purpose of tutorial let's assume that our Cake application is being stored on our localhost and the "webroot" of our application is under "http://localhost/".

The following instructions were made based on a following versions of software:
  • CakePHP 2.0.5
  • CKEditor 3.6.2
  • KCFinder 2.51

How to install and integrate CKEditor to CakePHP? 
  1. Download CKEditor from www.ckeditor.com
  2. Copy files from the zipped folder to "webroot/js/ckeditor/"
  3. In the view where you want to display the editor, put the following script on the top of the page (or somewhere before textarea which you want to contain editor):
    <?php echo $this->Html->script('ckeditor/ckeditor');?>
    This scipt will include the "webroot/js/ckeditor.js" file to your view.
  4. Create the textarea and give it a class named "ckeditor"
    <?php echo $this->Form->textarea('content',array('class'=>'ckeditor'))?>
Voila! The editor is now displaying instead of raw textarea.


How to install and integrate KCFinder to CakePHP?
  1. Download KCFinder from kcfinder.sunhater.com
  2. Copy files from the zipped folder to "webroot/js/kcfinder/"
  3. Open "webroot/js/kcfinder/config.php" file.
    In this file you can set several configurations, but we will focus on the ones that are most important for our file manager to work.
    KCFinder is disabled by default.
    If you want to give the ability of using file manager to all users in your website (so with no restrictions), change the value of 'disabled' to false.
    'disabled' => false,
    But giving everyone access to managing files on the server is probably not a good idea. Instead of this, you can enable file managment only when a specified session variable is set. The name of the session variable is on the bottom of config.php file:
    '_sessionVar' => &$_SESSION['KCEDITOR'],
    So, this is what you should do to make the file manager available after an authenticated user logs in:
    • leave the "disabled" to "true"
    • in the controller where you do the user authentication, after user has successfully logged in, enter this code to override the "disabled" value:
      $_SESSION['KCEDITOR']['disabled']=false;
      Remember to destroy this session variable after user logs out.
    • on the top of the "webroot/js/kcfinder/core/autoload.php" file insert:
      session_name('CAKEPHP');
  4. Still in the "config.php" file define the folder for uploaded files. By default the folder is "upload" in "kcfinder" directory.
    If you are developing an application on your localhost and the "webroot" is under a "http://localhost/" address, and you want to keep uploaded files in "webroot/upload" folder, the 'uploadURL' configuration should look like:
    'uploadURL' => "/app/webroot/upload",
    If you are developing your application under for example "http://localhost/my_app/" address, the uploadURL should look like:
    'uploadURL' => "/my_app/app/webroot/upload",
You can test if KCFinder displays and works correctly under the URL:
http://path_to_your_app/js/kcfinder/browse.php


How to integrate KCFinder to CKEditor in CakePHP?
Open the "webroot/js/ckeditor/config.js" file and modify editor config to contain following code:
CKEDITOR.editorConfig = function( config )
{
   config.filebrowserBrowseUrl = '/js/kcfinder/browse.php?type=files';
   config.filebrowserImageBrowseUrl = '/js/kcfinder/browse.php?type=images';
   config.filebrowserFlashBrowseUrl = '/js/kcfinder/browse.php?type=flash';
   config.filebrowserUploadUrl = '/js/kcfinder/upload.php?type=files';
   config.filebrowserImageUploadUrl = '/js/kcfinder/upload.php?type=images';
   config.filebrowserFlashUploadUrl = '/js/kcfinder/upload.php?type=flash';
};

If your app is being developed under for example http://localhost/my_app/, remember to put the whole path in the URLs, for example:
config.filebrowserBrowseUrl = '/my_app/js/kcfinder/browse.php?type=files';
After adding above six lines of code in the CKEditor configuration you should be able to see "Browse" button in image / link / Flash insert options.

And that's it!:-)