User Selectable Skins in Rails Applications

rails

One of the rails projects I am working on requires user-selectable ‘skins’ (or themes). This could mean that the HTML depends on a user setting, but for early stage development, we’re going to do the minimal option that could possibly work: when users select a ‘skin’, all they select is a CSS file.

CSS is surprisingly powerful. With display: none; and visibility: hidden, we could hide different parts of the site. We can change colors and sizes of elements as well. Until there is a business case for needing to generate differing HTML based on a user-selectable theme, this will probably work.

What will we need to implement this? We’ll need some set of themes - which will be .css (or, really, .scss) files. We need the application to be aware of the possible skin options, and provide a form element somewhere that allows the user to choose a theme. The app needs to persist their choice to the application database. Then, the application needs to render the appropriate theme on every page view, choosing the appropriate file.

There are two ways we could store a list of themes. We could store them in some configuration file somewhere, for example a .yml file in the config directory, perhaps themes.yml. Or we could check to see which themes are actually available in the themes CSS folder when the application starts.

The latter method has the advantage of making sure that the list of available themes is always consistent with the list of themes displayed. But the former method would allow us to have partial theme files in the themes CSS folder, perhaps ones that are unfinished. I’ll take the easier maintenance trade - let’s load the list of themes based on the files that are in the folder.

We’ll put our skin css files in app/assets/stylesheets/themes. Then, we can load the list of files with:

1
2
3
SKIN_PATH = Rails.root.join('app', 'assets', 'stylesheets', 'themes')
skins = Dir[Rails.root.join('app', 'assets', 'stylesheets', 'themes', '*')]
SKINS = skins.map{|x| x.gsub(SKIN_PATH.to_s, "").gsub("/", "").gsub(".scss", "")}

The first line just gets the absolute file path in a convenient way. The second gets a list of files. The third strips out everything except the file name itself, so that we can display the skin name to the user. (There’s probably a better way to do this somewhere in the Ruby or Rails API.)

We need to make sure that these files are included in Rail’s asset pipeline.

In config/initializers/assets.rb:

1
Rails.application.config.assets.precompile += ['themes/*']

Throw a couple CSS files in the app/assets/stylesheets/themes folder, and make sure that your defauilt app/assets/stylesheets/application.scss file isn’t auto-including the theme files. You can do this by moving your other css to a sub-directory of app/assets/stylesheets/ and then changing require_tree . in application.scss to require_tree ./<subdirectory>.

You’ll need to re-start your development server after adding the initialization file.

Then we can present a selectable list in the appropriate as so (using simple_form):

1
<%= f.input :skin, collection: SKINS, include_blank: false %>

And finally include the file in our layout:

1
2
3
<head>
  <%= stylehset_include_tag @model.skin %>
</head>

Fill in the appropriate details - the above line has to be wrapped in a form, of course, and skin needs to be allowed through Rails 4’s strong_params, and so on.

Happy Coding!


I'm looking for better ways to build software businesses. Find out if I find something.