Cakephp routing: prefixes and forms

Prefixes are a nice way to define routes for groups of actions, (see the cookbook).

All admin_ actions go with the /admin// url
For my project i needed both an admin route and a dashboard route, so i made prefixes for them like:

(/app/config/routes.php)

Router::connect('/dashboard/:controller/:action/*', array('prefix' => 'dashboard'));

Now in the view you can use the following:

echo $html->link('Profile', array('controller'=>'users', 'action'=>'dashboard_profile'), array('title'=>'Edit profile'))

But when it comes to forms things start to break.

For example the following:

echo $form->create('User', array('action'=>'dashboard_profile'));

or this one:

echo $form->create('User', array('url'=>array('controller'=>'users', 'action'=>'dashboard_edit')));

generate the following (wrong) link: /users/dashboard_profile/1

While it does point to the right action (dashboard_profile) you get an error message explaining that this is a private action. The only way to use this private action is to use the prefix method.

The reason for this is that the form generate part in the cake code doesn’t do anything with the routing, thus ignoring the prefix we’ve set.

After some digging in the api i found a cake-way to generate the right url:

echo $form->create('User', array('url'=>$html->url(array('action'=>'dashboard_profile'))));

We use the html helper, that does parse the prefix part and generates a nice correct url, namely: /dashboard/user/profile

The html helper does check for any prefixes in the routing and generates the right url.
Combining the form->create and the html->url we get the right url.

Filed Under: CakePHP, Code, English - read on

Validate single fields trough AJAX with CakePHP

Today i was busy making a form for a signup procedure and i tought: “Lets make it a bit easier for the customer by adding some real-time validation”. Of course this is a piece of cake with.. Cake!

Enabling JavaScript

First we need to load the script.aculo.us libaries.
Download them from script.aculo.us, extract the files and copy prototype.js in /lib to your cake js folder (app/webroot/js) and all the files in /bin to the cake js folder.

Then add the following to your default.ctp (app/views/layouts/default.ctp (if it’s not there create one))

<?php
if(isset($javascript)) 
{ 
    echo $javascript->link('prototype'); 
    echo $javascript->link('scriptaculous.js'); 
} 
?>

And in your controller (app/controllers/customers_controller.php) load JavaScript (note: you could also put this in app_controller to make it available in every view instad of putting it in every single controller).

var $helpers = array('Html', 'Javascript', 'Form', 'Ajax');

Enabling RequestHanlder

Now we need to make shure we serve the right content with the right request (return xml if it’s requested).
Add the following to your routes file (app/config/routes.php)

Router::parseExtensions();

Now open your controller (app/controllers/customers_controller.php) and add the following line:

var $components = array('RequestHandler');

We can now use the power of requesthandler \0/

Model

Lets make some basic validation rules: (app/models/customer.php)

var $validate = array(
 		'initials' 	=>	array('length' => array('rule' => array('minLength', '1'), 'message'=>'This field cannot be empty')),
		'lastname' 	=>	array('length' => array('rule' => array('minLength', '3'), 'message'=>'This field cannot be empty'))
	);

Now lets make some actions!

Controller

The add action in the controller (app/controllers/customers_controller.php)

function add() {
	// Ajax validation of single fields, check if xml is asked
	if ($this->RequestHandler->ext =='xml') {
		// If we have data, process it. If not send back an error.
		if(!empty($this->data['Customer'])){
			$this->cleanUpFields();
			// Validate the customer, if it's ok, show no errors. If not ok, show errors
			if ($this->Customer->create($this->data['Customer']) && $this->Customer->validates()) {
                $this->set('error', '0');
				$this->set('message', '');
            } else {
                $errorMessages = $this->validateErrors($this->Customer);
				$this->set('error', '1');
                $this->set('message', array_shift($errorMessages));
            }
		} else {
			$this->set('error', '1');
			$this->set('message', 'No data sent');
		}
	} else {
		// Normal validation and save
		if(!empty($this->data)) {
			$this->cleanUpFields();
			$this->Customer->create($this->data);
			if($this->Customer->save($this->data)) {
				$this->redirect(array('action'=>'thankyou'));
			} else {
				$this->Session->setFlash('Woot, something went wrong, please check the red fields!');
			}			
		}
	}
}

What we did is check if a xml file was requested by the client (this is what we use in the ajax request), if so, check only a single field and return if there is an error or not.

The XML views

In your customers view folder (app/views/customers) create a new folder named xml. In there create a new file called add.ctp.
This is the file the controller uses when it wants to send back a xml view.

<validation>
	<error><?php echo $error; ?></error>
	<message><?php echo $message;?></message>
</validation>

We also need to create a layout for the xml view, in the layouts folder there should be a folder named xml (app/views/layouts/xml)
Open it and create a file called default.ctp with the following content

<?php header('Content-type: text/xml'); ?> 
<?php echo $content_for_layout; ?>

The add view

Now lets make a form (app/views/customers/add.ctp)

<?php echo $form->create('Customer');?>

<fieldset>
<legend>Name</legend>
	<span>
		<?php echo $form->input('initials', array('label'=>'Initials', 'maxlength'=>'8', 'size'=>'8', 'class'=>'req', 'div'=>false)); ?>	
	</span>
	<span>
		<?php echo $form->input('lastname', array('label'=>'Last name', 'size'=>'20', 'class'=>'req', 'div'=>false)); ?>
	</span>
</fieldset>
<?php echo $form->end('Submit'); ?>

This is basically a simple form, but note the ‘class’=>’req’, we are going to use this to let javascript know wich fields to check (this might be handy if you don’t want to validate very single field in the form)

JavaScript

Now the javascript code (place it at the end of app/views/customers/add.ctp)

<script type="text/javascript">
	// Look for all req fields and put them in an array
	requiredFields = document.getElementsByClassName('req');
	
	// Loop the array and place observers on the req fields
     for(i=0; i<requiredFields.length;i++){
		new Form.Element.Observer(requiredFields[i], 2, liveActionCallback);	
	}

	// Callback if something happens in one of the req fields
	function liveActionCallback( element, value ) {
		
		// Funcion for handling the response XML file wich the Ajax.Request below gets for us
		var handlerFunc = function(t) {
	    	var xmlDoc = t.responseXML.documentElement;
			// Check if there is an error
			if(xmlDoc.getElementsByTagName('error')[0].firstChild.nodeValue == '1'){
				// You could do all kinds of stuff here, i'm just adding a classname to the form field wich makes it red
			 	$(element).addClassName('form-error');
			} else {
				// if there is no error, remove the req class 
				$(element).removeClassName('req');
			}
		}
		
		
		// Create a new ajax request, note the .xml added to the url, it makes shure we get an xml back from the controller.
		
		new Ajax.Request('/regwiz/customer.xml', {
					asynchronous:true, 
					evalScripts:true, 
					parameters:Form.Element.serialize(element), 
					onSuccess:handlerFunc
				}
			);
	}
</script>

CSS

As you can see i’m not doing much if there is an error (like not using the error message, but you could of course append a div with the error message, or alert it or… well whatever you want ;)).

To make it complete, here’s the CSS file is used.

/* ----- REQUIRED ----- */

form .req{
	border-right: 2px solid red;
}

form .form-error{
	display:block !important;
	background-color: #FFDFDF !important;
	border:1px dotted red;
}

Example

A few images with the different states: (note labels are in Dutch ;))
pre

The field with the .req class (it creates a red right border, that disappears when the field is valid)

A valid field:
valid field

And an invalid field:
invalid

And the ajax request (from Bugzilla)
Ajax request

And that’s basically it. If you don’t understand everything, please read the comments or try changing stuff :)

Filed Under: CakePHP, Code, English - read on

Login form design patterns

op flikcr staat een fotoset met alleen maar login forms, erg handig ter inspiratie voor je eigen formulier

Filed Under: Snippets - read on