The dropdown-menu (built by the select_tag) in my application should call the filter-category-action as soon as the user changes the value in the dropdown-menu AND hits the 'Go' button.
Now I'd like to get rid of the 'Go' button and have an observer (observe_field?) call the filter-category-action as soon as the user changes the value in the dropdown-menu.
Below you see the code I've written. It works using the 'Go'-Button but doesn't work by just changing the value in the dropdown-menu. What's wrong with my observe_category_select-helper?
View-partial with dropdown-menu and project list
<!-- drop down menu -->
<% form_tag(filter_category_path(:id), :method => :post, :class => 'categories') do %>
<label>Categories</label>
<%= select_tag(:category, options_for_select(Category.all.map {|category| [category.name, category.id]}, @category_id)) %>
<!-- i would like to get rid of this button -->
<%= submit_tag "Go" %>
<% end %>
<!-- list of projects related to categories chosen in drop down menu -->
<ul class="projects">
<% @projects.each do |_project| %>
<li>
<%= link_to(_project.name, _project) %>
</li>
<% end %>
</ul>
<%= observe_category_select -%>
HelperMethod
def observe_category_select
observe_field(
:category,
:url => filter_category_path(:id),
:with => 'category',
:on => 'change'
)
end
Javascript-Output of the HelperMethod
<script type="text/javascript">
//<![CDATA[
new Form.Element.EventObserver('category', function(element, value) {
new Ajax.Request('/categories/id/filter', {asynchronous:true, evalScripts:true, parameters:'category=' + encodeURIComponent(value) + '&authenticity_token=' + encodeURIComponent('edc8b20b701f72285068290779f7ed17cfc1cf8c')})
}, 'change')
//]]>
</script>
Categories controller
class CategoriesController < ApplicationController
def show
@category = Category.find(params[:id])
@category_id = @category.id
@projects = @category.projects.find(:all)
respond_to do |format|
format.html # index.html.erb
end
end
def index
@projects = Category.find(params[:id]).projects.find(:all)
respond_to do |format|
format.html # index.html.erb
end
end
def filter
@category = Category.find(params[:category])
@category_id = @category.id
@projects = @category.projects.find(:all)
respond_to do |format|
format.html # index.html.erb
end
end
end
Output of 'rake routes | grep filter'
filter_category POST /categories/:id/filter {:controller=>"categories", :action=>"filter"}
formatted_filter_category POST /categories/:id/filter.:format {:controller=>"categories", :action=>"filter"}
Your filter controller action needs to respond to Javascript instead of just to a normal HTTP request.
def filter
@category = Category.find(params[:category])
@category_id = @category.id
@projects = @category.projects.find(:all)
respond_to do |format|
format.js # filter.rjs
end
end
Or, if you want that action to respond in either context, put both in the block:
respond_to do |format|
format.html # filter.html.erb
format.js # filter.rjs
end
This requires you to have a view file filter.rjs that will look something like:
page.replace_html :id_of_element_to_replace_html, :partial => "name_of_partial"
My first thought was it may be some scoping issues. I assume that filter_category_path is one of your route path helpers - the id or the category value (in :with) may not be in scope in the helper method.
When you view the page, can you see the JavaScript that is output by the call to observe_field?
Using Firebug, can you see any Ajax requests being made?
Does anything appear in your development.log?
def observe_category_select
observe_field(
:category,
:url => filter_category_path(:id),
:with => :category_id,
:on => :onchange,
:update => :projects
)
end
The :onchange is the gotcha - took me a long time to sort it out. EDIT: Looks like you worked that out.
If you want to reset the dropdown and project display state include a function like this in the rendered partial:
<%= link_to_function "close category", :title => 'close category' do |page|
page.select("#category").each do |element|
element.value = "Choose Category"
page << "window.fireEvent($(\"category\"), 'change');"
end
page.replace_html :projects, "<li></li>"
end-%>
That will set the select list to the default value and remove all of the list items related to the last displayed category and get the ul ready for the new li's to be displayed from the default. The fireEvent has to be called or the observer won't work if you select the same category that was just hidden.
Since the observer is being fired the controller action has to deal with it:
def filter
begin
@category = Category.find(params[:category])
@category_id = @category.id
@projects = @category.projects.find(:all)
rescue
@category = "choose"
end
render :layout => false
end
If no category is found set @category to a string and test in the view partial
<% if @category.eql?('choose') %>
<li>choose category</li>
<% else %>
# loop through projects returning them wrapped in li tags
<% end %>
Not pretty but it works.
I had the same problem in my actual project. I solved it with the onchange attribute in the select tag:
<%= select_tag @procuct,
options_for_select(
@product.properties.map {|property| [property.name, property.id]}),
:onchange => "HERE_A_CALL_TO_YOUR_JS_FUNCTION();" %>
UPDATE: I put the Ajax Call in a JS Function, instead you can put everything in the onchange statement, I think you can also put "native Rails" JS methods in there.
FWIW - I'm still pretty new to rails and I stumbled on the fact that the new custom action needs to be defined in your routes.rb file (kinda obvious in hindsight, but I had never had to do this before).
If you're using restful routes, then just tack a :collection => {:filter => :get} to the end of your resource route line (e.g. map.resource :categories, :collection => {:filter => :get}).
Hope that helps somebody out.
来源:https://stackoverflow.com/questions/575862/rjs-using-observe-field-on-select-tag