Dynamic drop downs for product search

Hey Everyone,

I’m about to embark on having a search function for the products on my
app via dynamic dropdowns. A dropdown for make would only leave
AVAILABLE models of that make. selecting year would only leave
available makes from that year etc etc.

Any suggestions or good read ups ?

Any suggestions or good read ups ?
One place to start is with the Railscast on dynamic select menus:
#88 Dynamic Select Menus - RailsCasts

It is a bit old, (I believe it uses Prototype) but it is a good
starting point.

Eric

So I have implemented my own version of dynamic drop down menus. (This
is my first rails project nearing an end. Feel free to point out
better methods, criticism taken construction-ally)

So one thing to keep in mind with the drop downs are they don’t have
to be used in a linear fashion. The three options are year, make,
model. Model is by default empty due to how many potential models they
have in stock. (Oh yeah we only display in-stock vehicles). So you
could start the search by either selecting Year or Make. If you Select
a particular make, Models of that make will become available. As well
Years will be refreshed to only display Years with that particular
Make. Then you could further refine the options. To a particular Year.
This will adjust the Models dropdown again to only show Models of that
particular Year and Make.

You may step backward with any of the dropdown by re-selecting the
prompt option.

We’re dealing with a Vehicle Model. the attributes explain themselves.
I have a bad tendency of using random variable names which I tend to
clean up later. This is pre-clean up so if something looks weird it
probably is lol.

To start,

In my vehicle_controller I use a before filter:
before_filter :vehicle_select, :only =>
[:index, :preauctionspecials, :show]

and as a private method in application_controller:
def vehicle_select
@availableYears = Vehicle.find(:all, :select => “DISTINCT
year”, :order => “year DESC”)
@availableMakes = Vehicle.find(:all, :select => “DISTINCT
make”, :order => “make DESC”)
end

These are the default lists to fill the drop down menus. Which we will
use in the application.html erb layout file:

  • <%= render :partial => 'vehicles/ makeSelect', :locals => { :list => @availableYears, :box => :year, :selected => nil } %>
  • <%= render :partial => 'vehicles/ makeSelect', :locals => { :list => @availableMakes, :box => :make, :selected => nil } %>
  • <%= render :partial => 'vehicles/ makeSelect', :locals => { :list => [], :box => :model, :selected => nil } %>

Which utilizes the following partial:
Select <%= box %>: <%= collection_select(:Vehicle, box, list, box,
box,
{:prompt => “Search by #{box}”, :selected =>
selected},
{:onchange => "#{remote_function(:url => {:action
=> “update_dropdowns”},
:with =>
"‘current_id=’+id+

‘&current_value=’+value+

‘&fields=’+

‘year=’+$(Vehicle_year).value+’,’+

‘make=’+$(Vehicle_make).value+’,’+

‘model=’+$(Vehicle_model).value")}"}) %>

Defining :box allows me to both use the single parameter to fill the
prompt, text outside the options as well as the select name
(Vehicle[year],Vehicle[make] or Vehicle[model])

Each dropdown then uses the remote_function ajax helper. It passes the
current contents of all the boxes as well as defines which box the
change is coming from. I need to know all this information every time
because of the non-linear ability to change the boxes. We must see
which boxes have already been changed to make the current
values :selected option as well as refine all options in every field.

At this point when an option is changed we goto out
vehicle_controller:

def update_dropdowns

toSend = [] # creates an empty array
cond = {} # creates an empty hash

if !params.blank?
  fields = params[:fields].split(',').collect{ |s|

s.split(’=’).collect }

this will accept all params, find the fields passed from the form

and separate the content. All the fields and values are passed in a
single parameter to avoid parsing every paramter passed. Instead we
just check one parameter with every value. Otherwise the
authenticity_token, action, controller parameters were all getting
parsed as well.

After reading all the field values appropriately generate required

parameters based off the non-null values
fields.each do |param|
if (!param[1].blank?)
cond.store(param[0], param[1])
end
end
end

if !cond.empty?
  fields.each do |param|
    @objs = Vehicle.find(:all,
                          :conditions => cond,
                          :select => "DISTINCT #{param[0]}",
                          :order => "#{param[0]} DESC"
                          )

    toSend << [param[0], @objs, param[1]] #Save the results of the

query as well as the dropdown it came from
end
else
#if no values are selected (all prompts are selected again by the
user) reset all fields
toSend << [‘year’, Vehicle.find(:all, :select => “DISTINCT
year”, :order => “year DESC”), nil]
toSend << [‘make’, Vehicle.find(:all, :select => “DISTINCT
make”, :order => “make DESC”), nil]
toSend << [‘model’, [], nil]

end

#once we have a complete array of the new contents for the dropdowns
pass the array to the page display function:
change_selects(toSend)

end

Due to some values being numbers (year and price_sticker, currently
removed) A check has to occur. If the Year string is not turned into
and integer The dropdown_select will not recognize it as the :selected
item. If the string comes from a numeric field we change it to an
integer before passing it back to the partial.

protected

def change_selects(selectsToChange)
render :update do |page|
selectsToChange.each do |item|
page.replace_html item[0].to_s+“Select”, :partial => “vehicles/
makeSelect”, :locals => { :list => item[1], :box => item[0], :selected
=> item[0] == ‘year’ || item[0] == ‘price_sticker’ ? item[2].to_i :
item[2] }
end
end
end

Well that is it. It’s probably the first non-standard functionality
I’ve really written myself with rails so I understand if it needs some
work.

I’d be more then happy to hear suggestions on improving my rails
abilities,

thanks for reading,

brianp