JQuery in the CakePHP world (part 2 – is client side code all that great?)

(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!

m4s0n501
  • Javier

    Great article!

    I’ve used a jQuery validation plugin for CakePHP (probably the same one you mention), but never used AJAX validation.

    A few random thoughts about this subject (I know you wanted to keep things simple, so hopefully no offense here :-)):

    Right now we usually couple client and server code. For example, in this case in our jQuery function we expect the ID of a div to be the name of a field in the database. Maybe in the future we’ll see an easier way to mix CakePHP and jQuery code to keep things DRY.

    The same can be said about the URL. If we change the routes (for whatever reason, for example, copying the code to another project, as has already happened to me), we’d have to change the URL in our JavaScript code as well.

    I’m not sure if I understood a trick in your code. If there are other validation errors (I mean, the username is valid, but a password is required, so the model doesn’t validate), $errorArray[$this->params[‘form’][‘field’]] wouldn’t be set, so we’d have a warning. So, instead of checking if the field is set, you avoid this by setting debug level to 0, which would prevent us from knowing about *other* possible warnings.

    Is that correct?

    There might a typo since I see you load jQuery using the “false” option, but add the JavaScript specific to the view without using that option. Probably the second one should have this option as well.

    Thanks for the article again. I’m gonna test it in my current project.

  • http://teknoid.wordpress.com teknoid

    @Javier

    “Right now we usually couple client and server code. For example, in this case in our jQuery function we expect the ID of a div to be the name of a field in the database. Maybe in the future we’ll see an easier way to mix CakePHP and jQuery code to keep things DRY.”

    – A little bird tells me, that we might see a much tighter integration with CakePHP + jQuery in the near future ;)

    “I’m not sure if I understood a trick in your code. If there are other validation errors (I mean, the username is valid, but a password is required, so the model doesn’t validate), $errorArray[$this->params[‘form’][‘field’]] wouldn’t be set, so we’d have a warning. So, instead of checking if the field is set, you avoid this by setting debug level to 0, which would prevent us from knowing about *other* possible warnings.”

    – Not really, this would only be the case if you have ‘required’=>true, which is often misunderstood, overused and causes quite a bit of confusion:
    http://teknoid.wordpress.com/2008/11/04/requiredtrue-the-source-of-major-confusion/
    In our case we validate one field at a time (i.e. a field loses focus, then it is checked “behind the scenes”), so it either passes or an error for that one given field is triggered. Debug is set to 0, so that we do not get any output (i.e. the SQL log), which would mess with the results of our Ajax response. (This is something you probably do after you’ve ensured that everything is working properly).

    Regarding, ‘false’, that’s true… thanks, I’ve fixed it up ;)

  • Javier

    @teknoid

    Yeah, I’d already read your article about the ‘required’=>true thing. I found it very useful, as I used to have that misunderstanding.

    Right now what I usually do is setting ‘required’=>true when I’m adding a new model (‘on’=>’create’), and using VALID_NOT_EMPTY when I’m updating it. I tried using the SecurityComponent instead of doing this, but I found problems when using AJAX submits.

    About the Debug set to 0… Oops! I forgot about that. Now that you mention it, I’ve remembered I do the same for autocompletion fields.

  • http://teknoid.wordpress.com teknoid

    @Javier

    Alright, well… hopefully it clarifies some things, and those are excellent questions, by the way. They certainly do well in addition to the actual post.

    Cheers!

  • Pingback: links for 2009-01-22 « Richard@Home()

  • Pingback: CakePHP : signets remarquables du 22/01/2009 au 26/01/2009 | Cherry on the...()

  • http://mbavio.com.ar Martin Bavio

    What an amazing article! Thanks teknoid!

    • http://teknoid.wordpress.com teknoid

      @Martin Bavio

      Thanks for the kind words.
      You are quite welcome.

  • http://blog.zpon.dk Søren

    Very helpful! thanks!

  • http://teknoid.wordpress.com teknoid

    @Søren

    Good to hear.
    You’re welcome.

  • http://jonah.cubedwater.com Jonah Turnquist

    I guess I owe it to you to tell you that these last two posts were my first steps in finally getting around to learning jquery in the last week. Thanks!

  • http://teknoid.wordpress.com teknoid

    @Jonah Turnquist

    You’re quite welcome.
    Not bad for a week’s worth of effort ;)

  • http://youweyoucoding.com Erik Gyepes

    Great articles (part1 and part2), any chances that there will be also part3, part4,..? :o)

    Maybe it would be great if you show some more advanced techniques, or more effective ways (if they exists) how to check those forms and do other things. For instance it seems that this way we need to write new jQuery JS for all form fields, isn’t there some more effective way to handle this?

  • http://teknoid.wordpress.com teknoid

    @Erik Gyepes

    Thanks.

    At least part3 is definitely planned it will talk about making the JS code unobtrusive, as some kind readers have already pointed out. I do want to “prepare” for this topic as it needs to be done proper and with effective techniques.

    As far as having to write new jQuery code for additional validation, you can easily use an “input” selector (rather than a specific form input ID) to target any field… but, that being said, there is already a great (jQuery/cakephp) plug-in out there that handles all of that for you.

  • Grrr

    Great tutorial, looking forward to more!

    One thing: in case you don’t have the RequestHandler component added to your app_controller, you will need it in your UsersController:

    var $components = array(‘RequestHandler’);

  • aoia

    whats this “great plugin” out there? can’t find it

  • http://teknoid.wordpress.com teknoid

    @Grrr

    Good point, I should update the post to mention that…

    @aoia

    (Although the site doesn’t work for me at the moment) you should find the plug-in here: http://jeff.loiselles.com/projects/cake/live-validation/

  • Pingback: CakePHP biblioteka #1 « Labs()

  • Angus

    Great tutorial. I followed it and got it working but I am running into a problem. So, if I leave the form field blank, I would get the validate message. It works. But if I leave it blank again, the message appears, however, the previous message is still there, so the result is a list of validate messages appearing below the input field.

    I am wondering what I am doing wrong or if you or other commenters know how to remove the list of messages.

  • http://teknoid.wordpress.com teknoid

    @Angus

    This line: $(‘#’ + fieldName + ‘-exists’).remove();

    … should be getting rid of the existing error message. Please double-check with Firebug’s “inspect” tab to see what’s happening with your DOM. It should be working. I don’t recall if I’ve tested with blank fields, but I don’t see why that rule would work any differently.

  • kicaj

    function(error) {
    if(error.length != 0 ) {
    if($(‘#’ + fieldName + ‘-exists’).length == 0) {
    $(‘#ContactName’).after(” + error + ”);
    $(“input[type=’submit’]”).attr(“disabled”, “disabled”).css(‘color’, ‘#c0c0c0′);
    }
    } else {
    $(‘#’+ fieldName +’-exists’).remove();
    }

    This code repair duplicate add many divs for one field:)

  • http://teknoid.wordpress.com teknoid

    @kicaj

    Thanks for sharing.

  • angus

    Thanks @kicaj for the code and assistance. that works.

    I also added the following in addition to your code, at the very top, to display other validate messages

    if(error.length != 0){
    if($(‘#’ + fieldName + ‘-exists’).length == 1){
    $(‘#’ + emailField + ‘-exists’).remove();
    }


    }

    and thanks @teknoid for this great tutorial and assistance

  • kicaj

    I wrote next version, for all fields into your form:

    $(‘fieldset [id^=Contact]’).blur(function() {
    fieldName = $(this).attr(‘id’);
    fieldValue = $(this).attr(‘value’);
    fieldValidate = fieldName.substr(7).toLowerCase();

    $.post(‘/cega/contact/ajax_validate’, {
    field: fieldValidate,
    value: fieldValue
    },

    function(error) {
    if(error.length > 2) {
    if($(‘#’+ fieldName +’-exists’).length == 0) {
    $(‘#’+ fieldName).after(”+ error +”);
    }
    } else {
    $(‘#’+ fieldName +’-exists’).remove();
    }
    });
    });

    First is your regexp for select fields
    <… “ContactPhone”…

    You must only change first line and copy for your form:)

    Bye!

  • Prabh

    Hi,

    I need to set client side validation using Jquery in cakephp but it’s giving me javascript error for data[Fare][sName] as it’s taking it as an array.

    Any remedies???

    ThanQ.
    Prabh

  • http://teknoid.wordpress.com teknoid

    @Angus, kicaj

    Thanks for sharing your code, although I have a small request to please use the (bin) http://bin.cakephp.org/, to make the code easier to read and the blog cleaner ;) Plus you can save it for much easier copy/paste to other places.

    Thanks!

  • http://teknoid.wordpress.com teknoid

    @Prabh

    If you are reading the value of the field on the client side, use the field’s ‘id’ not ‘name’.

  • Prabh

    Hey thanx for replying. Here’s my code

    $(‘#addContent’).validate({
    rules:{
    data[Content][sPageName]:”required”
    }
    });

    How can i use Id over here? cos it gives error if i use

    $(‘#addContent’).validate({
    rules:{
    $(‘#pagename’):”required”
    }
    });

  • http://teknoid.wordpress.com teknoid

    @Prabh

    Sounds like you are using the Validation plugin, so I think this question better belongs with jQuery mailing list.
    That being said, a hint would be to convert the array to string using good ol’ JS. Although there might be a better alternative out there.

  • kicaj

    @teknoid: sorry, but i always forgot about bin.cakephp.org

  • kicaj

    I have one problem…
    When I was send form by submit button, I get errors without attribute ‘id=NameField-existe’, and when i use blur on input field, a get duplicate div errors

  • http://teknoid.wordpress.com teknoid

    @kicaj

    I’m not sure what would be causing it off top of my head. Maybe best to check with jQuery mailing list and provide sample of code.

  • Zedr

    How to do ajax form submit with this validation? Using custom code in jquery or $ajax->submit? Please help!

  • http://teknoid.wordpress.com teknoid

    @Zedr

    $ajax helpers rely on the prototype library for the time being, so you cannot use it with jQuery.

    You’ll need to run custom jQuery code for that… and as far how to actually do it with jQuery, there are lots of examples out there. If anything check at the jQuery mailing list or IRC.

  • Zedr

    after “if($this->User->validates()) {” my code is:

    $this->autoRender = false;
    $error=’OK';
    $this->set(compact(‘error’));
    $this->render(‘ajax_validate’,’ajax’);

    I m printing “ok” near the field when validated.
    But this works only for 1 field. My code for validation in model:

    var $validate = array(‘firstname’=>array(
    ‘rule’=>’alphaNumeric’,
    ‘required’=>true,
    ‘message’=>’The title cannot contain any symbols’));

    When I add “lastname” rules, nothing works. Not even the validation for firstname. I believe the ajax_validate action is made dynamic, but I got no success. I have blur event jquery code for lastname also.

  • http://teknoid.wordpress.com teknoid

    @Zedr

    How do you determine which field should display ‘OK’?

    I would also ask at the mailing list/IRC, this is not really the place to get best/fastest answers ;)

  • KC

    Hi!

    I have confirm password field in my form. The comparison of passwords does not work with this code. I even tried compareTo in model class in the validation. Custom comparison functions also do not work. Any idea? A piece of code would help.

  • http://teknoid.wordpress.com teknoid

    @KC

    This method validates one field at a time, not the entire form, you’ll have to make some adjustments to the way you pass values from the form. For example if you need to compare two fields store the values in the session and then run validation.

  • http://ilyas.edgelogics.com Ilyas Iqbal

    @teknoid

    I’ve read both of your articles on Jquery+CakePHP and found them informative, the only missing is the Demo and Zip file for the Demo. I’ve tried to implement both of the articles and efforts went in vain.

    Could you upload the Zip and Demo today? I urgently seeking for something good to add. Thanks

    Ilyas

  • http://teknoid.wordpress.com teknoid

    @Ilyas Iqbal

    I don’t have either one available. Be sure to follow steps exactly, and with some clever copy and pasting you should have it working in no-time.

  • rjh

    @teknoid
    Great tutorial – thank you! Also went through both and not getting the expected results. Going to go through it and created the same model/view/controllers, but i’ve messed around for a while and not getting anywhere.

  • http://teknoid.wordpress.com teknoid

    @rjh

    Sorry, you are having trouble. But what exactly is the problem?
    It’s kind of hard to do this in this format, maybe jump on IRC or check with the mailing list.

  • rjh

    @teknoid

    Thanks for getting back to me. Late night tinkering! Used firebug and discovered the path wasn’t correct! I had to use /subsite/users/ajax_validate and it works perfect! Thanks again.

    R

  • http://teknoid.wordpress.com teknoid

    @rjh

    Nice, glad to hear you got it solved.

  • http://irfaannujjoo.blogspot.com aliirfaan

    I’m validating field fname, notEmpty and I get this error
    Notice (8): Undefined index: UserFname [APP\controllers\users_controller.php, line 32]

  • http://teknoid.wordpress.com teknoid

    @aliirfaan

    It’s kind of hard to guess, but it seems that you have some data array where you are tying to access an undefined index.

  • sporty

    hi,

    nice work, thanks a lot for sharing, but I wanted to use only jquery’s plugin with cakephp without ajax.
    so I tried this code in “users_add.js”

    $(document).ready( function() {

    $(“#UserAddForm”).validate({
    rules: {
    username: {
    required: true
    minlength: 2
    }

    },
    messages: {
    username: {
    required: “* Required”
    }
    }

    });
    });

    but no client-side validation takes place…
    any suggestions please?

  • http://teknoid.wordpress.com teknoid

    @sporty

    I don’t know enough about that plug-in, to give you any reasonable answer.
    The only thing I can suggest is to check at jquery group or IRC.
    In the mean time, try to debug with firebug, to make sure the events get triggered as expected.

  • Dan

    I am trying.. I can make it work.. but I don’t exactly get what’s going on here..

    $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);

  • http://teknoid.wordpress.com teknoid

    @Dan

    You are passing the user data by ajax, and validating a form using your User validation rules, which you’ve set in the model.

  • fabian

    I used these code to parse through all the inputs and it doesn’t created double messages since if a message exists on a select field it instantly removes it and when the users leaves the field if there’s a new error or the same it will show up again.

    http://bin.cakephp.org/view/1352176375

  • aruns

    i am new in cakephp.can anyone please tel me how to use jquery validation in cakephp.for validating the textbox,password,checkbox, radiobutton.only by using jquery

  • http://teknoid.wordpress.com teknoid

    @aruns

    That’s exactly what’s explained here ;)

  • Ron

    @teknoid

    Great work man! I like both the posts very much and learned a lot of things. Keep posting!!!

  • http://teknoid.wordpress.com teknoid

    @Ron

    Thanks, glad it was helpful :)

  • Christopher Vrooman

    Teknoid,
    great article!

    I have a question for you regarding the error message you are returning. How would you handle a bilingual site? At least for me, in the model I’m returning *which* validation rule failed for a given field and then in the view the translated error is displayed. For example:

    in the Model:
    var $validate = array(
    ‘username’=> array(
    ‘unique’ => array(‘rule’=>’isUnique’)
    )
    );

    in the View:
    input(‘username’,
    array(‘id’=>’username’),
    ‘errors’ => array(‘unique’ => __(‘Sorry, this username already exists’, true)
    );
    ?>

    Sincerely,
    Christopher.

  • http://teknoid.wordpress.com teknoid

    @Christopher Vrooman

    Great question, and unfortunately I don’t have an answer off top of my head.
    There are some changes to validation logic in 1.3 that might actually solve this problem.

    But I would still urge you to check on google group, maybe someone knows for sure.

  • Pascal C

    Thanks for this post, but 2 problems with fonction(error)
    1-We need to know if div exist before add or remove it
    2-We need to trim the error

    Here my new code :

    function(error) {

    //return true if exist
    existElement = $(‘#’ + fieldName +’-exists’).length;

    //trim the blank space in error
    errorSTR = $.trim(error)

    if(errorSTR.length != 0) {

    if(!existElement){
    $(‘#username’).after(” + errorSTR + ”);
    }

    }
    else {

    if(existElement){
    $(‘#’ + fieldName + ‘-exists’).remove();
    }
    }

  • http://teknoid.wordpress.com teknoid

    @Pascal C

    Thank you for sharing you solution.
    Of course, the code provided is minimalistic (to say the least) and meant more as a proof of concept. I hope everyone can benefit from reading comments just like yours.

    cheers!

  • http://pkjchate.wordpress.com pkjchate

    Excellent Article thanks for sharing

  • http://dynamicwebstream.com Vikas Dwivedi

    Hi I found ur article useful..

  • http://www.yomedia.ro tibit

    i tried this code but is not the js code does not get called.
    i`m using the 1.2 cakephp version and 1.4.2 jquery version.

  • coolblue

    Thank you very much for the great article. It is nice and simple.

  • teknoid

    @coolblue

    You are welcome.

  • Wendy

    Thanks for an awesome tutorial. The only thing to add is that if you are using 1.3 then to include the js script you need to use the following syntax…

    echo $this->Html->script(“page_specific/users_add”);

    Everything is working for me except that the error message is not getting cleared out. If I type a username, then type another username that is still in use, I just get two boxes with the error message. This is only my second attempt with javascript, so I really don’t know if its something simple or not.

    Thx

    Any ideas how to solve this?

  • teknoid

    @Wendy

    I believe some solutions for this were posted in the comments…

  • Anniee

    Hi, I am using CakePHP 2.3.6. This tutorial is of great help, but not compatible with the version I am using. I tried to perform migration tasks on as much as I knew. For example replacing

    1) $this->RequestHandler->isAjax() with $this->request->is(‘ajax’)..
    2) $form by $this->Form
    and so on….
    But I am still missing something since my ajax is not getting called.
    All the scripts included are perfect as tested by firebug.