CSS is missing one quite important feature. It does not include any way to define variables or constants. This can be easily fixed using server-side CSS scripting.
Introduction
When you declare color of the element you have to provide literal value for it. The same goes for font sizes and other attributes. If your site is rich and you have plenty of selectors this means lots of repetitions of given value across CSS file(s).
Every time you want to adjust given color in your theme you have to do search and replace on your CSS file. This certainly is not the DRY way.
There are several solutions to this problem, for example:- Keep color declarations in separate CSS file. This separates color information from layout but you still have to follow search and replace pattern. And sooner or later (I bet on sooner) selectors in layout and color files will go out of sync.
- Use multiple classes for HTML elements. It is possible to provide two (or more) CSS classes in class attribute – one responsible for layout and another for colors. Unfortunatelly this brings unwanted elements of layout to HTML structure. I personally consider it almost as bad as using inline style attribute.
Further Reading
You can find discussion on this topic on Ruby on Rails weblog: Augmenting CSS with variables and more
Luke Redpath provides a simple implementation in his blog: Dynamic CSS in Rails
Discussion about “Shaun Inman’s similar PHP project on Eric Meyer’s blog”: http://meyerweb.com/eric/thoughts/2005/08/31/the-constants-gardener/
Adding Functionality
Luke’s solution will work well in most deployments. Still it would be nice to have implementation that:- Integrates into Rails
- Allows to use Rails caching capabilities
- Uses standard controller to drive the CSS generation (for example to deliver different stylesheets for different browsers depending on User-Agent header).
- Provides access to Rails model (for example to use per-user color theme settings stored in the data base)
Implementation
ActionView::Base allows to create and register new template handlers so let us use this:
Step One
First for the toughest part: creating a template handler. Create a file named rcss_handler.rb with following contnent in lib directory of you application.
require 'erb'
class RcssHandler
include ERB::Util
def initialize(action_view)
@action_view = action_view
end
def render(template, local_assigns)
@action_view.controller.headers["Content-Type"] = 'text/css'
b = binding
local_assigns.stringify_keys!
local_assigns.each { |key, value| eval "#{key} = local_assigns[\"#{key}\"]", b }
ERB.new(template, nil, '-').result(b)
end
end
ActionView::Base::register_template_handler 'rcss', RcssHandler
It ensures proper content type of sent data and applies all variables defined in controller to the template. The last line introduces type handler to ActionView.
Code for adding variables to template scope is taken directly from ActionView::Base implementation.
Step Two
Making the handler visible to your application. Edit your config/environment.rb file and add following line somewhere (preferably at the end):
require 'rcss_handler'
Step Three
Now the handler is ready to use and will be applied to all templates with type ‘rcss’. To feed it with one we need to create proper controller.
class RcssController < ApplicationController
def render_rcss
if params[:rcss] =~ /\.css$/
template = $`
else
template = params[:rcss]
end
render :action => template, :type => 'rcss', :layout => false
end
end
RegEx is not necessary, it simply makes sure so that we support CSS URLs with and without .css extention.
:type => 'rcss'makes sure that Rcss handler will be used:layout => falseskips layout rendering, otherwise parsed CSS would end up wrapped in HTML from default layout
Step Four
Now it is time to add nice routing: open config/routes.rb and add following lines before the most generic mapping:
map.connect 'rcss/:rcss', :controller => 'rcss', :action => 'render_rcss'
map.rcss 'rcss/:rcss', :controller => 'rcss', :action => 'render_rcss'
Step Five
Create your first RCSS file. Create file in app/views/rcss/default.rcss with following contents:
<%
highlight_backgroud = "gray"
highlight_color = "red"
%>
* {
background-color: <%= highlight_backgroud %>;
color: <%= highlight_color %>;
}
Step Six
Use rcss file in your layout using following line in <head/>:
<link href="/rcss/default.css" media="screen" rel="Stylesheet" type="text/css" />
Remember to restart the server before testing – only then your new configuration will be taken into account.
Enjoy your new, completly unreadible application layout.
Room For Improvement
- Provide template handler for CSS Server-Side Constants (needs customized parser)
- Ensure proper caching (add caching headers)
- Provide helper similar to
stylesheet_link_tag - ...
License
All code in this post is licensed under MIT license:
Copyright (c) 2005 Lukasz "Bragi Ragnarson" Piestrzeniewicz
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.