RESTful CSS
Got something to say?
Share your comments on this topic with other web professionals
In: Articles
Published on November 18, 2008
In this article I will propose a new method for organizing CSS that better maps to how popular web application frameworks are built; and I’ll also provide some helpful code to make this easy to accomplish. The examples I use are based on Ruby on Rails, but the concepts should be easily transferrable to other MVC frameworks.
MVC Frameworks?
MVC stands for Model-View-Controller. Many web application frameworks, including Ruby on Rails, CodeIgniter, Cake PHP, Django, and Sproutcore, are built utilizing an MVC architecture. It’s not extremely important that you understand how an MVC architecture works for the purpose of this article—if you want to know more, Wikipedia describes the overall concept. Here’s a basic illustration:
REST
In simple terms, REST is a set of principles for structuring a web application and RESTful refers to an application that follows those rules. Within a RESTful MVC application, controllers are limited to a standard set of actions to perform:
- Index—displays a list of items of a particular type
- Show—displays the details of a specific item
- New—displays a form for creating a new item
- Create—saves an item to the database using the data from New
- Edit—displays a form for editing an item
- Update—updates an item in the database using the data from Edit
- Destroy—removes an item from the database
The index
, show
, new
, and edit
actions actually produce Views, or pages that are sent to the browser to be displayed to the user. These four views/pages are repeated throughout most controllers in an application.
An example always helps, so consider a web application for tracking projects. The Projects controller would produce:
- an
index
page for listing all projects - a
show
page for displaying an individual project - a
new
page for creating a new project - and an
edit
page for editing a project
The Tasks controller and Users controller would act in a similar fashion.
But what has this got to do with CSS, you ask? Well, the CSS styles we use for these four page types will often be repeated across other controllers. In our project tracking app, many of the styles used on the form to create a new project will be reused on the forms for new users and new tasks. The same goes for the styles used to list items on index
pages, or to display the details of items on show
pages.
This relationship of styles to actions allows us to begin organizing our CSS into a group of stylesheets that map to those different page types:
/css /index.css /show.css //files/includes/new.css /edit.css
Essentially, what we are doing is creating a new layer of stylesheets that fall between global and single-page specific. This new layer allows us to move styles from the global stylesheet into a narrower scope, meaning less conflict between element styles, and a reduced need for complex selectors. For instance, if the show
and index
pages each have a unique table (for tabular data, of course!) we can apply styles to it with just the element name table
, in both index.css
and show.css
, without conflict.
Implementation
We’ll start by creating the four new stylesheets (index.css
, show.css
, /files/includes/new.css
, edit.css
) and putting them in the /files/includes/default.css stylesheets folder.
In case you were wondering, I’m not suggesting removing the use of a global stylesheet. I continue to use an application.css
for global styles (as well as a reset.css
and some CSS framework stylesheets, if that’s your thing). The styles that we’ll put into this new organization will be action specific.
You may also have wondered why there is a need for both a /files/includes/new.css
and an edit.css
when, more often than not, these two actions share the same form. While someone might find it useful to have them separated, I haven’t yet, so for now we’ll import /files/includes/new.css
into edit.css
:
@import url('/files/includes/new.css')
This method still allows us to add scoped styles for the edit action—the real reason we won’t just delete edit.css
is next.
Automagical Inclusion
The last thing we want to do is have to edit every page in our site to include the appropriate action-related stylesheet. So, within our global layout file, we’ll place some server-side code that will do this automatically. In Rails, the code looks like this:
/app/views/layouts/application.html.erb
<%= stylesheet_link_tag(controller.action_name, :media => "screen") %>
Which on a show
page would output:
<link href="/stylesheets/show.css" media="screen" rel="stylesheet" type="text/css" />
Pretty simple so far, but there are still some situations that we will need to address.
Overriding
In some instances we may prefer that a different action’s stylesheet be used. In an application I’m currently building, I have a singular resource: Dashboard
. Every user has one dashboard, meaning there’s no need for a list of dashboards, and the /files/includes/default.css action is show
, not index
. However, the dashboard’s design is similar to the index
pages of other controllers, so I would rather include index.css
. To accomplish this we’ll start by creating a new helper method in application_helper.rb. This will help keep the layout file clean as we build in more logic:
/app/helpers/application_helper.rb
def action_style(action = nil) @action_style = action || @action_style || controller.action_name end
And the code in the layout file now looks like this:
/app/views/layouts/application.html.erb
<%= stylesheet_link_tag(action_style, :media => "screen") %>
There are two parts to this method. First, it allows you to preset the @action_style
instance variable by passing in the action
parameter. Second, if the @action_style
variable has not been set it returns the current action’s name. In my dashboard’s view
file for the show
action, I can now set it to include index.css
instead, like so:
/app/views/dashboards/show.html.erb
<% action_style "index" %>
Rails Form Rendering
Another issue we need to account for is the way a Rails application will re-render a form when there are user errors that need to be corrected. Instead of redirecting back to the new
action, a Rails app will render the form within the create
action. The same goes for edit
and update
. This throws a wrench into our “automatic inclusion by action name” method, but it’s easily fixed by adding in a check that will overwrite create
with new
and update
with edit
:
def action_style(action = nil) @action_style = action || @action_style || { "create" => "new", "update" => "edit" } » [controller.action_name] || controller.action_name end
For those hoping to translate it to a different language, It could be written like this instead:
def action_style(action = nil) overwrite = Hash.new overwrite["create"] = "new" overwrite["update"] = "edit" if action @action_style = action elsif @action_style @action_style elsif overwrite.has_key?(controller.action_name) @action_style = overwrite[controller.action_name] else @action_style = controller.action_name end return @action_style end
One other thing we can now do with this method is remove edit.css
completely by overwriting edit
with new
. Depending on the needs of your app you may or may not want to do this, but the method would now look like this:
def action_style(action = nil) @action_style = action || @action_style || { "create" => "new", "update" => "new", "edit" => "new" } » [controller.action_name] || controller.action_name end
Hopefully at this point you can see how the hash can be updated to account for any other situation where you might need to translate any given action name to a different stylesheet.
Page Specific Styles
Now that we’ve accounted for action styles, we can go a step further by organizing our single-page specific styles to match the way our app is built. We do this by creating stylesheets that map to a specific controller. In the project tracking example above, we would create projects.css
for our Projects controller. Naming the files this way means we can use some of the same automatic inclusion we used for the action styles:
<%= stylesheet_link_tag(controller.controller_name, :media => "screen") %>
In this case, there isn’t much need to allow for overriding, so we won’t create a separate helper method for it. However, not every controller is going to need it’s own stylesheet, and we don’t want our pages calling non-existent stylesheets. To protect against this, we’ll create a method that checks to see if the stylesheet exists first:
def stylesheet_exist?(stylesheet) File.exist?(RAILS_ROOT + "/public/stylesheets/" + stylesheet + '.css') end
The code in the layout file would then look like this:
<%= stylesheet_link_tag(controller.controller_name, :media => "screen") » if stylesheet_exist?(controller.controller_name) %>
While we’re at it, let’s go ahead and add the same check for the action stylesheets as well:
<%= stylesheet_link_tag(action_style, :media => "screen") if stylesheet_exist?(action_style) %>
By adding it to the action styles, we can now cancel the automatic inclusion of an action style by overriding it with a fake stylesheet name:
<% action_style "blank" %>
Summary
Putting it all together, we have three stylesheet types:
- Global—application.css (and other global stylesheets)
- Action General—index.css, show.css, /files/includes/new.css
- Controller Specific—controller_name.css (e.g. projects.css)
Your application_helpers file should look like this:
/app/helpers/application_helper.rb
Your global head section should look something like this:
/app/views/layouts/application.html.erb
Performance
Many developers like to concatenate stylesheets at runtime to reduce the number of requests to the server. With this method you can’t do that, at least not with the action and controller stylesheets. While that may seem like a downside, it actually reduces the initial load of styles by distributing them to other sections of the site where global styles have already been cached. This is particularly good for the home page (or whichever page is typically hit first in your application). You can still gzip the individual stylesheets, which I would recommend anyway.
Multiple Stylesheets
I know there are some out there who can’t stand the idea of having multiple stylesheets to dig through when trying to track down what might be causing a certain bug, and that’s okay. For myself, Firebug is handy enough that I don’t consider that a big problem, and I’ve become a fan of concise, purposeful stylesheets where I don’t have to do too much scrolling or searching through to find definitions.
This method may not be for everyone. Some may find that a piece of it is all they need. I’ve found it to be convenient and flexible in my own work, and I hope you find the same.
Got something to say?
Share your comments with other professionals (25 comments)
Related Topics: Programming, Planning, CSS
Steve Heffernan is a partner at Sevenwire, a web application development agency whose current projects include Zencoder, Red is White, Notifly, and FlixCloud with On2. Steve has been working on the same side-side project, Roommate App, for the last 4 years and may actually launch it soon…maybe. Steve’s Linkedin Profile