Tag: cake 1.3

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=&quot;car-models&quot; style=&quot;display: none;&quot;>
  <?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.

(Update: had to strike that one out, since in 1.3+ this has been dramatically improved and is no longer relevant)

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=&quot;' + index + '&quot;>' + 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.

Top 10 things to look forward to in CakePHP 1.3

(Update: 8/26/2011… and a few years later we can look forward to CakePHP 2.0).

With CakePHP 1.3 release is just about on the horizon, there are a few developments that I am particularly excited about.
There are some great summaries and previews at code.cakephp.org, but still a few things are somewhat scattered, so here’s my attempt to better highlight some goodies in 1.3. Forgive shameless copy/paste in some parts, but really it goes without saying that all the credit goes to the developers for baking yet another awesome cake.

Maybe, the proper title should be “Top 10 things I’m looking forward to”… but either way, I hope you’ll find something useful and if there are some interesting features/updates that I’ve not included, please do share them in the comments.

So, without “further dudes”…

10. Missing behaviors trigger an error

Using behaviors that do not exist, now triggers a cakeError making missing behaviors easier to find and fix.

9. GROUP BY support in the Containable behavior

Finally, we should be able to do something along the lines of:

'contain' => array(
    'Answer' => array(
        'fields' => array('Answer.value', 'COUNT(*) AS count'),
        'group' => array('Answer.value')
    )
)

Which really makes reporting across multiple models much easier and any previously required hackery is no longer needed.

8. “Missing” model methods will trigger a warning

In the past, when Model::someMethod() was not found, the method itself was attempted to be executed as a query. Now, we should see a warning that such method isn’t there. This does affect some existing code, but is a sound improvement overall.

7. Support for engine type in MySQL DB

The new schema shell will properly recognize the engine type used by your tables (for example MyISAM vs InnoDB).
This was a bit of a pain in the “old days” when running a new schema would always default to MyISAM.

6. Helpers are now referenced by $this->HelperName

This is actually a very nice improvement because it ensures that your helper object will not conflict with some random variable from your view.
For example, if you have an ImageHelper and you also had an $image variable, in the old days the collision between the two would create major problems, especially because the conflict can happen completely unknowingly to the user (consider an app with lots and lots of views). Such an error was too easy to make.

5. Updates to the Form helper

What could be better than the lovely input(s) methods of the Form helper?
How about the suggestions outlined in this ticket

To summarize, we should have greater control over the form element placement therefore having much more flexibility in the way the forms are laid out.

4. Error logging with debug at zero

This has been a long-requested feature and it is finally making it’s way into 1.3
You can use Configure::write(‘log’, $val), to control which errors are logged when debug is off. By default all errors are logged.
The $val is a standard PHP constant such as E_WARNING.

3. New fixture task

Baking fixtures is now another tasty piece of cake. One of the lovely feature is that it properly handles UUID’s.

2. Sweet prefix routing
The old admin routing is gone in the way of proper prefix routing, so if you wish to have:
www.example.com/user/profiles/edit
www.example.com/admin/users/delete
www.example.com/editor/articles/publish

It is now easily accomplished with a simple setup in the core.php:

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

Of course, you’ll need appropriate “prefixed” action such as editor_publish() in your relevant controllers.

1. Brand new way to handle Javascript and Ajax

This is probably the most anticipated and welcomed change in 1.3. One of the great things about CakePHP has always been the fact that it did not depend on any other libraries or what have you … (well other than PHP). In the old days that paradigm was somewhat “broken” because Ajax helper required the use of the Prototype JS library.
It wasn’t necessarily a huge problem, but for people who preferred to work with other JS frameworks… the helpers were of little help and mostly dead weight in the framework.
Not only that, the the new JS helper and improved HTML helper solve a lot of other little problems, which make CakePHP 1.3 something to really look forward to.

Well… it’s no worth repeating what’s already said at 1.3 wiki regarding the new helpers.

Go cake!