Typo has had theme support since before Typo 2.5.0 was released, but the first version of Typo’s theme engine didn’t include the ability for themes to change any of the HTML generated by Typo’s views. Themes could change CSS, graphics, and the top-level layout that generated the HTML for the site, but individual HTML blocks, like comments or article bodies, couldn’t easily be changed.

The latest release, Typo 2.5.7, fixes this shortcoming. It’s now possible for themes to completely override any bit of HTML or XML that Typo generates. Here’s how it works:

Like all Rails apps, Typo stores all of its HTML views in app/views/<controller>/<viewname>. For example, the view that generates individual article pages is app/views/articles/read.rhtml. The theme system in Typo 2.5.7 and newer lets themes override views by putting a replacement file into themes/<themename>/views/<controller>/<viewname>. So if I created a new theme called scottstuff and wanted to override the read.rhtml view from above, I’d put the new view into themes/scottstuff/views/articles/read.rhtml. Typo would then read this view from the theme instead of app/views.

It’s also possible to replace the HTML generated by sidebar plugins; just put the replacement view into themes/<themename>/views/plugins/sidebars/<sidebarname>/content.rhtml.

Typo should pick up on the new views immediately, even in production mode, but you’ll have to flush the page cache before the views really take effect.Here’s how it works.

Rails doesn’t really have any notion of a “view search path”–it knows exactly which directory should contain every view. So, in order to add the ability to search multiple directories, I had to override parts of the Rails core. Fortunately, Ruby’s open classes are good for on-the-fly patching of things like this. Here’s the code that implements it:

module ActionView
  class Base
    alias_method :__render_file, :render_file
    
    def render_file(template_path, use_full_path = true, local_assigns = {})
      search_path = [
        "../themes/#{config[:theme]}/views",     # for components
        "../../themes/#{config[:theme]}/views",  # for normal views
        "."                                      # fallback
      ]
      
      if use_full_path
        search_path.each do |prefix|
          theme_path = prefix+'/'+template_path
          begin
            template_extension = pick_template_extension(theme_path)
          rescue ActionView::ActionViewError => err
            next
          end
          return __render_file(theme_path, use_full_path, local_assigns)
        end
      else
        __render_file(template_path, use_full_path, local_assigns)
      end
    end
  end
end

This lives in lib/renderfix.rb in Typo 2.5.7 and gets included explicitly by environment.rb. Feel free to use this in your own projects; it’s under the MIT license, just like the rest of Typo.