Real Open

real estate open sourced

A blog by

things I made ----------------
Appraisal Flow
Powered by Obtvse and Rails

Dynamic Select / Dropdown menus with Rails

I'm in the process of building a web application in Ruby on Rails to help me manage my appraisal work flow. Part of what I need to implement is a dropdown menu for selecting a property type and then a property sub-type where the second menu is automatically populated with the correct data on the client-side via javascript. In my case, I will utilize the information gathered from the property type crowdsourcing experiment.

After spending several days spinning my wheels, I finally found a method that works relatively well for creating a dynamic select / dropdown menu / also known as a cascading drop down in Rails. It look me a while to figure this out despite the wealth of RoR posts and stackoverflow questions out there. These types of menus are fairly common in web apps, so I thought it warrants a post for others looking for a solution.

Set up the App

Already have an app set up? Skip to to Generations.

1. rails new appraisals
2. cd appraisals

Generations

You can generate a full scaffold for the type and sub-type or just models depending on your method of adding data. I mention a resource on how to seed your database from a csv file at the end of this post. If you already have an app you're working in, you will likely need to add the type_id's to your primary model. Learn how to do that in the Rails Guides - 2.2 Creating a Standalone Migration. Make sure to add the ID integer columns!

1. rails generate scaffold appraisal name:string prop_type_id:integer prop_sub_type_id:integer
2. rails generate scaffold prop_type name:string
3. rails generate scaffold prop_sub_type name:string prop_type_id:integer
4. rake db:migrate

Routes

If you like, go ahead and delete the index.html from the public folder change your routes in the config/routes.rb file to the following:

Appraisals::Application.routes.draw do
  resources :prop_sub_types
  resources :prop_types
  resources :appraisals
  root :to => 'appraisals#index'
end

Models & Relations

So in our app, we have Appraisals. And each Appraisal has many property sub-types through property types. Now's a good time to review the Rails Guides on Active Record Associations. We need to set up these relations in for our models.

#appraisals/app/models/appraisal.rb
class Appraisal < ActiveRecord::Base
  attr_accessible :name, :prop_sub_type_id, :prop_type_id

  belongs_to :prop_type
  belongs_to :prop_sub_type
end

#appraisals/app/models/prop_type.rb
class PropType < ActiveRecord::Base
  attr_accessible :name

  has_many :prop_sub_types
  has_many :appraisals
end

#appraisals/app/models/prop_type.rb
class PropSubType < ActiveRecord::Base
  attr_accessible :name, :prop_type_id

  belongs_to :prop_type
  has_many :appraisals
end

Controllers

Add the following under the def new and def edit blocks in the appraisals_controller.rb and prop_types.rb controller. Make sure not to delete any of the existing code in the controllers created by the generate scaffold command. You don't need to make any changes to the prop_types.rb controller. The "..." line denotes existing scaffold code.

#appraisals/app/controllers/appraisals_controller.rb
def new
    ...
    @prop_types = PropType.all
    @prop_sub_types = PropSubType.all
    ...
end

def edit
    ...
    @prop_types = PropType.all
    @prop_sub_types = PropSubType.all
    ...
end

#appraisals/app/controllers/prop_sub_types_controller.rb
def new
    ...
    @prop_types = PropType.all
    ...
end

def edit
    ...
    @prop_types = PropType.all
    ...
end

Views

You will have to make several changes to views that will allow you to create a new property type parent , a sub-property type child and then a new Appraisal utilizing collection_select and grouped_collection_select. Slight modifications need to made to the _form partial as well as the index and show pages in views.

Property Types

#appraisals/app/views/appraisals/prop_types/index.html.erb
...
<% @prop_types.each do |prop_type| %>
  <tr>
    <td><%= prop_type.name %></td>
    ...
  </tr>
<% end %>
...

#appraisals/app/views/appraisals/prop_types/show.html.erb
...
<p>
  <b>Name:</b>
  <%= @prop_type.name %>
</p>
...

Property Subtypes

#appraisals/app/views/appraisals/prop_sub_types/_form.html.erb
...
  <div class="field">
    <%= f.label :name %><br />
    <%= f.text_field :name %>
  </div>
  <div class="field">
    <%= f.label :name %><br />
    <%= f.collection_select :prop_type_id, @prop_types, :id, :name %>
  </div>
 ...

#appraisals/app/views/appraisals/prop_sub_types/index.html.erb
...
<% @prop_sub_types.each do |prop_sub_type| %>
  <tr>
    <td><%= prop_sub_type.name %></td>
    <td><%= prop_sub_type.prop_type.name %></td>
    ...
  </tr>
<% end %>
...

#appraisals/app/views/appraisals/prop_sub_types/show.html.erb
...
<p>
  <b>Prop Subtype:</b>
  <%= @prop_sub_type.name %>
</p>

<p>
  <b>Prop Type:</b>
  <%= @prop_sub_type.prop_type.name %>
</p>
...

Appraisals

#appraisals/app/views/appraisals/appraisals/_form.html.erb
...
  <div class="field">
    <%= f.label :name %><br />
    <%= f.text_field :name %>
  </div>
  <div class="field">
    <%= f.collection_select :prop_type_id, PropType.order(:name), :id, :name, :prompt => "--  Select Property Type --" %>
  </div>
  <div class="field">
    <%= f.grouped_collection_select :prop_sub_type_id, PropType.order(:name), :prop_sub_types, :name, :id, :name, :prompt => "-- Select Property Subtype --"  %>
  </div>
  ...

#appraisals/app/views/appraisals/appraisals/index.html.erb
...
<table>
  <tr>
    <th>Name</th>
    <th>Property Type</th>
    <th>Property Subtype</th>
    ...
</tr>

<% @appraisals.each do |appraisal| %>
  <tr>
    <td><%= appraisal.name %></td>
    <td><%= appraisal.prop_type.name %></td>
    <td><%= appraisal.prop_sub_type.name %></td>
    ...
  </tr>
<% end %>
</table>
...

#appraisals/app/views/appraisals/appraisals/show.html.erb
...
<p>
  <b>Name:</b>
  <%= @appraisal.name %>
</p>

<p>
  <b>Property Type:</b>
  <%= @appraisal.prop_type.name %>
</p>

<p>
  <b>Prop Subtype:</b>
  <%= @appraisal.prop_sub_type.name %>
</p>
...

Client-Side CoffeeScript / jQuery

And now for a little jQuery or "Coffee Coffee" as a friend likes to say for some client-side magic. This will filter the second dropdown to just the property subtype children that belong to the property type parent. This code is basically the same as Ryan Bates used in Episode #88 (revised), but I took out the auto-hide for the sub-property type. If you want the second dropdown to be hidden until the first drop is selected, take a look here.

#appraisals/app/assets/javascripts/appraisals.js.coffee
jQuery ->
  prop_sub_types = $('#appraisal_prop_sub_type_id').html()
  $('#appraisal_prop_type_id').change ->
    prop_type = $('#appraisal_prop_type_id :selected').text()
    escaped_prop_type = prop_type.replace(/([ #;&,.+*~\':"!^$[\]()=>|\/@])/g, '\\$1')
    options = $(prop_sub_types).filter("optgroup[label='#{escaped_prop_type}']").html()
    if options
      $('#appraisal_prop_sub_type_id').html(options)
    else
      $('#appraisal_prop_sub_type_id').empty()

And that should get the dynamic dropdown's up and running!

Seed

You can also seed the property database with a csv file. See #88 Dynamic Select Menus (revised) for details. This is a good option if you already have a large data-set.

I'm considering taking the Property Types Open Sourced doc and creating a csv version as well as possibly building a simple "property types" web app / open API for the CRE tech community. Let me know if any of this is of interest to you.

FInal Note

Find any errors? Is there better way to create these types of select menus? Did you find this helpful? Any questions? I'll try to keep this post up-to-date with the current version of Rails. Don't hesitate to contact me with the say hi link to the left for feedback.

  • Tweet
Back to Blog