Ticket #32 (new enhancement)

Opened 1 year ago

suggestion for iterator over full set that yields each node and its level

Reported by: rails Assigned to: jcm
Priority: major Milestone:
Component: plugin Version:
Keywords: Cc: briansheehan78@gmail.com

Description

I coded up an iterator called traverse, it allows the nested set to be traversed in pre-order (the same as full_set.each), but as well as yielding each node, it yields its level, and the level of the next node relative to it. This makes building hierarchical menus & lists very easy:

>> puts menu = root.traverse("") {|node, level, delta_next| "###" * level << node.name << "\n"}
root
###child1.1
######child2.1
#########child3.1
###child1.2
###child1.3
=> nil
>> 

Using the relative level of the next node, a html list can be built:

  def display_nested_set(root, li_open = "<li>", li_close = "</li>", l_open = "<ul>", l_close = "</ul>" )
    root.traverse("") do |node, level, relative_level_of_next| 
      str =  li_open.dup
      str << link_to(node.name, category_path(node))
      if relative_level_of_next == 1 # Descending to first child 
        str << l_open
      elsif relative_level_of_next == 0 # Next node is this nodes next sibling
        str << li_close
      else # Moving back up the tree, relative_level_of_next.abs levels
        str << li_close
        str << ((l_close << li_close) *  relative_level_of_next.abs)
      end
      str
    end
  end  

Here's the method itself (the set is retrieved in one go with #full_set, there is no more DB access after that):

# traverse {|current_node, level, relative_level_of_next| block }
#
# Iterates through the nodes in the nested set rooted at the receiver, in a pre-order
# traversal. For each node visited, it yields that node, its level, and the relative level
# of the next node to be visited. The values returned by successive block executions are
# accumulated (in an array by default) and returned at the end 
# 
# The receiver always has a level of 0.
#
# If relative_level_of_next is 1, the next node is gauranteed to be the first (i.e. leftmost) child
# of the current node
# If it is 0, the next node is the next sibling of the the current node
# Otherwise, relative level is a negative integer, indicating how many levels above the current node
# the next node is
def traverse(accum = [])
  level = 0
  pre_order_array = full_set 
  (pre_order_array.size - 1).times do |i|
    curr_node = pre_order_array[i]
    next_node = pre_order_array[i + 1]
    distance = next_node.lft - curr_node.lft
    relative_level_of_next = -1 * (distance - 2)
    accum << yield(curr_node, level, relative_level_of_next)
    level += relative_level_of_next
  end
  # Last node is handled differently to the rest: since there is no next node,
  # the relative_level_of_next is the number of levels back up to the root level
  relative_level_of_next =  - 1 * (pre_order_array.first.rgt - pre_order_array.last.rgt)
  accum << yield(pre_order_array.last, level, relative_level_of_next)
end

Please feel free to contact me : briansheehan78<AT>gmail.com