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:
<div class="box" id="searchBox">
<form id="vehicleSearch" action="/vehicles">
<ul>
<li id="yearSelect"><%= render :partial =>
''vehicles/
makeSelect'', :locals => { :list => @availableYears, :box
=> :year, :selected => nil } %></li>
<li id="makeSelect"><%= render :partial =>
''vehicles/
makeSelect'', :locals => { :list => @availableMakes, :box
=> :make, :selected => nil } %></li>
<li id="modelSelect"><%= render :partial =>
''vehicles/
makeSelect'', :locals => { :list => [], :box => :model,
:selected =>
nil } %></li>
<li><input type="submit" value="Search"
/></li>
</ul>
</form>
</div>
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+
''¤t_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
--
You received this message because you are subscribed to the Google Groups
"Ruby on Rails: Talk" group.
To post to this group, send email to
rubyonrails-talk-/JYPxA39Uh5TLH3MbocFF+G/Ez6ZCGd0@public.gmane.org
To unsubscribe from this group, send email to
rubyonrails-talk+unsubscribe-/JYPxA39Uh5TLH3MbocFF+G/Ez6ZCGd0@public.gmane.org
For more options, visit this group at
http://groups.google.com/group/rubyonrails-talk?hl=en.