Using Ajax to Populate a Select Box in CakePHP

16th October 2008 CakePHP jQuery

My apologies for the lack of articles recently, after completing the monster Dvd application I've been pretty busy with regular work and a few freelance gigs. In a project I've been working on I had to dynamically grab some data and then use it in a select box, I thought this would be a great example to go through since its quite a common process in web development.

In this article I'm going to be using jQuery as my Javascript library, I know that CakePHP has built in support for script.aculo.us but its something I've never used and jQuery will do the job just fine.

The Form

The project I'm working on has multiple Users and they each have many Pages on this form there is a select box of Users and when I select a user I want a list of all their pages. I can use jQuery to get the list of Pages in the background by using Ajax.

Below is a standard form with a few extra bits that are required for Ajax. First the form has a class of ajax_page which I'll use to identify the form, I've also added a ajax_loading_image div which will act as a placeholder for the loading image to indicate when Ajax is in use. I've also added an extra class to the Page input so that I can update it.

<?php echo $form->create('Page',array('class'=>'ajax_page'));?>
	<fieldset>
 		<legend>Add Page</legend>
		<div class="ajax_loading_image"></div>
		<?php
		echo $form->input('user_id',array(
			'label'=>'User: <span>*</span>',
			'title'=>'User of Page. (Required)'
		));
		echo $form->input('page_id',array(
			'label'=>'Parent Page: <span>*</span>',
			'title'=>'Parent Page of Page. (Required)',
			'div'=>'input select ajax_page_id'
		));
// more fields...
<?php echo $form->end();?>

The Javascript

Ok so in the Javascript I've attached a change event handler to the User select box, this will fire whenever the select box has changed value. I'm then going to update the ajax_loading_image div with an image, this will indicate that the system is busy to the user and provides some essential feedback.

Using jQuery its really easy to setup an Ajax request, I've setup the URL which will be the controller and action that will be dealing with the Ajax request and passed some variables to the controller in the form of a query string. This will include an "ajax" variable and the "id" of the selected user.

The success function will deal with the result of the controller action, more specifically this will update the form with the new HTML that will be outputted by the controller action. Finally the Ajax loading image is removed to indicate that the ajax request is no longer in use.

// file: /app/webroot/js/common.js

// add page form
$('.ajax_page #PageUserId').change(function(){
	// selected value
	var selected = $(this).val();
	
	// set loading image
	ajax_loading_image('.ajax_loading_image');
	// ajax
	$.ajax({
		type: "POST",
		url: '/pages/ajax_get_user_pages',
		data: "ajax=true&id="+selected,
		success: function(msg){
			//console.log(msg);
			$('.ajax_page_id').html(msg);
			// remove loading image
			ajax_remove_loading_image('.ajax_loading_image');
		}
	});
});
});

// set a loading image
function ajax_loading_image(div) {
	$(div).html('<img src="/img/ajax_loading.gif" alt="Ajax Loading Image"/>');
}

// remove loading image
function ajax_remove_loading_image(div) {
	$(div).html('');
}

The Controller

The controller action is pretty straight forward, it retrieves the "user_id" from the params variable and uses it to get a list of Pages from the database that are assigned to that User. Note that I've set the layout to null so that the View file being outputted is not wrapped in any layout HTML.

// file: /controllers/page_controller.php

// get user pages for form
function ajax_get_user_pages() {
	// init
	$id = $this->params['form']['id'];
	$pages = array();
	$this->layout = null;

	if($id > 0) {
		// get pages
		$pages = $this->Page->find('list',array('conditions'=>'Page.user_id'=>$id));
	}

	// set
	$this->set(compact('pages'));
}

The View for the Ajax Action

The View file is a single call to create a form select box with the list of Pages from the database. It has a few more options to specifically identify it as a select box and to use the $pages variable that has been passed from the controller.

<?php
// file: /views/page/ajax_get_users_pages.ctp

if(!empty($pages)) {
echo $form->input('page_id',array(
	'label'=>'Parent Page: <span>*</span>',
	'title'=>'Parent Page of Page. (Required)',
	'type'=>'select',
	'options'=>$pages,
	'div'=>false,
	'name'=>'data[Page][page_id]'
));
}
?>

Wrapping Up

So whats going on here, well I created a form as usual and added a few extra divs and classes that will identify the form. Using jQuery I've attached a change event handler to the User select box and when a User is selected its "id" is sent to the ajax_get_users_pages() action in the Pages Controller.

Like with any Controller that outputs code you must also create a View and this is simply a single form input to display a list of all the Pages that the User has. The form HTML is then sent back to the Javascript code and I replace the existing form code with the new HTML from the controller and because we're using Ajax the page doesn't need to be refreshed.

Back to Home Page

Comments

Luke aka boobyWomack (01/12/2008 - 09:45)

Hi James

jsut wanted to say I love your sites tutorials for Cake :) this one, although has few comments, is one of the best. Really ncie intro to simple Jquery ajax with Cake.

Helped me get up and running with a little HABTM multi select dynamic add just now :)

James (01/12/2008 - 11:52)

@Luke: Hey thanks, glad this helped you out.

Kristoffer Darj (21/03/2009 - 16:39)

Superb.
Took a little while before I figured out I had to wrap the .js code into a .ready function. But I just got started with jQuery.

Umit (16/04/2009 - 09:08)

Hi James, i am trying filling a select box with jquery ajax. it is functioning correctyl until i do Configure::write('debug', 0);
ufter that code ajax response return nothing. can you hlp me pls.

James (18/04/2009 - 07:33)

@Umit: Hey thanks for commenting, I'm not too sure why your ajax request isn't working when you set debug to 0. Try using firebug to check if the Ajax call is actually being called and try outputting all your controller code to the screen to pinpoint where its failing.

Miguel (07/06/2009 - 11:57)

Thanks, really helped me out! :)

George (13/07/2009 - 07:32)

Thanks, awesome post. You make AJAX calls with Cake seem dead simple.

I'm actually more comfortable doing it this way than using the built in Cake methods/helper.

Cheers James for your time, your posts are quality.

Hector (02/09/2009 - 18:31)

First of all, thanks a lot for the code! It's really great!

I made it work as it is but I'm having a little trouble when having three consecutive select boxes. I change the first value, and the second one loads up fine. I repeated the whole process, created the pages and the funcitions for the third one to load up when changing the second one, but it doesn't work!

I checked with Firebug, and I'm not entering the function:
$('.ajax_page #PageUserId').change(function(){
when calling it for the second select box, which in my case would be like calling:
$('.ajax_page #PageId').change(function(){

Any idea of why this is happening?

Thanks in advance!
HP

James (02/09/2009 - 23:04)

@Hector: The only thing I can think of is when you are populating the second select box via ajax the Javascript "change" event is being lost so you may need to rebind it after the HTML is updated. What I usually do it put the "$('.ajax_page #PageId').change(function(){" inside a function so that after an ajax call is made the function is recalled. Let me know how you get on.

Cesar (23/09/2009 - 11:19)

Thanks a lot for the explanation and the tip about firebug. I was struggling a while understanding what was not working before browsing the firebug console. There I noticed that the call to the method / action was improperly redirected: http://localhost/pages/ajax_get_user_pages' instead of http://localhost/%app%/pages/ajax_get_user_pages. I included the app name in the url in common.js, line 13. And that was it. (Well I actually ran into another issue by forgeting to "auth/allow" the ajax_get_user_page action.

Scotto (10/12/2009 - 08:44)

Shouldnt the following line be changed?
$pages = $this->Page->find('list',array('conditions'=>'Page.user_id'=>$id));

to

$pages = $this->Page->find('list',array('conditions'=> array('Page.user_id'=>$id)));

James (10/12/2009 - 10:16)

@Scotto: Yes you're right, thanks for pointing that one out!

nomeo (01/02/2010 - 03:32)

Thank you for your tutorial.. I really helped me out.. :)

truetone (26/02/2010 - 07:12)

Thanks for this! Worked like a charm for me.

Giuseppe (20/07/2010 - 02:42)

Thanks!!! :)

Add Your Comment

Full Apps

Request a Feature Application Ajax Store Locator Application FilmDB Application TVDB Application CakeBattles Application CakeCatalog Application

Recently Watched Films

Mr Brooks Rec Pathology Diary of the Dead

TV Shows

The Shield Flight of the Conchords

Hosting

I've been using Dreamhost for my hosting for over 2 years and it's pretty good for what you pay. If you're signing up how about showing me some love and use this link so I get a referal bonus to help pay my server costs.

Site by James Fairhurst 2008-2010, all rights reserved and all that malarky