Simple “MVC” Javascript for CakePHP

September 9, 2009 by Adam · Leave a Comment 

On a recent project we had the need to provide Javascript across a very large project, with lots of controllers, and lots of actions.  We had a number of requirements for the Javascript:

Requirements

  1. We wanted the Javascript to be object oriented
  2. Although the application would not be public facing, internally the users were using a wide variety of platforms, and some were very old or very restrictive, so the application needed to degrade gracefully
  3. We didn’t want any inline Javascript, as with so many view files management would be a nightmare, and not very DRY

Design

Once the application was fully working without Javascript, we came up with a simple, yet powerful way to structure our code so that it required a minimal amount of modification to our views, yet allowed us the flexibility to customise every page.  Our solution is not really MVC, as we only use the controller part of MVC, but it would be relatively easy to use the same method in cake to add the M & V.  In a future post I may add this.

Implementation

The first step was to create app.js in webroot/js.  This file would act as the Front Controller for our Javascript and handle the dispatching of or application.  app.js should look something like this:

App = {
	controller: null,
	action: null,

	afterFilter: function() {
		if(isDefined(App.controller, 'afterFilter')) {
			eval(App.controller + '.afterFilter();');
		}
	},

	beforeFilter:  function() {
		if(isDefined(App.controller, 'beforeFilter')) {
			eval(App.controller + '.beforeFilter();');
		}
	},

	dispatch: function() {
		App.beforeFilter();

		if(isDefined(App.controller, App.action)) {
			eval(App.controller + '.' + App.action + '();');
		}

		App.afterFilter();
	}
}

function isDefined(object, variable) {
	return (typeof(eval(object)[variable]) != 'undefined');
}

Breaking this up, you should see some familiar concepts from cake – we have our application-wide beforeFilter and afterFilter callbacks, which also call the beforeFilter and afterFilter methods on our current controller, and our dispatch function, which starts the Javascript execution, and defines the order of the callbacks. The isDefined function checks to see if the current action is defined on the current controller before trying to call it, this means that we don’t get a bunch of nasty undefined function errors if we don’t write a Javascript method for every action in our cake application.

The code that pulls it all together (and makes it nice and simple), it the code that we put in our cake layout file:

< ?php
	link(
		array(

			//frameworks
			'prototype/prototype',
			'scriptaculous/scriptaculous',

			//plugins
			'prototip/prototip',
			'lightview/lightview',

			//mvc
			'app',
			'mvc/' . low($this->params['controller'])
		)
	);
?>
<script type="text/javascript">
	window.onload = function() {
		App.controller = '< ?php echo low($this->params['controller']); ?>';
		App.action = '< ?php echo low($this->params['action']); ?>';
		App.dispatch();
	}
</script>

What we’re doing here is including our Javascript files in the normal cake way, with one exception – we are also loading a file in the directory /js/mvc with the same name as the current executing controller.  Below that we are attaching a function to the onload event, and setting the controller and action variables to the current executing cake controller and action, and finally starting the whole thing off with a call to App.dispatch().

Example

The above code is very simple – but we think, very flexible, in it allows us to execute Javascript application wide, or on an action by action basis, allows us to keep everything Object Oriented, and means we don’t have to modify our view code to add new Javascript functionality, which is exactly what we need.

Search = {
	beforeFilter: function() {
		Event.observe(
			'SearchQuery',
			'click',
			function() {
				if($('SearchQuery').value == 'Enter keyword(s)...') {
					$('SearchQuery').value = ''
				}
			}
		);
	},

	results: function() {
		alert('Here are your results...')
	}
}

In the above example, we use the beforeFilter to remove the Enter keyword(s)… text from the search query box, which is available on every page in the search controller, and provide the user with a useless prompt on the results action, saying ‘Here are your results…’.   Technically this should go into a Javascript “view” as it is a DOM manipulation, but that can wait for a future iteration.

Share and Enjoy:
  • Print
  • Digg
  • Sphinn
  • del.icio.us
  • Facebook
  • Mixx
  • Google Bookmarks

Related posts:

  1. Simple Sluggable Behavior for CakePHP
  2. Integrating the Auth Component with a bespoke 3rd party Single Sign-On Service in CakePHP
  3. Easy Multi Page Forms (or Wizards) with CakePHP

About Adam
Adam is senior developer for Image+ Ltd in Coventry, England. He has been developing websites for over 10 years, and currently freelances through his own company, Votive Media. Follow him on twitter for more!

Speak Your Mind

Tell us what you're thinking...
and oh, if you want a pic to show with your comment, go get a gravatar!