Click here to Skip to main content
15,908,674 members
Articles / Programming Languages / Ruby

A PropertyGrid implemented in Ruby on Rails

Rate me:
Please Sign up or sign in to vote.
4.79/5 (4 votes)
22 Nov 2013CPOL6 min read 19.7K   147   3  
Using JQuery UI and minimal Javascript to create a dynamic property grid editor that can be initialized in a fluid programming style or with a minimal DSL.

Image 1

Get The Source From GitHub

git clone 

The code for this control can now be installed as a gem:

gem install property_grid 

and can be downloaded from here:

git clone


I needed a general purpose property grid editor that supported some fancy things like date/time pickers, color pickers, etc., based on record fields known only at runtime (this is ultimately a part of my next installment of the "Spider UI" article series.)  There's a snazzy Javascript-based property grid here, but I wanted something that was minimally Javascript and more Ruby-on-Rails'ish.  I also wanted a server-side control that could interface well with record field types and that would dynamically generate the grid based on schema information like table fields.

I have put together is a set of classes to facilitate building the content of a property grid control on the server-side.  You will note that I opted for actual classes and a "fluid" programming style, but if you don't like the way the actual implementation looks using a "fluid" technique, I have also put together a very minimal internal Domain Specific Language (DSL) that you can use instead -- basically just method calls that hide (using static data) the internal management of building the property grid instance.

As in my previous articles, I will be using Sass and Slim scripting for the CSS and HTML markup.

Supporting Classes

There are several supporting classes:

  • PropertyGrid - the container for the groups and group properties
  • Group - a group of properties
  • GroupProperty - a property within a group

Class PropertyGrid

# A PropertyGrid container
# A property grid consists of property groups.
class PropertyGrid
  attr_accessor :groups

  def initialize
    @groups = []

  # Give a group name, creates a group.
  def add_group(name)
    group = = name
    @groups << group
    yield(group)         # yields to block creating group properties
    self                 # returns the PropertyGrid instance

There are two important points to this class:

  1. Because add_group executes yield(group), the caller can provide a block for adding group properties. 
  2. Because add_group returns self, the caller can continue, in fluid programming style, to add more groups.

Thus, we can write code like this:

@property_grid =
  add_group('Text Input') do |group|
    # add group properties here.
  end.  #<----  note this syntax
  add_group('Date and Time Pickers') do |group|
    # add group properties here.

Notice the "dot": end. - because add_group returns self after the yield, we can use a fluid programming style to continue adding groups.

Class Group

# Defines a PropertyGrid group
# A group has a name and a collection of properties.
class PropertyGroup
  attr_accessor :name
  attr_accessor :properties

  def initialize
    @name = nil
    @properties = []

  def add_property(var, name, property_type = :string, collection = nil)
    group_property =, name, property_type, collection)
    @properties << group_property

A group has a name and manages a collection of properties.  The add_property class returns self, so again we can use a fluid notation:

group.add_property(:prop_c, 'Date', :date).
  add_property(:prop_d, 'Time', :time).
  add_property(:prop_e, 'Date/Time', :datetime)

Notice the "dot" after each call to add_property, allowing us to call add_property again, operating on the same group instance.

Nothing about this is stopping us from using more idiomatic Ruby syntax, for example: <<, 'Date', :date) <<, 'Time', :time) <<, "Date/Time", :datetime)

Class GroupProperty

This class is the container for the actual property:

include PropertyGridHelpers

class GroupProperty
  attr_accessor :property_var
  attr_accessor :property_name
  attr_accessor :property_type
  attr_accessor :property_collection

  # some of these use jquery:
  def initialize(var, name, property_type, collection = nil)
    @property_var = var
    @property_name = name
    @property_type = property_type
    @property_collection = collection

  def get_input_control
    form_type = get_property_type_map[@property_type]
    raise "Property '#{@property_type}' is not mapped to an input control" if form_type.nil?
    erb = get_erb(form_type)


I will discuss what get_erb does later.

Note that three fields are required:

  1. The symbolic name of the model's property
  2. The display text of the property
  3. The property type

Optionally, a collection can be passed in, which supports dropdown controls.  The collection can either be a simple array:

['Apples', 'Oranges', 'Pears']

or a "record", implementing id and name attributes, for example:

# A demo of using id and name in a combo box
class ARecord
  attr_accessor :id
  attr_accessor :name

  def initialize(id, name)
    @id = id;
    @name = name

@records =
[, 'California'),, 'New York'),, 'Rhode Island'),

which is suitable for collections of ActiveRecord objects.

Class ControlType

This class is a container for the information necessary to render a web control:

class ControlType
  attr_accessor :type_name
  attr_accessor :class_name

  def initialize(type_name, class_name = nil)
    @type_name = type_name
    @class_name = class_name

This is very basic - it's just the type name and an optional class name.  At the moment, the class name is used just for jQuery controls.

Defining Property Types

Property types are defined in property_grid_helpers.rb - this is a simply function that returns an array of hashes of type => ControlType.

def get_property_type_map
    datetime:'text_field', 'jq_dateTimePicker'),
    time:'text_field', 'jq_timePicker'),
    color:'text_field', 'jq_colorPicker'),

It is here that you would extend or change the specification for how types map to web queries.  Obviously you're not limited to using jQuery controls.

What Would a DSL Implementation Look Like?

Let's see what it would look like if I wrote the property grid container objects as a DSL.  If you're interested, there's a great tutorial on writing internal DSL's in Ruby here, and what I'm doing should look very similar.  Basically, DSL's use a builder pattern, and if you're interested in design patterns in Ruby, here's a good tutorial.

What we want is to be able to declare a property grid instance as if it were part of the Ruby language.  So I'll start with:

@property_grid = new_property_grid
group 'Text Input'
group_property 'Text', :prop_a
group_property 'Password', :prop_b, :password
group 'Date and Time Pickers'
group_property 'Date', :prop_c, :date
group_property 'Time', :prop_d, :date
group_property 'Date/Time', :prop_e, :datetime
group 'State'
group_property 'Boolean', :prop_f, :boolean
group 'Miscellaneous'
group_property 'Color', :prop_g, :color
group 'Lists'
group_property 'Basic List', :prop_h, :list, ['Apples', 'Oranges', 'Pears']
group_property 'ID - Name List', :prop_i, :db_list, @records

The implementation consists of three methods:

  1. new_property_grid 
  2. group 
  3. property

that are essentially factory patterns for building an instance of the property groups and their properties.  The implementation is in a module and leverages our underlying classes:

module PropertyGridDsl
  def new_property_grid(name = nil)
    @__property_grid =


  def group(name)
    group = = name
    @__property_grid.groups << group


  def group_property(name, var, type = :string, collection = nil)
    group_property =, name, type, collection) << group_property


This implementation takes advantage of the variable @__property_grid which maintains the current instance being applied in the DSL script.  We don't use a singleton pattern because we want to allow for multiple instances of property grids on the same web page.

The advantages are fairly obvious - the resulting script to generate the property grid is compact and readable.  The above DSL is simple - it's effectively nothing more than helper methods that wrap the details of working with the underlying classes. 

As Martin Fowler writes here, while an internal DSL can often increase "syntactic noise", a well written DSL should actually decrease "syntactic noise", as this simple DSL does.  For example, compare the DSL:

@property_grid = new_property_grid
group 'Text Input'
group_property 'Text', :prop_a 

with a non-DSL implementation: 

@property_grid =
  add_group('Text Input') do |group|
    group.add_property(:prop_a, 'Text').
    add_property(:prop_b, 'Password', :password)

Certainly working with the class implementation, even in its "fluid" form, is noisier than the DSL!

Putting It Together

You will need a view, a controller, and a model to put this all together.

The View 

The basic view is straight-forward.  Given the model, we instantiate a list control where each list item is itself a table with two columns and one row:

=fields_for @property_grid_record do |f|
      - @property_grid.groups.each_with_index do |group, index|
        li.expanded class="expandableGroup#{index}" =
          div class="property_group#{index}"
                th Property
                th Value
              - do |prop|
                    = prop.property_name
                    - # must be processed here so that ERB has the context (the 'self') of the HTML pre-processor.
                    = render inline:

  = javascript_tag @javascript
    $(".jq_dateTimePicker").datetimepicker({dateFormat: 'mm/dd/yy', timeFormat: 'hh:mm tt'});
    $(".jq_timePicker").timepicker({timeFormat: "hh:mm tt"});

I'm not going to bother showing the CSS that drives the visual presentation of this structure. 


Note that there are two javascript sections.  One is coded directly in the form to support the jQuery dateTimePicker, timePicker, and the colorPicker controls.

The other javascript is programmatically generated because it controls whether the property group is collapsed or expanded, which requires unique handlers for each property group.  Since this is known only at runtime, the javascript is generated by this function (in property_grid_helpers.rb):

def get_javascript_for_group(index)
  js = %Q|
      var hidden = $(".property_group[idx]").is(":hidden"); // get the value BEFORE making the slideToggle call.

      // At this point, $(".property_group0").is(":hidden");

      if (!hidden) // Remember, this is state that the div WAS in.
  |.gsub('[idx]', index.to_s)


The ERB 

Note this line from above:

= render inline:

This takes ERB code that has been generated programmatically as well, as we need a control specific to the property type.  This is generated by the function get_erb which we saw earlier.

# Returns the erb for a given form type. This code handles the construction of the web control that will display
# the content of a property in the property grid.
# The web page must utilize a field_for ... |f| for this construction to work.
def get_erb(form_type)
  erb = "<%= f.#{form_type.type_name} :#{@property_var}"
  erb << ", class: '#{form_type.class_name}'" if form_type.class_name.present?
  erb << ", #{@property_collection}" if @property_collection.present? && @property_type == :list
  erb << ", options_from_collection_for_select(f.object.records, :id, :name, f.object.#{@property_var})" if @property_collection.present? && @property_type == :db_list
  erb << "%>"


The Model

We need a model for our property values.  In the demo, the model is in property_grid_record.rb:

class PropertyGridRecord < NonPersistedActiveRecord
  attr_accessor :prop_a
  attr_accessor :prop_b
  attr_accessor :prop_c
  attr_accessor :prop_d
  attr_accessor :prop_e
  attr_accessor :prop_f
  attr_accessor :prop_g
  attr_accessor :prop_h
  attr_accessor :prop_i
  attr_accessor :records

  def initialize
    @records =
      [, 'California'),, 'New York'),, 'Rhode Island'),

    @prop_a = 'Hello World'
    @prop_b = 'Password!'
    @prop_c = '08/19/1962'
    @prop_d = '12:32 pm'
    @prop_e = '08/19/1962 12:32 pm'
    @prop_f = true
    @prop_g = '#ff0000'
    @prop_h = 'Pears'
    @prop_i = 2

All this does is initialize our test data.

The Controller

The controller puts it all together, instantiating the model, specifying the property grid properties and types, and acquiring the programmatically generated javascript:

include PropertyGridDsl
include PropertyGridHelpers

class DemoPageController < ApplicationController
  def index


  def initialize_attributes
    @property_grid_record =
    @property_grid = define_property_grid
    @javascript = generate_javascript_for_property_groups(@property_grid)

  def define_property_grid
    grid = new_property_grid
    group 'Text Input'
    group_property 'Text', :prop_a
    group_property 'Password', :prop_b, :password
    group 'Date and Time Pickers'
    group_property 'Date', :prop_c, :date
    group_property 'Time', :prop_d, :date
    group_property 'Date/Time', :prop_e, :datetime
    group 'State'
    group_property 'Boolean', :prop_f, :boolean
    group 'Miscellaneous'
    group_property 'Color', :prop_g, :color
    group 'Lists'
    group_property 'Basic List', :prop_h, :list, ['Apples', 'Oranges', 'Pears']
    group_property 'ID - Name List', :prop_i, :db_list, @property_grid_record.records


There is also the supporting function (in property_grid_helpers.rb):

def generate_javascript_for_property_groups(grid)
  javascript = ''

  grid.groups.each_with_index do |grp, index|
    javascript << get_javascript_for_group(index)


And voila:

Image 2


Something like this should be easily ported to C# / ASP.NET as well, and I'd be interested to hear from anyone who does so.  Otherwise, enjoy and tell me how you've enhanced the concept. 


This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Written By
Architect Interacx
United States United States
Home Page:

All my life I have been passionate about architecture / software design, as this is the cornerstone to a maintainable and extensible application. As such, I have enjoyed exploring some crazy ideas and discovering that they are not so crazy after all. I also love writing about my ideas and seeing the community response. As a consultant, I've enjoyed working in a wide range of industries such as aerospace, boatyard management, remote sensing, emergency services / data management, and casino operations. I've done a variety of pro-bono work non-profit organizations related to nature conservancy, drug recovery and women's health.

Comments and Discussions

-- There are no messages in this forum --