Module YuiTree::YuiTreeHelper
In: vendor/plugins/yui_tree/lib/yui_tree.rb

The helper module - methods for use within views.



YUI_TREE_DEFAULT_BASE_URI = '//'.freeze   Default option values should all come from the configuration YAML data, but the user may have elected to delete entries or omit the entire file.
YUI_TREE_DEFAULT_BODY_CLASS = 'yui-skin-sam'.freeze
YUI_TREE_DEFAULT_DIV_CLASS = 'yui-tree-container'.freeze
YUI_MISSING_OPTS_ERROR = 'You must give a value for the ":%s" key of the options hash in "yui_tree()"'.freeze

Public Instance methods

Include CSS and JavaScript related to the YUI tree if it is used by the current view (established by a “YuiTree::ClassMethods.uses_yui_tree“ call made within the Controller). Invoke this helper within the HEAD section of an XHTML view using, for example, this line of code:

  <%= include_yui_tree_if_used -%>

Note that a trailing newline is output so you can call the helper with the “-%>” closing ERB tag (as shown in the previous paragraph) to avoid inserting a single blank line into your output in the event that the plugin is not used by the current view.

If you like to keep indentation in your rendered HTML files, you may want any lines output by this plugin to be indented too. Pass an optional string to the method to cause all output from this call to be prefixed accordingly.


     # File vendor/plugins/yui_tree/lib/yui_tree.rb, line 430
430:     def include_yui_tree_if_used( line_prefix = nil )
431:       yui_tree_init( line_prefix ) if using_yui_tree?
432:     end

Returns ‘true’ if configured to use the YUI tree control for the view related to the current request. See the “uses_yui_tree“ class method for more information.


     # File vendor/plugins/yui_tree/lib/yui_tree.rb, line 438
438:     def using_yui_tree?
439:       ! @uses_yui_tree.nil?
440:     end

Build a YUI tree at the point where the method is called - e.g. place within the body of an XHTML document using “<%= yui_tree(…) %>”. By default the tree has no option buttons next to entries and is designed to allow the selection of any single item from within it.

An options hash must be passed. It may contain any of the options specified as available in a call to “YuiTree::ClassMethods.uses_yui_tree“ and must at least contain values for the following keys unless they have been given values in the YAML configuration file (uncommon) or the view’s corresponding controller’s call to “YuiTree::ClassMethods.uses_yui_tree“ (more common):

  • :xhr_url_method
  • :target_form_field_id
  • :select_leaf_only
  • :root_model or :root_collection (see below)
  • :root_title_method (perhaps, if using :root_model)
  • :root_collection - if your model does not match the characteristics described for option “:root_model“ in the “YuiTree::ClassMethods.uses_yui_tree“ method documentation, then you must provide an array of objects defining the roots of the tree. Construct array entries by calling “YuiTree::make_node_object“. The resulting collection is later turned into a JavaScript-safe array by a wider call to “to_json“ on a JavaScript options hash which gets created by the YUI tree instantiation helper code; you don’t need to worry about HTML or JavaScript escaping of any values yourself.

The following option values can be specified technically in any of the locations described by the documentation for the “YuiTree::ClassMethods.uses_yui_tree“ method, but it is very uncommon to use them anywhere other than a call here, to “yui_tree“. Each of the items is optional and modifies tree behaviour in some way. Some of the options work together, so use of one may mean you must also use another. Any such relationships are described below:

:include_blank:A string if you want that string to be included at the top of the roots of the tree as an item that indicates nothing/none/blank - an ID of zero is associated with it. This option works whether you use the plugin’s generation of the root dataset via “:root_model“ or if you generate your own dataset and provide it through “:root_collection“. Of course, you can always add your own blank entry/entries in the latter case, but using the “:include_blank“ might be more convenient and keep your code clean.

The reason you pass a string rather than, say, a boolean is so that you can pass any relevant message, including one looked up from an internationalisation locale file (assuming Rails >= 2.3.x).

Note that this string is used if all nodes are deselected so that none are highlighted, and if the “:target_name_field_id“ option has been set up; the string is written as innerHTML of that name field.

:exclude:An ID (as an integer or string) or an object with a method ‘id’ which returns its ID, or an array of either of these types (they can be mixed in the same array). Specifies items which will not be included in the tree even if the Controller returns them at any point. This works at the node addition level in JS, so it is *not a security feature*. If you don’t want something to be seen by a user, don’t let the Controller send it out in the first place. The exclusion feature is useful if using, say, a tree to assign a parent to an existing item within a hierarchy - you don’t want to let the user try and assign the item itself as a parent, so exclude it.

If you exclude a non-leaf item, it will be shown in the tree but will not be selectable. This way, children can still be selected. At present the code is “sort of” clever enough to know if no children are going to be present due to exclusions, in that it will try to fetch child nodes but when they all get excluded the Yahoo tree view itself will stop trying further and change the node indicator on the parent to being a branch, rather than a toggle control. It does mean one technically unnecessary AJAX request and associated UI update delay though, but it’s a much simpler mechanism than trying to work such things out up-front.

:expand:As “:exclude“, but specifies nodes which will be automatically expanded whenever they are added to the tree. Node that if you add a non-root node to the exclusions list, you must also add any if its parents (i.e. its complete set of ancestors) back to the root if you want that whole branch to be expanded automatically. Otherwise the branch starting from the child will only automatically expand if the user expands the parent node which contains it.
:highlight:As “:exclude“ but only takes a single item not an array; specifies an item to be highlighted when the tree is created. Once highlighted initially, the item is forgotten and will not be auto-highlighted again. If a non-root item then it will only be highlighted when the branch containing it is opened; in general you will probably want to use the “:expand“ option in cojunction with “:highlight“ for non-root nodes.
:multiple:A boolean (defaults to ‘false’) which says that multiple items may be selected in the tree. See below for more.
:multi_expand:An optional value; by default it inherits whatever has been set for the “multiple” option (see above) and thus defaults to ‘false’ if neither is present in the caller’s options hash. The option changes the expansion behaviour of nodes in the tree. Normally, a multiple selection tree allows multiple nodes to be open while a single selection tree only allows one ‘branch’ to be visible at any time; if a user opens a new branch, the others are closed. If you want to allow any number of branches to be open, set the option to ‘true’. Although setting this to ‘false’ and :multiple to ‘true’ is allowed and does work, this is discouraged as the result is likely to be very confusing for users.
:form_leaf_nodes_only:If ‘true’, only the IDs of leaf nodes will be written into the form field identified by the mandatory “:target_form_field_id“ option. Defaults to ‘false’ so that any selected (highlighted) node is included, leaf or otherwise.
:data_for_xhr_call:An optional string which if included will be added to the parameters used when the XHR call to retrieve more node items is made. If using the built-in “YuiTree::ClassMethods.yui_tree_handled_xhr_request?“ method then this is of no use, but if you write a custom handler then the data string provides a crude but effective way of passing in external data. Usually, the data is used as some kind of filter, restricting the range of nodes which the XHR handler method would otherwise have returned. Note that the string will be run through the ‘j’ helper to make it JS-safe, so some characters may not be treated quite how you expect - avoid literal single quotes, double quotes and backslashes in particular. The string will appear in the params hash under the key “:data“. An empty string given here is treated the same way as if the option had been completely omitted.

The options hash *must not* contain any of the following keys since this would interfere with values set for those same options elsewhere and provoke undefined behaviour (one part of your view would disagree with another part of your view). If present, these keys will be deleted.

  • :body_class

Any other YUI tree options not listed above are irrelevant here.

Note that you must *never mix* the use of options “:root_model“ and “:root_collection“ between this call and any other places where options may be specified; if you do, results will be undefined.

Multiple selection trees

If ‘:multiple’ is ‘true’ the tree allows multiple check boxes to be selected. Selecting a child does not cause its parents to change state by default - the user is free to choose any collection of nodes without constraint - but that can be changed with options listed below.

Option key “:highlight“ now begins to work as “:expand“ or “:exclude“ in that it can take a single item or an array of items to be highlighted. If you use “:expand“ to auto-expand highlighted nodes, remember that you need to specify all ancestors of any non-root node you want expanded.

The form field specified in “:target_form_field_id“ has its value populated using a comma-separated list of IDs of selected items. The container specified in “:target_name_field_id“, if any, will have its value populated using a space-separated list of names of selected items. You can customise this list of names with the following extra options only relevant to multiple selection trees using “:target_name_field_id“:

:name_field_separator:Text to use as a separator between items in the name field. Defaults to a single space. Must be JS single quoted string safe. May be HTML (e.g. “
:name_include_parents:If you want to include the names of all parent nodes whenever a node’s label is used for the name field so that each text entry reflects the whole of the branch of the tree used to reach the node in question, then set this option to a string which is used as a separator between each of the parent items. Must be JS single quoted string safe. May be HTML or even an empty string to directly concatenate parent names. By default names of parents are not included - only the label of the individual node is added to the name field.
:name_leaf_nodes_only:If ‘true’, use only labels of leaf nodes for name field text; don’t include others. Useful in conjunction with the “:name_include_parents“ option, since when the latter is in use, individual entries in the name field will already indicate the labels for the complete branch leading to a node so including names of non-leaf items would only lead to duplication. By default names of all nodes are included.

Once multiple items are selectable, it becomes desirable to be able to control the YUI tree’s highlight propagation features - that is, when a node is selected, should its parent and/or child nodes be automatically selected as well? The following options control this behaviour. The settings apply to every node in the tree.

:propagate_up:If ‘true’, selecting any node causes all parent nodes on the same branch to be selected, if any. By default this option is disabled.
:propagate_down:If ‘true’, selecting any node causes all child nodes in the tree below this node, if any, to be selected. By default this option is disabled.


     # File vendor/plugins/yui_tree/lib/yui_tree.rb, line 650
650:     def yui_tree( options )
652:       # Reject options which are explicitly not allowed here.
654:       options.delete( :body_class )
656:       # Merge default from-YAML options, options given in the "uses_yui_tree"
657:       # call and mandatory passed-in options for this method. We store the view
658:       # options in the main options store to override previously set defaults.
660:       defaults = YuiTree.default_options.merge( @yui_tree_options || {} )
661:       options  = defaults.merge( options )
663:       # Extract items of interest for which default values exist.
665:       xhr_timeout          = options.delete( :xhr_timeout_ms       ) || YUI_TREE_DEFAULT_TIMEOUT
666:       body_class           = options.delete( :body_class           ) || YUI_TREE_DEFAULT_BODY_CLASS
667:       div_class            = options.delete( :div_class            ) || YUI_TREE_DEFAULT_DIV_CLASS
668:       div_id               = options.delete( :div_id               ) || YUI_TREE_DEFAULT_DIV_ID
669:       select_leaf_only     = options.delete( :select_leaf_only     ) || YUI_TREE_DEFAULT_LEAF_ONLY
671:       # Extract other options, raising exceptions if anything mandatory has
672:       # been omitted.
674:       xhr_url_method       = options.delete( :xhr_url_method       ) || raise( YUI_MISSING_OPTS_ERROR % :xhr_url_method       )
675:       target_form_field_id = options.delete( :target_form_field_id ) || raise( YUI_MISSING_OPTS_ERROR % :target_form_field_id )
676:       target_name_field_id = options.delete( :target_name_field_id )
677:       root_model           = options.delete( :root_model           )
678:       root_title_method    = options.delete( :root_title_method    ) || YuiTree::YUI_TREE_DEFAULT_TITLE_METHOD
679:       root_collection      = options.delete( :root_collection      )
680:       include_blank        = options.delete( :include_blank        )
681:       exclude              = options.delete( :exclude              )
682:       expand               = options.delete( :expand               )
683:       highlight            = options.delete( :highlight            )
684:       form_leaf_nodes_only = options.delete( :form_leaf_nodes_only ) || false
685:       data_for_xhr_call    = options.delete( :data_for_xhr_call    )
687:       multiple             = options.delete( :multiple             ) || false
688:       multi_expand         = options.delete( :multi_expand         )
689:       propagate_up         = options.delete( :propagate_up         ) || false
690:       propagate_down       = options.delete( :propagate_down       ) || false
691:       name_field_separator = options.delete( :name_field_separator ) || ' '
692:       name_include_parents = options.delete( :name_include_parents )
693:       name_leaf_nodes_only = options.delete( :name_leaf_nodes_only ) || false
695:       xhr_url      = send( xhr_url_method )
696:       multi_expand = multiple if ( multi_expand.nil? )
698:       # Allow minor API misuse...
700:       multiple     = true  if ( multiple.to_s     == 'true'  )
701:       multiple     = false if ( multiple.to_s     == 'false' )
702:       multi_expand = true  if ( multi_expand.to_s == 'true'  )
703:       multi_expand = false if ( multi_expand.to_s == 'false' )
705:       # Build a root collection if working with the "root_model" option.
707:       if ( root_model.nil? && root_collection.nil? )
708:         raise ( YUI_MISSING_OPTS_ERROR % 'root_model" or ":root_collection' )
709:       elsif root_collection.nil?
710:         root_model      = root_model.to_s.constantize
711:         root_instances  = root_model.roots()
713:         if ( root_model.respond_to?( :apply_default_sort_order ) )
714:           root_model.apply_default_sort_order( root_instances )
715:         end
717:         root_collection = do | root_item |
718:           YuiTree::make_node_object(
719:   ,
720:             root_item.send( root_title_method ),
721:             root_item.leaf?
722:           )
723:         end
724:       end
726:       # Add in a blank entry if necessary.
728:       unless ( include_blank.nil? )
729:         blank_item = YuiTree::make_node_object( '0', include_blank, true )
730:         root_collection.unshift( blank_item )
731:       end
733:       # Build the DIV for the tree then initialise the object for the lengthy
734:       # script which manages the tree. Compile and return the HTML and JS data.
736:       result = content_tag(
737:         :div,
738:         '',
739:         {
740:           :class => "#{ div_class } ygtv-checkbox",
741:           :id    => div_id
742:         }
743:       ) + "\n"
745:       options_hash = {
747:         # Mandatory
749:         :divID              => div_id,
750:         :multiple           => !! multiple,
751:         :rootCollection     => root_collection,
752:         :xhrURL             => xhr_url,
753:         :xhrTimeout         => xhr_timeout,
754:         :exclude            => coerce_to_array( exclude   ),
755:         :expand             => coerce_to_array( expand    ),
756:         :highlight          => coerce_to_array( highlight ),
757:         :formFieldID        => target_form_field_id,
759:         # Optional
761:         :bodyClass          => body_class,
762:         :dataForXHRCall     => data_for_xhr_call,
763:         :selectLeafOnly     => !! select_leaf_only,
764:         :propagateUp        => !! propagate_up,
765:         :propagateDown      => !! propagate_down,
766:         :nameFieldID        => target_name_field_id,
767:         :nameLeafNodesOnly  => !! name_leaf_nodes_only,
768:         :nameFieldSeparator => name_field_separator,
769:         :nameFieldBlank     => include_blank,
770:         :nameIncludeParents => name_include_parents,
771:         :formLeafNodesOnly  => form_leaf_nodes_only,
772:         :multiExpand        => !! multi_expand
774:       }.delete_if { | k, v | v.nil? }
776:       # The use of 'to_json' ensures that strange JS characters get properly
777:       # escaped, among other things.
779:       js = "  var options = #{ options_hash.to_json };\n" <<
780:            "  new uk_org_pond_yui_tree_support( options );"
782:       return ( result << javascript_tag( js ) )
783:     end