Define the “#uses_yui_tree” class
method, which sets up instance variables “@yui_tree_options
”
and “@uses_yui_tree
” within any controller calling the method.
See “self.included” in the plugin code for more information.
The “#uses_yui_tree”
method can be passed two optional options hashes. The first specifies
options for the YUI tree. The second is passed directly to the Rails before_filter
method as filter options and this lets you state conditions for which “#uses_yui_tree” applies
(e.g. “:only => ...
” for only certain actions or
“:except => ...
” for all actions except those listed). See
the Rails filter API documentation
for full details.
Most options for YUI trees can be specified in any of three places:
In the YAML configuration file.
In a call to “#uses_yui_tree” from a controller.
In a call to the “YuiTree::YuiTreeHelper#yui_tree” helper method from a view.
Options in the YAML file are application-global and overriden by those specified in a controller, which are controller-global and in turn overriden by local options given to helper method calls in views.
Some options only make sense if specified in the YAML configuration file and so cannot be specified elsewhere. When installed the plugin writes a default configuration file in “config/yui_tree.yml”; see this for further information on the option meainings:
:version
:javascript_base_uri
:additional_yui_javascripts
(an optional item)
The following options usually only make sense if given in the YAML configuration file and have default values written there, but they can be specified per-controller or in each helper call in views if you really want to do that:
:xhr_timeout
:div_class
(the class assigned to any DIV into which a YUI
tree is built)
These options can appear in the YAML file or here only, but not in helper calls for individual views:
:body_class
(a class name added to any existing class name(s)
on the BODY element of the HTML document) - if "yui-skin-sam
"
is used then CSS files for this skin will be included automatically, else
you must manually ensure that all relevant CSS resources are included.
Other options must be specified in calls to “#uses_yui_tree”, or to a helper function like “YuiTree::YuiTreeHelper#yui_tree” as they have no default values. You could put them into the YAML file but due to the nature of the options it is very likely that you’ll specify them in method calls instead. In fact it’s more likely that you will want to specify them in calls made in views rather than here in “#uses_yui_tree”, but you can establish Controller-global defaults by specifying the options here if you find this useful.
:root_model
If your model supports tree/nested-set like behaviour - for example, if
your model declares “acts_as_nested_set
” via the Awesome Nested Set
plugin - then specify the root model class here (e.g specify
“:root_model => Location
” or “:root_model =>
Category
”) as a Controller-global default. Nested set behaviour
assumes a class method called “roots” which takes no parameters and returns
an array of model instances which are tree roots; and an instance method
“leaf?” which also takes no parameters and returns ‘true
’ if
the instance has no children (is a leaf node), else returns
‘false
’.
The root model is used by YUI tree creation code (e.g. see helper method
“YuiTree::YuiTreeHelper#yui_tree”)
to generate the root data set for the initial tree view. Alternatively,
specify this when you make the call to e.g. “YuiTree::YuiTreeHelper#yui_tree”
if you don’t want to set a default here. By default the helper code calls
an instance method called “name” to obtain label text for the tree nodes.
Use the “:root_title_method
” option (see below) to specify a
different method name if necessary.
The root and child items can be sorted in various ways. See method “yui_tree_handled_xhr_request?” method for further information.
If you want to supply the collection of root objects yourself, rather than
using a “:root_model
” (see above) and its “roots” class
method, you can do so when building the tree in a view through a call to
the “YuiTree::YuiTreeHelper#yui_tree”
helper method. See its “:root_collection
” option for details.
Whenever you need to assemble an array of objects to be used as roots or
children, you must always build those objects with the “YuiTree.make_node_object”
method.
:root_title_method
If using “:root_model
” (see above) but with a model which uses
something other than a “name” method to return text for showing in tree
nodes, then specify the alternative method’s name here as a symbol. It must
be an instance method for the model in question and should return a
non-empty string.
:xhr_url_method
Name of a method which will be invoked to get the prefix
of a URL used to fetch more tree data (when parent nodes are expanded for
the first time). This is usually a path to a controller’s ‘index’ action,
e.g. “:locations_path
” or “:categories_path
”. The
corresponding Controller action must return an array of child data. It can
use the “yui_tree_handled_xhr_request?” method to do this or use entirely
custom code. It must return an array of child data as described above (see
“:root_model
”), generating array entries with the “YuiTree.make_node_object”
method. The parent’s database ID is delivered to the controller action via
the “params” hash under key “:tree_parent_id
” and may have a
string, symbol or integer value type.
Some options are likely to be set per-tree but do have default values:
:select_leaf_only
Set to ‘true
’ if leaves (nodes without children) can be
selected but parents cannot, else ‘false
’. Has an internal
default value of ‘false
’ (i.e. can select anything).
A few YUI tree options can be set as global defaults when making calls to “#uses_yui_tree”, but such defaults only make sense if you use a single tree in any of the containing controller’s views. For example, the option giving the ID of enclosing DIV inside which the tree is built falls into this category, since no two elements in a valid HTML document are allowed to have the same ID. It is up to you where you choose to specify these options - here, or individual calls to “YuiTree::YuiTreeHelper#yui_tree”.
:div_id
ID given to the DIV container into which trees are built. Only specify this as a controller-global default if you are only going to use one tree control in any given view, else more than one DIV container for more than one tree view would have the same ID; or make very sure that any views using more than one tree view specify overriding DIV IDs in those views’ calls to “YuiTree::YuiTreeHelper#yui_tree”.
:div_class
As “:div_id
”, but even less frequently used! Usually you will
want application-wide styles to apply to trees via CSS and thus specify a
single application-global class name in the YUI tree plugin’s YAML
configuration file, rather than specifying a controller-global value here,
or even a per-tree value in indivdual calls to “YuiTree::YuiTreeHelper#yui_tree”.
Nonetheless, the facility exists to use any of those three approaches
should they prove useful to you.
:target_form_field_id
Whenever a node is selected, its database ID is written as a value of the
“value
” (sic.) attribute of the HTML element identified by
this option so that a related form submission will contain the selected ID.
The field is usually a hidden INPUT element.
Although the tree has a multiple selection mode (see the documentation for “YuiTree::YuiTreeHelper#yui_tree” for details), by default it assumes that you always want to ultimately let the user select a single item in the tree and have that item’s database ID passed back to a controller in a related form submission.
A value of 0 may indicate that a blank entry is use and has been selected.
See option “:include_blank
” in helper method “YuiTree::YuiTreeHelper#yui_tree”.
An empty string indicates that no nodes were highlighted at all. If you allow such things you should probably interpret this the same as the user explicitly selecting a blank entry. It’s good to allow both as it isn’t too friendly to assume the user will mean that deselecting all nodes means “none/blank”; it’s good to have an explicit entry for that. You can still fault a no-selection form submission from the controller if the target form field value is empty rather than zero, should you wish to do so.
:target_name_field_id
Optional; if included, then when a node is selected, its display text is written as innerHTML inside the identified element. This lets you show the user which node was selected by its text, as well as by the TreeView’s own highlight mechanism. This is handy if you’re worried that a selected node in a collapsed branch remains selected, albeit invisibly - a TreeView implementation quirk, it seems.
If you specify a value for the “:include_blank
” option in a
call to helper method “YuiTree::YuiTreeHelper#yui_tree”,
then note that the specified string will be written into the name field if
either a “blank” tree entry item is explicitly selected, or if all items in
the tree are entirely deselected. This goes back to the idea in
“:target_form_field_id
” above of a zero ID value, or a blank
ID value, being usually treated as the same thing; but again, the
controller can always treat the two conditions as distinct from one another
if it so wishes.
# Location model "acts_as_nested_set"; uses Awesome Nested Set plugin; # thus has "roots" and "leaf?" methods which act as expected. Location # objects have a displayable name property in "name". uses_yui_tree( { :root_model => Location, :xhr_url_method => :locations_path }, { :only => [ :edit, :update ] } )
This prepares the Controller for a YUI tree using a “Location” model. The tree will obtain new data by a JSON XHR request to the URL returned by a call to “locations_path” with “.js” added as a suffix. Only the “:edit” and “:update” actions can use the tree. Listing both of these actions in the “:only” clause is vital since the Controller’s “update” code might re-render the “edit” view (and thus the tree) because of form validation errors. Alternatively use “:exclude” rather than “:only”, or omit such a clause altogether to prepare all the controller’s views for YUI trees.
An XHR URL method of “locations_path” will (assuming sane routes!) lead to
an “index” action of a controller for the Location model. The index method
must be aware of the YUI tree JSON requests - see
“yui_tree_handled_xhr_request?” or use the usual Rails “respond_to do
|format| ... format.js do ...
” construct.
def index return if yui_tree_handled_xhr_request?( Location ); # ...other processing code... end
A helper method such as “YuiTree::YuiTreeHelper#yui_tree” must be invoked somewhere in the “:edit” action’s view (e.g. in “edit.html.erb”) so that the tree actually gets built:
<%= hidden_field_tag( 'foo', '[currently selected item ID here]' ) %> <%= yui_tree( :target_form_field_id => 'foo' ) %>
# File lib/yui_tree/yui_tree.rb, line 247 def uses_yui_tree( tree_options = {}, filter_options = {} ) proc = Proc.new do | c | c.instance_variable_set( :@yui_tree_options, tree_options ) c.instance_variable_set( :@uses_yui_tree, true ) end before_filter( proc, filter_options ) # See the dummy, static version of "yui_tree_handled_xhr_request?" below # for documentation. # self.class_eval %Q( define_method( :yui_tree_handled_xhr_request? ) do | model, *optional | result = false # WARNING: By default Rails treats XHR requests as ".js" format, if # no other format indication (e.g. filename extension, very specific # HTTP Accept header) exists. As a result this code can be run for # other XHR requests coming into your controller even though you # think it ought not to be. Hence the "params.has_key?" check, to try # and guard against accidental execution. if ( request.xhr? && params.has_key?( :tree_parent_id ) ) respond_to do | format | format.js do # Use find_by_id() rather than just find() to avoid an exception if # the item cannot be located. parent = model.find_by_id( params[ :tree_parent_id ] ) if ( parent.nil? ) render :json => [] else children = parent.children() if ( model.respond_to?( :apply_default_sort_order ) ) model.apply_default_sort_order( children ) end children.map!() do | child | YuiTree::make_node_object( child.id, child.send( optional[ 0 ] || YuiTree::YUI_TREE_DEFAULT_TITLE_METHOD ), child.send( optional[ 1 ] || :leaf? ) ) end render :json => children end result = true end # format.js do end # respond_to do | format | end # if ( request.xhr? ... ) result # Can't do "return result"; leads to ThreadError exception. end # define_method... ) end
When the tree calls back via AJAX and needs some children to fill in a tree branch, call here to return appropriate data. Pass in the model to find from (e.g. Category, Location - a class, not a string or symbol), then optionally the name of the model instance method used to obtain the text to show for each node (default is “name”) and the name of a method used to find out if the item is a leaf (default is “leaf?”) - it should return ‘true’ if so, else ‘false’. Models which “act_as_nested_set” using the Awesome Nested Set plug-in are compatible with the defaults.
If you want the tree order items automatically sorted then add a class method called “apply_default_sort_order” to your model. This is passed an array of model object instances and should sort the array in-place (e.g. with the Array “sort!” method). The method’s return value is ignored. If the model has no “apply_default_sort_order” method then the collection retrieved from the database is left in default sort order. If you have simple requirements then using a call to Rails’ “default_scope” method from your model is the most efficient way to achieve sorted results. For example, in your model issue:
default_scope( { :order => 'name DESC' } )
…to have all collections of that model returned by finder methods sorted in descending order by a “name” field by default. This applies to all finds done by your application, not just those related to YUI trees, so use the “apply_default_sort_order” approach to restrict the ordering to YUI tree views only.
A controller example inside an action which gets invoked when the XHR request comes in:
def action_name return if yui_tree_handled_xhr_request?( Location, :title, :isLeaf? ) # ...rest of normal action code... end
This would handle YUI tree requests for a Location model using method “title” (rather than the default of “name”) to obtain the human-readable names of locations and method “isLeaf?” (rather than the default of “leaf?”) to determine whether or not the item is at the end of a branch.
# File lib/yui_tree/yui_tree.rb, line 354 def yui_tree_handled_xhr_request? # This method is generated dynamically at run-time, the dynamic version # overwriting this one. The method here exists purely so that the RDoc # documentation generator will create documentation for the method. # Please see the implementation of "uses_yui_tree" above to see the code # for the dynamically generated method. end