Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Themes in Rails app

I would like to make a Rails app that lets users enter data and then allow them to change the page's theme. This way their data could be styled differently based on the theme they choose. How would I go about doing this?

  1. Change stylesheet?
  2. Two separate views with different classes/elements?
  3. Dynamically just change classes/ids/selectors?
  4. ?

Thanks

like image 538
AdamT Avatar asked Aug 13 '12 05:08

AdamT


Video Answer


3 Answers

The easiest way to theme a site is to simply link to a different stylesheet. You can do this dynamically using something like:

# in app/views/layouts/application.html.erb
<%= stylesheet_link_tag :application %>
<%= stylesheet_link_tag #{current_theme} %>

# in app/helpers/application_helper
def current_theme
  # You'll have to implement the logic for a user choosing a theme
  # and how to record that in the model.
  # Also, come up with a better name for your default theme, like 'twentyeleven' ;)
  current_user.theme || 'default'
end

Then you can have a couple manifests for themes. For example, your assets directory can look something like this:

  • app/assets/stylesheets
    • application.css
    • buttons.css
    • theme1/
      • index.css
      • buttons.css
    • theme2/
      • index.css
      • buttons.css

This will get you started with pure css theming. At some point you'll probably want to also them javascript and html layouts. When you start finding the need to do something like this in your html:

<% if current_theme == 'theme1' %>
  <li>...
<% elsif current_theme == 'theme2' %>
  <b>...
<% end %>

then it's time to implement a more robust theming framework:

  • namespace your html templates by theme (e.g. app/views/themes/theme1/users/index.html.erb) and render the themed version instead of the default
  • namespace just the partials by template (e.g. app/views/themes/theme1/users/_form.html.erb) and add a helper method like render_themed_partial
  • similar to the above approaches, but when the themes get very large, you should consider putting them into their own gems as rails engines

Note: This is all for static themes. For dynamic themes (e.g. where an admin can login and edit the stylesheets or html), you'll have to store theming information in the database. Depending on your architecture, you may be able to provide a set of static themes, and then another theme that dynamically grabs styling data from the database. At that point, you're developing a CMS, however, so it's outside the scope of this answer :)

like image 155
Ben Taitelbaum Avatar answered Sep 21 '22 07:09

Ben Taitelbaum


If we create different style-sheets for each theme and while making small change, then we need to make the same change in all the style-sheets. It will be really a head-ache. Alternate way is to use SASS concepts (mixins).

Add in your Gemfile

gem 'sass-rails'

then

bundle install

Now you need to have your css styles in one SCSS file. basic_styles.scss

$font_color: #565656;
$font-size: 13px;
$success-color: #088A08;
$error-color: #B40404;
@mixin basic_styles($dark_color,$light_color,$bullet_image) 
{
.footer
  {
    background-color: rgba($dark_color,0.9);
    color: $light_color;
    text-align: center;
    position: fixed;
    bottom:0;
    left:0;
    width: 100%;
    height: 15px;
    border-top: 1px solid lighten($dark_color, 9%);     
    padding-left: 10px;
    padding-right: 10px;       
    font-size: $font_size - 2; 
  }
  h3,hr,a,input
  {
    color: $dark_color;
  }
  h3
  {
    margin-top: 2px;
    margin-bottom: 2px;
  }
  hr {
    border-color: lighten($dark_color, 30%) -moz-use-text-color #FFFFFF;
    border-left: 0 none;
    border-right: 0 none;
    border-style: solid none;
    border-width: 1px 0;
  }
  .btn 
  {
    background-color: $dark_color;
    border-color: darken($dark_color, 15%);    
    border-radius: 4px 4px 4px 4px;
    border-style: solid;
    border-width: 1px;
    color: #FFFFFF;
    cursor: pointer;
    display: inline-block;
    line-height: 18px;
    padding: 3px 10px 3px 10px;
    text-shadow: 0 1px 1px rgba(0, 0, 0, 0.25);
    vertical-align: middle;
  }
  .btn:hover
  {
    text-shadow: 0 1px 1px rgba(0, 0, 0, 0.75);
    -moz-box-shadow: 0px 0px 2px 1px lighten($dark_color, 10%);
    -webkit-box-shadow: 0px 0px 2px 1px lighten($dark_color, 10%);
    box-shadow: 0px 0px 2px 1px lighten($dark_color, 10%);
  }
  .success
  {
    color: $success-color;
  }
  .error
  {
    color: $error-color;
  }
}

Then you can create any number of themes. For Example Theme_Blue.scss

@import "basic_styles";
$dark_color: #08c;
$light_color: #FFFFFF;
$bullet_image: 'bullet_blue.png';
@include basic_styles($dark_color,$light_color,$bullet_image);

Now in your html

<%= stylesheet_link_tag "Theme_Blue" %>

will use the all the css classes specified in basic_styles.scss with blue colors.

You can add any number of Theme files like Theme_Blue.scss. And change to

<%= stylesheet_link_tag current_user.theme %>

In this way, you need to modify only the basic_styles.scss for any modifications.

like image 44
Vimalkumar Kalimuthu Avatar answered Sep 23 '22 07:09

Vimalkumar Kalimuthu


I managed to extract the essence from Chamnap's answer (which didn't work for some reason - maybe the version of Rails?).

class ApplicationController < ActionController::Base
  layout :layout_selector

  def layout_selector
    # puts "*** layout_selector #{session.to_json}"
    name = ['bootstrap', 'mytheme'][session[:theme].to_i] 
    # puts "*** loading theme #{name}"
    prepend_view_path "app/themes/#{name}/views"
    name
  end

You can read up on it here:

  • http://guides.rubyonrails.org/layouts_and_rendering.html#finding-layouts
  • http://apidock.com/rails/AbstractController/Layouts/ClassMethods/layout

You will probably have to add to the asset path and pre-compile list (or else use the gem without using theme method).

  Dir.glob("#{Rails.root}/app/themes/*/assets/*").each do |dir|
    config.assets.paths << dir
  end

  config.assets.precompile += [ Proc.new { |path, fn| fn =~ /app\/themes/ && !%w(.js .css).include?(File.extname(path)) } ]
  config.assets.precompile += Dir["app/themes/*"].map { |path| "#{path.split('/').last}/all.js" }
  config.assets.precompile += Dir["app/themes/*"].map { |path| "#{path.split('/').last}/all.css" }

Remember to have JS and images in subdirectories with the theme name. They might be separate on the server, but to the browser and cache, /images/logo.png looks the same to both themes. So you have to use /images/theme1/logo.png and /images/theme2/logo.png.

like image 30
Chloe Avatar answered Sep 24 '22 07:09

Chloe