Rails / Sortable lists
From WhyNotWiki
[edit] Introduction
This is about making lists/trees that you can reorder by dragging and dropping. Using Scriptaculous and Ajax.
[edit] The simplest Ajax request (good for debugging)
<%= sortable_element :menu,
:url => { :action => 'inspect_params' },
:complete => "alert(request.responseText);" %>
%>
class _Controller < ApplicationController
...
def inspect_params
render :text => params.inspect
end
end
[edit] [Troubleshooting (category)] The drag and drop is working, except it's not making any Ajax request!
Do your list items have ids? They must have ids (for example, node_1, node_2, ...) or it will silently fail to work.
Why does it need the ids? So that it can submit the list of IDs (in order, since the whole reason you made your list sortable was so that you can use drag and drop to reorder the items), sort of like this:
Parameters: {"list"=>["1", "3", "2"], "action"=>"inspect_params", "controller"=>"task_tree"}
The IDs that are submitted to the Ajax action are extracted from the IDs of the li elements in the list.
[edit] Reference for Scriptaculous
This isn't Rails-specific, but here it is...
http://wiki.script.aculo.us/scriptaculous/show/SortableListsDemo Sortable Lists Demo in scriptaculous wiki
http://wiki.script.aculo.us/scriptaculous/show/GhostlySortableDemo Ghostly Sortable Demo in scriptaculous wiki
[edit]
Rails Recipes book
...
HowToUseDragAndDropSorting in Ruby on Rails (http://wiki.rubyonrails.org/rails/pages/HowToUseDragAndDropSorting).
logicnazi,
Also I think sortables have some issues if you try and update the sortable list while the list is working. You may wish to stop sortables (with Sortable.dstroy) before updating the list and then restrart it.
[edit] The sort / update method of the controller
HowToUseDragAndDropSorting in Ruby on Rails (http://wiki.rubyonrails.org/rails/pages/HowToUseDragAndDropSorting).
def update_positions params[:sortable_list].each_index do |i| item = ListItem.find(params[:sortable_list][i]) item.position = i item.save end @list = List.find(:all, :order => 'position') render :layout => false, :action => :list enddef update_positions params[:sortable_list].each_with_index do |id, position| ListItem.update(id, :position => position) end render :nothing => true end
Rails Recipes book, p. 24
def sort @grocery_list = GroceryList.find(params[:id]) @grocery_list.food_items.each do |food_item| food_item.position = params['grocery-list'].index(food_item.id.to_s) + 1 food_item.save end render :nothing => true end
[edit] Drag-and-drop between sortable lists
How to implement drag-and-drop between sortable lists with Rails at Hans Friedrich (http://blog.hansfriedrich.com/?p=4).
A recent project required the use of not just one but TWO drag-and-drop sortable lists on the same page. The lists are used for sorting a set of names into left and right columns, and then sorting them in arbitrary order within those columns....
The difficulty is that although dragging and sorting in the browser works great, our Rails action “sort” doesn’t know what to do with itself now. All of a sudden items are coming and going instead of just presenting themselves for a simple reordering like they used to.
The problem stems from approaching sorting by matching up items from a single list to their new positions provided by a array of values in params[’your-list-id’]. Because a new item could be introduced at any time, the @yourlist.funky_items collection in our example is now an unreliable source of knowledge about what should and should not be in a list. We need to start giving more action to the array we’re getting with our Ajax call - the items in that array give us the real man-on-the-street picture of what items our list should have.
Rewritten, the “sort” method is now prepared to handle not only sorting, but accepting and removing items as well:
def sort if params[‘your-list-id-1′] then sortables = params[‘your-list-id-1′] elsif params[‘your-list-id-2′] then sortables = params[‘your-list-id-2′] end sortables.each do |id| currentitem = FunkyItem.find_by_id(id.to_i) currentitem.position = sortables.index(id) + 1 currentitem.lists_of_stuff_column_id = params[:id] currentitem.save end render :nothing => true end
[edit] Rails / Sortable trees
[edit]
Most of this is a specialization of Rails / Sortable lists.
[edit] Scriptaculous's Sortable's tree:true option
Demo: http://script.aculo.us/playground/test/functional/sortable_tree_test.html
It's really neat! But... I found it a bit hard to use, actually. Too easy to accidentally move a node up or down a level to a different subtree. Having whole (large) subtrees bumping down dynamically to make room for the element I'm dragging is kind of "neat", but quite distracting and confusing, especially since it does this for every single element as you drag over it, which means it's doing it for a lot of elements...
Conclusion: Don't use the tree:true option. I think generally I would prefer to only allow "easy"/"quick" re-ordering (using the Scriptaculous "Sortable") for nodes at the same level. The user interface to move a node to a different subtree can be accomplished with "plain old" Draggables -- only updating the view after the Ajax request (to assign a new parent) has been processed and the server responds with some RJS to update the view.
A bit laggy perhaps, but it's better than having the [skitterish] feeling of having everything constantly moving/jumping around as you're dragging a node. Ideally, it would be updated on the client-side immediately and then maybe highlighted when the Ajax request completes, but I don't (currently) how easy that would be...
[edit] Sur's AjaxTree
Source Code For Ajax Based Drag Drop Navigation Tree in Ruby on Rails (http://ajaxonrails.wordpress.com/2006/08/18/sorce-code-for-ajax-based-drag-drop-navigation-tree-for-rails/) (2006-11-26).
...
http://ajaxonrails.wordpress.com/2006/11/26/ajaxonrailsdragdroptree/ (the source code for the above)
http://svn.tylerrick.com/public/rails/examples/ajaxtree
Comments:
- You can drag and drop a node to a different parent but not to a different position (well, of course, because it doesn't even keep track of position -- it uses acts_as_tree, which is an unordered tree)
- It has +/- icons for each node that let you collapse/expand that subtree
- When you drag and drop, it reloads the entire tree, expanding the subtree that you dropped onto and collapsing all (non-ancestor) trees.
- There is no indication when you are over a droppable element.
- Bug: If you drag onto a collapsed node (say 'item4'), it won't just add it as a child of item4, but will actually add it as a child of the last child of item4, which is item4.3 in the demo.
- By the author's own admission [1], this code has "lagged behind the current trends followed in Rails development" and he was new to Rails when he started it, so it may not use the best conventions...
[edit] Sortable tree addition / discussion on Rails-spinoffs list
http://lists.rubyonrails.org/pipermail/rails-spinoffs/2006-March/002890.html [Rails-spinoffs] sortable tree?
http://wrath.rubyonrails.org/pipermail/rails-spinoffs/2006-February/subject.html#2632 The Rails-spinoffs February 2006 Archive by subject
[edit] sortable tree fix and enhancement for acts_as_nested_set+better_nested_set plugin

http://dev.rubyonrails.org/ticket/7807
- 7807 ([PATCH] Sortable tree fixes for #4691 (adding sub-items to empty branches)) - Rails Trac - Trac
1. #4691 fix: you may add any branch under any node and, thus, create new branch. 2. acts_as_nested_set & better_nested_set plugin integration. onChange provides 2 more arguments: movement type and reference object (drop-on-element). Three movements are supported: "left_to", "right_to" and "to_child_of".http://dev.rubyonrails.org/attachment/ticket/7807/sortable_tree_fix_and_enhancement.2.diff
