Tags

, , , , ,

(part 1)

Now, how is this an introduction to jQuery + CakePHP?… well, let’s see in a minute.

First, I’d like to say that with the advent of frameworks such as jQuery, better browsers, which do a nicer job of supporting standards, and even faster PC’s and larger/better monitors, we can happily write better client-side code.

Meaning the following rules are slightly not as important anymore:

  1. A client’s browser may not support my code
  2. A client is not fast enough to process the code
  3. A client is never to be trusted

While the first two rules are becoming less and less applicable, the third one stays true for a while (and for a good reason).

Where does it come into play?

For purposes of this topic, and I can imagine many other cases, where it would be true, we’ll say “Data Validation”.

Indeed, if you validate data on the client-side only, you are running into a serious risk of getting some “really bad” data. So, always double-check the data on the server (that’s a rule that should rarely be bent).

The good news is that nothing is stopping us from doing data validation on the client, the server and might even throw in a little AJAX.

So, with that little foreword let’s see how we can do jQuery-based field validation, using CakePHP’s model validation rules.
(Worth to note, that there is an excellent jQuery plug-in, which is made to work with CakePHP that does great client/server validation for you, but this is an intro, so we’ll just take a look at some basics).

Let’s start with our typical User Model.
We’ll do a very simple validation to keep the code from getting obnoxious:

    class User extends AppModel {

          var $name = 'User';

          var $validate = array('username'=>
                                  array('rule'=>'isUnique',
                                          'message'=>'Sorry, this username already exists'));

    }

We’ll do one single check, whether or not the “username” field is unique. (Nothing unusual so far).

Let’s do a simple view (add.ctp):

<?php
    //let's load up the jQuery core
    echo $javascript->link('jquery/jquery.min', false);

    //and now... some file that will be specific to this view (page)
    echo $javascript->link('jquery/page_specific/users_add', false);
?>

<?php echo $form->create(); ?>
<?php echo $form->input('username', array('id'=>'username')); ?>
<?php echo $form->input('some_other_field'); ?>
<?php echo $form->end('Add User'); ?>

If you haven’t read part 1 (or haven’t worked much with JS/jQuery), some of this might get a little confusing. So I urge you
to do so and then come back.

Anyways, the only thing of interest here is the fact that I gave the “username” field an ID, that’s different from defau< array(‘id’=>’username’), the default DOM ID would be “UserUsername” (i.e. Camel-cased model and field name), but this is different than the field in the DB and what a model would expect. So this is just a little preparation to make our lives easier down the road.

Now comes our jQuery code, which is users_add.js:

$(document).ready( function() {

    $('#username').blur( function () {

        fieldName = $(this).attr('id');
        fieldValue = $(this).val();

        $.post('/users/ajax_validate', {
                                        field: fieldName,
                                        value: fieldValue
                                        },
               function(error) {

                   if(error.length != 0) {

                       $('#username').after('<div class="error-message" id="'+ fieldName +'-exists">' + error + '</div>');
                   }
                   else {
                       $('#' + fieldName + '-exists').remove();
                   }
               });
     });

});

To make the long story short, we post our field name (username) and our value (whatever it happens to be) to our User’s controller ajax_validate action. This only happens when the “username” field loses focus $(‘#username’).blur(… either by the user clicking away with the mouse or tabbing onto “some_other_field”.

So, we are going to let the user know that the username might be already taken while she is filling out the rest of the form.

Let’s use our Model’s validation rule and some good ol’ controller logic to get this working…

class UsersController extends AppController {

      var $name = 'Users';

      function add() {
          if(!empty($this->data)) {
              $this->User->save($this->data);
          }
      }

      function ajax_validate() {
          Configure::write('debug', 0);

          if($this->RequestHandler->isAjax()) {

              $this->data['User'][$this->params['form']['field']] = $this->params['form']['value'];

              $this->User->set($this->data);

              if($this->User->validates()) {
                  $this->autoRender = false;
              }
              else {
                 $errorArray = $this->validateErrors($this->User);

                 $this->set('error', $errorArray[$this->params['form']['field']]);
              }
          }
      }
    }

You can happily skip the add() action, since you’ve seen an example of that about a million times already.

Let’s look at our ajax_validate()
(By the way don’t forget to add var $components = array(’RequestHandler’); to your app controller or Users controller, as one of my kind readers had pointed out).

First, we create the data ($this->data['User']['username']) array to validate…
$this->data['User'][$this->params['form']['field']] = $this->params['form']['value'];

Remember, that our $this->params['form']['field'] and $this->params['form']['value'] come from jQuery. And because we gave the field the right ID the $this->params['form']['field'] can be applied to any field, thus making it all pretty dynamic (and the data array is built exactly as CakePHP would expect).

So then, we simply attempt to validate the data, as often with $this->User->validates().

In case it does, we need not return or render anything. (You could, but for this example we’ll keep it simple). The idea is that if the “username” is unique, there isn’t effectively anything further that needs to be done.

On the other hand, if the “username” is not unique, we extract the error message and set it for the view, to return back to our jQuery.

In this case we simply return the string, that was defined in our model. (‘Sorry, this username already exists’). You can return xml, JSON or whatever else… again keeping things simple.

Alright, we do need some view to “display” (in proper terms return) the error message back to jQuery script.

Here it is (ajax_validate.ctp):

<?php
    echo $error;
?>

Whew…

Now let’s take another look at a part of our jQuery code:

[sourcecode language="javascript"]

function(error) {

if(error.length != 0) {
$(‘#username’).after(‘

‘ + error + ‘

‘);
}
else {
$(‘#’ + fieldName + ‘-exists’).remove();
}
}
[/cc]

If anything came back from our ajax_validate() action, we add a div with the error message”

[sourcecode language="javascript"]
$(‘#username’).after(‘

‘ + error + ‘

‘);
[/cc]

If the user corrects the error, we happily remove the div from the DOM and… be done with it!