app/helpers/the_sortable_tree_helper.rb
module TheSortableTreeHelper
# Publicated by MIT
# Nested Set View Helper
# Ilya Zykin, zykin-ilya@ya.ru, Russia [Ivanovo, Saint Petersburg] 2009-2014
# github.com/the-teacher
# Default renderers
TREE_RENDERERS = {
:tree => RenderTreeHelper,
:sortable => RenderSortableTreeHelper,
:expandable => RenderExpandableTreeHelper,
:nested_options => RenderNestedOptionsHelper
}
###############################################
# Common Base Methods
###############################################
def define_class_of_elements_of tree
case
when tree.is_a?(ActiveRecord::Relation) then tree.name.to_s.underscore.downcase
when tree.empty? then nil
else tree.first.class.to_s.underscore.downcase
end
end
def build_tree_html context, render_module, options = {}
render_module::Render::render_node(self, options)
end
###############################################
# Shortcuts
###############################################
def just_tree tree, options = {}
build_server_tree(tree, { :type => :tree }.merge!(options))
end
def sortable_tree tree, options = {}
build_server_tree(tree, { :type => :sortable }.merge!(options))
end
def nested_options tree, options = {}
build_server_tree(tree, { :type => :nested_options }.merge!(options))
end
def expandable_tree tree, options = {}
build_server_tree(tree, { :type => :expandable }.merge!(options))
end
###############################################
# Server Side Render Tree Helper
###############################################
def build_server_tree(tree, options= {})
result = ''
tree = Array.wrap tree
opts = {
# node and base node params
:id => :id, # node id field
:title => :title, # name of title fileld
:node => nil, # node
# base options
:type => :tree, # tree type
:root => false, # is it root node?
:level => 0, # recursion level
:namespace => [], # :admin
# BOOST! hash
:boost => {}
}.merge!(options)
# Basic vars
root = opts[:root]
node = opts[:node]
# namespace prepare [Rails require]
opts[:namespace] = Array.wrap opts[:namespace]
# Module with **Render** class
opts[:render_module] = TREE_RENDERERS[opts[:type]] unless opts[:render_module]
# Define tree class
opts[:klass] = define_class_of_elements_of(tree) unless opts[:klass]
# BOOST PATCH (BUILD ONCE)
# solution of main perfomance problem
# magick index-hash, which made me happy!
# Performance comparison
# Boost OFF: 8000 nodes, 3 levels => (Views: 176281.9ms | ActiveRecord: 189.8ms)
# Boost ON: 8000 nodes, 3 levels => (Views: 8987.8ms | ActiveRecord: 141.6ms)
if opts[:boost].empty?
tree.each do |item|
num = item.parent_id || 0
opts[:boost][num.to_s] = [] unless opts[:boost][num.to_s]
opts[:boost][num.to_s].push item
end
end
unless node
# RENDER ROOTS
roots = opts[:boost]['0']
# Boost OFF
# roots = tree.select{ |node| node.parent_id.blank? }
# define roots, if it's need
if roots.nil? && !tree.empty?
# Looks like the new algo really does what we need (28 sept. 2016)
# Thanks to: https://github.com/patrick-nits
#
ids = tree.map(&:id)
opt_ids = opts[:boost].keys.map(&:to_i)
candidate_ids = (ids + opt_ids).uniq - (ids & opt_ids) # xor
roots = candidate_ids.map {|c| opts[:boost][c.to_s]}.compact.flatten
end
# children rendering
if roots
roots.each do |root|
_opts = opts.merge({ :node => root, :root => true, :level => opts[:level].next, :boost => opts[:boost] })
result << build_server_tree(tree, _opts)
end
end
else
# RENDER NODE'S CHILDREN
children_res = ''
children = opts[:boost][node.id.to_s]
# Boost OFF
# children = tree.select{ |_node| _node.parent_id == node.id }
opts.merge!({ :has_children => children.blank? })
unless children.nil?
children.each do |elem|
_opts = opts.merge({ :node => elem, :root => false, :level => opts[:level].next, :boost => opts[:boost] })
children_res << build_server_tree(tree, _opts)
end
end
result << build_tree_html(self, opts[:render_module], opts.merge({ :root => root, :node => node, :children => children_res }))
end
raw result
end
end