# Dynamic drop downs for product search

**URL:** <https://www.ruby-forum.com/t/dynamic-drop-downs-for-product-search/182453>\
**Category:** Rails\
**Created:** [January 28, 2010, 10:44am UTC](https://www.ruby-forum.com/t/dynamic-drop-downs-for-product-search/182453 "2010-01-28T10:44:03Z")\
**Posts on this page:** 3\
**Page:** 1

<div class="post-metadata">

**Author:** ![brianp](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/b/94ad74/32.png) [@brianp](https://www.ruby-forum.com/u/brianp)\
**Post date:** [January 28, 2010, 10:44am UTC](https://www.ruby-forum.com/t/dynamic-drop-downs-for-product-search/182453/1 "2010-01-28T10:44:03Z")

</div>

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 ?

---

<div class="post-metadata">

**Author:** ![brianp](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/b/94ad74/32.png) [@brianp](https://www.ruby-forum.com/u/brianp)\
**Post date:** [January 28, 2010, 8:10pm UTC](https://www.ruby-forum.com/t/dynamic-drop-downs-for-product-search/182453/2 "2010-01-28T20:10:03Z")

</div>

> Any suggestions or good read ups ?  
> One place to start is with the Railscast on dynamic select menus:  
> [#88 Dynamic Select Menus - RailsCasts](http://railscasts.com/episodes/88-dynamic-select-menus)

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

Eric

---

<div class="post-metadata">

**Author:** ![brianp](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/b/94ad74/32.png) [@brianp](https://www.ruby-forum.com/u/brianp)\
**Post date:** [February 9, 2010, 10:17am UTC](https://www.ruby-forum.com/t/dynamic-drop-downs-for-product-search/182453/3 "2010-02-09T10:17:10Z")

</div>

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
