lib/netzke/tree/base.rb
module Netzke
module Tree
# Ext.tree.Panel-based component with the following features:
#
# * CRUD operations
# * Persistence of node expand/collapse state
# * Node reordering by DnD
#
# Client-side methods are documented here: http://api.netzke.org/client/classes/Netzke.Tree.Base.html.
#
# == Simple example
#
# class Files < Netzke::Tree::Base
# def configure(c)
# super
# c.model = "FileRecord"
# c.columns = [
# {name: :name, xtype: :treecolumn}, # this column will show tree nodes
# :size
# ]
# end
# end
#
# == Instance configuration
#
# The following config options are supported:
#
# [model]
#
# Name of the ActiveRecord model that provides data to this Tree, e.g. "FileRecord"
# The model must respond to the following methods:
#
# * TreeModel.root - the root record
# * TreeModel#children - child records
#
# Note that the awesome_nested_set gem implements the above, so, feel free to use it.
#
# [columns]
#
# An array of columns to be displayed in the tree. See the "Columns" section in the `Netzke::Grid::Base`.
# Additionally, you probably will want to specify which column will have the tree nodes UI by providing the
# `xtype` config option set to `:treecolumn`.
#
# [root]
#
# By default, the component will pick whatever record is returned by `TreeModel.root`, and use it as the root
# record. However, sometimes the model table has multiple root records (whith `parent_id` set to `nil`), and all
# of them should be shown in the panel. To achive this, you can define the `root` config option,
# which will serve as a virtual root record for those records. You may set it to `true`, or a hash of
# attributes, e.g.:
#
# c.root = {name: 'Root', size: 1000}
#
# Note, that the root record can be hidden from the tree by specifying the `Ext.tree.Panel`'s `root_visible`
# config option set to `false`, which is probably what you want when you have multiple root records.
#
# [scope]
#
# A Proc or a Hash used to scope out grid data. The Proc will receive the current relation as a parameter and must
# return the modified relation. For example:
#
# class Books < Netzke::Grid::Base
# def configure(c)
# super
# c.model = Book
# c.scope = lambda {|r| r.where(author_id: 1) }
# end
# end
#
# Hash is being accepted for conivience, it will be directly passed to `where`. So the above can be rewritten as:
#
# class Books < Netzke::Grid::Base
# def configure(c)
# super
# c.model = Book
# c.scope = {author_id: 1}
# end
# end
#
# [drag_drop]
#
# Enables drag and drop in the tree.
#
# == Persisting nodes' expand/collapse state
#
# If the model includes the `expanded` DB field, the expand/collapse state will get stored in the DB.
autoload :Endpoints, 'netzke/tree/endpoints'
class Base < Netzke::Base
NODE_ATTRS = {
boolean: %w[leaf checked expanded expandable qtip qtitle],
string: %w[icon icon_cls href href_target qtip qtitle]
}
include Netzke::Grid::Configuration
include Netzke::Grid::Endpoints
include Netzke::Grid::Services
include Netzke::Grid::Actions
include Netzke::Grid::Components
include Netzke::Grid::Permissions
include Netzke::Basepack::Columns
include Netzke::Basepack::Attributes
include Netzke::Basepack::DataAccessor
include Netzke::Tree::Endpoints
client_class do |c|
c.extend = "Ext.tree.Panel"
c.require :extensions
c.mixins << "Netzke.Grid.Columns"
c.mixins << "Netzke.Grid.EventHandlers"
c.translate *%w[are_you_sure confirmation]
end
action :search do |c|
c.excluded = true
end
class << self
def server_side_config_options
super + [:model]
end
# Borrow translations from the grid for now
def i18n_id
"netzke.grid.base"
end
end
def columns
add_node_interface_methods(super)
end
# Overrides Grid::Services#get_records
def get_records(params)
if params[:id] == 'root'
model_adapter.find_root_records(config[:scope])
else
model_adapter.find_record_children(model_adapter.find_record(params[:id]), config[:scope])
end
end
# Overrides Grid::Services#read so we send records as key-value JSON (instead of array)
def read(params = {})
{}.tap do |res|
records = get_records(params)
res["children"] = records.map{|r| node_to_hash(r, final_columns).netzke_literalize_keys}
res["total"] = count_records(params) if config[:enable_pagination]
end
end
def node_to_hash(record, columns)
model_adapter.record_to_hash(record, columns).tap do |hash|
if is_node_expanded?(record)
hash["children"] = model_adapter.find_record_children(record, config[:scope]).map {|child| node_to_hash(child, columns).netzke_literalize_keys}
end
end
end
def is_node_expanded?(record)
record.respond_to?(:expanded) && record.expanded?
end
# Overrides `Grid::Configuration#configure_client`
def configure_client(c)
super
c.root ||= model_adapter.record_to_hash(model_adapter.root, final_columns).netzke_literalize_keys
end
private
def update_record(record, attrs)
if config.drag_drop && attrs['parentId']
parent_id = attrs['parentId'] == 'root' ? nil : attrs['parentId']
model_adapter.set_record_value_for_attribute(record, { name: 'parent_id' }, parent_id)
end
super
end
# Adds attributes known to Ext.data.NodeInterface as meta columns (only those our model responds to)
def add_node_interface_methods(columns)
columns.clone.tap do |columns|
NODE_ATTRS.each do |type, attrs|
add_node_interface_methods_by_type!(columns, attrs, type)
end
end
end
def add_node_interface_methods_by_type!(columns, attrs, type)
attrs.each do |a|
next unless model_adapter.model_respond_to?(a.to_sym)
columns << {type: type, name: a, meta: true}
end
end
end
end
end