CakePHP tricks: Getting your scaffold ready for AJAX views

Warning! What follows is a lot more technical than our usual posts and will take some working knowledge of PHP, AJAX and MVC frameworks. Want to see more of this kind of post? Let us know what you think in the comments.

CakePHP is a great tool for rapid development and testing out ideas, however we often find that the basis of a good idea relies on a great interface, and in this day and age, that often means employing varying degrees of AJAX (Asynchronous Javascript and XML) in the user interface (or in CakePHP/MVC framework lingo, the view).

Scaffold is a great way to add a quick front end to a database and start seeing how everything links together and how it might look, however in more involved concepts it falls down on usability, and it doesn’t come with support for AJAX requests out of the box, so to speak. While the general consensus is that you shouldn’t use scaffold in production environments (and we would agree with that assertion), building AJAX support into the scaffold would certainly help to work out the concept of an involved front end in the initial stages.

So without further ado, let’s see how we can do a quick and dirty change to the scaffold ready to provide us with responses that we can use for AJAX calls from the view. The example below uses the CakePHP 2.0 release, available from CakePHP.org.

First, we will need to tell the CakePHP install to pay attention to the extensions we ask for, so that we can request for example /ourmodel/add.json, and it will provide us with information different to that shown at /ourmodel/add. This is fairly straight forward. Just crack open

/app/Config/routes.php

and add the following to the bottom of the file:

1
2
//read extensions of requested URL's
Router::parseExtensions();

Next we need to modify the standard scaffold which lives at:

/lib/Cake/Controller/Scaffold.php

Don’t edit the file here though, as Cake has a way for you to override this file (and indeed every other file in a similar way) by creating a file in the app directory. This means you won’t mangle any of the core files, making it difficult to upgrade the core at a later date. So, in order to override the original Scaffold.php, we will copy it to:

/app/Controller/Scaffold.php

Next, let’s decide on a format for the JSON we are going to output to our views when we request a JSON file. For the purpose of this tweak let’s return the following:

  • A “success” field that is either true or false so we can make decisions on how to treat the data in the view, such as changing the class of the response to red or green based on success or failure.
  • A “data” field containing the data we’ve added to the database if the action has been a success should we need to display it in some way
  • An “error” field containing any errors we have encountered if the action has not been a success so that we can alert the user to the specific area that is the problem
  • A “flash_message” field containing the friendly message to display to the user telling them what happened when the action was executed.

Now all that remains is to make some adjustments to the relevant parts of the scaffold file. Comments are included so hopefully you can see the parts of the code that have been added over and above the original file. First off, let’s change the function that would normally return a message to the user and render the next view, _sendMessage.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
	protected function _sendMessage($message) {	
		if ($this->_validSession) {
			//Spot an AJAX request
			if ($this->request->is('ajax')) {
				//Set the content type, dump the data and the message into a JSON string to be parsed by the javascript and die
				$this->controller->RequestHandler->setContent('json');
				$jsonDataArray['success'] = true;
				$jsonDataArray['data'] = $this->ScaffoldModel->read();
				$jsonDataArray['flash_message'] = $message;
				die(json_encode($jsonDataArray)); //the cake way would be to disable autoRender, however this causes warnings
			} else { 
				//we don't have AJAX so do the normal scaffold actions
				$this->controller->Session->setFlash($message);
				$this->controller->redirect($this->redirect);
			}
		} else {
			$this->controller->flash($message, $this->redirect);
		}
	}

And finally we need to make a change to the function that saves or updates the model – _scaffoldSave.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
	protected function _scaffoldSave(CakeRequest $request, $action = 'edit') {
 
		$formAction = 'edit';
		$success = __d('cake', 'updated');
		if ($action === 'add') {
			$formAction = 'add';
			$success = __d('cake', 'saved');
		}
 
		if ($this->controller->beforeScaffold($action)) {
			if ($action == 'edit') {
				if (isset($request->params['pass'][0])) {
					$this->ScaffoldModel->id = $request['pass'][0];
				}
				if (!$this->ScaffoldModel->exists()) {
					throw new NotFoundException(__d('cake', 'Invalid %s', Inflector::humanize($this->modelKey)));
				}
			}
 
			if (!empty($request->data)) {
				if ($action == 'create') {
					$this->ScaffoldModel->create();
				}
 
				if ($this->ScaffoldModel->save($request->data)) {
					if ($this->controller->afterScaffoldSave($action)) {
						$message = __d('cake',
							'The %1$s has been %2$s',
							Inflector::humanize($this->modelKey),
							$success
						);
						return $this->_sendMessage($message);
					} else {
						return $this->controller->afterScaffoldSaveError($action);
					}
				} else {
					if ($this->_validSession) {
						//Spot an AJAX request
						if ($this->request->is('ajax')) {
							//Set the content type to return, dump the error data and the message into a JSON string to be parsed by the javascript and die
							$this->controller->RequestHandler->setContent('json');
							$jsonDataArray['success'] = false;
							$jsonDataArray['error'] = $this->ScaffoldModel->validationErrors;
							$jsonDataArray['flash_message'] = 'Please correct errors below.';
							die(json_encode($jsonDataArray)); //the cake way would be to disable autoRender, however this causes warnings
						}
						//We don't have AJAX so perform the original function
						$this->controller->Session->setFlash(__d('cake', 'Please correct errors below.'));
						}
				}
			}
 
			if (empty($request->data)) {
				if ($this->ScaffoldModel->id) {
					$this->controller->data = $request->data = $this->ScaffoldModel->read();
				} else {
					$this->controller->data = $request->data = $this->ScaffoldModel->create();
				}
			}
 
			foreach ($this->ScaffoldModel->belongsTo as $assocName => $assocData) {
				$varName = Inflector::variable(Inflector::pluralize(
					preg_replace('/(?:_id)$/', '', $assocData['foreignKey'])
				));
				$this->controller->set($varName, $this->ScaffoldModel->{$assocName}->find('list'));
			}
			foreach ($this->ScaffoldModel->hasAndBelongsToMany as $assocName => $assocData) {
				$varName = Inflector::variable(Inflector::pluralize($assocName));
				$this->controller->set($varName, $this->ScaffoldModel->{$assocName}->find('list'));
			}
 
			return $this->_scaffoldForm($formAction);
		} elseif ($this->controller->scaffoldError($action) === false) {
			return $this->_scaffoldError();
		}
	}

And that’s it! Let us know if you have other ways of achieving similar goals or whether this helps with your next CakePHP project in the comments.

This entry was tagged , , , , , , , . Bookmark the permalink.


Click here to leave a reply