6.3. Using
Drag-and-Drop to Reorder Slides
The scaffolding we have for editing a slideshow
shows just the slideshow attributes that are stored directly in the
slideshows table: the slideshow's
name and the date on which it was created. The most important part
is missing: the photos that are part of the slideshow!
By now, you've probably realized that this is
because the scaffolding code deals with only one database table:
the slideshows table. The
relationship data about which photos are assigned to a slideshow
and their order in the slideshow are stored in the slides table. Scaffolding does not handle
relationships, so you have to write the code to edit this
relationship data.
We're going to display a list of thumbnails of
all the photos that are in a slideshow, and then let the user
reorder them using drag-and-drop. If you've had to struggle through
implementing drag-and-drop before, you're not going to believe how
easy this is going to be. Here's a hint: this will take a total 34
additional lines of Ruby, CSS, and RHTML template!
Let's start by reviewing the current
implementation of the edit action in the slideshow
controller:
def edit
@slideshow = Slideshow.find(params[:id])
end
This action expects to find the ID of the
slideshow to edit passed in as the id parameter, which is
normally decoded from the URL. You find the slideshow with that ID
and assign that slideshow object to the instance variable
@slideshow, so that it can be accessed in the view
template.
That is really all that's needed here, so you
won't have to add any code to this method. The changes will start
with the edit view template, so edit the template photos/app/views/slideshows/edit.rhtml and make it
look like this (the changes are in bold):
<h1>Editing slideshow</h1>
<%= link_to 'Play this Slideshow',
:action => 'show', :id => @slideshow %>
<div id='slideshow-contents'>
<%= render :partial => 'show_slides_draggable' %>
</div>
<div id='slideshow-attributes'>
<%= start_form_tag :action => 'update', :id => @slideshow %>
<%= render :partial => 'form' %>
<%= submit_tag 'Save Attributes' %>
<%= end_form_tag %>
</div>
Notice that the existing <%= render
:partial => 'form' %> is wrapped in a
<div> tag with an id attribute of
slideshow-attributes. You will use this name in one of
your CSS files to control how this section is displayed.
There is also a completely new section that
displays thumbnails of the photos in the slideshow:
<div id='slideshow-contents'>
<%= render :partial => 'show_slides_draggable' %>
</div>
This code also uses a <div> tag
with an id attribute, for the same reason: to use a CSS
file to control its appearance. This div also renders a
new partial view template named show_slides_draggable,
which we will create next.
Create the file photos/app/views/slideshows/_show_slides_draggable.rhtml
with the following contents:
<ol id='sortable_thumbs'>
<% for slide in @slideshow.slides %>
<li id='thumbs_<%= slide.id %>' class='slides'>
<%= thumbnail_tag slide %>
</li>
<% end %>
</ol>
<%= sortable_element('sortable_thumbs',
:url => {:action => 'update_slide_order'}) %>
The first part is pretty standard stuff. We're
creating an HTML ordered list, in which each list item is a
thumbnail image of one of the photos in the slideshow (note that
the thumbnail_tag helper function that was created
earlier). However, it's the last two lines that do the heavy
lifting.
sortable_element is a helper function
that generates the JavaScript code that turns our list into a
user-sortable, drag-and-drop-capable list. It wraps this list an
HTML form, and the :url option specifies the URL to post
to the server whenever the user changes the order of the list. In
this case, it calls the action method update_slide_order
in our slideshow controller. This call works in the background
using an Ajax call.
The update_slide_order method is pretty
simple as well. Edit photos/app/controllers/slideshows_controller.rb,
and add this method:
def update_slide_order
params[:sortable_thumbs].each_with_index do |id, position|
Slide.update(id, :position => position)
end
end
This method iterates through each slide in the
list, extracting its ID and position in the list, and uses this
information to update that slide's database row with its new
position. Let's walk through this code in a little more detail:
-
params is
a hash that holds all the parameters sent to the server in the HTTP
request. params[:sortable_thumbs] retrieves the parameter
for the sortable_thumbs list, which is an ordered array of
the IDs of each thumbnail in the list.
-
each_with_index is a Ruby iterator that, just like the
each iterator, walks through the array one item at a time.
But on each iteration, each_with_index passes to the code
block both the object held in the array (the slide id) and
its index in the array (which is assigned to
position).
-
Slide.update(id, :position =>
position) then calls the Slide model class to update
the slide identified by id with its new position.
We're almost ready to give it a try, but first
let's edit photos/public/stylesheets/slideshows.css
and add
some formatting instructions for the two div IDs we
created. Add the following at the end of the file:
#slideshow-contents {
float: left;
width: 11em;
padding: 0.50em;
text-align: center;
border-right: thin solid #bbb;
padding: 0.50em;
padding-bottom: 10em;
}
#slideshow-attributes {
margin-left: 23em;
padding-left: 1.5em;
padding-top: 1.5em;
}
This causes the contents of the slideshow (which
will be a list of thumbnail images) to be displayed down the left
side of the page, and the slideshow's attributes will be displayed
immediately to the right of the thumbnails.
Let's see how this looks. Browse to http://127.0.0.1:3000/slideshows/list,
and click the edit link for our one and only slideshow. It will
look like Figure
6-2.
Click on one of the photos, and try dragging it
around. When you drop it into a new location,
update_slide_order is called to write the new order to the
database.
Let's fix one minor thing here before we move
on. Wouldn't it be better to see the number of each photo appear
vertically aligned in the middle of the thumbnail instead of at the
bottom? Because the HTML for each thumbnail image is created by our
own helper function, thumbnail_tag, we just need to edit
that function and add a vertical-align style attribute.
First, edit photos/app/helpers/slideshows_helper.rb,
and add the code shown in bold:
module SlideshowsHelper
def thumbnail_tag(slide)
image_tag("photos/#{slide.photo.thumbnail}",
:style=>"vertical-align:middle") if slide
end
end
Now, refresh your browser: the list numbers are
nicely centered, as you can see in Figure 6-3.
With a very small amount of code, we added a
very nice drag-and-drop user interface for reordering the slides in
a slideshow. But we're just getting started with our Ajax-enabled
user interface.
 |