How to turn CakePHP datetime select element into date and time pickers

This uses CakePHP 2.2.4

The CakePHP FormHelper datetime element is functional but also fairly tedious to use, here is a picture of how it looks.

standard-datetime

With a small amount of effort you can change your view to use date and time pickers that present a much richer experience to your users.

Rich Date Picker:

CakePHP Date Picker

Rich Time Picker:

CakePHP Time Picker

In this example I’m using a model called ‘tasks‘ that contains a field named ‘due‘ of type datetime.
The model, controller and view are all using the default code generated by the ‘bake’ console command.

Third party components – I’ve found two easy to use libraries:

1. Zebra_Datepicker 1.6.2
http://stefangabos.ro/jquery/zebra-datepicker/

2. jQuery timePicker 0.2
https://github.com/perifer/timePicker

Download both of these and extract the files.
Locate the JavaScript files from the downloads and put the following into your CakePHP app/webroot/js:
jquery.timePicker.min.js
zebra_datepicker.js

Locate the CSS files and put the following into CakePHP app/webroot/css:
timePicker.css
zebra_datepicker.css
calendar.png

If you want to move calendar.png into an images folder then make sure you update the URL links in the zebra_datepicker.css file

Link to these in your default layout app/View/Layouts/default.ctp:

//CSS
echo $this->Html->css('zebra_datepicker');
echo $this->Html->css('timePicker');

//JavaScript
echo $this->Html->script('jquery.timePicker.min');
echo $this->Html->script('zebra_datepicker');

Note that you must already have a jQuery script linked to your template, if not the following above the previous 2 lines:

echo $this->Html->script('http://code.jquery.com/jquery.min.js');

In the view file, convert the existing datetime input to text:

echo $this->Form->input('due', array('type' => 'text'));

Now for the JavaScript. I don’t want to disturb what CakePHP is trying to do so firstly I hide the TaskDue element, then add separate text inputs for date and time. Then I activate widgets for each of these elements. Finally I add a submit event to put the values in those widgets back into the CakePHP input element in the format it is expecting.

Here is the code which should be added to the bottom of the document:


You may need to change the timePicker CSS a little to work with the default CakePHP CSS:

div.time-picker {
  position: absolute;
  height: 191px;
  width:200px;
  overflow: auto;
  background: #fff;
  border: 1px solid #aaa;
  z-index: 99;
  margin: 0;
}
div.time-picker-12hours {
  width:200px;
}
div.time-picker ul {
  list-style-type: none;
  margin: 0;
  padding: 0;
}
div.time-picker li {
  cursor: pointer;
  height: 12px;
  padding: 4px 3px;
  color: #000;
}
div.time-picker li.selected {
  background: #0063CE;
  color: #fff;
}

WordPress 3.4 child theme editor style problem

You may want some of your theme styling to show up in the WordPress visual editor so a user gets a better idea of how it will look without having to continually hit the preview button.

The WordPress documentation recommends the use of child themes instead of editing an existing theme directly – their site has some good information on how do to this but it takes a bit of digging to find out how to affect the editor with your styling as well.

In the child theme folder you are supposed to create a file called functions.php and put in a function call to add_editor_style(); then create a file editor-style.css in the same folder and any styles in there will be added to the existing editor styles.

When I tried this (WordPress 3.4) it simply didn’t work – after some trial and error I found that simply by changing the filename to e.g. child-editor-style.css and using that name in the function call: add_editor_style( 'child-editor-style.css' ); it all started working the way it was supposed to.

Here’s an example showing some custom styling in the editor – in this case it is a right floated callout:

Update 02/07/12 – I raised this as a bug when I found the problem and it has now been fixed in release 3.4.1
(if interested see http://core.trac.wordpress.org/ticket/21026)

CakePHP 2.0 change error layout for admin

Most sites must have a separate admin area to the main site and many will be using a different layout for the admin area. CakePHP will render errors using the default template so your admin area can look inconsistent and messy if an error has to be raised.

The solution to this follows. I can’t take claim for coming up with this but I’ve adapted from a variety of sources and updated for CakePHP 2.0.

Firstly you need to add some code to /app/Controller/AppController.php, if you don’t have one of these then copy it over from /lib/Cake/Controller/AppController.php. The beforeRender method will check if there is an error then checks if you are using Admin routing. If that is the case the layout is changed:

class AppController extends Controller {

	public $helpers = array('Ulc', 'Html', 'Form', 'Js', 'Session', 'Text');
	public $components = array('Auth', 'Session');

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

	public function beforeRender() {
		$this->_configureErrorLayout();
	}

	public function _configureErrorLayout() {
		if ($this->name == 'CakeError') {
			if ($this->_isAdminMode()) {
				$this->layout = 'admin';
			} else {
				$this->layout = 'default';
			}
		}
	}

	public function _isAdminMode() {
		$adminRoute = Configure::read('Routing.prefixes');
		if (isset($this->params['prefix']) && in_array($this->params['prefix'], $adminRoute)) {
			return true;
		}
		return false;
	}
}

Now in production mode (debug = 0 in core.php) CakePHP will tend to use 2 different error views so it may be worth creating your own customised views.
Copy error400.ctp and error500.ctp from lib/Cake/Views/Errors into app/Views/Errors.
If there is anything you want to display differently in the admin version you can use:

if ($this->layout == 'admin') {}

to detect that you are in the admin area and then hide unnecessary markup that may need to be shown to regular site visitors.

With help from: http://nuts-and-bolts-of-cakephp.com/2009/04/30/give-all-of-your-error-messages-a-different-layout/ and http://bin.cakephp.org/saved/50047

CakePHP 2.0 paginate on related model

Just a quick note on this one as it took a while to discover how to it.

Say you have a relationship between two tables and you want to be able to use the built in pagination helper to generate sort links on the content in the related table.
One way of doing this is to add virtual fields to bring the related column(s) into the Model.

In your controller:

$this->Post->virtualFields['user_name'] = 'User.name';

then in your view you can generate a sort link for this field with:

echo $this->Paginator->sort('user_name');

CakePHP validation on a Search Form

I wanted to validate the fields on a search form (e.g. date, price) like the add/edit forms work when you bake or use the scaffolding.

Why is this a problem.

CakePHP has great built in validation but it is tied into saving data.
If you have a search form on your website, nothing needs to be saved and there probably isn’t even a suitable Model anyway.
Another issue is that I wanted it to work where my index view contains the search form and this posts to the results action. The results action processes the search and the view displays the search results. If there’s a validation error you need to go back to the index view.

I did consider just using jQuery to validate the form contents before it went up to the server – but I’m sure you all know that it is poor practice to just use client side validation.

To validate like the add or edit scaffolding the view needs to submit to its own action just like they do.
However that would mean the search results would also be displayed within the same view.

  1. I could run the query and then render a different view (but then the URL won’t change).
  2. I could store the search params in a session and redirect to the results view, then retrieve the search params, run the search and display it.

I began to worry how well pagination would work with either of these options.

I decided to try this with the way I wanted it to work – the search view submitting to a different action which displays the results, then if invalid I just needed a way of displaying the search form again and highlighting the errors.

The trick is to store validation messages and posted form data in Sessions and redirect back to the search form.

First I considered reading the Session in the view and injecting into CakePHP variables.
The code below shows how to add validation information within a view:

validationErrors['Category']['name'][0] = 'Validation message text here';
$this->validationErrors['Category']['name'][0] = 'Price should be < 100';
$this->request->data['Category']['name'] = 'Form field data in here';
$this->request->data['Category']['price'] = 123;
?>

In practice it is easier to inject those messages in the Controller.
Many thanks for this blog article which showed me how to do it:
http://www.jamesfairhurst.co.uk/posts/view/validating_cakephp_data_from_another_model/

In the controller action which receives form input you do the following:

$post = $this->request->data['Category'];
if (empty($post['search_text'])) {
	$this->Category->validationErrors['search_text'][0] = 'You must provide search text.';
}
if (empty($post['price'])) {
	$this->Category->validationErrors['resort_id'][0] = 'You must provide a price.';
}

if (count($this->Category->validationErrors) > 0) {
	$this->Session->write('Category', $this->data);
	$this->Session->write('CategoryErrors', $this->Category->validationErrors);
	$this->redirect(array('action' => 'index'));
}

This checks for errors, stores the errors and data in sessions and redirects back to the search form.

And in the controller action which displays the form:

//see if there were any validation problems
if ($this->Session->check('Category')) {   
	// get data the user posted   
	$chalet = $this->Session->read('Category');   
	// get the errors   
	$errors = $this->Session->read('CategoryErrors');   
	// set their data for view   
	$this->request->data['Category'] = $chalet['Category'];   
	// set validation errors for view   
	$this-> Category->validationErrors = $errors;   
	// delete the session data   
	$this->Session->delete('Category');   
	$this->Session->delete('CategoryErrors');   
}

This breaks CakePHP’s principles of Fat Models and Skinny Controllers (maybe I could move some of this code into Model functions) but I’d be very interested to see what other ways there are of doing this.