rails process for saving order of awesome_nested_set using jquery & nestedsortables
I have a categories model made with the fantastic awesome_nested set. I have successfully generated the drag and drop tree and successfully generated the complete hash of this tree using SERIALIZELIST plugin and sent it to the "array" method that I have added to my categories controller. (using jquery and nestedsortables) The hash from my log looks like so ...
Processing CategoriesController#array (for 127.0.0.1 at 2010-08-19 23:12:18) [POST] Parameters: {"ul"=>{"0"=>{"class"=>"", "id"=>"category_1", "children"=>{"0"=>{"class"=>"", "id"=>"category_4", "children"=>{"0"=>{"class"=>"", "id"=>"category_3"}}}}}, "1"=>{"class"=>"", "id"=>"category_2", "children"=>{"0"=>{"class"=>"", "id"=>"category_5"}, "1"=>{"class"=>"", "id"=>"category_6"}}}}}
i'm just having trouble with the sort function.
Awesome nested set does provide a few move functions but I can't seem to get my head around it.
I want to do something like this when the user hits save (btw it does an ajax request and passes the above data correctly)
def array
newlist = params[:ul]
newlist.each_with_index do |id, index, children|
#insert code here for saving the re-ordered array
end
render :nothing => true
end
I hope this is enough information and hope someone can answer this question.
Cheers,
Matenia
----------- UPDATE AND PROGRESS -----------
Since posting this a few days ago, I mucked around with the logger.info in my dev environment to see what was going on behind the scenes.
I ended up writing 2 functions. One to go through the roots of the array and the other to recursively move the children and childrens children into place. But this ends up with too many database calls (there may be no other way to do it though).
the code looks like so ...
def array
# fetch the current tree
@allcategories = Category.all
# assign the sorted tree to a variable
newlist = params[:ul]
# initialize the previous item
previous = nil
#loop through each item in the new list (passed via ajax)
newlist.each_with_index do |array, index|
# get the category id of the item being moved
moved_item_id = array[1][:id].split(/category_/)
# find the object that is being moved (in database)
@current_category = Category.find_by_id(moved_item_id)
# if this is the first item being moved, move it to the root.
unless previous.nil?
@previous_item = Category.find_by_id(previous)
@current_category.move_to_right_of(@previous_item)
else
@current_category.move_to_root
end
# then, if this item开发者_Python百科 has children we need to loop through them
unless array[1][:children].blank?
# unless there are no children in the array, send it to the recursive children function
childstuff(array[1], @current_category)
end
# set previous to the last moved item, for the next round
previous = moved_item_id
end
render :nothing => true
end
def childstuff(node, category)
# find the category that has been passed into the function
@selected_category = Category.find(category)
for child in node[:children]
child_id = child[1][:id].split(/category_/)
child_category = Category.find_by_id(child_id)
child_category.move_to_child_of(@selected_category)
#if this child has children -- run recursion on this function
unless child[1][:children].blank?
childstuff(child[1], child_category)
end
end
end
I hope someone can shed some light on how to make this more efficient and how to reduce the number of database calls. I have thought about writing other functions, but theyre all going to do the same thing.
For this particular project, I don't believe there would be more than 100 different categories. It's not the best way, but it works.
Cheers again,
Matenia
THE FINAL WORKAROUND
I had an issue with the above code where it wasn't saving the children properly. Here is my latest attempt, which seems to work well.
def array
# assign the sorted tree to a variable
newlist = params[:ul]
# initialize the previous item
previous = nil
#loop through each item in the new list (passed via ajax)
newlist.each_with_index do |array, index|
# get the category id of the item being moved
moved_item_id = array[1][:id].split(/category_/)
# find the object that is being moved (in database)
@current_category = Category.find_by_id(moved_item_id)
# if this is the first item being moved, move it to the root.
unless previous.nil?
@previous_item = Category.find_by_id(previous)
@current_category.move_to_right_of(@previous_item)
else
@current_category.move_to_root
end
# then, if this item has children we need to loop through them
unless array[1][:children].blank?
# NOTE: unless there are no children in the array, send it to the recursive children function
childstuff(array[1], @current_category)
end
# set previous to the last moved item, for the next round
previous = moved_item_id
end
Category.rebuild!
render :nothing => true
end
def childstuff(mynode, category)
# logger.info "node = #{node} caegory = #{category}"
#loop through it's children
for child in mynode[:children]
# get the child id from each child passed into the node (the array)
child_id = child[1][:id].split(/category_/)
#find the matching category in the database
child_category = Category.find_by_id(child_id)
#move the child to the selected category
child_category.move_to_child_of(category)
# loop through the children if any
unless child[1][:children].blank?
# if there are children - run them through the same process
childstuff(child[1], child_category)
end
end
end
still too many database calls, but I guess that's the price to pay for wanting this functionality as it needs to re-record each item in the database.
Hope this helps someone else in need. Feel free to msg me if anyone wants help with this.
AWESOME NESTED SET + JQUERY DRAG AND DROP + SERIALIZELIST PLUGIN ....
Cheers,
Matenia
see edited question above for final workaround ..I have posted the code on github .. although it may have a few bugs and needs refactoring BADLY! JQUERY NESTED SORTABLES - DRAG AND DROP - AWESOME NESTED SET
UPDATE: Added rails 3 example to repo with slightly cleaner code
The same issue came up when upgrading a rails 2.3 app to 3.1. In my case, I only wanted to sort one depth (root or not). Here's what I ended up with:
# Fetch all IDs (they will be in order)
ids = params[:sort].collect { |param| param[/^page_(\d+)$/, 1] }
# Remove first item from array, moving it to left of first sibling
prev = Page.find(ids.shift)
prev.move_to_left_of(prev.siblings.first)
# Iterate over remaining IDs, moving to the right of previous item
ids.each_with_index { |id, position| current = Page.find(id); current.move_to_right_of(prev); prev = current }
精彩评论