Use CakePHP + jQuery to build dynamic selects…

First, I can’t believe I’ve missed a whole month of posting…. damn 28 days :(

Anyway, a recent post on the bakery http://bakery.cakephp.org/articles/view/dynamic-select-boxes-with-ajax-jquery prompted me to show a slightly more accurate approach on working with the given scenario.

(I don’t mean to piggy-back on someone’s work, but I feel it deserves a little “touch-up”).

If you don’t feel like reading the other post, the basic idea is to build a dynamic select list using CakePHP + jQuery.

For this example we’ll first select a car make and then build a select list of available models using jQuery.

In order to accomplish this, first of all, the appropriate association should be established between the models:
Car hasMany CarModel

Based on that we can have two controllers:

  1. cars_controller.php
  2. car_models_controller.php

Next, of course, we’ll need some actions and views…

(The simple add/edit/etc… you could easily “bake”, so I’ll just focus on jQuery and relevant views at this point).

In CarsController we’ll add a list_models() method…

Now let’s take a look at the relevant view (list_models.ctp).
Again, here we are only focusing on the two drop-downs.

<?php $this->Html->script('views/cars/list_models.js', array('inline' => FALSE)); ?>

<?php 
  echo $this->Form->input('Car.name', array('empty' => 'Select One', 'options' => $names, 'id' => 'car-name'));  
?>

<div id="car-models" style="display: none;">
  <?php echo $this->Form->input('CarModel.name', array('type' => 'select', 'id' => 'car-model-name')); ?>
</div>

First, we’ll load up the jQuery script, which is relevant to the view. Despite my previous conventions, I find it much easier to replicate the structure of your JS file placement exactly as you’d do for the views. With one obvious difference, that all JS goes under /webroot/js/views/some_controller/same_as_view_name.js

You’ll notice that I wrapped the second select input into a div, which is hidden by default.
This is just one approach, but you certainly could leave it visible in your UI and populate it with an:
'empty' => 'Select Car First' … just a matter of choice here, I guess.

Next, comes our cars_controller.php:
I’m only showing the “interesting” actions.

  public function list_models() {
    $this->set('names', $this->Car->find('list'));
  }
  
  public function get_models_ajax() {
   Configure::write('debug', 0);
   if($this->RequestHandler->isAjax()) {
     $this->set('carModels', $this->Car->CarModel->find('list', 
                            array('conditions' => 
                                        array('CarModel.car_id' => $this->params['url']['carId']),
                                  'recursive' => -1)));
   }
 }

Let’s review the code a little… The list_models() method doesn’t really do anything special, it simply sets the car names to be used for the first select list in the view.

The get_models_ajax() will be called via jQuery in order to build our second select input. We are turning off debug here, so that any “extra” output does not mess with the returned data…

Yet, a side note… I am referring to SQL debug, officially produced by cake, or timestamp…
Keep the debug “on” and the resulting output (in case of errors) will be seen in the firebug console… and if you don’t have firebug, then I don’t know how to debug AJAX stuff.

Also, note the $this->params['url']['carId']. This value will come from our first select list, which lists the car names with the corresponding ID’s from the database. That is because we’ve previously established a proper model association, therefore finding all the models for a given car (car_id) is no trouble at all now. (Oh, and please don’t forget to include RequestHandler in your list of required components, see the manual for more info).

Next, we still need a view for our get_models_ajax() action. The purpose of that view would be to return all the $carModels, which as you see we are setting in the controller.

Here it is, get_models_ajax.ctp:

<?php
  if(isset($carModels)) {
    echo $this->Js->object($carModels);
  }
?> 

(Too much for such a simple task (view and all)?… well, respect MVC and it will not come back to bite you in the ass later.)

The view is not terribly interesting, but one thing to note is that $this->Js->object($carModels); will convert the array of data, which is returned by the find('list') in the controller, into a JSON object.

Mental note… You certainly don’t have to work with JSON and any type of data can be returned back to the client, but for simple AJAX communication between the client and the server I find JSON to be most convenient format.

Alright, last, but not least let’s see the jQuery snippet that makes all the magic happen.

list_models.js

$(document).ready(function(){
  $('#car-name').live('change', function() {
    if($(this).val().length != 0) {
      $.getJSON('/cars/get_models_ajax', 
                  {carId: $(this).val()},
                  function(carModels) {
                    if(carModels !== null) {
                      populateCarModelList(carModels);
                    }
        });
      }
    });
});

function populateCarModelList(carModels) {
  var options = '';
  
  $.each(carModels, function(index, carModel) {
    options += '<option value="' + index + '">' + carModel + '</option>';
  });
  $('#car-model-name').html(options);
  $('#car-models').show();
  
}

Unfortunately it would take a few more days to explain every line of code in detail, and there are quite a few jQuery tutorials our there that will do a better job of explaining it, so I hope a little googl’ing will answer any outstanding questions.
… but I do want to point out a few things.

First, we are using jQuery’s handy $.getJSON, which does a GET request to a given URL with some data and returns the results back to our client.
Remember this piece: $this->params['url']['carId']? Well, that’s exactly where the carId value is coming from… i.e. the select input value, as specified between the curly brackets. Of course, there is no point in sending empty values to the server, therefore we wrap the entire chunk of AJAX code into if($(this).val().length != 0)… this will prevent jQuery making the extra call to the server if the “empty” option is selected.

Next, we already know that the data returned from the server will be a JSON object. So, before attempting to do anything with the returned data we check for some valid/good/existing data with:
if(carModels !== null)
In this example carModels is our JSON object, which is returned by CakePHP back to jQuery.

When all said and done, we use yet another awesome tool $.each to traverse the JSON object (i.e. carModels) and build our options list.
Finally, we add the freshly built HTML options list to the contents of our second select input and display it to the user.

We are pretty much done now, but just for some more detailed Q&A you can read further, if interested.

Q. Why use .live('change'... instead of just .change?

A. .live is a great tool to use if you are manipulating the DOM in some way and need to work with freshly inserted element. Granted in this example it is not necessary, but I wanted to show it off anyway. Just keep in mind that this approach is available and could be a life-saver at times.

Q. Why create populateCarModelList() function?
A. I like to keep things separated as much as possible, and who knows this function might come in handy for other reasons in a more complex application.

Q. Shouldn’t the get_models_ajax() action go into the CarModels Controller ?
A. Truth be told… it should. For the sake of simplicity I kept it in the same controller as the other method, but it would be “more proper” to place it in the CarModels Controller.

Q. Why did I assign DOM ID’s to the drop down elements, doesn’t cake do that automagically?
A. It does indeed, but cake’s DOM ID’s look like SomeModelThenField. In the world of CSS it is almost an unwritten rule that ID’s most often represented as some-model-then-field… so that’s my basic goal there. Thanks to a tip from Mark Story I promise to show in an upcoming post how to override the default CamelCasedID’s with dash-separated-ones.

Let’s help out CakePHP’s pagination

While it would be sweet, if the paginated data was usually not much more involved than a simple SELECT * FROM some_table… Unfortunately it is not.

In many cases there are a few, JOINs to related models, and likely a GROUP BY statement somewhere (maybe DISTINCT ?, who knows).

What I am getting at, is an SQL issue if that’s what you’d call it…

count(*) (which CakePHP uses properly, indeed) will yield pretty strange results if you have JOINs and GROUP BY (or any variation thereof… or “worse” yet, things like HAVING, etc.).

The display of records to the end-user will “disagree” with the number, which count(*) produces.
This is because count(*) actually counts everything (meaning across various models in the case of a JOIN).
The main issue comes up when one attempts to paginate the result-set from a complex query. You might see only 20 records displayed due to GROUP BY, but count(*) will actually return 43 (for example) because it does not actually count in the same way SELECT returns the records.

However, with a little help this problem is quite easily resolved…

Remember, we can always override the paginateCount() method inside a model.

For example, if we have an Order Model, just to get the expected count of records we’ll add:

public function paginateCount($conditions = null, $recursive = 0, $extra = array()) {
    if($this->statusId) {
      $conditions = array('conditions' => array('Order.order_status_id' => $this->statusId));
    }
    $results = $this->find('count', array_merge(array('recursive' => -1), $conditions));
  	return $results;
  }

With this scenario, we can get an accurate count of all orders, or just the ones that have some specific status.

The bottom line is that we know exactly what needs to be counted, and overriding paginateCount() allows us to do just that, regardless of what the actual record fetching query is like.

Make updateAll() fire behavior callbacks

For a while now updateAll() would not trigger any of the model’s behavior callbacks.

This presents a problem for a couple of reasons:

  1. The updateAll() method allows to easily do things like field_value = field_value + 1, because unlike other similar methods it does not escape fields/values
  2. Secondly we cannot rely on proper Behavior execution, which can lead to some unexpected results

What we have below is a simple override (and a very rough patch at this point) that will trigger afterSave() of the attached behavior as expected.

Take the code below and place into the model, which uses updateAll() at some point.

public function updateAll($fields, $conditions = true) {
    $db =& ConnectionManager::getDataSource($this->useDbConfig);
    $created = FALSE;
    $options = array();
    if($db->update($this, $fields, null, $conditions)) {  
      $created = TRUE;      
      $this->Behaviors->trigger($this, 'afterSave', array($created, $options));
      $this->afterSave($created);
      $this->_clearCache();
      $this->id = false;
      return true;
    }
  return FALSE;
 }

Again, this works for my immediate needs and doesn’t break any tests. However there are a few things that can be improved to make it more consistent with other methods like save(), for example.

Well, either way I am hoping that this will help someone.

Quickly grant Auth access to multiple controllers

Common way to allow Auth access to all actions in a controller is to do something like this:

//In some controller 

public function beforeFilter() {    
  $this->Auth->allow('*');
  parent::beforeFilter();
}

However it can get pretty tedious if you’ve got lots of controllers and have to go through a bunch of them to enable (or disable) access.

Instead, try something like this in your AppController:

public function beforeFilter() {    
  $this->Auth->loginAction = array('controller' => 'users', 'action' => 'login');
  $this->Auth->loginRedirect = array('controller' => 'users', 'action' => 'index');    
  $this->allowAccess();
}
  
private function allowAccess() {
  if(in_array($this->name, array('Pages'))) {
    $this->Auth->allow('*');  
  }
}

The above will let Auth to access everything in the PagesController.
If you need to grant access to additional controllers simply add them to the array of names:
array('Pages', 'Books', 'Customers', 'Etc')

Having to deal with a single file to grant/deny access just makes things easier…
You could even make that “grantable” array a property of the AppController.

Bye-bye $cakeDebug…

The infamous $cakeDebug variable, which has been around for a long time and managed to annoy a few people in the process, has been replaced in the recent builds of CakePHP 1.3 with an element.

See the ticket here:
http://cakephp.lighthouseapp.com/projects/42648/tickets/35-the-implementation-of-dbo_sourcephp-logging-needs-changed

The two points to take away are:

  1. Automatic SQL dumps have been removed from DboSource
  2. If you wish to display SQL dumps, as before… replace any occurrence of $cakeDebug in your layouts with echo $this->element('sql_dump');

One of the obvious benefits, is that you no longer have to tweak the core to take control of the SQL debug. As always, simply place sql_dump.ctp in your own app (i.e. app/views/elements) and do what you wish with the output.

Just to test things out let’s throw the SQL debug into the Firebug console, rather than directly into the view.

We’ll need to modify the sql_dump.ctp (which now has been copied into our own app), just a little bit.

Around line 35, let’s replace the default output for SQL dump, for something like this:

//unchanged part of the sql_dump.ctp above this line

//here we are outputting the queries into the Firebug console
foreach($logs as $source => $logInfo) {
  foreach($logInfo['log'] as $key => $query) {    
    echo $this->Html->scriptBlock('console.log("' . $query['query'] . '")');  
  }   
}

For the real world example, this may not be a reasonable thing to do by any means… but it does show how easy it is now to output the SQL debug wherever you need it (logging, debugging, stylizing, parsing, sending to some remote destination… up to your imagination really).

Overriding default URL’s (aka persistent routes)

CakePHP’s routes allow for an easy way to serve content by using URL aliases.

Let’s take an example from the manual:

Router::connect(
    '/cooks/:action/*', array('controller' => 'users', 'action' => 'index')
);

By having the user access the URL such as example.com/cooks/index simply shows the default view for example.com/users/index (as was our intention).

However, if you have a bunch of links on that page, which point to other actions in the Users controller, the URL’s will remain as example.com/users/some_other_action. In some cases it may not be desired, i.e. if a visitor entered the section of your site by using the “cooks” URL, you don’t want to suddenly confuse them by presenting a completely different link to other actions. Otherwise, you may simply wish to be consistent (or sneaky).

A simple override of the Helper::url() method allows us to solve this problem.
(Place the following in your app_helper.php)

public function url($url = NULL, $full = FALSE) {
  if(strstr($this->params['url']['url'], 'cooks') && $url['controller'] == 'users') {
    $url['controller'] = 'cooks';
  }
      
  return parent::url($url, $full);
}

That’s it, now any link that would otherwise point to example.com/users/some_other_action will now point to example.com/cooks/some_other_action.

p.s. You might want to add an additional check: isset($url['controller'])

Improved form handling in CakePHP 1.3

Here is a typical, simple form done with cake’s form helpers:

echo $this->Form->create('Article', array('action' => 'test'));

echo $this->Form->input('Article.title');
echo $this->Form->input('Article.body');
echo $this->Form->input('Article.user_id', array('type' => 'hidden'));

echo $this->Form->end('Add Aricle with Tags and Comment');

Which outputs the following HTML:

<form id="ArticleTestForm" method="post" action="/articles/test" accept-charset="utf-8">
    <fieldset style="display:none;">
        <input type="hidden" name="_method" value="POST" />
    </fieldset>
    <div class="input text">
        <label for="ArticleTitle">
            Title
        </label>
        <input name="data[Article][title]" type="text" maxlength="50" value="" id="ArticleTitle" />
    </div>
    <div class="input textarea">
        <label for="ArticleBody">
            Body
        </label>
        <textarea name="data[Article][body]" cols="30" rows="6" id="ArticleBody">
        </textarea>
    </div>
    <input type="hidden" name="data[Article][user_id]" value="" id="ArticleUserId" />
    <div class="submit">
        <input type="submit" value="Add Aricle with Tags and Comment" />
    </div>
</form>

This is fine and all, but one thing worth noting is that default behavior is wrapping each element in a div, and in some cases this may not be desirable. Either a legacy CSS or a front-end developer preference, might require the form to be structured (wrapped) differently.

Let’s see how we can set some defaults for our form with CakePHP 1.3., while simplifying the code a little at the same time by using the inputs() method…

 echo $this->Form->create('Article', array('action' => 'test', 
                                              'inputDefaults' => array(
                                                'div' => array('tag' => 'p'),
                                                'before' => '-- goes before the label --',
                                                'after' => '-- goes after the input --')));    
    
echo $this->Form->inputs(array('Article.title', 
                                               'Article.body', 
                                               'Article.user_id' => array('type' => 'hidden')), 
                                      array('legend' => 'Article submit'));

echo $this->Form->end('Add Aricle');

Which produces:

<form id="ArticleTestForm" method="post" action="/articles/test" accept-charset="utf-8">
    <fieldset style="display:none;">
        <input type="hidden" name="_method" value="POST" />
    </fieldset>
    <fieldset>
        <legend>
            New Article
        </legend>
        <p class="input text">
            -- goes before the label --
            <label for="ArticleTitle">
                Title
            </label>
            <input name="data[Article][title]" type="text" maxlength="50" value="" id="ArticleTitle" />
           -- goes after the input --
        </p>
        <p class="input textarea">
            -- goes before the label --
            <label for="ArticleBody">
                Body
            </label>
            <textarea name="data[Article][body]" cols="30" rows="6" id="ArticleBody">
            </textarea>
           -- goes after the input --
        </p>
        <input type="hidden" name="data[Article][user_id]" value="" id="ArticleUserId" />
    </fieldset>
    <div class="submit">
        <input type="submit" value="Add Aricle" />
    </div>
</form>

Of course for a simple form this may not be a very significant improvement, but having the ability to set defaults for the entire form and relying on some automagic for the rest, certainly makes one’s life easier.

Easy CakePHP API search with Firefox

Some of you might know this, but…

  1. Go here: http://mycroft.mozdev.org/search-engines.html?name=cakephp
  2. Click the “CakePHP API” link (or any other you might think is useful)
  3. Add “CakePHP API” to the list of engines available in the search bar?… Most definitely “Add”
  4. (Sure, let’s try it right away)
  5. Ctrl + K (windows) or similar shortcut for your OS
  6. Type search string, hit “Enter”
  7. See results

Nice.

CakePHP 1.3 helps with team-based development workflow…

I do have to say that in 1.2 some of the features described here are available as well, however CakePHP 1.3 takes it slightly to the next level to make team-based development even easier… With SVN or Git it is already easy enough to make team-based development readily accessible, that being said DB (schema and data) management during development process has always been a little bit painful.

Without too much dwelling let’s consider the following scenario. We have our favorite developers Joe and Bob.
(By the way, we are not going to cover any specific version control system here).

Bob starts up the project.
1. He creates the relevant Database with tables, adds a few models, controllers, actions, views.
2. He writes some code and populates the DB in the process with some dummy data.

So far, this should be familiar to most. Generally speaking, once Bob is done with the initial logical point of setup, he would commit all his work into some version control system.

Here’s where the pain of the past came into play…

While Joe can easily grab the application files, which are needed for the project, he is missing the DB structure and some data to continue working on the project.
Since early versions of CakePHP we’ve had a few built-in and third-party tools to help with that process, but they were lacking something… in one way or another. Not to say they didn’t get the job done, just the overall package wasn’t quite there.

Let’s see what to do in cake 1.3..

Bob fires up the command line and…

#cake schema generate

… which creates a php-based, cake-like schema.php file, which holds the information about the table structure.

Next, Bob types…

#cake bake fixture all -records -count 15

(Thanks Mark Story!)

This will bake all fixtures based on the current DB structure… and populate them with maximum of 15 records, which are currently in the database tables.
Fixtures are “nice little things”, but as far as we are concerned here, they simply hold our test data, so that Joe can have easy access to it.

OK, now that Bob was nice enough to do all of the above. He can happily commit all of the application code (including our newly baked schema and fixtures).

Moving on, at this point Joe is ready to get started on the project. He checks out the initial code from the version control repository.

Once the code is on his local development machine, Joe runs:

#cake schema create

Which, as you might have guessed, crates all the tables based on the schema developed and committed by Bob.
(It goes without saying that appropriate CakePHP and ultimately database configuration should already be in place on Joe’s development box).

Now that the tables are created Joe can begin his work, and while it is important to remember the “empty state” of an application, more often than not, some data is needed to get things rolling.

Well, all Joe needs to do at this point is:

#cake fixtures

Which, loads all the data from the fixtures into the newly created tables in Joe’s local database. Nice, eh?
At this point both development environments are fully in-sync and the progress can continue.
The above feature is currently in development and will not be available (or might change) depending on when you are reading this post.

A few things to note…

  1. I rarely find it useful to attempt to alter my schema. If the latest check-in has the required data and updated schema, I can safely drop all the tables, recreate them and insert new data
  2. To address the point above, all of my schema files are in the version control system, so if for whatever reason I need to go back, I simply check out the required revision and rebuild the schema (and add data if needed).
  3. This process can be followed back and forth for as many iterations as necessary.
  4. Human connection is necessary. If there is conflict in the code or schema… check with your fellow developer Bob or Joe and come to an agreement over a beer.