CakePHP: Update a select box using ajax

I noticed a lot of new comers asking this question: I want a select box to be updated using ajax when I change the current item in another select box .
So this is a quick tutorial on how to do exactly that.

Here is the scenario: We have a select box filled with categories, when we select a category, another select box is updated with article titles. Fair enough ?
First thing to do is create the DB tables:

  1.  
  2. CREATE TABLE categories (
  3. id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY ,
  4. name VARCHAR( 30 ) NOT NULL
  5. );
  6.  
  7. CREATE TABLE articles (
  8. id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY ,
  9. title VARCHAR( 30 ) NOT NULL ,
  10. category_id INT UNSIGNED NOT NULL ,
  11. INDEX ( category_id )
  12. );
  13.  

Ok next create the models, put them in app/models of course.

  1.  
  2. <?php
  3. // category.php
  4. class Category extends AppModel {
  5. var $name = Category’;
  6. }
  7. ?>
  8. <?php
  9. // article.php
  10. class Article extends AppModel {
  11. var $name = Article’;
  12. var $belongsTo = array(Category’);
  13. }
  14. ?>
  15.  

now comes the articles’ controller, we’ll have an action index where we’ll display the select boxes.

  1.  
  2. <?php
  3. // app/controllers/articles_controller.php
  4. class ArticlesController extends AppController {
  5.  
  6.   var $name = Articles’;
  7.   var $helpers = array(Html’,Form’,Javascript’,Ajax’);
  8.   var $components = array(RequestHandler’);
  9.  
  10.   function index() {
  11.     $this->set(categories’, $this->Article->Category->generateList());
  12.   }
  13.  
  14. }
  15. ?>
  16.  

Nothing fancy for the moment, we add the Javascript and Ajax helpers to the list of helpers that will be available in the view domain ( layout + views ).
We use the RequestHandler component, it will detect ajax requests and set proper header and layout for us, sweet isn’t it!
Our next step is to create a layout, we could just use the default layout but we need to include the prototype and scriptaculous libraries. make sure you downloaded the files and put them in webroot/js
Add these two lines in the head section of your layout:

  1.  
  2. echo $javascript->link(prototype’);
  3. echo $javascript->link(’scriptaculous’);
  4.  

If you’re using 1.2 ( not sure it’s available in 1.1 ) you can write it in one line of code:

  1.  
  2. echo $javascript->link(array(prototype’,’scriptaculous’));
  3.  

To be honest, you don’t need scriptaculous here, we’re not going to use it, just include prototype.
All right, all this above was basically just preparation, now here’s the view file of the index action. Create a folder named articles under app/views. Create a file in it, name it index.ctp if you’re using 1.2 or index.thtml if you’re using 1.1 (thtml works in 1.2 too but it’s depreciated).
Here is its content:

  1.  
  2. <?php
  3. // app/views/articles/index.(thtml|ctp)
  4. echo $html->selectTag(Category/id’, $categories, null, array(id’ => categories’));
  5.  
  6. echo $html->selectTag(Article/id’,array(), null,array(id’ =>articles’));
  7.  
  8. $options = array(url’ => update_select’,update’ => articles’);
  9.  
  10. echo $ajax->observeField(categories’,$options);
  11. ?>
  12.  

In 1.2, selectTag() is depreciated, change it to select().

  1.  
  2. echo $form->select(Category.id’, array(options’=>$categories), null, array(id’ => categories’));
  3.  
  4. echo $form->select(Article.id’,array(), null, array(id’ =>articles’));
  5.  

All right, nothing fancy either, we create two select boxes, we populate one with category names set from the action. We give both selects an id and we call observeField. This method of the ajax helper basically observes a DOM element for a change, if it occurs, it calls an url and updates a DOM element with the content returned from that url.
Let’s move on to create the action that we’ll be requested by observeField.
In the ArticlesController add this code:

  1.  
  2. function update_select() {
  3.   if(!empty($this->data[Category’][id’])) {
  4.     $cat_id = (int)$this->data[Category’][id’];
  5.     $options = $this->Article->generateList(array(category_id’=>$cat_id));
  6.     $this->set(options’,$options);
  7.   }
  8. }
  9.  

and it’s view..

  1.  
  2. <?php
  3. // update_select.(ctp|thtml)
  4. if(!empty($options)) {
  5.   foreach($options as $k => $v) {
  6.     echo "$v";
  7.   }
  8. }
  9. ?>
  10.  

Fun fun..now populate the db tables with some dummy data and test it out.
This was just one way of doing it, we could return JSON, XML or a script that’ll get executed.
Easier than that you die.