Using the color-tools gem for theme support

When offering branding options to your customers/users, neither a cusomizable CSS stylesheet nor Liquid is unobtrusive from an integration or usability perspective.

Fall back to your design roots

A standard 3 color palette composed of a

  • dominant color
  • interactive color ( complement to your dominant color scheme )
  • tonic color ( highlights and accents )

requires exactly 3 inputs from your customer/user from which a gradient based scheme can be generated server side for each of the above colors.

Requirements

Austin Ziegler's color-tools gem ( bundled as a dependency when installing pdf-writer ) already implements most of the required functionality.

Install with

sudo gem install color-tools

Parsing hexadecimal (HTML) color values

module Format
  EMAIL = /^[_a-z0-9\+\.\-]+\@[_a-z0-9\-]+\.[_a-z0-9\.\-]+$/i
  HTTP_URI = /^https?:\/\/\S+$/
  HEX_COLOR = /(#)([a-fA-F0-9][a-fA-F0-9])([a-fA-F0-9][a-fA-F0-9])([a-fA-F0-9][a-fA-F0-9])/
end

Format::HEX_COLOR should match the 6 and 7 digit hex color variations eg. #CCCCCC and CCCCCC

The Theme model

class Theme < ActiveRecord::Base
  set_table_name 'themes'

  belongs_to :account
  validates_presence_of :title, :dominant_color, :interactive_color, :tonic_color
  validates_format_of :dominant_color, :with => Format::HEX_COLOR, :message => ' is not a valid HEX color.'.t
  validates_format_of :interactive_color, :with => Format::HEX_COLOR , :message => ' is not a valid HEX color.'.t
  validates_format_of :tonic_color, :with => Format::HEX_COLOR , :message => ' is not a valid HEX color.'.t
  validates_uniqueness_of :title, :scope => 'account_id'
  validates_presence_of :active, :in => 0..1

  serialize :dominant, Array
  serialize :interactive, Array
  serialize :tonic, Array

  delegate :to_s, :to => :title

  class << self 

    def suggested_colors
      Color::RGB::constants.reject{|c| %w(PDF_FORMAT_STR Metallic).include? c }.collect!{|c| eval("Color::RGB::#{c}").html}   
    end

    def color_to_palette( color )
      Color::Palette::MonoContrast.new( Color::RGB.from_html( color ) ).background.to_a.collect!{|c| [c.first, c.last.html] }
    end

  end

  def to_param; "#{self.id}-#{self.title.to_url}" end

  def generate_color_scheme!
    %w(dominant interactive tonic).each do |c|
      self.send("#{c}=".to_sym,  Theme.color_to_palette( self.send("#{c}_color") ) )
    end
  end

  def active?() self.active == 1 end

  def activate!
    return if self.active?
    Theme.transaction do
      self.account.themes.update_all("themes.active = '0'")
      self.update_attribute(:active, '1')
    end
  end

  def before_destroy; !self.active? end 

end

Theme observer

class ThemeObserver < ActiveRecord::Observer

  def before_validation( theme )
    theme.active = 0 unless theme.attribute_present?(:active)
  end

  def before_save( theme ) theme.generate_color_scheme! end

end

Console examples

Shamelessly rip these common colors from color-tools.

>> Theme.suggested_colors
=> ["#cd853f", "#f8f8ff", "#333333", "#ffffe0", "#dc143c", "#a0522d", "#f0e68c", "#e6e6e6", "#48d1cc", "#ff8c00", "#696969", "#ee82ee", "#ffa07a", "#a52a2a", "#da70d6", "#008000", "#808080", "#800000", "#b8860b", "#ff0000", "#708090", "#add8e6", "#f0f8ff", "#ffe4b5", "#8fbc8f", "#9acd32", "#778899", "#d2691e", "#db7093", "#fffaf0", "#ff69b4", "#b3b3b3", "#9370db", "#8b008b", "#f4a460", "#d3d3d3", "#f5f5dc", "#6b8e23", "#9400d3", "#008080", "#4d4d4d", "#00ff00", "#00ffff", "#ffc0cb", "#ffd700", "#c71585", "#9932cc", "#c0c0c0", "#0000ff", "#e6e6fa", "#ffa07a", "#deb887", "#eee8aa", "#1e90ff", "#d02090", "#000000", "#66cdaa", "#a9a9a9", "#bc8f8f", "#adff2f", "#808080", "#ffdead", "#483d8b", "#fffafa", "#f08080", "#faebd7", "#ff7f50", "#ffefd5", "#228b22", "#1a1a1a", "#778899", "#3cb371", "#556b2f", "#2e8b57", "#cd5c5c", "#cccccc", "#6b8e23", "#ff1493", "#d8bfd8", "#90ee90", "#ffe4c4", "#dda0dd", "#daa520", "#4d4d4d", "#32cd32", "#00008b", "#8b0000", "#87ceeb", "#fff0f5", "#191970", "#eee8aa", "#d19275", "#f5deb3", "#20b2aa", "#deb887", "#4169e1", "#808080", "#999999", "#66cdaa", "#006400", "#00ff7f", "#e0ffff", "#00ffff", "#000080", "#2f4f4f", "#ff00ff", "#1a1a1a", "#b0c4de", "#6495ed", "#ffdab9", "#556b2f", "#7b68ee", "#fff5ee", "#4b0082", "#cccccc", "#00bfbf", "#ffebcd", "#d3d3d3", "#ffa500", "#ff6347", "#daa520", "#008b8b", "#faf0e6", "#b0e0e6", "#666666", "#e9967a", "#7cfc00", "#f5fffa", "#6a5acd", "#b22222", "#5f9ea0", "#87cefa", "#98fb98", "#f5f5f5", "#f0fff0", "#a9a9a9", "#0000cd", "#8b4513", "#999999", "#fafad2", "#2f4f4f", "#7fffd4", "#fdf5e6", "#4682b4", "#dcdcdc", "#fff8dc", "#b0c4de", "#ffdab9", "#333333", "#fffff0", "#ff8c00", "#00fa9a", "#fff5ee", "#e6e6e6", "#ffb6c1", "#696969", "#8a2be2", "#ff4500", "#40e0d0", "#ff00ff", "#808080", "#b8860b", "#800080", "#ffffff", "#666666", "#fffacd", "#e9967a", "#ffe4e1", "#708090", "#7fff00", "#8470ff", "#b22222", "#afeeee", "#ffff00", "#ba55d3", "#f0fff0", "#bdb76b", "#fa8072", "#b3b3b3", "#f0ffff", "#fafad2", "#00ced1", "#808000", "#d2b48c"]

Visualizing generated palettes from a saved model.

>> t = Theme.find(1)
>> t.dominant_color
=> "#BBCC44"
>> t.dominant      
=> [[5, "#ecf7f8"], [0, "#44aabb"], [-2, "#33808c"], [1, "#60b7c5"], [-1, "#3a919f"], [2, "#73bfcc"], [-5, "#071113"], [3, "#a2d4dd"], [-4, "#112b2f"], [4, "#d0eaee"], [-3, "#22555e"]]
>> t.interactive_color
=> "#44AABB"
>> t.interactive
=> [[5, "#ecf7f8"], [0, "#44aabb"], [-2, "#33808c"], [1, "#60b7c5"], [-1, "#3a919f"], [2, "#73bfcc"], [-5, "#071113"], [3, "#a2d4dd"], [-4, "#112b2f"], [4, "#d0eaee"], [-3, "#22555e"]]
>> t.tonic_color
=> "#CC6644"
>> t.tonic      
=> [[5, "#faf0ec"], [0, "#cc6644"], [-2, "#994d33"], [1, "#d47d60"], [-1, "#ad573a"], [2, "#d98c73"], [-5, "#140a07"], [3, "#e6b3a2"], [-4, "#331a11"], [4, "#f2d9d0"], [-3, "#663322"]]

Each color palette is an Array, with 11 items.

  • indexes -1 to -5 : 5 darker shades of the original color
  • index 0 : the original color
  • index 1 to 5 : lighter shades of the orginal color

Injecting to the view

There's two easy ways to reflect this in your view ...

  • Inline style declaration, overriding color values defined in external stylesheets
  • Use a seperate CSS controller, set content type to text/css and reference in your document head like any other external stylesheets (and the purists are happy too)

About this entry