Dynamic CSS with Rails

October 24th, 2008

I’ve sometimes wondered why CSS isn’t more dynamic.

Take, for example, the styles applied to the sub-menu on the services page of this site. To achieve the highlighting of the current service, I combine a Rails view and CSS as follows:

The Rails View

<div id="<%= @current_service.name %>">
  <div id="secondaryNav">
    <ul>
      <% @services.each do |service| %> 
        <li class="<%= service.name %>"><%= link_to service.heading, service_path(service) %></li>
      <% end %>
    </ul>        
  </div>
</div>

The CSS

#agileweb #secondaryNav .agileweb a,
#java #secondaryNav .java a,
#coaching #secondaryNav .coaching a,
#mentoring #secondaryNav .mentoring a,
#overview #secondaryNav .overview a,
#advice #secondaryNav .advice a {
  color: #99CC00;
  text-decoration: underline;
}

Handling a new service

That does the trick nicely until I decide to add a new service. Wouldn’t it be nice to have that CSS regenerated dynamically whenever I create, update or delete a service?

Enter a new Ruby module:

# lib/dynamic_css.rb
module DynamicCss
  def generate_services_nav_links_css
    return if RAILS_ENV == "test"
    FileUtils.cd File.expand_path("public/stylesheets", RAILS_ROOT)
    File.open("servicesnav.css", "w") do |out|
      service_names = []
      services = Service.find :all
      services.each { |s| service_names << s.name }
      service_names.each_with_index do |name, i|
        out.print "##{name} #secondaryNav .#{name} a"
        if i + 1 < service_names.size
          out.puts ","
        else
          out.puts " {"
        end
      end  
      out.puts "  color: #99CC00;"
      out.puts "  text-decoration: underline;"
      out.puts "}"
    end
  end
end

Then a small adjustment to invoke the css regeneration via a filter in my admin services controller:

class Admin::ServicesController < AdminLayoutController
  include DynamicCss
  
  after_filter :generate_services_nav_links_css, :only => [:create, :update, :destroy]

# remainder of controller

end

Lastly, to ensure that the servicesnav.css file exists by the time one of the public services pages is requested:

# config/initializers/services_nav_css.rb

include DynamicCss

generate_services_nav_links_css

Admittedly this is a specific case, but this example shows that it is relatively straightforward to dynamically generate CSS within a Rails app if required.

Tags: css rails

Comments

“Selected” isn’t a very semantic class name though. I’d rather have a few lines extra CSS. Saves sometimes having to make changes to your view code when you want to make changes to your styles. It also keeps the markup from becoming to presentationy (new word..).

Posted by: Dylan 1 day later

Ah, Charles. Now that I have formatted your comment so that it displays properly, allow me to congratulate you on two counts. Firstly on being the first reader to comment on this blog. And, secondly, for pointing out a simpler solution that should have been blindingly obvious to me. Oh well, the sense of having contributed something worthwhile was fun while it lasted.

Posted by: Keith Pitty about 3 hours later

Surely having the template spit out

<li class="selected">

if current_service == service is a simpler solution all round? About 25 fewer lines of code and one less artifact/server roundtrip in order to load the page.

Posted by: Charles Miller about 1 hour later

Comments are closed for this post.