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
- We wanted the Javascript to be object oriented
- 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
- 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.
Related posts: