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-t...@googlegroups.com. To unsubscribe from this group, send email to rubyonrails-talk+unsubscr...@googlegroups.com. For more options, visit this group at http://groups.google.com/group/rubyonrails-talk?hl=en.