Digital Web Magazine

The web professional's online magazine of choice.

RESTful CSS

Got something to say?

Share your comments on this topic with other web professionals

In: Articles

By Steve Heffernan

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:

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:

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
/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, new.css, edit.css) and putting them in the default 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 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 new.css into edit.css:

@import url('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 default 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:

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

Media Temple

via Ad Packs