Dynamic select box with CakePHP 2.0

I wanted some auto populating Select boxes in a site I was creating so that when I changed a Category the Subcategories would automatically update. CakePHP can do this pretty easily but it is let down by the documentation as there are no examples.

Initially I came up with a version that wrote JSON into a JavaScript variable in the page and then used jQuery to achieve the updating of select elements but I wanted to do this the Cake way which uses AJAX and as little code as possible.

Here is a simplified solution to demonstrate how it can be done.

This assumes a new CakePHP site already configured with a database connection. I used CakePHP 2.0.3 but this may also work with 1.3 (?)

1. Models

We need 3 associated tables

CREATE TABLE `categories` (
  `id` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,
  `name` VARCHAR(60) DEFAULT NULL,
  PRIMARY KEY (`id`)
);
 
INSERT INTO `categories` (`id`, `name`)
VALUES
	(1,'books'),
	(2,'music'),
	(3,'electronics');
 
CREATE TABLE `posts` (
  `id` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,
  `title` VARCHAR(50) DEFAULT NULL,
  `subcategory_id` INT(10) UNSIGNED DEFAULT NULL,
  PRIMARY KEY (`id`)
);
 
INSERT INTO `posts` (`id`, `title`, `subcategory_id`)
VALUES
	(1,'The title',1),
	(2,'A title once again',4),
	(3,'Title strikes back',7);
 
CREATE TABLE `subcategories` (
  `id` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,
  `category_id` INT(10) UNSIGNED DEFAULT NULL,
  `name` VARCHAR(60) DEFAULT NULL,
  PRIMARY KEY (`id`)
);
 
INSERT INTO `subcategories` (`id`, `category_id`, `name`)
VALUES
	(1,1,'fiction'),
	(2,1,'biography'),
	(3,1,'children'),
	(4,2,'classical'),
	(5,2,'rock'),
	(6,2,'jazz'),
	(7,3,'camera'),
	(8,3,'audio'),
	(9,3,'tv');

Bake the 3 Models for these and allow CakePHP to define model associations for you.

2. Controllers

Bake a Controller for the Post Model with the default CRUD actions.

While you are at it you will also need to bake the CRUD Views for the Post Controller.

If you browse to the posts index page now you can view the data.
In this example I will add the Category and Subcategory select lists to the Add view, so now we need to change this so that the selection in the second list changes according to the selection in the first list.
This will be done via the Js Helper so make it available at the top of the Posts controller (after the Class declaration) with:

public $helpers = array('Js');

You need a Subcategories Controller with a single action to provide the data via AJAX:

<?php
App::uses('AppController', 'Controller');
 
class SubcategoriesController extends AppController {
 
	public function getByCategory() {
		$category_id = $this->request->data['Post']['category_id'];
 
		$subcategories = $this->Subcategory->find('list', array(
			'conditions' => array('Subcategory.category_id' => $category_id),
			'recursive' => -1
			));
 
		$this->set('subcategories',$subcategories);
		$this->layout = 'ajax';
	}
}

3. Views

The view is a very simple AJAX view that renders the option tags that go within the select tag.

<!-- file path View/Subcategories/get_by_category.ctp -->
<?php foreach ($subcategories as $key => $value): ?>
<option value="<?php echo $key; ?>"><?php echo $value; ?></option>
<?php endforeach; ?>

4. Putting it all together

In the Post Add view, file path: View/Posts/add.ctp we can finally add the Js methods that make the dynamic updating happen. This is the cryptic bit that I struggled with for a few hours as although the CakePHP documentation outlines all the options there are not any complete examples.
Firstly add a categories select element to the form (and change the order of the elements):

<?php
	echo $this->Form->input('category_id');
	echo $this->Form->input('subcategory_id');
	echo $this->Form->input('title');
?>

Add the following code to the bottom of the view, this uses the Js Helper to create the necessary jQuery to perform the updating:

<?php
$this->Js->get('#PostCategoryId')->event('change', 
	$this->Js->request(array(
		'controller'=>'subcategories',
		'action'=>'getByCategory'
		), array(
		'update'=>'#PostSubcategoryId',
		'async' => true,
		'method' => 'post',
		'dataExpression'=>true,
		'data'=> $this->Js->serializeForm(array(
			'isForm' => true,
			'inline' => true
			))
		))
	);
?>

This is saying – watch the HTML element with Id PostCategoryId for a change. When it changes update the HTML element with Id PostSubcategoryId with the response from subcategories/GetByCategory, the data option is used to send the current value from the initial select element.

Before you leap to test this you need to make changes to the default layout to include jQuery and provide a place for your scripts to be written out.

<?php
// file path View/Layouts/default.ctp
?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
	<?php echo $this->Html->charset(); ?>
	<title>
		<?php echo $title_for_layout; ?>
	</title>
	<?php
		echo $this->Html->meta('icon');
 
		echo $this->Html->css('cake.generic');
	?>
</head>
<body>
	<div id="container">
		<div id="header">
			<h1>Dynamic Select Box Demonstration</h1>
		</div>
		<div id="content">
 
			<?php echo $this->Session->flash(); ?>
 
			<?php echo $content_for_layout; ?>
 
		</div>
		<div id="footer">
			footer
		</div>
	</div>
	<?php echo $this->element('sql_dump'); ?>
	<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.6.4/jquery.min.js"></script>
	<!-- scripts_for_layout -->
	<?php echo $scripts_for_layout; ?>
	<!-- Js writeBuffer -->
	<?php
	if (class_exists('JsHelper') && method_exists($this->Js, 'writeBuffer')) echo $this->Js->writeBuffer();
	// Writes cached scripts
	?>
</body>
</html>

Now you can test the application. On changing the category select list, the subcategory one will automatically update to show the relevant options.

This entry was posted in PHP. Bookmark the permalink.

81 Responses to Dynamic select box with CakePHP 2.0

  1. kharuz88 says:

    Im new to this Cakephp and currently im using cakephp 1.3.
    My Question is is cakephp1.3 compatible with your tutorial because i follow the the step but it seem it cant populate the 2nd select list.
    Please help me ^_^

  2. Miglos says:

    This is what I’m looking for! Since it’s my first CakePhp project I have lots of doubts, for example, what do you mean by “Bake the 3 Models for these and allow CakePHP to define model associations for you. “.
    Does it means to create the CRUD model, view, controller files for each table through console?
    Thanks!

  3. miglos says:

    Well, it finally worked!!! I did all the baking for my models through the command prompt, had to do it twice, but it’s all worth it
    I’m using CakePHP 2.0 and now I have to a second level, hopefully I’ll have this done this year!

  4. miglos says:

    It’s the first time I use a framework and all I have to say is CakePHP rocks!!!

  5. Mog says:

    Found this very useful. Thank you.

  6. Paulo H3nrique Alves says:

    Very very util Richard.

    Thanks!!

  7. Vinícius Hipólito says:

    Great post, you saved my job hahaha. I spend almost 2 days to find this. Good job ^^

    And, it works in 1.3, just remove the line “App::uses(‘AppController’, ‘Controller’);”
    and change “$this->request->data['Post']['category_id'];” to “$this->data['Post']['category_id'];” in in SubcategoriesController.

  8. John H says:

    Seriously thank you for this tutorial. Works great.

    Bonus thanks to that Vinicius dude for the help with getting it working on 1.3

  9. Bill says:

    Thanks for this tutorial, excellent work.

  10. Matthew says:

    If you are having trouble getting this to work, as I was, pay mind to your layout file. If you are using a default layout for all pages you have to add $this->layout = false to the getByCategory function.

  11. SnowLeo says:

    I tested this example on the version of CakePHP 2.1.
    And I have not earned a conclusion without the inclusion of the following categories of code in the controller entry PostsController.php:

    $ categories = $ this-> Post-> Subcategory-> Category-> find ('list');
    $ subcategories = $ this-> Post-> Subcategory-> find ('list');
    $ this-> set (compact ('categories', 'subcategories'));

    After that, everything worked fine, just need to add code to initialize the list of subcategories when displaying the page “Add Post”.

    • sgaknis says:

      YES pay attention to this. Does not work without the above…!

      • Gajendra says:

        Yes,otherwise category not displayed,please add these thing in article otherwise nice article to start with JSON Onject in cakephp.

        Thanks
        Richard

    • its_fhm says:

      Hey, I’ve been struggling to add these lines of code in order to get it to work for the newest versions of cake.

      Where exactly do I implement this? what line round about in the PostController?

      I’m still using the exact data from the tutorial for reference, everything is working 100% besides the list for category not loading, thus making the functionality pointless! I’ve tried on top, in the add function, and just randomly but cant manage to get it to work. Please advise!

  12. Bisuke says:

    Hi Guys, I am new with CakePhp and trying to implement this features on Cake 2.06. I tried the code, somehow it’s not working.
    I have some question:
    the View/Subcategories/get_by_category.ctp -> shouldn’t this be name as getByCategory.ctp?

    Next is on the add.ctp page
    Js->get(‘#PostCategoryId’)->event(‘change’, -> shouldn’t the #PostCategoryID be name as #category_id instead?
    ‘update’=>’#PostSubcategoryId’, -> shouldn’t the #PostSubcategoryId as subcategory_id?

    Please do advice, thank you in advance.

    • Richard says:

      Hi,
      I checked the CakePHP view naming conventions which look like they have been updated recently and it says The basic pattern is /app/View/Controller/underscored_function_name.ctp.
      As for #PostCategoryID – I suggest you take a look at the HTML that CakePHP is generating for the form element for category_id and see what id it has been assigned. In my version as the form was created with the Post model, that was prefixed on all form elements, if you are using a different model then your id’s will be different.

  13. Claire Boussard says:

    Thanks for the tutorial, it works well (better when man do’nt forget to authorise the getBy function), but I run into performance issues.

    In France we have departments (about 100), and in each department communes, there are 39 000 communes in all. I want to select a department, and then have the list of the communes for that department to pick one. It works, but I must wait very long for the response, the javascript script seems not responding, hitting continue several times, at the end it works.

    Have you got an idea ?

    • Claire Boussard says:

      This issue is resolved : there was an error in the model, in the description of the belongsto join, the foreign key was missing, letting cake to do the cartesian product of the two tables…
      Nothing related with javascript.

  14. tag says:

    Thank you. It was really helpful.

  15. Paulo says:

    Thank u. very useful, it helped me a lot, thanks

  16. Adam says:

    Thanks, this page was a huge help.

    Question: I want my subcategory select to appear correct when the page loads. Currently my page loads listing all categories and all subcategories; the subcategory select doesn’t refresh until the user clicks into categories (because it’s listening for the change event).

    Would I use an event other than “change”? Or is some other workaround to execute getByCategory (with whatever default category ID shows up in the category select) when the page loads?

    Thanks in advance.

    • Richard says:

      Hi Adam,
      I wouldn’t use JavaScript to change this when the page loads.
      The initial contents from the list are coming from the database in the add action of the posts controller. You should be able to add in a database select there to populate the lists with whatever contents you want to display.

  17. Adam says:

    Richard,

    Thanks, yes – that makes sense. I modified the query for subcategory such the default contents of the Subcategory select match the default Category select. Cool!

    One more question: Any thoughts on a 3-way relationship? e.g. Category/Subcategory/Sub-subcategory.

    At present, when Category changes the Subcategory updates OK, and when Subcategory changes Sub-subcategory updates OK. Is there any way I can force the Sub-subcategory (which is listening for a click event on Subcategory) to update when the Category changes? Can Sub-subcategory listen for click events on multiple items? In pseudo-code for Sub-subcategory, something like:

    $this->Js->get(‘#PostCategoryId’ || ‘#SubCategoryId’ )

    At the moment, it’s possible for someone to pick a valid Category, a valid Subcategory but an invalid Sub-subcategory (if Subcategory never registered a click event. Thanks in advance for your thoughts!

  18. karan shah says:

    dear i have use this example in cake 2.1 but i think your bit of code for jquery is not working for me will you please help me how can i make it success…..

    simply jquery code is not working so postbake is not occure.

    thanks in advance

  19. mkhuda says:

    i’ve got a problem when accesing HOST/subcategories/getbycategory

    Notice (8): Undefined index: Post [APP\Controller\SubcategoriesController.php, line 7]

    why is this ? (i using cakePHP 2.2.x)

  20. amir says:

    great Job. it works for me .

  21. Ricky says:

    Hello,

    How would I use this for a many to many relationship? I tried to get this to work on my models but I can’t get it to work.

  22. Abu Zaid says:

    Thanks a lot… this working perfect with cake 1.3.x and it’s really simple to implementation.

    May i ask something of your code ? Can you explain what the usage for this line :
    if (class_exists(‘JsHelper’) && method_exists($this->Js, ‘writeBuffer’)) echo $this->Js->writeBuffer();

    Because it don’t work if i don’t write that line of code in my view/layout.
    I write that line in my view (at the bottom) and not in my default layout.

  23. Thanks dude.. :D
    it work like a charm!! :D

  24. Vipin says:

    Thanks dude……….

  25. Vipin says:

    Please help me to find error in this

    $this->Js->get(‘#subject_id’)->event(‘change’,
    $this->Js->request(array(
    ‘controller’=>’Performancerecords’,
    ‘action’=>’getByDate’
    ), array(
    ‘update’=>$exam_date_options,
    ‘async’ => true,
    ‘method’ => ‘post’,
    ‘dataExpression’=>true,
    ‘data’=> $this->Js->serializeForm(array(
    ‘isForm’ => true,
    ‘inline’ => true
    ))
    ))
    );

  26. Vipin says:

    thanks dude i make it

  27. Vipin says:

    Hey Friend..Can we use $this->Js->get() as genric function for two or three models
    you tried it..i am new please help me out…………………..thanks in advance

  28. andre says:

    Works great except for one thing. I can’t understand why, but when validation found errors (i.e. some empty fields) the page reloads with the validation messages but the two selects are empty.. any suggestions?
    Using cakephp 2

  29. brillian musyafa says:

    good job,
    this is very helpfull

  30. Rafael Santos Sá says:

    Nice! It worked for me. (Cake 2.2)

    But I realised that I had to call writeBuffer() in my view add.ctp and not in default.ctp.

    And ajax layout was not working because I was missing RequestHandler component.

  31. sandeep says:

    I getting error undefined index and noting is displaying

    function ajaxstates()
    {
    //echo debug(isset($this->request->data['Listing']['Country_Name']));
    $conditions = isset($this->request->data['Listing']['Country_Name']);
    $states = $this->State->find(‘list’,array(‘conditions’=>array(‘State.Country_Id’ => $conditions),’recursive’ => -1));
    $this->set(‘states’,$states);
    $this->layout=”ajax”;
    }

  32. Gonzalo says:

    Great man!!… It was very useful!!

  33. Mauri says:

    don’t work
    $this->request->data is empty in cotroller..
    why?

  34. Fábio says:

    Hello,
    I follow exactly the tutorial, but don’t update the field subcategory.
    I only change the names of fields and I checked all the names, I think that I make all the things right.
    But don’t work.
    The field subcategory don’t update.
    Can you help me ?
    Thanks so much for the tutorial, is exactly what I needed.

  35. Jaff says:

    category on add post is blank???? using cakephp 2.3.0 beta.. not getting from where category would be populate in post add view??? please help.. your precious input is appreciable. Thanks

    • Jaff says:

      Hi all CakePHP 2+ coders. Thanks to the comments of SnowLeo. This will work and only work in CakePHP 2+ if your follow SnowLeo solution, however rest is ok. Really great tutorial Thanks Richard :) ;)

  36. mpe says:

    Excellent … just what I was looking for and works perfectly.

    Now I’m trying to dynamically populate many form fields (address details) when a user makes a text box selection.

    Is this the right approach?

    Thanks in advance,

    Michael

  37. Pingback: CakePHP update multiple form fields from selectbox change « CakePHP Articles

  38. Shad says:

    Thank you a lot! This helped me much.

  39. Gie-Art says:

    Thanks… very helped! :)

  40. Ujelang says:

    Can someone send me the whole project please? I couldnt find the way to make it work… :(

  41. Vio says:

    The jquery doesn’t update in cake php 2.3. Wher is the problem???

  42. Vio says:

    Im sorry the jquery work but categories_id=0

  43. Vio says:

    Can someone confirme if is work in 2.3???

  44. Ryan says:

    Vio, look at the script being produced by the JS Helper. In my app it was messing up the URL inserting a ‘\’ next to each ‘/’ (So URL read AppName/\Controller/\Action). For now I have hardcoded the JS based on what the helper made. By removing the extra slashes my drop downs now update as they should :)

    Thanks a billion times to the OP

  45. Carl says:

    thanks for publishing your solution.
    I was on the way, but …
    One thing, that took me a long time,
    is that your approach doesn’t work with the security component in cake –
    you have to unlock the fields before.

  46. Jo says:

    Dear All,

    I tried this but while trying to run it does not show any results. But while clicking the category from the dropdown box i am getting the alert, but while alerting the html alert(html) it doesnt work. Please help

    $(document).ready(function () {
    $(“#UserCategoryId”).bind(“change”, function (event)
    {$.ajax({async:true, data:$(“#UserCategoryId”).serialize(), dataType:”html”, success:function (data, textStatus) {$(“#UserSubcategoryId”).html(data);}, type:”post”, url:”\/jothirajan\/cakephp\/subcategories\/getByCategory”});
    return false;});} );

    Thank you.

  47. Daniele says:

    You need to sanitize the response… a malicious user can inject a … which can be executed when the AJAX callback updates the target div with the new rendered view…

  48. Karey says:

    Thanks for this tutorial Richard. I can confirm that this works with CakePHP v2.3.2. My model relations were a little bit different, but regardless it worked like a charm. I just need to make some adjustments to the code for it to work with the jQuery Chosen plugin for CakePHP and I’ll be much happier. Great post!

  49. Adam says:

    This works great, but how do I create the edit view for posts? I also have not been able to get the drop down lists to begin with default values. To get it to work properly, I had to remove the subcategory array from the controller so that the drop down list remained empty. Then I had to add an empty value to the category drop down list that would force the user to select a category instead of having “books” selected by default. That at least gets my add page functional, I just really need an example to follow for the edit page.

    Thanks!

  50. that was awesome man, Tanku

  51. Paul says:

    Thanks, for the great lesson, works like a charm :)
    I have got one question, regards editing records:
    When adding records everything works OK, but what if I need the same thing when editing records. I mean, when the user opens the editing page, the subcategory component should show the value and not be empty. How to make it show the required subcategory…

  52. hey man, thx 4 the great tutorial! rly helped me A LOT!

    can you explain a few things to me?

    1 – how to display the select box 4 the subcategory only AFTER a category is selected?
    2 – how to develop this in a way that that the category and subcategory models are not necessarily tied to the model Post like in that controller part $this->request->data['Post']
    3 – How to develop in a way that the Javascript part is not necessarily tied up to the Post itself? I mean, lets say 4 instance I have other models that need to access that category and subcategory select boxes and stuff.

  53. DavePgh says:

    Fantastic! Worked like a charm!

  54. John SHU says:

    In cakephp-2.3.6 and probably 2.3 in general to get it working,

    1. Change the ‘method’ from ‘post’ to ‘get’
    2. To access the data now in the controller you need to use: $this->request->query['data']['ModelName']['field'] or how ever your data structure looks like. But be sure to access the data using $this->request->query as you are no longer using a ‘post’ method.

    This is what worked for me after languishing over this for about 4 hrs.

    Great Tutorial though

  55. Christian says:

    Good morning to all,

    Sorry for my basic English.

    I have been several days to make a Dynamic select box but I did not succeed.
    I’m using version 2.3.3 and I followed this guide and many more.

    I do not know what I’m doing wrong, someone could lend me your help? or put a complete example for this version on Github or send it to me by email?

    I’m a little desperate with this.

    Thank you very much to everyone in advance.

    Greetings.

  56. Devasish says:

    Thnaks a lot……..

  57. Devasish says:

    I have done my job with your help.
    can u please tell me how to set the loading Gif here.
    I am using the follwing code……

    Loading…
    Js->get(‘#StudentMainAdmissionCourseId’)->event(
    ‘change’,
    $this->Js->request(
    array(
    ‘controller’ => ‘years’,
    ‘action’ => ‘getByCourse’
    ),
    array(
    ‘update’ => ‘#StudentMainAdmissionYearId’,
    ‘before’ => $this->Js->get(‘#loading’)->effect(‘fadeIn’),
    ‘complete’ => $this->Js->get(‘#loading’)->effect(‘fadeIn’),
    ‘async’ => true,
    ‘method’ => ‘post’,
    ‘dataExpression’ => true,
    ‘data’ => $this->Js->serializeForm(
    array(
    ‘isForm’ => true,
    ‘inline’ => true
    )
    )

    )
    )
    );
    ?>

    but its not working. no output is there when I add ‘before’ and ‘complete’ options

  58. Sharif Khan says:

    Excellent! Got it to work with my data beautifully. Just a quick question: How do I trigger that event after the page is loaded? I have copied it into my edit.ctp page and I want to populate the subcategory dropdown based on the loaded value of the category dropdown.

    Thanks

  59. cele says:

    Thanks!!!

  60. mark says:

    Regarding those chained select boxes I wrote an article about AJAX and a 2.x solution to it: http://www.dereuromark.de/2014/01/09/ajax-and-cakephp
    Especially in light of the deprecation of the JS helper I tried to use jQuery to handle the response, which isn’t that bad really.
    Regards, Mark

  61. Jose says:

    Thanks for the tutorial, at least, it works for me!!

  62. Kim says:

    It worked pretty fine. Many thanks, my friend!

  63. chowen says:

    Hello, the example works fine to me but however if I used same way for 3 dropdown box its not working for the 3rd.
    I mean when 1box is changed then 2nd is also changed but 3rd one does not change at the same time.

    Do you have any idea?

  64. Maxtor says:

    I am in the same situation like kharuz88

    i have followed the tutorial step by step , nothing was missed , however it does not populate the second list , may be jquery or cakephp issue ????? i am using jquery 2.1.0 (Minified) and CakePHP 2.4.6 ( recent version )

  65. shahram says:

    it works with some changes.

  66. Divya says:

    Is it compatible with 2.x because it is not working for me and i am using cakephp 2.4

  67. Divya says:

    It is not working for me.. and i am using cakephp 2.4..

  68. shahram says:

    it works with 2.4

  69. Beowulfdgo says:

    what changes… please tell us…
    It is not working for me.. and i am using cakephp 2.4..

Leave a Reply

Your email address will not be published. Required fields are marked *


1 + = 8

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>