Archive: May, 2008

Don't forget your primary key

If are you are using a non-standard column name for your primary key (something other than ‘id’), maybe from a legacy database, it is very important not to forget to set the var $primaryKey in your model. This little oversight can break a lot of things without giving any clue as to what the problem is.

A common issue is to have column named ‘ID’ instead of ‘id’, which will cause CakePHP to behave strangely and it’s not very obvious as to what is really going on.

So, if you are having some strange issues with find methods or the data is not comming back as you’d expect, always double-check your primary key name.

JSON output with CakePHP

Update (2/14/2011): Take a look at a more robust approach that works with recent 1.3 versions of CakePHP.

Update (9/25/2008): Since the writing of this post, the CakePHP manual has been updated with information on using the RequstHandler, which is an excellent supplement to the approach described below. You should definitely familiarize yourself with the way it works:

http://book.cakephp.org/view/174/Request-Handling

If you are doing some AJAX development there is a very good chance that you’d like to output your data in a JSON format.

Let’s say that we would like to output all of our user’s data (User model) as JSON. Well, like many other things, it couldn’t be easier with CakePHP.

Let’s create a new action called viewAllJson in our users controller…

function viewAllJson() {
  $this->layout = 'ajax';
  $users = $this->User->find('all', array('recursive' => -1));
  $this->set(compact('users'));
}

This should be pretty much self explanatory. You are using the default AJAX layout, which should be just a blank page, since all you need is the output of the JSON data (no other HTML/text should be present). You are finding all Users and then setting the resulting array for the view. Note, that I’m using the Bindable behavior, hence the use of the ‘restrict’ key (you might need to change that if you don’t use Bindable).

Now we need to create a view (view_all_json.ctp) so that we can display the JSON data.

<?php
Configure::write('debug', 0);
echo json_encode($users);
?>

… and really that’s all there is to it.

JQuery autocomplete in CakePHP

This is a quick example on how to setup an autocomplete field using JQuery and CakePHP.

Let’s assume that we have a Product model and products controller. Our goal is to allow the user to type in a few characters and then to display a matching list of products from the DB by using autocomplete.

First things first, be sure that you’ve included the JQuery library. If you are using $scripts_for_layout, then in your view file you’d probably do something like: $javascript->link(‘jquery/jquery.min’, false);
Next, you have to include the autocomplete plugin… There are a few of them out there.
Well, this one does the trick for me: http://www.pengoworks.com/workshop/jquery/autocomplete.htm

(Please refer to the documentation at the link above for details on how the JQuery autocomplete works and for an example of what it looks like. You can also grab the relevant CSS and the loader image from there).

After you save it somewhere in your JS directory (perhaps js/jquery/plugins) you should include it in the view just like this: $javascript->link(‘jquery/plugins/autocomplete’, false);

Now you need the form field that will be used as an autocomplete field. The plugin, by default, will expect a field with an id = “autoComplete”. So, as part of your form, you’ll probably do something like

<?php echo $form->text('Product.name', array('size'=>'30', 'id'=>'autoComplete')); ?>

(Remember that we are assuming the Product model here)

Not too bad, so far. Alright, now we have to trigger the autocomplete by creating some JavaScript that will actually attach the autocomplete action to your form element.
Make a new JS file and call it autocompleteAction.js (don’t forget to also include it in the view).

Here’s what it should look like:

$(document).ready(function(){
$("#autoComplete").autocomplete("/products/autoComplete",
{
minChars: 2,
cacheLength: 10,
onItemSelect: selectItem,
onFindValue: findValue,
formatItem: formatItem,
 autoFill: false
 });
});

function selectItem(li) {
	findValue(li);
}

function findValue(li) {
	if( li == null ) return alert("No match!");

// if coming from an AJAX call, let's use the product id as the value
	if( !!li.extra ) var sValue = li.extra[0];

	// otherwise, let's just display the value in the text box
else var sValue = li.selectValue;

alert("The value you selected was: " + sValue);
}

function formatItem(row) {
	if(row[1] == undefined) {
		return row[0];
	}
	else {
		return row[0] + " (id: " + row[1] + ")";
	}
}

Just to cover the basics, the above action will tell the autocomplete to kick-in after you’ve typed two characters into your form field (minChars: 2). The lineSeparator: “^” tells autocomplete to jump onto the next line whenever it encounters the ^ character. I will explain this a little more later. It is worth noting that the default new line character is “\n”, but I could not get it to work with CakePHP so I’ve decided to use a somewhat obscure character instead, which is unlikely to appear in a product name. This is not a very good solution by any means and someday I will come back to figuring it out. (Update: this works just fine now, so no longer such trickery as I’ve used before is required).

So what’s going on here? Well, the autocomplete needs to get the list of available items (products in our case) somewhere, and this code tells it exactly where to look: autocomplete(“/products/autoComplete”… You’ve probably guessed that in terms of CakePHP you need to have a products controller and an autoComplete action. Now is a a good time to create one.

It should look something like:

function autoComplete() {

 Configure::write('debug', 0);
 $this->layout = 'ajax';

 $products = $this->Product->find('all', array(
   'conditions'=>array('Product.name LIKE'=>$this->params['url']['q'].'%'),
   'fields'=>array('name', 'id')));

$this->set('products', $products);
}

So basically we are looking for all products who’s name matches (LIKE) the two characters that we’ve typed in the form field. We are returning a product name and product id fields from the DB. Note the: $this->params['url']['q']. The autocomplete plugin uses the GET method to pass the form data to your controller and it expects to have a q= as the query string, and that’s how you read that query string in CakePHP. Now we’ve found some products and set the resulting array for the view. The view will actually be the list that pops under the text field once you start typing. Let’s create one (auto_complete.ctp):

if(!empty($products)) {
foreach($products as $product) {
  echo $product['Product']['name']. '|' .$product['Product']['id'] . "\n";
 }
}
else {
 echo 'No results';
}

Here we are looping through the products array and displaying the name and the id, of course if the array happens to be empty we will display ‘No results’.

That’s all there is to it. With some clever copy/paste and possibly a few quick changes this thing should be working like a charm in just less than twenty minutes.

Of course you’ll probably want to come up with some better usage for it than doing a JS alert…

Multiple checkboxes

One question that I’ve seen come up a few times is: “How do I handle multiple checkboxes in a form?”
The only trick here is the way you name your checkbox form element. Let’s consider an example where you’d like to select multiple messages (by using checkboxes) and then delete these messages in your controller.

Let’s say that in your controller you did a $messages = $this->Message->find… and you have set the resulting array for the view $this->set(‘messages’, $messages).
Now, in the view you will loop through all the $messages and construct a form by doing something like this:

<?php foreach ($messages as $message): ?>
    <tr>
		<td><?php echo $form->checkbox('Messages.id.['.$message['Message']['id'].']', array('value' => $message['Message']['id'])); ?></td>
        <td>
            <?php echo $html->link($message['Message']['subject'], '/messages/read/'.$message['Message']['id']);?>
        </td>
		<td><?php echo $message['Message']['status']; ?></td>
        <td><?php echo $message['Message']['created']; ?></td>
    </tr>
    <?php endforeach; ?>

So your checkboxes will now be named Messages.id.[1], Messages.id.[2], Messages.id.[3]… and so on.

Now if you’d like to delete all selected messages, do this in your ‘deleteMsg’ action in the controller:

<?php

foreach($this->data['Messages'] as $key => $value) {
 if($value != 0) {
   $this->Message->del($value);
 }
}

 

Hooray JQuery

I’ve completely abandoned using Prototype/Scriptaculous in favor of JQuery.

I simply prefer the way JQuery does things:

  • Simple syntax
  • No more in-line JS
  • Lots of great plug-ins
  • JQuery UI has some very nice widgets
  • Lightweight

In terms of CakePHP it means that I can no longer rely on the built-in AJAX helpers. No big deal, I find that manually writing JQuery is not hard at all (and I’ve never really done any JS programming). And the helpers are actually lacking in some more complex features.

So bottom line is that you should at least spend a few hours to see what JQuery is capable of and how easy it is to use it. It might be a great benefit in the long run and chances are it will open up some great possibilites for your next app.

Excellent HTML table helper

If your application requires to display a lot of data from the DB, then I suggest you take a look at this nice, little HTML table helper: http://cakeforge.org/snippet/detail.php?type=snippet&id=162

Basically it will allow you to pass a data array from the find method and it will construct an HTML table with just a few lines of code.

Be sure to take a look at the source code for a complete usage example.

One issue that I’ve come across is that it’s not easy to setup conditional links. Meaning, to be able to display “Activate” or “Deactivate” depending on your current User status in the DB, for example. I will probably add a modification for this feature in the next few days.

Oh and here’s some CSS that will produce pretty nice looking tables:

table
{
	border-collapse: collapse;
}

th {
	font-weight: bold;
font-size: 11px;
font-family: Verdana, Arial, Helvetica, sans-serif;
	text-transform: uppercase;
	text-decoration: none;
	color: #6D929B;
border-left: 1px solid #F5FAFA;
border-top: 1px solid #F5FAFA;
	border-right: 1px solid #C1DAD7;
	border-bottom: 1px solid #C1DAD7;
letter-spacing: 2px;
	text-transform: uppercase;
	text-align: left;
padding: 5px 12px 5px 5px;
	background: #CAE8EA;
}

td {
font-size: 11px;
font-family: Verdana, Arial, Helvetica, sans-serif;
	color: #000;
	border: 1px solid #C1DAD7;
background: #fff;
	padding: 3px 12px 3px 3px;

}

tr.altRow td {
background: #F5FAFA;
	color: #000;
}

CSS files and $scripts_for_layout

You probably know that if you have $scripts_for_layout in your site’s layout, then you can include JS files from the view. For example, if you’d like to include ‘myScript.js’ from some view you would add the following code: $javascript->link(‘myScript’, false);

Basically the second param (false), will tell CakePHP not to include the file in-line, but rather where the $scripts_for_layout is located.

A less known feature is that you can do the same for your CSS files. So, if you need to include some style that is specific to a given view, add something like this to your view file:

$html->css(‘myViewCss’, ‘stylesheet’, array(“media”=>”all” ), false);

Again, you simply need to specify a ‘false’ parameter. This will tell CakePHP to include the CSS file where the $scripts_for_layout variable is located (which should be in the ).

Fatten up your Models

CakePHP models, unlike super models should be fat. It’s a good practice to keep redundant code out of your controllers. Here’s a quick example on how you can keep your controllers skinny and models fat.

Let’s say you need to get a list of all public and active products…

$productList =	$this->Product->find('list', array(
'conditions' => array(
'Product.is_public'=>1,
'Product.status'=>2)),
'Product.name ASC');
 

So the above code will select all products where is_public = 1 (assuming that means the products are public) and status = 2 (let’s say 2 means active).

Now if you have a bunch of different actions that require you to get such a list you’d have to repeat this same code over and over. Sounds like a bad idea…

Instead you should move this code into your Product model and create a method there called getList (for example). Remember that in the model you won’t need $this->Product. Adjust the code to read $this->find, the rest remains the same.

Now in the controller all you have to do is:

$this->Product->getList();

You could even make getList more interesting by allowing the user to pass parameters, such as values for is_public and status.

Not only does this help to keep your code cleaner, but if your rules change to say that you need to get all products with status 3 instead of 2 you only need to modify the code in your Product model, rather than in a lot of different actions.

Do I have any errors in my form?

A nice little trick to check if you have some errors in the view is to use the $session object.

Try this (in the view): pr($this->validationErrors) or pr($session->validationErrors), be sure to actually have some errors in the form…

I leave it up to you to come with some nice usage for it.

Well, here’s a little hint: try creating an element that will check if the above array is empty or set, and if so display some generic message, such as “Please fix the errors below…” this is especially useful for long HTML forms where the actual error message (or field error) might fall below the screen fold.

You gotta love… Bindable behavior

Update: as of 05/18/2008 there is a new core Containable behavior, which is an evolution of a few previous behaviors, including Bindable. If you are just looking into starting to use this behavior I suggest you go with the Containable, since it’s going to be the new standard in the CakePHP core. However, the examples on Containable are still scarce, so it’s a good idea to familiarize yourself with the way Bindable works. They share many similarities in syntax and overall approach.

If you haven’t started using Bindable behavior yet, I suggest you drop everything and get started on it right away. http://bakery.cakephp.org/articles/view/bindable-behavior-control-your-model-bindings

I promise, it will make your life much easier. The best part is that it is completely unobtrusive. You’ll probably have to go back and remove those bindModel/unbindModel calls you have in your code to make it lighter and prettier, but overall no changes to existing code are required.

Here’s a little example on how bindable can easily allow you to fetch Model data with somewhat complex conditions.

Example models:
User – has many Profiles
User – has many Tasks

My goal is to retrieve an active profile information (is_active =1 in the DB) for a given User. While at it, I’m also going to grab the Tasks.

It couldn’t be easier using bindable (Assuming that $id variable is defined or passed to the action):

<?php
$result = $this->User->find('all', array('conditions'=>array('User.id'=>$id),
'restrict' => array('Task', 'Profile' => array('conditions' => array('Profile.is_active'=>1)))));
?>

Of course more details and examples on how to use bindable can be found at the above article in the Bakery.