jQuery changing form values not affecting the DOM

With jQuery it is very easy to change the value of a form element, here’s a quick example that changes the text for a form element with id=’input1′

$('#input1').val('this is the new value');

On the page you will see your input change as requested. If you look at the value with jQuery there are no surprises.

console.log($(#input1).val());
//will output 'this is a new value'

However you may be surprised that it hasn’t been changed if you try to look at it with jQuery in a different way. Assuming that input1 is contained in a div with id ‘div1’.

console.log($('#div1').html());

You will see this

The DOM doesn’t look like it has been updated. This certainly wasn’t the behaviour I expected from this so it took a lot of digging to find out what was going on.

It turns out that the HTML value attribute isn’t necessarily the value that is displayed on the page, instead it is known as the default value – used when the page is first loaded or if the form is reset.

The solution. If you want to update the value that appears on the page and the value that’s retrievable by the .html() method is this:

$('#input1').val('this is the new value');
//before jQuery 1.6
$('#input1').attr('defaultValue', 'this is the new value');
//after jQuery 1.5.2
$('#input1').attr('value', 'this is the new value');

Set the ‘defaultValue’ or ‘value’ attribute depending on your version of jQuery as well as using the .val() method. You’ll find that getting the html with jQuery will now show the correct value.

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.

Dynamic select box with CakePHP 2.0

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 (?)

1. Models

We need 3 associated tables

CREATE TABLE `categories` (
  `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `name` varchar(60) DEFAULT NULL,
  PRIMARY KEY (`id`)
);

INSERT INTO `categories` (`id`, `name`)
VALUES
	(1,'books'),
	(2,'music'),
	(3,'electronics');

CREATE TABLE `posts` (
  `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `title` varchar(50) DEFAULT NULL,
  `subcategory_id` int(10) unsigned DEFAULT NULL,
  PRIMARY KEY (`id`)
);

INSERT INTO `posts` (`id`, `title`, `subcategory_id`)
VALUES
	(1,'The title',1),
	(2,'A title once again',4),
	(3,'Title strikes back',7);

CREATE TABLE `subcategories` (
  `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `category_id` int(10) unsigned DEFAULT NULL,
  `name` varchar(60) DEFAULT NULL,
  PRIMARY KEY (`id`)
);

INSERT INTO `subcategories` (`id`, `category_id`, `name`)
VALUES
	(1,1,'fiction'),
	(2,1,'biography'),
	(3,1,'children'),
	(4,2,'classical'),
	(5,2,'rock'),
	(6,2,'jazz'),
	(7,3,'camera'),
	(8,3,'audio'),
	(9,3,'tv');

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:

request->data['Post']['category_id'];
		
		$subcategories = $this->Subcategory->find('list', array(
			'conditions' => array('Subcategory.category_id' => $category_id),
			'recursive' => -1
			));
		
		$this->set('subcategories',$subcategories);
		$this->layout = 'ajax';
	}
}

3. Views

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):

Form->input('category_id');
	echo $this->Form->input('subcategory_id');
	echo $this->Form->input('title');
?>

Add the following code to the bottom of the view, this uses the Js Helper to create the necessary jQuery to perform the updating:

Js->get('#PostCategoryId')->event('change', 
	$this->Js->request(array(
		'controller'=>'subcategories',
		'action'=>'getByCategory'
		), array(
		'update'=>'#PostSubcategoryId',
		'async' => true,
		'method' => 'post',
		'dataExpression'=>true,
		'data'=> $this->Js->serializeForm(array(
			'isForm' => true,
			'inline' => true
			))
		))
	);
?>

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.





	Html->charset(); ?>
	
		<?php echo $title_for_layout; ?>
	
	Html->meta('icon');

		echo $this->Html->css('cake.generic');
	?>


	
Session->flash(); ?>
element('sql_dump'); ?> Js, 'writeBuffer')) echo $this->Js->writeBuffer(); // Writes cached scripts ?>

Now you can test the application. On changing the category select list, the subcategory one will automatically update to show the relevant options.