Drag&Drop with widgets 2.3 3.0
In the we did a simple tweeter. Now what if we’d like to dump an item? Besides that twitter doesn’t allow deleting (do they?), let’s start writing a trashbin widget.
When dropping an item the bin widget deletes it from the database. To stay in sync with our model, the list should also redraw. You’ll learn how to do that using event triggering in this lesson.
Dragging items
Drag&Drop with JavaScript is well done in jQuery UI. We need to include the new JS lib in our project.
Next, the listed items have to be draggable.
1- widget_div do 2 %ul 3 - for tweet in @tweets 4 %li{'data-id' => tweet.id} 5 = tweet.text 6 7 %p 8 What are you up to? 9 10 - form_tag "", "data-event-url" => url_for_event(:submit) do 11 = text_field_tag 'text' 12 13 = submit_tag "Tweet!" 14 15 :javascript 16 var form = $("##{widget_id} form"); 17 18 form.submit(function() { 19 $.ajax({url: form.attr("data-event-url"), data: form.serialize()}) 20 return false; 21 }); 22 23 $("##{widget_id} li").draggable({revert: "invalid"});
This happens in line 23 using the Draggable API.
Creating the trashbin widget
The bin widget is just a generator away.
$ script/generate widget trashbin_widget display --haml
exists app/cells/
create app/cells/trashbin_widget
exists test/widgets
create app/cells/trashbin_widget.rb
create app/cells/trashbin_widget/display.html.haml
create test/widgets/trashbin_widget_test.rb
The TrashbinWidget is bloody simple.
Our standard view draws the bin image that I’d stole somewhere.
In line 5 we define the image from line 3 as Droppable.
On drop, we send an AJAX request to the widget’s :drop event url appending the dropped item’s id.
Catching the dropped
Remember that the trashbin widget handles drop events.
Simply put, when the bin sees a drop event, it will invoke its trash state.
In line 12 we replace the current widget on the page with the display view.
Triggering events
What’s that trigger statement in line 10?
Well, we simply inform the outer world that we altered the tweets table – since we ruthlessly deleted an item.
Bubbling events
According to Apotomo’s bubbling event concept, the tweetDeleted event will bubble up to root. Why not instruct the list widget to catch it and update itself?
1class TwitterWidget < Apotomo::Widget 2 responds_to_event :submit, :with => :process_tweet 3 4 def display_form 5 @tweets = Tweet.find(:all) 6 render 7 end 8 9 def process_tweet 10 Tweet.new(:text => param(:text)).save 11 12 @tweets = Tweet.find(:all) # this is wet! 13 replace :view => :display_form 14 end 15 16 def redraw 17 replace :state => :display_form 18 end 19 20end
We just add the redraw state (line 16) which invokes display_form and replaces the widget content in the browser (line 17).
Since events bubble up, we need to attach an event handler to root. That can be done in the controller.
1class DashboardController < ApplicationController 2 include Apotomo::Rails::ControllerMethods 3 4 has_widgets do |root| 5 root << widget(:twitter_widget, 'parrot', :display_form) 6 root << widget(:trashbin_widget, 'bin', :display) 7 root.respond_to_event :tweetDeleted, :with => :redraw, :on => 'parrot' 8 end 9 10 def index 11 end 12end
Line 7 instructs root to call updating on the list widget 'parrot' when encountering the respective event.
However, putting observer instructions in the controller is not very clean and breaks widget encapsing, since the controller knows details about the widgets’ internals.
There are two ways to make this suck less.
- Have a container that wraps both widgets.
- Use the
after_addhook to add an observer.
Both approaches use Apotomo’s event bubbling.

