Recipe 8.2. Creating a Custom Report with Drag and Drop


Problem

Users are used to drag-and-drop applications, but it's really hard to put the feature into a web application. How do you make your web application more like a desktop application by adding drag-and-drop functionality?

Solution

Within Rails, you can use the drag-and-drop functionality of the script.aculo.us JavaScript library to allow users to select the columns of their reports.

To demonstrate this, suppose you are providing a web interface to a customer database that will be used by a number of people in your organization. Each person viewing the report will likely have a different idea about what fields he would like to see displayed. You want to provide an easy-to-use and responsive interface that lets users customize their own version of the report.

Your report selects from the customers table in your database. That table is defined as follows:

db/schema.rb:

ActiveRecord::Schema.define(:version => 1) do   create_table "customers", :force => true do |t|      t.column "company_name", :string     t.column "contact_name", :string     t.column "contact_title", :string     t.column "address", :string     t.column "city", :string     t.column "region", :string     t.column "postal_code", :string     t.column "country", :string     t.column "phone", :string     t.column "fax", :string   end  end

Create a view that lists the columns of the customers table and makes each of those columns draggable. Then define a region to receive the dragged columns. Add a link that runs the report, and another that resets it.

app/views/customers/report.rhtml:

<h1>Custom Report</h1> <% for column in Customer.column_names %>   <div  style="cursor:move;">     <%= column %>;   </div>     <%= draggable_element("#{column}", :revert => false) %> <% end %> <div >   <% if session['select_columns'] %>     <%= session['select_columns'].join(", ").to_s %>   <% end %> </div> <%= link_to_remote "Run Report",                    :update => "report",                    :url => { :action => "run" } %> (<%= link_to "reset", :action => 'reset' %>) <div > </div> <%= drop_receiving_element("select-columns",                            :update => "select-columns",                            :url => { :action => "add_column" }) %>

In the Customers controller, define add_column to respond to the Ajax requests that are triggered when columns are dropped into the receivable region. Also define a method that runs the report, and another that resets it by clearing out the select_columns key of the session hash.

app/controllers/customers_controller.rb:

class CustomersController < ApplicationController   def report    end   def add_column     if session['select_columns'].nil?       session['select_columns'] = []     end     session['select_columns'] << params[:id]     render :text => session['select_columns'].join(", ").to_s   end   def run     if session['select_columns'].nil?       render :text => '<p style="color: red;">no fields selected</p>'     else               @customers = Customer.find_by_sql("select            #{session['select_columns'].join(", ").to_s} from customers")       render :partial => 'report'     end   end   def reset     session['select_columns'] = nil     redirect_to :action => 'report'   end end

Now create a partial view called _report.rhtml to display the report itself:

app/views/customers/_report.rhtml:

<table>   <tr>   <% for column in session['select_columns'] %>     <th><%= column %></th>    <% end %>   </tr>      <% for customer in @customers %>     <tr>     <% for column in session['select_columns'] %>       <td><%=h customer.send(column) %></td>      <% end %>     </tr>   <% end %> </table>

The layout needs to include the JavaScript libraries as well as define the look of the receivable region in the report view:

app/views/layouts/customers.rhtml:

<html> <head>   <title>Customers: <%= controller.action_name %></title>   <%= stylesheet_link_tag 'scaffold' %>   <%= javascript_include_tag :defaults %>   <style type="text/css">     #select-columns {       position:         relative;       width:            400px;       height:           90px;       background-color: #e2e2e2;       border:           2px solid #ccc;       margin-top:       20px;       padding:          10px;     }   </style>  </head> <body> <p style="color: green"><%= flash[:notice] %></p> <%= yield %> </body> </html>

Discussion

The report view starts off by iterating over the columns of the customer table. Within this loop, each column has a div tag with an id that matches the column name. The loop also calls draggable_element, which specifies that each of the div elements will be draggable. Setting the :revert option to false means a column that is moved away from its original position won't spring back into place when the mouse button is released. The solution also adds style="cursor:move;" to the draggable div elements. This style reinforces the dragging metaphor by changing the cursor when it moves over a draggable element.

Next, the view defines a div element with an id of select-columns; this element provides the destination that columns are moved to. The drop_receiving_element method takes the id of this div element and associates a call to the add_column action each time columns are dragged into the region. The :update option of drop_receiving_element specifies that the contents of the receiving element are to be replaced by the output rendered by add_column. add_column stores the selected columns in an array in the session hash. That array is joined with commas and displayed in the receiving div tag.

The link generated by link_to_remote triggers the run action, which runs the report. The :update option puts the output of the partial rendered by run into the div element with the specified id. The run action takes the columns from the session array, if there is one, and builds an SQL query string to pass to the find_by_sql method of the Customer model. The results from that query are stored in @customers, and made available to the _report.rhtml partial when it is rendered.

Most of the requests in the solution are Ajax requests from the XMLHttpRequest object. reset is the only method that actually refreshes the page. With a little instruction, most users find well-designed drag-and-drop interfaces intuitive, and much more like familiar desktop applications than the static HTML alternative. If there are accessibility issues, you may want to provide an alternate interface for those who need it.

Figure 8-2 shows three columns selected for the report and its rendered output.

Figure 8-2. A customizable report that uses drag and drop for field selection


See Also

  • Section 8.3"




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