Check for Available Username with CakePHP, Ajax and jQuery

September 27th, 2010 Leave a comment 9 comments
Like the article?
Username Taken Error

When a user registers for your web site, it is usually a requirement that they have to select a unique username. It can be a pain when your usual usernames are taken. You keep submitting the form just to wait and then see an error message that the username is already taken. Using Ajax to check and see if a username is available before the user submits the form will make your registration page more friendly. Here is one method for doing it using CakePHP, Ajax and jQuery.

First, we are going to create a very lighweight user model. We will just use a username, password and email address. Let’s start by creating our database table:

CREATE TABLE users (
	id integer NOT NULL auto_increment,
	username varchar(20) NOT NULL,
	password varchar(40) NOT NULL,
	email varchar(100) NOT NULL,
	PRIMARY KEY (id),
	UNIQUE KEY username (username)
);

Next, we will create a basic user model. In our model, we’ll define some validation rules. This will prevent errors if we try to save a user with incorrect data but we will also use the validation rules to support a traditional error message system in case we have a user with javascript turned off. In /app/models/user.php, we will add the following:

<?php
	class User extends AppModel {
		var $name = 'User';
		var $validate = array(
			'username' => array(
				'username_not_empty' => array(
					'rule' => 'notEmpty',
					'message' => 'This field is required',
					'last' => true
				),
				'username_alphanumeric' => array(
					'rule' => 'alphaNumeric',
					'message' => 'Usernames must contain only letters and numbers',
					'last' => true
				),
				'username_unique' => array(
					'rule' => 'isUnique',
					'message' => 'That username is already in use',
					'last' => true
				),
			),
			'email' => array(
				'email_not_empty' => array(
					'rule' => 'notEmpty',
					'message' => 'This field is required',
					'last' => true
				),
				'email_unique' => array(
					'rule' => 'isUnique',
					'message' => 'That email is already in use',
					'last' => true
				)
			),
			'password' => array(
				'password_not_empty' => array(
					'rule' => 'notEmpty',
					'message' => 'This field is required',
					'last' => true
				)
			)
		);
?>

A couple of quick notes on our model. First, we are using the newer style validation. When supplying multiple validation rules for each field, we provide a name for the rule. The message attribute will be used to provide the user with a descriptive error message. The last attribute tells CakePHP to stop validation if a rule fails. This prevents a situation where a user might get error messages that do not make sense in context. For example, the error message about the username needing to contain only letters and numbers wouldn’t make much sense if they left it blank. By supplying ‘last’ => true, a blank username will only generate the “required field” error message. Finally, I haven’t shown it here but I usually include validation rules for minimum and maximum length. It is good defensive coding practice to do so.

Now let’s set up a view. We’re going to register using the “register” action of a user controller so our view file will be located in /app/views/users/register.ctp. But before we get into the view itself, let’s take a minute to consider the layout. The layout can be thought of as a theme or template for the rest of the pages. We are not going to get into a lot of details on the layout here, but we need to do two things in our layout that are important. First, we need to make sure that the jQuery library is included. We can do that by linking it in the head section of our layout or we can use the HtmlHelper to include it. Next, we need to make sure that we are using the $scripts_for_layout variable in the head of the layout. See the CakePHP manual for more details on this.

With the layout properly prepared, we can create the view which will show a registration form. Here is an example:

<?php
	$this->Html->script('users/check_username',array('inline'=>false));
	echo '<h1>Create an Account</h1>';
	echo $form->Create('User',array(
				'action'=>'register',
				'inputDefaults' => array(
					'div'=>false,
					'label'=>false
					),
				));
	if (isset($errors)) {
		$form->error = $errors;
	}
	echo '<fieldset>';
	echo '<legend>Create an Account</legend>';
	echo '<ol>';
	echo '<li><label for="Username">Username';
	if ($form->isFieldError('username')) {
		echo '<br/><strong>' .  $form->error('username') . '</strong>';
	}
	echo '</label>';
	echo $form->text('username');
	echo '<span class="ajax_status"><img src="/img/ajax-loader.gif" alt="Checking..." /></span>';
	echo '<span id="username_ajax_result"></span>';
	echo '</li>';
	echo '<li><label for="Email">Email';
	if ($form->isFieldError('email')) {
		echo '<br/><strong>' . $form->error('email') . '</strong>';
	}
	echo '</label>';
	echo $form->text('email');
	echo '</li>';
	echo '<li><label for="Password">Password';
	if ($form->isFieldError('password')) {
		echo '<br/><strong>' . $form->error('password') . '</strong>';
	}
	echo '</label>';
	echo $form->password('password');
	echo '</li>';
	echo '</ol>';
	echo '</fieldset>';
	echo $form->end('Submit');
?>

First of all, regular CakePHP users will point out that the form helper can output the labels and errors. That is true but I prefer this style of formatting and want to use an ordered list for my form fields. Of course, I will style these both with CSS to make them look good. Typically, I set list-style: none; for the order list and display: block; for the label.

Most of this view is pretty standard stuff. When a user submits the form, our model will validate the submitted data. If there are errors, the controllers sets the $errors variable. In our view, we set $form->error to $errors in order to display the error messages. We have also set up a couple things to prepare for the Ajax part of our application. The first line of our view uses the HtmlHelper to set include a javascript in the head of our HTML. In this example, the script will be located in the file /app/webroot/js/users/check_username.js. By setting ‘inline’ to false, we have told CakePHP to include this script as part of the $scripts_for_layout variable which we discussed earlier.

We have also set up a span for displaying the progress and the final status of our ajax call. Next to the field for the username, we have created a span with the class “ajax_status”. In this span, we have loaded an animated gif to show that we are working at checking the username. Incidentally, you can create and download your own ajax-loader.gif from http://www.ajaxload.info. Obviously, we don’t want the loading image to show all the time, so we will add a css style to hide it for now:

.ajax_status {
	display: none;
}

We are going to need two additional CSS styles in a little bit. One will be used to display an error message if the username is taken and the other will be used to display a message if the username is available. These styles are:

.ajax_success {
	color: #036A0D;
}

.ajax_error {
	color: #FF0000;
}

Now we are ready to look at our controller. This will be the user_controller.php located in /app/controllers. First, we will create the register function to handle our submitted data:

<?php 
	class UserController extends AppController {
		var $name = 'Users';
		
		function register() {
			if (!empty($this->data)) {
				if ($this->User->save($this->data)) {
					// Success 
					$this->redirect('/users/login/');
				} else {
					$this->set('errors', $this->User->invalidFields());
				}
			}
		}
?>

This is a pretty basic and standard CakePHP add action. We first check to see if any data was submitted. If so, then we attempt to save the data using the associated User model. If the save method returns true, we successfully created a new user account and the user is redirected to the login screen. If however, the save method fails, then we set the $errors variable in the view to the model’s list of invalid fields. This is how we get the useful error messages to the user.

Now, we need to add an action for the Ajax request to check the username. To do this, we are going to use CakePHP’s prefix routing. To do this, we are going to edit /app/config/core.php and set the following line:

Configure::write('Routing.prefixes', array('ajax'));

This changes our default routing slightly. The easiest way to describe this is with an example. In our case, we want use the URL: /ajax/users/check_username/. With our routing prefix, this URL will cause CakePHP to call the action ajax_check_username on the Users controller. Likewise, if we wanted an Ajax version of the add form, we could create an ajax_add method in the Users controller. It would be available at the URL: /ajax/users/add/

.

Now, we need to create our Ajax method. We are going to add the following function into our Users controller.

function ajax_check_username() {
	$this->layout = 'ajax';

	if (!empty($this->data)) {
		if ($this->data['User']['username'] == '') {
			$this->set('value', 0);
		} else {
			$u = $this->User->findByUsername($this->data['User']['username']);
			if (empty($u)) {
				$this->set('value', 1);
			} else {
				$this->set('value', 0);
			}
		}
	} else {
		$this->set('value', 0);
	}
}

First, we tell CakePHP that we want to use the Ajax layout. This is a blank layout. We will be creating a view template for our Ajax action so we use $this->set to set our return values. We are going to post the username using Ajax, so we need to see if we received any posted data. We do this the same way we would with a traditional post request. We test to see if $this->data is empty. If it is not, we received some data. Next, we use the findByUsername method to look for an user with the posted username. If $u is empty, that means that we did not find any users with that username so we return 1. Otherwise, we return 0 meaning that the username is taken. Finally, if we did not receive any post data, we return 0. This is another defensive coding practice. An empty username is invalid so we say it is not available.

I mentioned needing a view for the Ajax action. Here is a basic view (found in /app/views/users/ajax_check_username.ctp):

<?php
	if (isset($value)) {
		echo $value;
	}
?>

Now it is time for the real magic. We are going to look at check_username.js. This script handles sending the username to our Ajax method and displaying the outcome. Here is the script:

$(document).ready(function() {
	$("#UserUsername").focusout(function() {
		$(".ajax_status").css('display','inline');
		$("#username_ajax_result").css('display','none');
		var $username = $("#UserUsername").val();
		$.post("/ajax/users/check_username/", {data:{User:{username:$username}}}, function(data) {
			$(".ajax_status").css('display', 'none');
			if (data == 1) {
				$("#username_ajax_result").attr('class','ajax_success');
				$("#username_ajax_result").text('Username is available');
			} else {
				$("#username_ajax_result").attr('class', 'ajax_error');
				$("#username_ajax_result").text('Username is already in use');
			}
			$("#username_ajax_result").css('display','inline');
                });
        });
});

Here we see the real beauty of using jQuery. In just a few short lines, we have managed to manipulate our HTML, send a request to the server and handle the return. Let’s look at this step by step.

The first line is a standard jQuery construct. When the document is ready, it will fire the function contained here. In the next line, we bind to the focusout event of our username text input. When the form field has focus (i.e. someone is typing it) and they move to the next field, this event will be triggered. We are selecting the form field by using the id generated automatically by CakePHP’s FormHelper. When the focusout event fires, we are first going to display the span with the “ajax_status” CSS class. If you remember from our view, this span contains the animated gif showing that we are working on a request. We also hide the span with the id “username_ajax_result”. If you remember our view, this span is empty so you might be wondering why we would hide it. If a user tried a username and it was taken, they might try a new one. This would fire the focusout event again so we would want to hide the previous result while we check the new username.

Next, we are going to send the username to our Ajax method. We define a variable and assign it to the value set in the username form field. Then, we are ready to use jQuery’s .post function to send the request. jQuery.post is a shorthand version of the usual .ajax method. We pass post the URL, the data, and a function we want to run on success. Keep in mind that here we are only talking about successfully, getting a response. We will have to look at that response to determine if the username if available or not. Also not, when we send the username, we set up the data[‘User’][‘username’] structure. This way data from our Ajax methods will be formatted the same way as data posted from the CakePHP FormHelper.

The final step happens in the function that jQuery calls on a successful Ajax request. First, we set display: none; on the ajax_status CSS class, effectively hiding our animated “working” gif. Then we look at the data returned by the Ajax call. If the value is 1, our username is available. We will set the class for the span with the “username_ajax_result” id to ‘ajax_success’. We will also insert text with a friendly message to the user that their username is available. If the value of the return data is not 1, we will set an error message and set the CSS class to ‘ajax_error’. Finally, we will set the display for the “username_ajax_result” span to ‘inline’.

We have looked at a simple way to make a CakePHP registration form more responsive by using jQuery to make an Ajax request. This should give you the basics to begin using Ajax with jQuery and CakePHP in your own applications. It might make sense, for example, to do the same duplicate check on the user’s email address. You could also, with a bit of work, do all of your form validation with Ajax. Fortunately, CakePHP and jQuery make it easy to build Ajax enabled applications.

Help us spread the word!
  • Twitter
  • Facebook
  • LinkedIn
  • Pinterest
  • Delicious
  • DZone
  • Reddit
  • Sphinn
  • StumbleUpon
  • Google Plus
  • RSS
  • Email
  • Print
If you liked this article, consider enrolling in one of these related courses:
Don't miss another post! Receive updates via email!

9 comments

  1. Henrique Machado says:

    Hello! Thanks for you code.

    You need to fix some syntax for commas, etc.. and the $.post(“ajax/users/check_username”,{data:{User:{username:$username}}},function(data) {

    should be changed to

    $.post(“ajax/users/check_username”,{data:{User:{username:$username}}},function(data) {

    Thanks

  2. Chris says:

    Hello,

    Thank you for your excellent tutorial. I have followed it to the letter, but I have the following problem:

    The line “…..$.post(“ajax/users/check_username”……” should call the “ajax_check_username” method in my Users controller. However, in my implementation it actually tries to call an “ajax” method, which does not exist – hence the error.

    So, surely my routing is not functioning properly? Well no, because the following URL in the browser takes me to the correct method and returns 0 (as no data was posted):

    127.0.0.1:80/cakephp/ajax/users/check_username

    If I created an “ajax” method and copy the code into it, and create an “ajax” view of course, then the code works fine! However, this doesn’t solve the issue.

    Forgive my naivety, but I beleive the post function in the check_username.js sending the URL to a relative address. I.e. the URL resolves to “localhost/cakephp/USERS/ajax/users/check_username” (with ‘users’ as the root) instead of “localhost/cakephp/ajax/users/check_username” (root as the root, the correct one). (I’ve capitalised ‘users’ to make it stand out).

    I’ve tested this by inserting the absolute address in the JS script, and hey presto it works, but doesn’t necesarily solve the problem as it is terrible practice (so I believe) to hard code a URL in such a way/case.

    I’d appreciate your input.

    P.S. Thanks to Henrique Machado; your note was very useful – I was wondering why no request was being returned, and the reason was the extra forward slashes as you pointed out.

  3. Chris says:

    Hello,

    As an update to my own question, I believe I also have the solution:

    In my Apache htdocs folder (localhost) I have more folders than just the cakephp folder (e.g. phpadmin, etc.) and so while the root of my project is ‘htdocs/cakephp’, my web root is of course ‘htdocs’. Hence, when specifying a relative URL (e.g. “ajax/users/check_username”) I must actually use “/cakephp/ajax/users/check_username”.

    Problem solved.

    One note: Henrique’s post suggested removing the leading and trailing forward slashes ‘/’ from the original tutorial’s URL. In light of my own situation, if I were to remove the leading backslash then my solution would not work (i.e. “cakephp/ajax…” is bad, but “/cakephp/ajax…” is good).

    Plus a final thought: when it comes to production time for my app, any references to “/cakephp/…” would be irrelevant as the root would then be “www.myappexample.com/”; and so, then question is, how should I go forward to avoid needing to replace all instances of “/cakephp/” ready for production?

    Thanks again.

    • Chris, thank you so much for taking this tutorial for a ride! 😉 It’s great to know that people out there are taking advantage of it. As far as relative vs. absolute paths and “dev” vs. “prod”, I usually try to keep my web root structure consistent across environments by configuring Apache VirtualHost(s) accordingly. So in dev your VirtualHost would say:

      <VirtualHost *:2030>
        ServerName localhost
        DocumentRoot /var/www/htdocs/cakephp
        ...
      </VirtualHost>
      

      where as in Prod, it would say:

      <VirtualHost *:80>
        ServerName learncomputer.com
        DocumentRoot /var/www/htdocs
        ...
      </VirtualHost>
      

      This way you can always rely on your “web” root being the same in dev and prod (i.e /ajax/…) regardless of your physical directory structure.

  4. Lee says:

    Awesome tutorial! I’m new to cakePHP and this really helped me understand how it integrates with ajax and jquery. Thank you thank you!

  5. Yoss says:

    Hi Michael,

    First of all: my compliments for this excellent tutorial, very well written and very complete, down to all the necessary details. I wish they were all like that 🙂

    Having said that: I cannot get it to work on my local Apache (a XAMPP installation in Windows).
    Whenever the $.post(“/ajax/users/check_username/ is called, I always get (in Chrome and Firefox) :

    POST localhost/ajax/users/check_email 403 (Forbidden)

    (Obviously I’m trying to check the email address instead of the username).

    I’ve got this in my core.php:

    Configure::write(‘Routing.prefixes’,array(‘admin’, ‘user’, ‘ajax’));

    The admin and user are there because I use Prefix routing to protect actions from being used by the wrong people
    (based on http://bakery.cakephp.org/articles/watermark86/2010/09/23/user-permissions-based-on-a-routing-prefix).

    I’ve tried this in routes.php:

    Router::connect(‘/ajax/users/check_email’, array(‘controller’ => ‘users’, ‘action’=>’check_email’, ‘ajax’=>true));

    But it makes no difference, I keep getting the same error…
    Perhaps I don’t get it because so far I only have 2 weeks experience with CakePHP …:-(
    Do you have any ideas?

    Thanks in advance,
    Yoss Vanderwielen

  6. detys says:

    here is the corrected code for your user model
    was missing a few tags and , and ‘

    var $validate = array(
    	'username' => array(
    		'username_not_empty' => array(
    			'rule' => 'notEmpty',
    			'message' => 'This field is required',
    			'last' => true
    		),
    		'username_alphanumeric' => array(
    			'rule' => 'alphaNumeric',
    			'message' => 'Usernames must contain 
    only letters and numbers',
    			'last' => true
    		),
    		'username_unique' => array(
    			'rule' => 'isUnique',
    			'message' => 'That username is already 
    in use.',
    			'last' => true
    		),
    	),
    	'email' => array(
    		'email_not_empty' => array(
    			'rule' => 'notEmpty',
    			'message'=> 'This field is required',
    			'last' => true
    		),
    		'email_unique' => array(
    			'rule' => 'isUnique',
    			'message' => 'That email is already 
    in use.',
    			'last' => true
    		),
    	),
    	'password' => array(
    		'password_not_empty' => array(
    			'rule' => 'notEmpty',
    			'message' => 'This field is required.',
    			'last' => true
    		)
    	),
    );
    

Comment