I have implemented an ajax-based button to add and remove a calendar event from a user’s “wish list”. The button behaves as a toggle-switch. If the event is already on their wish list, it will remove it; if it is not, then it will add it.
My issue is that the button works correctly when the page is first loaded, but after clicking it and replacing it with a new button_to via AJAX, it no longer works.
I looked at this post but he has a slightly different architecture and I am not looking to completely refactor this just yet if I can avoid it. I have a separate model for the saved events. There are three models that come into play here: users, events, and saved_events. There is a many-to-many relationship between users and events which is represented as a saved_event. Here is the relevant code from the models.
user.rb:
has_many :saved_events
has_many :events, :through => :saved_events
event.rb:
has_many :saved_events
has_many :watchers, :class_name => "User", :through => :saved_events, :source => :user
saved_event.rb:
belongs_to :event
belongs_to :user
I have configured pretty standard routes.
routes.rb:
resources :saved_events, :only => [:index,:create,:destroy]
resources :users
resources :events
I initially want to see this button an an event’s page, so I added it to the event’s show.html.erb. (I realize this isn’t quite DRY yet – I am waiting until it works correctly to move it into a partial)
events/show.html.erb:
<% if !@is_watched %>
<%= button_to "Add to Wishlist", { :action => 'create', :controller => 'saved_events', :id => @event.id }, :remote => true %>
<% else %>
<%= button_to "Remove from Wishlist", saved_event_path(@event), :remote => true, :method => 'delete' %>
<% end %>
events_controller.rb:
def show
@event = Event.find(params[:id])
@is_watched = @event.watchers.exists?(current_user) if !current_user.nil?
respond_to do |format|
format.html # show.html.erb
format.xml { render :xml => @event }
format.json { render :json => @event }
end
end
saved_events_controller.rb:
def create
logger.debug params.to_yaml
@event = Event.find(params[:id])
if !current_user.events.exists?(params[:id])
current_user.events << @event
end
respond_to do |format|
format.js
end
end
def destroy
logger.debug params.to_yaml
@event = current_user.events.find(params[:id])
current_user.events.delete(@event)
respond_to do |format|
format.js
end
end
My goal with the ajax was just to swap out the add form with the remove form (or vice-versa). So I have a create.js.erb and a destroy.js.erb which just use the same code from events/show.html.erb.
saved_events/create.js.erb:
$(".button_to").html("<%= escape_javascript button_to "Remove from Wishlist", saved_event_path(@event), :remote => true, :method => 'delete' %>");
saved_events/destroy.js.erb:
$(".button_to").html("<%= escape_javascript (button_to "Add to Wishlist", { :action => 'create', :controller => 'saved_events', :id => @event.id }, :remote => true) %>");
This is what I see in the log if the second button click is to “remove”
Started DELETE "/saved_events?id=1" for 127.0.0.1 at 2011-12-02 11:56:58 -0600
ActionController::RoutingError (No route matches [DELETE] "/saved_events"):
This is what I see in the log if the second button click is to “add”
Started POST "/saved_events/1" for 127.0.0.1 at 2011-12-02 11:59:12 -0600
ActionController::RoutingError (No route matches [POST] "/saved_events/1"):
On initial load of the page, the add button has “/saved_events?id=1” as its action, so I am confused as to why it would be “/saved_events/1” for the second button click.
It seems the issue was from my jquery. I needed to wrap the form in a div, and then call jquery.html() on that div rather than the form itself. I did some other refactoring in the controllers, which I won’t post here for brevity, but here are my new js.erb files:
create.js.erb:
destroy.js.erb: