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.
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.
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.
I could run the query and then render a different view (but then the URL won’t change).
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 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.
I wanted some auto populating Select boxes in a site I was creating so that when I changed a Category the Subcategories would automatically update. CakePHP can do this pretty easily but it is let down by the documentation as there are no examples.
Initially I came up with a version that wrote JSON into a JavaScript variable in the page and then used jQuery to achieve the updating of select elements but I wanted to do this the Cake way which uses AJAX and as little code as possible.
Here is a simplified solution to demonstrate how it can be done.
This assumes a new CakePHP site already configured with a database connection. I used CakePHP 2.0.3 but this may also work with 1.3 (?)
Bake the 3 Models for these and allow CakePHP to define model associations for you.
2. Controllers
Bake a Controller for the Post Model with the default CRUD actions.
While you are at it you will also need to bake the CRUD Views for the Post Controller.
If you browse to the posts index page now you can view the data.
In this example I will add the Category and Subcategory select lists to the Add view, so now we need to change this so that the selection in the second list changes according to the selection in the first list.
This will be done via the Js Helper so make it available at the top of the Posts controller (after the Class declaration) with:
public $helpers = array('Js');
You need a Subcategories Controller with a single action to provide the data via AJAX:
The view is a very simple AJAX view that renders the option tags that go within the select tag.
$value): ?>
4. Putting it all together
In the Post Add view, file path: View/Posts/add.ctp we can finally add the Js methods that make the dynamic updating happen. This is the cryptic bit that I struggled with for a few hours as although the CakePHP documentation outlines all the options there are not any complete examples.
Firstly add a categories select element to the form (and change the order of the elements):
This is saying – watch the HTML element with Id PostCategoryId for a change. When it changes update the HTML element with Id PostSubcategoryId with the response from subcategories/GetByCategory, the data option is used to send the current value from the initial select element.
Before you leap to test this you need to make changes to the default layout to include jQuery and provide a place for your scripts to be written out.
It is easy to create an AJAX sortable list with CakePHP 2.0, using jQuery that automatically updates the database, however it is hard to find documentation explaining how to do it.
Here is my explanation – it assumes a basic knowledge of CakePHP.
Step 1: Data
Lets use a simple categories table which includes a column for sort_order. This will be used to define the order the records are displayed on the page.
Bake a model, controller and views for this table so that we have some files to work with.
You should be able to go onto your website at http://yoururl/categories and see the default index view.
Layout
In order to use the sortable feature, you will need to edit the default layout. If you don’t already have your own layout you can copy the CakePHP default one (which is used if your own one isn’t found) from /lib/Cake/View/Layouts/default.ctp and place it in app/View/Layouts/default.ctp.
Edit this so that the writeBuffer method is one of the final lines, just before the close body tag: