Managing CSS and JavaScript files within a Zend Framework App
When I’m creating a large external facing website or application, one of the biggest messes I used to end up making was in my CSS and JavaScript files.
As a developer/designer (jack of all trades, master of none, if you will) I tend to stay away from some of the more common developer solutions that take care of a lot of the design overhead (e.g., jQuery UI framework, Dojo, etc). Flexibility is paramount to good design — boxing yourself into a specific layout for every page is bound to produce a boring, forgettable website. Design is highly underrated to developers, I believe a good aesthetic sense is a skill worth maturing for anyone who develops web applications. After all, you can have the most beautiful underlying code, but if it’s presentation is anything short of beautiful it completely loses its “wow” factor.
Full design control usually means large, thousand plus line CSS files that equal serious pain when it comes to maintenance or building upon. If you don’t namespace your selectors carefully you’ll end up paying for it down the road. And heaven forbid you apply a default HTML tag styling. So, ranting aside, how do you maintain flexibility and still keep tidy CSS and JavaScript? My solution is to keep the very same directory/file structure that is used for the application for my CSS and JavaScript. The best part is by writing a very simple view helper, it’s super easy to automate the proper head link and script file inclusions.
Here’s what my CSS and JavaScript view helpers look like:
File: application/views/helpers/JavascriptHelper.php
getRequest();
$file_uri = 'media/js/' . $request->getControllerName() . '/' . $request->getActionName() . '.js';
if (file_exists($file_uri)) {
$this->view->headScript()->appendFile('/' . $file_uri);
}
}
}
File: application/views/helpers/CssHelper.php
getRequest();
$file_uri = 'media/css/' . $request->getControllerName() . '/' . $request->getActionName() . '.css';
if (file_exists($file_uri)) {
$this->view->headLink()->appendStylesheet('/' . $file_uri);
}
return $this->view->headLink();
}
}
With that done, add the helper to your layout in the section:
File: application/layouts/scripts/layout.phtml
<title>My app title</title>
<?= $this->headTitle() ?>
<?= $this->headMeta() ?>
<? $this->headLink()->appendStylesheet('/media/css/global.css') ?>
<?= $this->headLink()->appendStylesheet('/media/css/iefix.css','screen','lt IE 7') ?>
<?= $this->cssHelper() ?>
<?= $this->javascriptHelper() ?>
Now, anytime a controller action is invoked it will look for a javascript and css file in the same controller/action file path. In the above examples I’ve hard-coded the CSS and Javascript parent directories (/public/media for me) directly in to the code, but this would be pretty easy if you wanted to throw it in a config file or something instead. Otherwise just change to your preferred directory and your good to go.
The next thing I’d like to explore is automatically packing/minifying all CSS and Javascript into one file (or possibly outputting them directly to the layout in-line) and then caching the results of that to optimize bandwidth usage. If I ever have the luxury of that being a priority, that is
I love the general idea and your specific view helper.
Even better, I am relieved that you, too, use page-specific (in the case of an MVC-framework like Zend Framework, controller-action-specific) styles and scripts. Looking at so many sites and samples, it was beginning to appear that best practice was a single global css file and a single javascript file (to reduce the number of HTTP requests, etc). But it always bugged me that there would be some styles/scripts that would be page-specific, so why should they be parsed/executed on *every* page?
Thanks for a bit validation that at least I am not alone in this. On the other hand, any ideas on the best way to address this – like your allusion to packing/minifying in a way that would still create significant caching benefits – would be most welcome.
David Weinraub
February 1, 2010 at 10:37 pm
Thanks! I’m glad this was useful for someone else. It’s also nice to see that someone else shares my point of view. As much as we try to avoid it, CSS is a language with it’s own set of best practices.
My thoughts for a packer/minifier would probably be to take hints from this project and this one (if not just using them outright). Instead of including the file, read the contents of the file and send it to the packer/minifier class. Send the returned value to a Zend_Cache object. The view helper could handle the caching and either output the script inline (which would be the best bandwidth optimizing, however, I suspect you might run into some CSS rendering issues) or include it all in one simple file.
If I have time, I’ll try to whip this up — but in my experience thus far, this is more of a nice-to-have than a requirement. When I first started using my CSS/JS view helpers I was very concerned about loading an additional file per controller/action, but in practice I’ve discovered the overhead is pretty minimal for the maintainability you end up getting out of it.
Andy Baird
February 2, 2010 at 12:37 am
I would say that gziping your css/js is a must for reducing the load on your servers! I’m had pretty good YSlow results using php_speedy in the past http://aciddrop.com/2009/02/02/php-speedy-wp-052-bug-fix/. If any of us ever have the luxury of time, this would be nice to have ported over to Zend Framework
David
February 4, 2010 at 1:23 pm
Hi, interesting article (saw this here and on the zend site). I figured I would share my approach for handling CSS/JS files in zend since it is quite different.
http://www.devpatch.com/2010/02/view-plugin/
Thoughts and opinions are welcome.
Ben
February 6, 2010 at 11:32 am
Interesting, but I like to keep details about presentation out of my controllers as much as possible. The code in your layout plugin I usually code directly into the layout (but then again, I don’t mind putting a few PHP tags in my layouts as long as they control markup, I know some people prefer not too)
Also, even for me I don’t use a separate JS/CSS file for every controller/action, but because the view helper only loads the files *when* they exist that works perfectly fine.
Andy Baird
February 6, 2010 at 11:25 pm
Always good to see different approaches, I think this is especially true with ZF since it is not strictly structured.
Not sure if this matters for your application but caching the view seems like a good idea if you use this method. I would think there would be some performance hit by doing the file system read. Just something that I was noodling on when re-reading your article.
Thanks again for your thoughts.
Ben
February 8, 2010 at 11:22 am