Wednesday, October 26, 2005

Full Theme Support in Rails, Revisited

OK, so after a short discussion and some experiments, I've realized that the Typo approach to themes has a lot more merit than I initially thought... For general use it still has some trouble areas, but that's nothing that can't be fixed.

I've created a new theme_generator that takes their general approach and makes it easy to leverage in any application. Even an existing one.

How to use it...

Usage is extremely easy. First, use the generator to create a default theme.

  ./script/generate theme default

(It doesn't have to be called 'default', it can be whatever you'd like)

This will create the main themes folder structure, the initial theme files for the theme ('default' in this case), and it creates the plugins/components needed to support themes.

Now you can use themes as easily as you do layouts. The following will use a layout named 'main' in the 'default' theme.

class ApplicationController < ActionController::Base
  layout 'main'
  theme 'default'
end

Here's an example of per-user themes:

class ApplicationController < ActionController::Base
  layout 'default'
  theme :get_user_theme

  def get_user_theme
    # This assumes, of course, your User has a 'theme' field
    return @session[:user].nil? ? 'default' : @session[:user].theme
  end
end

Differences from Typo themes...

I did take the liberty of tweaking of couple of things (beyond setting the theme in your controller). The main difference is that I changed the cached theme structure.

Before:

/public
  /images
    /theme
  /stylesheets
    /theme
  /javascript
    /theme

After:

/public
  /themes
    /[THEME_NAME]
      /images
      /stylesheets
      /javascript

What does this mean? For one thing, it's a lot easier to remove cached theme files... Just delete the public/themes folder.

Also, you should be using relative paths to images in your CSS. For example:

BODY {
  background: #FFF url(../images/page-bg.jpg) top left;
}

Requirements...

Update: theme_generator is now hosted on RubyForge.

The theme_generator requires Rails >= 0.14. The easiest way to install the theme_generator is by running:

  sudo gem install theme_generator

It's that simple. Feel free to install it and let me know what you think.

Monday, October 24, 2005

Supporting Themes In Your Rails Application

Update: There's an updated version of the theme_generator. See the blog post or download it.

I was pretty impressed after looking at the theme support in Typo 2.5. However, I don’t think it’s a system that you should replicate if you want your application to support simple themes.

Before I get too far along let me make it clear that it’s not my intention to cast aspersions on Typo, it’s an excellent tool. In fact, I plan on switching to it from WordPress in the near future.

My issue with Typo’s theme scheme (heh) is that it doesn’t leverage the web-server as it should. Rails is impressive, and it’s caching is quite good. But web-servers like Apache have been around for many years. They are really good at what they do, so let them do it! Rails can’t compete with the server’s ability to deliver static content like CSS and graphics.

Following is how I’ve been implementing themes in Rails applications. In addition to relying on the web-server to provide static files, it also leverages existing Rails infrastructure wherever possible. Bear in mind, I’m not suggesting the following to be an end-all solution. Nor is it a drop-in replacement for Typo’s theme support. Merely think of it as a good starting point.

I’ve been using this approach enough that I’ve created a generator for it.

theme_generator

Usage

After you’ve installed the generator, just call it with the name of the theme you’d like to create.

./script/generate theme blue-bird

It will then add theme_engine.rb to your lib/ folder and create the required file structure.

Note: On Windows, it looks more like:

ruby script\\generate theme blue-bird

File Structure

Let’s start by having a look at the file structure it creates. Here’s an example for a theme named ‘blue-bird’.

app/
  views/
    layouts/
      themes/
        blue-bird/
  public/
    themes/
      blue-bird/
        images/
        javascripts/
        theme.css

Just from looking at that, you can probably get a good idea where I’m going.

Static Files

All of the static files, CSS, Images, and JavaScripts are under the app/public/themes directory, as they should be. Apache (or lighttpd, or whatever) will happily serve these up for us.

To reference theme-specific images from the CSS it’s as simple as:

BODY {
  background: url( images/my-bg.png );
}

Be sure to use a relative path to the image. CSS will look for images relative to the CSS file, not the page, remember?

Layouts

You’ll notice under the app/views/layouts folder, I’ve added a theme structure similar to the one under app/public/. As an example, app/views/layouts/themes/default/main.rhtml is the main layout for the ‘default’ theme.

In your controller, you’ll tell Rails that you want a themed layout like this:

  class MyController << ApplicationController
    layout :themed_layout
  end

Current Theme

The only thing that’s left to your controller is indicating which theme is currently active. It’s as simple as overriding the current_theme method.

class MyController << ApplicationController
  layout :themed_layout

  def current_theme
    'blue-bird'
  end

end

To set the theme for the entire application, you can add that to your ApplicationController instead (in app/controllers/application.rb).

Tag Helpers

To aid in retrieving themed media, helper methods have been added to the ActionView::Helpers::AssetTagHelper module.

  • theme_image_path
  • theme_stylesheet_path
  • theme_javascript_path
  • theme_stylesheet_link_tag
  • [More to come…]

Usage is generally quite simple. In your view, if you want to reference a themed image:

<img  src="<%= theme_image_path 'my-image.png' % />" />

Last, But Not Least

This needs to be included at the bottom of your environments.rb:

require_dependency 'theme_engine'

That’s it really—pretty straight forward. If you look at the theme_engine.rb you’ll see it’s a less-is-more type solution.

In the next few days I’ll polish the theme_engine some more, based on the feedback y’all provide, and refactor it into a plugin.

Thursday, October 20, 2005

Simple Row Style Alternator

In your view (rhtml):

<table>
  <thead>
    <tr>
      <th>Name</th>
    </tr>
  </thead>
  <tbody>
  <% for user in @users %>
    <tr class="<%= row_style %>">
      <td><%= user.name %></td>
    </tr>
  <% end %>
  </tbody>
</table>

In your helper (rb): Updated!

  def row_style
    @row_index ||= 0
    @row_index += 1
    @row_index % 2 == 0 ? "Even" : "Odd"
  end