Recipe 8.7. Letting a User Reorder a List


Problem

You want users to be able to rearrange the order of elements in a list by dragging the items into different positions. When an item is dropped into its new position, the application needs to save the new position of each element.

Solution

Define a database schema containing the items that are to be sorted. In this case, the chapters of a book should be in a definite order. Thus, the chapters table contains a position column to store the sort order. The following migration sets up books and chapters tables, and populates them with data from the MySQL Cookbook:

db/migrate/001_build_db.rb:

class BuildDb < ActiveRecord::Migration   def self.up     create_table :books do |t|       t.column :name, :string     end     book = Book.create :name => 'MySQL Cookbook'     create_table :chapters do |t|       t.column :book_id, :integer       t.column :name, :string       t.column :position, :integer     end     Chapter.create :book_id => book.id,                    :name => 'Using the mysql Client Program', :position => 1      Chapter.create :book_id => book.id,                     :name => 'Writing MySQL-Based Programs', :position => 2      Chapter.create :book_id => book.id,                     :name => 'Record Selection Techniques', :position => 3      Chapter.create :book_id => book.id,                     :name => 'Working with Strings', :position => 4      Chapter.create :book_id => book.id,                     :name => 'Working with Dates and Times', :position => 5      Chapter.create :book_id => book.id,                     :name => 'Sorting Query Results', :position => 6      Chapter.create :book_id => book.id,                     :name => 'Generating Summaries', :position => 7     Chapter.create :book_id => book.id,                     :name => 'Modifying Tables with ALTER TABLE', :position => 8     Chapter.create :book_id => book.id,                     :name => 'Obtaining and Using Metadata', :position => 9     Chapter.create :book_id => book.id,                     :name => 'Importing and Exporting Data', :position => 10   end   def self.down     drop_table :books     drop_table :chapters   end end

Now, set up a one-to-many Active Record association (e.g., one book has many chapters and every chapter belongs to a book):

app/models/chapter.rb:

class Chapter < ActiveRecord::Base   belongs_to :book end

app/models/book.rb:

class Book < ActiveRecord::Base   has_many :chapters, :order => "position"  end

Your layout includes the default JavaScript libraries and also defines the style of the sortable list elements:

app/views/layouts/book.rhtml:

<html>   <head>     <title>Book</title>     <%= javascript_include_tag :defaults %>     <style type="text/css">       body, p, ol, ul, td {         font-family: verdana, arial, helvetica, sans-serif;         font-size:   13px;         line-height: 18px;       }         li {         position: relative;         width: 360px;         list-style-type: none;         background-color: #eee;         border: 1px solid black;         margin-top: 2px;         padding: 2px;       }     </style>   </head>   <body>     <%= yield  %>   </body> </html>

The Book controller defines an index method to set up the initial display of a book and its chapters. The order method responds to the XMLHttpRequest calls and updates the sort order in the model.

app/controllers/book_controller.rb:

class BookController < ApplicationController   def index     @book = Book.find(:first)   end   def order     order = params[:list]     order.each_with_index do |id, position|         Chapter.find(id).update_attribute(:position, position + 1)     end     render :text => "updated chapter order is: #{order.join(', ')}"    end end

The view iterates over the book object that is passed in and displays its the chapters. A call to the sortable_element helper acts on the DOM element containing the chapter list, making its contents sortable via dragging:

app/views/book/index.rhtml:

<h1><%= @book.name %></h1> <ul >   <% for chapter in @book.chapters -%>     <li  style="cursor:move;"><%= chapter.name %></li>   <% end -%> </ul> <p ></p> <%= sortable_element 'list',        :update => 'order',         :complete => visual_effect(:highlight, 'list'),        :url => { :action => "order" } %>

Discussion

The call to sortable_element takes the id of the list you want sorted. The :update option specifies which element, if any, is to be updated by the action that's called. The :complete option specifies the visual effect that indicates when a sort action is complete. In this case, we highlight the list element with yellow when items are dropped into a new position. The :url option specifies that the order action is called by the XMLHttpRequest object.

The script.aculo.us library does the heavy lifting of making the list items draggable. It's also responsible for producing an array of position information based on the final, numeric part of the id of each element. This array is passed to the Book Controller to update the model with the latest element positions.

The Book controller's order action saves the updated positions, which are in the params hash, into the order array. each_with_index is called to iterate over the order array, passing the contents and position of each element into the code block. The block uses the contents of each element (id) as an index to find the Chapter object to be updated, and each Chapter object's position attribute is assigned the position of that element in the order array.

With all the chapter position information updated, the order action renders a message about the new positions, as text, for display in the view.

Figure 8-7 shows the chapter list before and after some reordering.

Figure 8-7. A sortable list of chapters that uses drag and drop


See Also

  • The script.aculo.us drag-and-drop demonstration at http://demo.script.aculo.us/shop




Rails Cookbook
Rails Cookbook (Cookbooks (OReilly))
ISBN: 0596527314
EAN: 2147483647
Year: 2007
Pages: 250
Authors: Rob Orsini

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net