1
YUI.add('gallery-treeview', function(Y) {
6
Event = YAHOO.util.Event,
35
YAHOO.widget = YAHOO.widget || {};
37
var Widget = YAHOO.widget;
40
* The treeview widget is a generic tree building tool.
42
* @title TreeView Widget
43
* @requires yahoo, dom, event
44
* @optional animation, json, calendar
45
* @namespace YAHOO.widget
49
* Contains the tree view state data and the root node.
52
* @uses YAHOO.util.EventProvider
54
* @param {string|HTMLElement} id The id of the element, or the element itself that the tree will be inserted into.
55
* Existing markup in this element, if valid, will be used to build the tree
56
* @param {Array|Object|String} oConfig (optional) If present, it will be used to build the tree via method <a href="#method_buildTreeFromObject">buildTreeFromObject</a>
59
var TV = function(id, oConfig) {
61
TV.superclass.constructor.call(this);
63
if (id) { this._treeinit(id); }
65
this.buildTreeFromObject(oConfig);
66
} else if (Lang.trim(this._el.innerHTML)) {
67
this.buildTreeFromMarkup(id);
76
// YAHOO.augment(TV, YAHOO.util.EventProvider);
77
Y.extend(TV, Y.PortBase, {
80
* The id of tree container element
87
* The host element for this tree
95
* Flat collection of all nodes in this tree. This is a sparse
96
* array, so the length property can't be relied upon for a
97
* node count for the tree.
105
* We lock the tree control while waiting for the dynamic loader to return
112
* The animation to use for expanding children, if any
113
* @property _expandAnim
120
* The animation to use for collapsing children, if any
121
* @property _collapseAnim
128
* The current number of animations that are executing
129
* @property _animCount
136
* The maximum number of animations to run at one time.
143
* Whether there is any subscriber to dblClickEvent
144
* @property _hasDblClickSubscriber
148
_hasDblClickSubscriber: false,
151
* Stores the timer used to check for double clicks
152
* @property _dblClickTimer
153
* @type window.timer object
156
_dblClickTimer: null,
159
* A reference to the Node currently having the focus or null if none.
160
* @property currentFocus
161
* @type YAHOO.widget.Node
166
* If true, only one Node can be highlighted at a time
167
* @property singleNodeHighlight
172
singleNodeHighlight: false,
175
* A reference to the Node that is currently highlighted.
176
* It is only meaningful if singleNodeHighlight is enabled
177
* @property _currentlyHighlighted
178
* @type YAHOO.widget.Node
183
_currentlyHighlighted: null,
186
* Sets up the animation for expanding children
187
* @method setExpandAnim
188
* @param {string} type the type of animation (acceptable values defined
189
* in YAHOO.widget.TVAnim)
191
setExpandAnim: function(type) {
192
this._expandAnim = (Widget.TVAnim.isValid(type)) ? type : null;
196
* Sets up the animation for collapsing children
197
* @method setCollapseAnim
198
* @param {string} type of animation (acceptable values defined in
199
* YAHOO.widget.TVAnim)
201
setCollapseAnim: function(type) {
202
this._collapseAnim = (Widget.TVAnim.isValid(type)) ? type : null;
206
* Perform the expand animation if configured, or just show the
207
* element if not configured or too many animations are in progress
208
* @method animateExpand
209
* @param el {HTMLElement} the element to animate
210
* @param node {YAHOO.util.Node} the node that was expanded
211
* @return {boolean} true if animation could be invoked, false otherwise
213
animateExpand: function(el, node) {
215
if (this._expandAnim && this._animCount < this.maxAnim) {
216
// this.locked = true;
218
var a = Widget.TVAnim.getAnim(this._expandAnim, el,
219
function() { tree.expandComplete(node); });
222
this.fire("animStart", {
236
* Perform the collapse animation if configured, or just show the
237
* element if not configured or too many animations are in progress
238
* @method animateCollapse
239
* @param el {HTMLElement} the element to animate
240
* @param node {YAHOO.util.Node} the node that was expanded
241
* @return {boolean} true if animation could be invoked, false otherwise
243
animateCollapse: function(el, node) {
245
if (this._collapseAnim && this._animCount < this.maxAnim) {
246
// this.locked = true;
248
var a = Widget.TVAnim.getAnim(this._collapseAnim, el,
249
function() { tree.collapseComplete(node); });
252
this.fire("animStart", {
266
* Function executed when the expand animation completes
267
* @method expandComplete
269
expandComplete: function(node) {
271
this.fire("animComplete", {
275
// this.locked = false;
279
* Function executed when the collapse animation completes
280
* @method collapseComplete
282
collapseComplete: function(node) {
284
this.fire("animComplete", {
288
// this.locked = false;
292
* Initializes the tree
294
* @parm {string|HTMLElement} id the id of the element that will hold the tree
297
_treeinit: function(id) {
298
this._el = Y.Selector.query('#' + id, null, true);
299
this.id = Y.guid(this._el,"yui-tv-auto-id-");
302
* When animation is enabled, this event fires when the animation
306
* @param {YAHOO.widget.Node} oArgs.node the node that is expanding/collapsing
307
* @param {String} oArgs.type the type of animation ("expand" or "collapse")
309
this.publish("animStart", this);
312
* When animation is enabled, this event fires when the animation
314
* @event animComplete
316
* @param {YAHOO.widget.Node} oArgs.node the node that is expanding/collapsing
317
* @param {String} oArgs.type the type of animation ("expand" or "collapse")
319
this.publish("animComplete", this);
322
* Fires when a node is going to be collapsed. Return false to stop
326
* @param {YAHOO.widget.Node} node the node that is collapsing
328
this.publish("collapse", this);
331
* Fires after a node is successfully collapsed. This event will not fire
332
* if the "collapse" event was cancelled.
333
* @event collapseComplete
335
* @param {YAHOO.widget.Node} node the node that was collapsed
337
this.publish("collapseComplete", this);
340
* Fires when a node is going to be expanded. Return false to stop
344
* @param {YAHOO.widget.Node} node the node that is expanding
346
this.publish("expand", this);
349
* Fires after a node is successfully expanded. This event will not fire
350
* if the "expand" event was cancelled.
351
* @event expandComplete
353
* @param {YAHOO.widget.Node} node the node that was expanded
355
this.publish("expandComplete", this);
358
* Fires when the Enter key is pressed on a node that has the focus
359
* @event enterKeyPressed
361
* @param {YAHOO.widget.Node} node the node that has the focus
363
this.publish("enterKeyPressed", this);
366
* Fires when the label in a TextNode or MenuNode or content in an HTMLNode receives a Click.
367
* The listener may return false to cancel toggling and focusing on the node.
370
* @param oArgs.event {HTMLEvent} The event object
371
* @param oArgs.node {YAHOO.widget.Node} node the node that was clicked
373
this.publish("clickEvent", this);
376
* Fires when the focus receives the focus, when it changes from a Node
377
* to another Node or when it is completely lost (blurred)
378
* @event focusChanged
380
* @param oArgs.oldNode {YAHOO.widget.Node} Node that had the focus or null if none
381
* @param oArgs.newNode {YAHOO.widget.Node} Node that receives the focus or null if none
384
this.publish('focusChanged',this);
387
* Fires when the label in a TextNode or MenuNode or content in an HTMLNode receives a double Click
388
* @event dblClickEvent
390
* @param oArgs.event {HTMLEvent} The event object
391
* @param oArgs.node {YAHOO.widget.Node} node the node that was clicked
394
this.publish("dblClickEvent", {
395
onSubscribeCallback: function() {
396
self._hasDblClickSubscriber = true;
401
* Custom event that is fired when the text node label is clicked.
402
* The node clicked is provided as an argument
406
* @param {YAHOO.widget.Node} node the node clicked
407
* @deprecated use clickEvent or dblClickEvent
409
this.publish("labelClick", this);
412
* Custom event fired when the highlight of a node changes.
413
* The node that triggered the change is provided as an argument:
414
* The status of the highlight can be checked in
415
* <a href="YAHOO.widget.Node.html#property_highlightState">nodeRef.highlightState</a>.
416
* Depending on <a href="YAHOO.widget.Node.html#property_propagateHighlight">nodeRef.propagateHighlight</a>, other nodes might have changed
417
* @event highlightEvent
419
* @param node {YAHOO.widget.Node} the node that started the change in highlighting state
421
this.publish("highlightEvent",this);
427
// store a global reference
428
TV.trees[this.id] = this;
430
// Set up the root node
431
this.root = new Widget.RootNode(this);
434
if (this._initEditor) {
441
* Builds the TreeView from an object.
442
* This is the method called by the constructor to build the tree when it has a second argument.
443
* A tree can be described by an array of objects, each object corresponding to a node.
444
* Node descriptions may contain values for any property of a node plus the following extra properties: <ul>
445
* <li>type: can be one of the following:<ul>
446
* <li> A shortname for a node type (<code>'text','menu','html'</code>) </li>
447
* <li>The name of a Node class under YAHOO.widget (<code>'TextNode', 'MenuNode', 'DateNode'</code>, etc) </li>
448
* <li>a reference to an actual class: <code>YAHOO.widget.DateNode</code></li>
450
* <li>children: an array containing further node definitions</li></ul>
451
* A string instead of an object will produce a node of type 'text' with the given string as its label.
452
* @method buildTreeFromObject
453
* @param oConfig {Array|Object|String} array containing a full description of the tree.
454
* An object or a string will be turned into an array with the given object or string as its only element.
457
buildTreeFromObject: function (oConfig) {
458
var build = function (parent, oConfig) {
459
var i, item, node, children, type, NodeType, ThisType;
460
for (i = 0; i < oConfig.length; i++) {
462
if (Lang.isString(item)) {
463
node = new Widget.TextNode(item, parent);
464
} else if (Lang.isObject(item)) {
465
children = item.children;
466
delete item.children;
467
type = item.type || 'text';
469
switch (Lang.isString(type) && type.toLowerCase()) {
471
node = new Widget.TextNode(item, parent);
474
node = new Widget.MenuNode(item, parent);
477
node = new Widget.HTMLNode(item, parent);
480
if (Lang.isString(type)) {
481
NodeType = Widget[type];
485
if (Lang.isObject(NodeType)) {
486
for (ThisType = NodeType; ThisType && ThisType !== Widget.Node; ThisType = ThisType.superclass.constructor) {}
488
node = new NodeType(item, parent);
495
build(node,children);
501
if (!Lang.isArray(oConfig)) {
506
build(this.root,oConfig);
509
* Builds the TreeView from existing markup. Markup should consist of <UL> or <OL> elements containing <LI> elements.
510
* Each <LI> can have one element used as label and a second optional element which is to be a <UL> or <OL>
511
* containing nested nodes.
512
* Depending on what the first element of the <LI> element is, the following Nodes will be created: <ul>
513
* <li>plain text: a regular TextNode</li>
514
* <li>anchor <A>: a TextNode with its <code>href</code> and <code>target</code> taken from the anchor</li>
515
* <li>anything else: an HTMLNode</li></ul>
516
* Only the first outermost (un-)ordered list in the markup and its children will be parsed.
517
* Nodes will be collapsed unless an <LI> tag has a className called 'expanded'.
518
* All other className attributes will be copied over to the Node className property.
519
* If the <LI> element contains an attribute called <code>yuiConfig</code>, its contents should be a JSON-encoded object
520
* as the one used in method <a href="#method_buildTreeFromObject">buildTreeFromObject</a>.
521
* @method buildTreeFromMarkup
522
* @param id {string|HTMLElement} The id of the element that contains the markup or a reference to it.
524
buildTreeFromMarkup: function (id) {
525
var build = function (markup) {
526
var el, child, branch = [], config = {}, label, yuiConfig;
527
// Dom's getFirstChild and getNextSibling skip over text elements
528
for (el = Dom.getFirstChild(markup); el; el = Dom.getNextSibling(el)) {
529
switch (el.tagName.toUpperCase()) {
533
expanded: Dom.hasClass(el,'expanded'),
534
title: el.title || el.alt || null,
535
className: Lang.trim(el.className.replace(/\bexpanded\b/,'')) || null
537
// I cannot skip over text elements here because I want them for labels
538
child = el.firstChild;
539
if (child.nodeType == 3) {
540
// nodes with only whitespace, tabs and new lines don't count, they are probably just formatting.
541
label = Lang.trim(child.nodeValue.replace(/[\n\t\r]*/g,''));
543
config.type = 'text';
544
config.label = label;
546
child = Dom.getNextSibling(child);
550
if (child.tagName.toUpperCase() == 'A') {
551
config.type = 'text';
552
config.label = child.innerHTML;
553
config.href = child.href;
554
config.target = child.target;
555
config.title = child.title || child.alt || config.title;
557
config.type = 'html';
558
var d = document.createElement('div');
559
d.appendChild(child.cloneNode(true));
560
config.html = d.innerHTML;
561
config.hasIcon = true;
564
// see if after the label it has a further list which will become children of this node.
565
child = Dom.getNextSibling(child);
566
switch (child && child.tagName.toUpperCase()) {
569
config.children = build(child);
572
// if there are further elements or text, it will be ignored.
574
if (YAHOO.lang.JSON) {
575
yuiConfig = el.getAttribute('yuiConfig');
577
yuiConfig = YAHOO.lang.JSON.parse(yuiConfig);
578
config = YAHOO.lang.merge(config,yuiConfig);
589
children: build(child)
598
// var markup = Dom.getChildrenBy(Dom.get(id),function (el) {
599
// var tag = el.tagName.toUpperCase();
600
// return tag == 'UL' || tag == 'OL';
603
var markup = Y.Selector.query('#' + this.id + '> ul,ol');
606
this.buildTreeFromObject(build(markup[0]));
611
* Returns the TD element where the event has occurred
612
* @method _getEventTargetTdEl
615
_getEventTargetTdEl: function (ev) {
616
var target = ev.target,
617
crit = 'td .ygtvrow',
618
targetEl, pnode = target.get('parentNode');
620
if (target && pnode && !pnode.hasClass('ygtvrow')) {
621
target = target.ancestor('td');
622
pnode = target && target.get('parentNode');
629
targetEl = target._node || target;
631
// If it is a spacer cell, do nothing
632
if (/\bygtv(blank)?depthcell/.test(targetEl.className)) {
636
// If it has an id, search for the node number and see if it belongs to a node in this tree.
638
var m = targetEl.id.match(/\bygtv([^\d]*)(.*)/);
639
if (m && m[2] && this._nodes[m[2]]) {
647
*.DOM, Event listener for click events
648
* @method _onClickEvent
651
_onClickEvent: function (ev) {
653
td = this._getEventTargetTdEl(ev),
656
toggle = function (force) {
658
if (force || !node.href) {
664
// For some reason IE8 is providing an event object with
665
// most of the fields missing, but only when clicking on
666
// the node's label, and only when working with inline
667
// editing. This generates a "Member not found" error
668
// in that browser. Determine if this is a browser
669
// bug, or a problem with this code. Already checked to
670
// see if the problem has to do with access the event
671
// in the outer scope, and that isn't the problem.
672
// Maybe the markup for inline editing is broken.
681
node = this.getNodeByElement(td);
686
// exception to handle deprecated event labelClick
687
// @TODO take another look at this deprecation. It is common for people to
688
// only be interested in the label click, so why make them have to test
689
// the node type to figure out whether the click was on the label?
691
// target = Event.getTarget(ev);
694
// if (Dom.hasClass(target, node.labelStyle) || Dom.getAncestorByClassName(target,node.labelStyle)) {
695
if (target.hasClass(node.labelStyle) || target.ancestor(node.labelStyle)) {
696
this.fire('labelClick', node);
699
// If it is a toggle cell, toggle
700
if (/\bygtv[tl][mp]h?h?/.test(td.className)) {
703
if (this._dblClickTimer) {
704
window.clearTimeout(this._dblClickTimer);
705
this._dblClickTimer = null;
707
if (this._hasDblClickSubscriber) {
708
this._dblClickTimer = window.setTimeout(function () {
709
self._dblClickTimer = null;
710
if (self.fire('clickEvent', {event:ev,node:node}) !== false) {
715
if (self.fire('clickEvent', {event:ev,node:node}) !== false) {
724
* Event listener for double-click events
725
* @method _onDblClickEvent
728
_onDblClickEvent: function (ev) {
729
if (!this._hasDblClickSubscriber) {
733
var td = this._getEventTargetTdEl(ev);
738
if (!(/\bygtv[tl][mp]h?h?/.test(td.className))) {
740
this.fire('dblClickEvent', {
742
node: this.getNodeByElement(td)
745
if (this._dblClickTimer) {
746
window.clearTimeout(this._dblClickTimer);
747
this._dblClickTimer = null;
752
* Event listener for mouse over events
753
* @method _onMouseOverEvent
756
_onMouseOverEvent:function (ev) {
758
if ((target = this._getEventTargetTdEl(ev)) && (target = this.getNodeByElement(target)) && (target = target.getToggleEl())) {
759
target.className = target.className.replace(/\bygtv([lt])([mp])\b/gi,'ygtv$1$2h');
763
* Event listener for mouse out events
764
* @method _onMouseOutEvent
767
_onMouseOutEvent: function (ev) {
769
if ((target = this._getEventTargetTdEl(ev)) && (target = this.getNodeByElement(target)) && (target = target.getToggleEl())) {
770
target.className = target.className.replace(/\bygtv([lt])([mp])h\b/gi,'ygtv$1$2');
774
* Event listener for key down events
775
* @method _onKeyDownEvent
778
_onKeyDownEvent: function (ev) {
779
var target = ev.target,
780
targetEl = target._node,
781
node = this.getNodeByElement(targetEl),
787
if (newNode.previousSibling) {
788
newNode = newNode.previousSibling;
790
newNode = newNode.parent;
792
} while (newNode && !newNode._canHaveFocus());
793
if (newNode) { newNode.focus(); }
798
if (newNode.nextSibling) {
799
newNode = newNode.nextSibling;
802
newNode = (newNode.children.length || null) && newNode.children[0];
804
} while (newNode && !newNode._canHaveFocus);
805
if (newNode) { newNode.focus();}
810
if (newNode.parent) {
811
newNode = newNode.parent;
813
newNode = newNode.previousSibling;
815
} while (newNode && !newNode._canHaveFocus());
816
if (newNode) { newNode.focus();}
822
focusOnExpand = function (newNode) {
823
self.unsubscribe('expandComplete',focusOnExpand);
824
moveFocusRight(newNode);
826
moveFocusRight = function (newNode) {
828
if (newNode.isDynamic() && !newNode.childrenRendered) {
829
self.subscribe('expandComplete',focusOnExpand);
835
if (newNode.children.length) {
836
newNode = newNode.children[0];
838
newNode = newNode.nextSibling;
841
} while (newNode && !newNode._canHaveFocus());
842
if (newNode) { newNode.focus();}
845
moveFocusRight(newNode);
851
window.open(node.href,node.target);
853
window.location(node.href);
858
this.fire('enterKeyPressed',node);
862
newNode = this.getRoot();
863
if (newNode.children.length) {newNode = newNode.children[0];}
864
if (newNode._canHaveFocus()) { newNode.focus(); }
868
newNode = newNode.parent.children;
869
newNode = newNode[newNode.length -1];
870
if (newNode._canHaveFocus()) { newNode.focus(); }
875
// case KEY.PAGE_DOWN:
877
case 107: // plus key
879
node.parent.expandAll();
884
case 109: // minus key
886
node.parent.collapseAll();
896
* Renders the tree boilerplate and visible nodes
900
var html = this.root.getHtml(),
903
if (!this._hasEvents) {
904
Y.on('click', this._onClickEvent, el, this);
905
Y.on('dblclick', this._onDblClickEvent, el, this, true);
906
Y.on('mouseover', this._onMouseOverEvent, el, this, true);
907
Y.on('mouseout', this._onMouseOutEvent, el, this, true);
908
Y.on('keydown', this._onKeyDownEvent, el, this, true);
910
this._hasEvents = true;
914
* Returns the tree's host element
916
* @return {HTMLElement} the host element
920
var el = Y.Selector.query('#' + this.id, null, true);
928
* Nodes register themselves with the tree instance when they are created.
930
* @param node {Node} the node to register
933
regNode: function(node) {
934
this._nodes[node.index] = node;
938
* Returns the root node of this tree
940
* @return {Node} the root node
942
getRoot: function() {
947
* Configures this tree to dynamically load all child data
948
* @method setDynamicLoad
949
* @param {function} fnDataLoader the function that will be called to get the data
950
* @param iconMode {int} configures the icon that is displayed when a dynamic
951
* load node is expanded the first time without children. By default, the
952
* "collapse" icon will be used. If set to 1, the leaf node icon will be
955
setDynamicLoad: function(fnDataLoader, iconMode) {
956
this.root.setDynamicLoad(fnDataLoader, iconMode);
960
* Expands all child nodes. Note: this conflicts with the "multiExpand"
961
* node property. If expand all is called in a tree with nodes that
962
* do not allow multiple siblings to be displayed, only the last sibling
966
expandAll: function() {
968
this.root.expandAll();
973
* Collapses all expanded child nodes in the entire tree.
974
* @method collapseAll
976
collapseAll: function() {
978
this.root.collapseAll();
983
* Returns a node in the tree that has the specified index (this index
984
* is created internally, so this function probably will only be used
985
* in html generated for a given node.)
986
* @method getNodeByIndex
987
* @param {int} nodeIndex the index of the node wanted
988
* @return {Node} the node with index=nodeIndex, null if no match
990
getNodeByIndex: function(nodeIndex) {
991
var n = this._nodes[nodeIndex];
992
return (n) ? n : null;
996
* Returns a node that has a matching property and value in the data
997
* object that was passed into its constructor.
998
* @method getNodeByProperty
999
* @param {object} property the property to search (usually a string)
1000
* @param {object} value the value we want to find (usuall an int or string)
1001
* @return {Node} the matching node, null if no match
1003
getNodeByProperty: function(property, value) {
1004
for (var i in this._nodes) {
1005
if (this._nodes.hasOwnProperty(i)) {
1006
var n = this._nodes[i];
1007
if ((property in n && n[property] == value) || (n.data && value == n.data[property])) {
1017
* Returns a collection of nodes that have a matching property
1018
* and value in the data object that was passed into its constructor.
1019
* @method getNodesByProperty
1020
* @param {object} property the property to search (usually a string)
1021
* @param {object} value the value we want to find (usuall an int or string)
1022
* @return {Array} the matching collection of nodes, null if no match
1024
getNodesByProperty: function(property, value) {
1026
for (var i in this._nodes) {
1027
if (this._nodes.hasOwnProperty(i)) {
1028
var n = this._nodes[i];
1029
if ((property in n && n[property] == value) || (n.data && value == n.data[property])) {
1035
return (values.length) ? values : null;
1040
* Returns a collection of nodes that have passed the test function
1041
* passed as its only argument.
1042
* The function will receive a reference to each node to be tested.
1043
* @method getNodesBy
1044
* @param {function} a boolean function that receives a Node instance and returns true to add the node to the results list
1045
* @return {Array} the matching collection of nodes, null if no match
1047
getNodesBy: function(fn) {
1049
for (var i in this._nodes) {
1050
if (this._nodes.hasOwnProperty(i)) {
1051
var n = this._nodes[i];
1057
return (values.length) ? values : null;
1060
* Returns the treeview node reference for an ancestor element
1061
* of the node, or null if it is not contained within any node
1063
* @method getNodeByElement
1064
* @param el {HTMLElement} the element to test
1065
* @return {YAHOO.widget.Node} a node reference or null
1067
getNodeByElement: function(el) {
1069
var p=el, m, re=/ygtv([^\d]*)(.*)/;
1076
return this.getNodeByIndex(m[2]);
1082
if (!p || !p.tagName) {
1087
while (p.id !== this.id && p.tagName.toLowerCase() !== "body");
1093
* When in singleNodeHighlight it returns the node highlighted
1094
* or null if none. Returns null if singleNodeHighlight is false.
1095
* @method getHighlightedNode
1096
* @return {YAHOO.widget.Node} a node reference or null
1098
getHighlightedNode: function() {
1099
return this._currentlyHighlighted;
1104
* Removes the node and its children, and optionally refreshes the
1105
* branch of the tree that was affected.
1106
* @method removeNode
1107
* @param {Node} node to remove
1108
* @param {boolean} autoRefresh automatically refreshes branch if true
1109
* @return {boolean} False is there was a problem, true otherwise.
1111
removeNode: function(node, autoRefresh) {
1113
// Don't delete the root node
1114
if (node.isRoot()) {
1118
// Get the branch that we may need to refresh
1119
var p = node.parent;
1124
// Delete the node and its children
1125
this._deleteNode(node);
1127
// Refresh the parent of the parent
1128
if (autoRefresh && p && p.childrenRendered) {
1136
* wait until the animation is complete before deleting
1137
* to avoid javascript errors
1138
* @method _removeChildren_animComplete
1139
* @param o the custom event payload
1142
_removeChildren_animComplete: function(o) {
1143
this.unsubscribe(this._removeChildren_animComplete);
1144
this.removeChildren(o.node);
1148
* Deletes this nodes child collection, recursively. Also collapses
1149
* the node, and resets the dynamic load flag. The primary use for
1150
* this method is to purge a node and allow it to fetch its data
1151
* dynamically again.
1152
* @method removeChildren
1153
* @param {Node} node the node to purge
1155
removeChildren: function(node) {
1157
if (node.expanded) {
1158
// wait until the animation is complete before deleting to
1159
// avoid javascript errors
1160
if (this._collapseAnim) {
1161
this.subscribe("animComplete",
1162
this._removeChildren_animComplete, this, true);
1163
Widget.Node.prototype.collapse.call(node);
1170
while (node.children.length) {
1171
this._deleteNode(node.children[0]);
1174
if (node.isRoot()) {
1175
Widget.Node.prototype.expand.call(node);
1178
node.childrenRendered = false;
1179
node.dynamicLoadComplete = false;
1185
* Deletes the node and recurses children
1186
* @method _deleteNode
1189
_deleteNode: function(node) {
1190
// Remove all the child nodes first
1191
this.removeChildren(node);
1193
// Remove the node from the tree
1198
* Removes the node from the tree, preserving the child collection
1199
* to make it possible to insert the branch into another part of the
1200
* tree, or another tree.
1202
* @param {Node} node to remove
1204
popNode: function(node) {
1205
var p = node.parent;
1207
// Update the parent's collection of children
1210
for (var i=0, len=p.children.length;i<len;++i) {
1211
if (p.children[i] != node) {
1212
a[a.length] = p.children[i];
1218
// reset the childrenRendered flag for the parent
1219
p.childrenRendered = false;
1221
// Update the sibling relationship
1222
if (node.previousSibling) {
1223
node.previousSibling.nextSibling = node.nextSibling;
1226
if (node.nextSibling) {
1227
node.nextSibling.previousSibling = node.previousSibling;
1230
if (this.currentFocus == node) {
1231
this.currentFocus = null;
1233
if (this._currentlyHighlighted == node) {
1234
this._currentlyHighlighted = null;
1238
node.previousSibling = null;
1239
node.nextSibling = null;
1242
// Update the tree's node collection
1243
delete this._nodes[node.index];
1247
* Nulls out the entire TreeView instance and related objects, removes attached
1248
* event listeners, and clears out DOM elements inside the container. After
1249
* calling this method, the instance reference should be expliclitly nulled by
1250
* implementer, as in myDataTable = null. Use with caution!
1254
destroy : function() {
1255
// Since the label editor can be separated from the main TreeView control
1256
// the destroy method for it might not be there.
1257
if (this._destroyEditor) { this._destroyEditor(); }
1258
var el = this.getEl();
1259
Event.removeListener(el,'click');
1260
Event.removeListener(el,'dblclick');
1261
Event.removeListener(el,'mouseover');
1262
Event.removeListener(el,'mouseout');
1263
Event.removeListener(el,'keydown');
1264
for (var i = 0 ; i < this._nodes.length; i++) {
1265
var node = this._nodes[i];
1266
if (node && node.destroy) {node.destroy(); }
1269
this._hasEvents = false;
1274
* TreeView instance toString
1276
* @return {string} string representation of the tree
1278
toString: function() {
1279
return "TreeView " + this.id;
1283
* Count of nodes in tree
1284
* @method getNodeCount
1285
* @return {int} number of nodes in the tree
1287
getNodeCount: function() {
1288
return this.getRoot().getNodeCount();
1292
* Returns an object which could be used to rebuild the tree.
1293
* It can be passed to the tree constructor to reproduce the same tree.
1294
* It will return false if any node loads dynamically, regardless of whether it is loaded or not.
1295
* @method getTreeDefinition
1296
* @return {Object | false} definition of the tree or false if any node is defined as dynamic
1298
getTreeDefinition: function() {
1299
return this.getRoot().getNodeDefinition();
1303
* Abstract method that is executed when a node is expanded
1305
* @param node {Node} the node that was expanded
1306
* @deprecated use treeobj.subscribe("expand") instead
1308
onExpand: function(node) { },
1311
* Abstract method that is executed when a node is collapsed.
1312
* @method onCollapse
1313
* @param node {Node} the node that was collapsed.
1314
* @deprecated use treeobj.subscribe("collapse") instead
1316
onCollapse: function(node) { },
1319
* Sets the value of a property for all loaded nodes in the tree.
1320
* @method setNodesProperty
1321
* @param name {string} Name of the property to be set
1322
* @param value {any} value to be set
1323
* @param refresh {boolean} if present and true, it does a refresh
1325
setNodesProperty: function(name, value, refresh) {
1326
this.root.setNodesProperty(name,value);
1328
this.root.refresh();
1332
* Event listener to toggle node highlight.
1333
* Can be assigned as listener to clickEvent, dblClickEvent and enterKeyPressed.
1334
* It returns false to prevent the default action.
1335
* @method onEventToggleHighlight
1336
* @param oArgs {any} it takes the arguments of any of the events mentioned above
1337
* @return {false} Always cancels the default action for the event
1339
onEventToggleHighlight: function (oArgs) {
1341
if ('node' in oArgs && oArgs.node instanceof Widget.Node) {
1343
} else if (oArgs instanceof Widget.Node) {
1348
node.toggleHighlight();
1355
/* Backwards compatibility aliases */
1356
var PROT = TV.prototype;
1358
* Renders the tree boilerplate and visible nodes.
1361
* @deprecated Use render instead
1363
PROT.draw = PROT.render;
1365
/* end backwards compatibility aliases */
1369
* Running count of all nodes created in all trees. This is
1370
* used to provide unique identifies for all nodes. Deleting
1371
* nodes does not change the nodeCount.
1372
* @property YAHOO.widget.TreeView.nodeCount
1379
* Global cache of tree instances
1380
* @property YAHOO.widget.TreeView.trees
1388
* Global method for getting a tree by its id. Used in the generated
1390
* @method YAHOO.widget.TreeView.getTree
1391
* @param treeId {String} the id of the tree instance
1392
* @return {TreeView} the tree instance requested, null if not found.
1395
TV.getTree = function(treeId) {
1396
var t = TV.trees[treeId];
1397
return (t) ? t : null;
1402
* Global method for getting a node by its id. Used in the generated
1404
* @method YAHOO.widget.TreeView.getNode
1405
* @param treeId {String} the id of the tree instance
1406
* @param nodeIndex {String} the index of the node to return
1407
* @return {Node} the node instance requested, null if not found
1410
TV.getNode = function(treeId, nodeIndex) {
1411
var t = TV.getTree(treeId);
1412
return (t) ? t.getNodeByIndex(nodeIndex) : null;
1417
* Class name assigned to elements that have the focus
1419
* @property TreeView.FOCUS_CLASS_NAME
1423
* @default "ygtvfocus"
1426
TV.FOCUS_CLASS_NAME = 'ygtvfocus';
1432
var YAHOO = Y.Port(),
1436
* The base class for all tree nodes. The node's presentation and behavior in
1437
* response to mouse events is handled in Node subclasses.
1438
* @namespace YAHOO.widget
1440
* @uses YAHOO.util.EventProvider
1441
* @param oData {object} a string or object containing the data that will
1442
* be used to render this node, and any custom attributes that should be
1443
* stored with the node (which is available in noderef.data).
1444
* All values in oData will be used to set equally named properties in the node
1445
* as long as the node does have such properties, they are not undefined, private or functions,
1446
* the rest of the values will be stored in noderef.data
1447
* @param oParent {Node} this node's parent node
1448
* @param expanded {boolean} the initial expanded/collapsed state (deprecated, use oData.expanded)
1451
YAHOO.widget.Node = function(oData, oParent, expanded) {
1452
if (oData) { this._nodeinit(oData, oParent, expanded); }
1455
// YAHOO.augment(YAHOO.widget.Node, YAHOO.util.EventProvider);
1456
Y.extend(YAHOO.widget.Node, Y.PortBase, {
1459
* The index for this instance obtained from global counter in YAHOO.widget.TreeView.
1466
* This node's child node collection.
1467
* @property children
1473
* Tree instance this node is part of
1480
* The data linked to this node. This can be any object or primitive
1481
* value, and the data can be used in getNodeHtml().
1495
* The depth of this node. We start at -1 for the root node.
1502
* The node's expanded/collapsed state
1503
* @property expanded
1509
* Can multiple children be expanded at once?
1510
* @property multiExpand
1516
* Should we render children for a collapsed node? It is possible that the
1517
* implementer will want to render the hidden data... @todo verify that we
1518
* need this, and implement it if we do.
1519
* @property renderHidden
1522
renderHidden: false,
1525
* This flag is set to true when the html is generated for this node's
1526
* children, and set to false when new children are added.
1527
* @property childrenRendered
1530
childrenRendered: false,
1533
* Dynamically loaded nodes only fetch the data the first time they are
1534
* expanded. This flag is set to true once the data has been fetched.
1535
* @property dynamicLoadComplete
1538
dynamicLoadComplete: false,
1541
* This node's previous sibling
1542
* @property previousSibling
1545
previousSibling: null,
1548
* This node's next sibling
1549
* @property nextSibling
1555
* We can set the node up to call an external method to get the child
1557
* @property _dynLoad
1564
* Function to execute when we need to get this node's child data.
1565
* @property dataLoader
1571
* This is true for dynamically loading nodes while waiting for the
1572
* callback to return.
1573
* @property isLoading
1579
* The toggle/branch icon will not show if this is set to false. This
1580
* could be useful if the implementer wants to have the child contain
1581
* extra info about the parent, rather than an actual node.
1588
* Used to configure what happens when a dynamic load node is expanded
1589
* and we discover that it does not have children. By default, it is
1590
* treated as if it still could have children (plus/minus icon). Set
1591
* iconMode to have it display like a leaf node instead.
1592
* @property iconMode
1598
* Specifies whether or not the content area of the node should be allowed
1607
* If true, the node will alway be rendered as a leaf node. This can be
1608
* used to override the presentation when dynamically loading the entire
1609
* tree. Setting this to true also disables the dynamic load call for the
1618
* The CSS class for the html content container. Defaults to ygtvhtml, but
1619
* can be overridden to provide a custom presentation for a specific node.
1620
* @property contentStyle
1627
* The generated id that will contain the data passed in by the implementer.
1628
* @property contentElId
1634
* Enables node highlighting. If true, the node can be highlighted and/or propagate highlighting
1635
* @property enableHighlight
1639
enableHighlight: true,
1642
* Stores the highlight state. Can be any of:
1644
* <li>0 - not highlighted</li>
1645
* <li>1 - highlighted</li>
1646
* <li>2 - some children highlighted</li>
1648
* @property highlightState
1656
* Tells whether highlighting will be propagated up to the parents of the clicked node
1657
* @property propagateHighlightUp
1662
propagateHighlightUp: false,
1665
* Tells whether highlighting will be propagated down to the children of the clicked node
1666
* @property propagateHighlightDown
1671
propagateHighlightDown: false,
1674
* User-defined className to be added to the Node
1675
* @property className
1692
spacerPath: "http://l.yimg.com/a/i/space.gif",
1693
expandedText: "Expanded",
1694
collapsedText: "Collapsed",
1695
loadingText: "Loading",
1699
* Initializes this node, gets some of the properties from the parent
1701
* @param oData {object} a string or object containing the data that will
1702
* be used to render this node
1703
* @param oParent {Node} this node's parent node
1704
* @param expanded {boolean} the initial expanded/collapsed state
1706
_nodeinit: function(oData, oParent, expanded) {
1708
YAHOO.widget.Node.superclass.constructor.call(this);
1712
this.index = YAHOO.widget.TreeView.nodeCount;
1713
++YAHOO.widget.TreeView.nodeCount;
1714
this.contentElId = "ygtvcontentel" + this.index;
1716
if (Lang.isObject(oData)) {
1717
for (var property in oData) {
1718
if (oData.hasOwnProperty(property)) {
1719
if (property.charAt(0) != '_' && !Lang.isUndefined(this[property]) && !Lang.isFunction(this[property]) ) {
1720
this[property] = oData[property];
1722
this.data[property] = oData[property];
1727
if (!Lang.isUndefined(expanded) ) { this.expanded = expanded; }
1730
* The parentChange event is fired when a parent element is applied
1731
* to the node. This is useful if you need to apply tree-level
1732
* properties to a tree that need to happen if a node is moved from
1733
* one tree to another.
1735
* @event parentChange
1738
this.publish("parentChange");
1740
// oParent should never be null except when we create the root node.
1742
oParent.appendChild(this);
1747
* Certain properties for the node cannot be set until the parent
1748
* is known. This is called after the node is inserted into a tree.
1749
* the parent is also applied to this node's children in order to
1750
* make it possible to move a branch from one tree to another.
1751
* @method applyParent
1752
* @param {Node} parentNode this node's parent node
1753
* @return {boolean} true if the application was successful
1755
applyParent: function(parentNode) {
1760
this.tree = parentNode.tree;
1761
this.parent = parentNode;
1762
this.depth = parentNode.depth + 1;
1764
// @todo why was this put here. This causes new nodes added at the
1765
// root level to lose the menu behavior.
1766
// if (! this.multiExpand) {
1767
// this.multiExpand = parentNode.multiExpand;
1770
this.tree.regNode(this);
1771
parentNode.childrenRendered = false;
1773
// cascade update existing children
1774
for (var i=0, len=this.children.length;i<len;++i) {
1775
this.children[i].applyParent(this);
1778
this.fire("parentChange", this);
1784
* Appends a node to the child collection.
1785
* @method appendChild
1786
* @param childNode {Node} the new node
1787
* @return {Node} the child node
1790
appendChild: function(childNode) {
1791
if (this.hasChildren()) {
1792
var sib = this.children[this.children.length - 1];
1793
sib.nextSibling = childNode;
1794
childNode.previousSibling = sib;
1796
this.children[this.children.length] = childNode;
1797
childNode.applyParent(this);
1799
// part of the IE display issue workaround. If child nodes
1800
// are added after the initial render, and the node was
1801
// instantiated with expanded = true, we need to show the
1802
// children div now that the node has a child.
1803
if (this.childrenRendered && this.expanded) {
1804
this.getChildrenEl().style.display = "";
1811
* Appends this node to the supplied node's child collection
1813
* @param parentNode {Node} the node to append to.
1814
* @return {Node} The appended node
1816
appendTo: function(parentNode) {
1817
return parentNode.appendChild(this);
1821
* Inserts this node before this supplied node
1822
* @method insertBefore
1823
* @param node {Node} the node to insert this node before
1824
* @return {Node} the inserted node
1826
insertBefore: function(node) {
1827
var p = node.parent;
1831
this.tree.popNode(this);
1834
var refIndex = node.isChildOf(p);
1835
p.children.splice(refIndex, 0, this);
1836
if (node.previousSibling) {
1837
node.previousSibling.nextSibling = this;
1839
this.previousSibling = node.previousSibling;
1840
this.nextSibling = node;
1841
node.previousSibling = this;
1843
this.applyParent(p);
1850
* Inserts this node after the supplied node
1851
* @method insertAfter
1852
* @param node {Node} the node to insert after
1853
* @return {Node} the inserted node
1855
insertAfter: function(node) {
1856
var p = node.parent;
1860
this.tree.popNode(this);
1863
var refIndex = node.isChildOf(p);
1865
if (!node.nextSibling) {
1866
this.nextSibling = null;
1867
return this.appendTo(p);
1870
p.children.splice(refIndex + 1, 0, this);
1872
node.nextSibling.previousSibling = this;
1873
this.previousSibling = node;
1874
this.nextSibling = node.nextSibling;
1875
node.nextSibling = this;
1877
this.applyParent(p);
1884
* Returns true if the Node is a child of supplied Node
1886
* @param parentNode {Node} the Node to check
1887
* @return {boolean} The node index if this Node is a child of
1888
* supplied Node, else -1.
1891
isChildOf: function(parentNode) {
1892
if (parentNode && parentNode.children) {
1893
for (var i=0, len=parentNode.children.length; i<len ; ++i) {
1894
if (parentNode.children[i] === this) {
1904
* Returns a node array of this node's siblings, null if none.
1905
* @method getSiblings
1908
getSiblings: function() {
1909
var sib = this.parent.children.slice(0);
1910
for (var i=0;i < sib.length && sib[i] != this;i++) {}
1912
if (sib.length) { return sib; }
1917
* Shows this node's children
1918
* @method showChildren
1920
showChildren: function() {
1921
if (!this.tree.animateExpand(this.getChildrenEl(), this)) {
1922
if (this.hasChildren()) {
1923
this.getChildrenEl().style.display = "";
1929
* Hides this node's children
1930
* @method hideChildren
1932
hideChildren: function() {
1934
if (!this.tree.animateCollapse(this.getChildrenEl(), this)) {
1935
this.getChildrenEl().style.display = "none";
1940
* Returns the id for this node's container div
1942
* @return {string} the element id
1944
getElId: function() {
1945
return "ygtv" + this.index;
1949
* Returns the id for this node's children div
1950
* @method getChildrenElId
1951
* @return {string} the element id for this node's children div
1953
getChildrenElId: function() {
1954
return "ygtvc" + this.index;
1958
* Returns the id for this node's toggle element
1959
* @method getToggleElId
1960
* @return {string} the toggel element id
1962
getToggleElId: function() {
1963
return "ygtvt" + this.index;
1968
* Returns the id for this node's spacer image. The spacer is positioned
1969
* over the toggle and provides feedback for screen readers.
1970
* @method getSpacerId
1971
* @return {string} the id for the spacer image
1974
getSpacerId: function() {
1975
return "ygtvspacer" + this.index;
1980
* Returns this node's container html element
1982
* @return {HTMLElement} the container html element
1985
// return Dom.get(this.getElId());
1986
return document.getElementById(this.getElId());
1990
* Returns the div that was generated for this node's children
1991
* @method getChildrenEl
1992
* @return {HTMLElement} this node's children div
1994
getChildrenEl: function() {
1995
// return Dom.get(this.getChildrenElId());
1996
return document.getElementById(this.getChildrenElId());
2000
* Returns the element that is being used for this node's toggle.
2001
* @method getToggleEl
2002
* @return {HTMLElement} this node's toggle html element
2004
getToggleEl: function() {
2005
// return Dom.get(this.getToggleElId());
2006
return document.getElementById(this.getToggleElId());
2009
* Returns the outer html element for this node's content
2010
* @method getContentEl
2011
* @return {HTMLElement} the element
2013
getContentEl: function() {
2014
// return Dom.get(this.contentElId);
2015
return document.getElementById(this.contentElId);
2020
* Returns the element that is being used for this node's spacer.
2022
* @return {HTMLElement} this node's spacer html element
2025
getSpacer: function() {
2026
return document.getElementById( this.getSpacerId() ) || {};
2031
getStateText: function() {
2032
if (this.isLoading) {
2033
return this.loadingText;
2034
} else if (this.hasChildren(true)) {
2035
if (this.expanded) {
2036
return this.expandedText;
2038
return this.collapsedText;
2047
* Hides this nodes children (creating them if necessary), changes the toggle style.
2050
collapse: function() {
2051
// Only collapse if currently expanded
2052
if (!this.expanded) { return; }
2054
// fire the collapse event handler
2055
var ret = this.tree.onCollapse(this);
2057
if (false === ret) {
2061
ret = this.tree.fire("collapse", this);
2063
if (false === ret) {
2068
if (!this.getEl()) {
2069
this.expanded = false;
2071
// hide the child div
2072
this.hideChildren();
2073
this.expanded = false;
2078
// this.getSpacer().title = this.getStateText();
2080
ret = this.tree.fire("collapseComplete", this);
2085
* Shows this nodes children (creating them if necessary), changes the
2086
* toggle style, and collapses its siblings if multiExpand is not set.
2089
expand: function(lazySource) {
2090
// Only expand if currently collapsed.
2091
if (this.isLoading || (this.expanded && !lazySource)) {
2097
// When returning from the lazy load handler, expand is called again
2098
// in order to render the new children. The "expand" event already
2099
// fired before fething the new data, so we need to skip it now.
2101
// fire the expand event handler
2102
ret = this.tree.onExpand(this);
2104
if (false === ret) {
2108
ret = this.tree.fire("expand", this);
2111
if (false === ret) {
2115
if (!this.getEl()) {
2116
this.expanded = true;
2120
if (!this.childrenRendered) {
2121
this.getChildrenEl().innerHTML = this.renderChildren();
2125
this.expanded = true;
2129
// this.getSpacer().title = this.getStateText();
2131
// We do an extra check for children here because the lazy
2132
// load feature can expose nodes that have no children.
2134
// if (!this.hasChildren()) {
2135
if (this.isLoading) {
2136
this.expanded = false;
2140
if (! this.multiExpand) {
2141
var sibs = this.getSiblings();
2142
for (var i=0; sibs && i<sibs.length; ++i) {
2143
if (sibs[i] != this && sibs[i].expanded) {
2149
this.showChildren();
2151
ret = this.tree.fire("expandComplete", this);
2154
updateIcon: function() {
2156
var el = this.getToggleEl();
2158
el.className = el.className.replace(/\bygtv(([tl][pmn]h?)|(loading))\b/gi,this.getStyle());
2161
// el = Dom.get('ygtvtableel' + this.index);
2162
el = document.getElementById('ygtvtableel' + this.index);
2164
if (this.expanded) {
2165
Dom.replaceClass(el,'ygtv-collapsed','ygtv-expanded');
2167
Dom.replaceClass(el,'ygtv-expanded','ygtv-collapsed');
2173
* Returns the css style name for the toggle
2175
* @return {string} the css class for this node's toggle
2177
getStyle: function() {
2178
if (this.isLoading) {
2179
return "ygtvloading";
2181
// location top or bottom, middle nodes also get the top style
2182
var loc = (this.nextSibling) ? "t" : "l";
2184
// type p=plus(expand), m=minus(collapase), n=none(no children)
2186
if (this.hasChildren(true) || (this.isDynamic() && !this.getIconMode())) {
2187
// if (this.hasChildren(true)) {
2188
type = (this.expanded) ? "m" : "p";
2191
return "ygtv" + loc + type;
2196
* Returns the hover style for the icon
2197
* @return {string} the css class hover state
2198
* @method getHoverStyle
2200
getHoverStyle: function() {
2201
var s = this.getStyle();
2202
if (this.hasChildren(true) && !this.isLoading) {
2209
* Recursively expands all of this node's children.
2212
expandAll: function() {
2213
var l = this.children.length;
2214
for (var i=0;i<l;++i) {
2215
var c = this.children[i];
2216
if (c.isDynamic()) {
2218
} else if (! c.multiExpand) {
2228
* Recursively collapses all of this node's children.
2229
* @method collapseAll
2231
collapseAll: function() {
2232
for (var i=0;i<this.children.length;++i) {
2233
this.children[i].collapse();
2234
this.children[i].collapseAll();
2239
* Configures this node for dynamically obtaining the child data
2240
* when the node is first expanded. Calling it without the callback
2241
* will turn off dynamic load for the node.
2242
* @method setDynamicLoad
2243
* @param fmDataLoader {function} the function that will be used to get the data.
2244
* @param iconMode {int} configures the icon that is displayed when a dynamic
2245
* load node is expanded the first time without children. By default, the
2246
* "collapse" icon will be used. If set to 1, the leaf node icon will be
2249
setDynamicLoad: function(fnDataLoader, iconMode) {
2251
this.dataLoader = fnDataLoader;
2252
this._dynLoad = true;
2254
this.dataLoader = null;
2255
this._dynLoad = false;
2259
this.iconMode = iconMode;
2264
* Evaluates if this node is the root node of the tree
2266
* @return {boolean} true if this is the root node
2268
isRoot: function() {
2269
return (this == this.tree.root);
2273
* Evaluates if this node's children should be loaded dynamically. Looks for
2274
* the property both in this instance and the root node. If the tree is
2275
* defined to load all children dynamically, the data callback function is
2276
* defined in the root node
2278
* @return {boolean} true if this node's children are to be loaded dynamically
2280
isDynamic: function() {
2284
return (!this.isRoot() && (this._dynLoad || this.tree.root._dynLoad));
2290
* Returns the current icon mode. This refers to the way childless dynamic
2291
* load nodes appear (this comes into play only after the initial dynamic
2292
* load request produced no children).
2293
* @method getIconMode
2294
* @return {int} 0 for collapse style, 1 for leaf node style
2296
getIconMode: function() {
2297
return (this.iconMode || this.tree.root.iconMode);
2301
* Checks if this node has children. If this node is lazy-loading and the
2302
* children have not been rendered, we do not know whether or not there
2303
* are actual children. In most cases, we need to assume that there are
2304
* children (for instance, the toggle needs to show the expandable
2305
* presentation state). In other times we want to know if there are rendered
2306
* children. For the latter, "checkForLazyLoad" should be false.
2307
* @method hasChildren
2308
* @param checkForLazyLoad {boolean} should we check for unloaded children?
2309
* @return {boolean} true if this has children or if it might and we are
2310
* checking for this condition.
2312
hasChildren: function(checkForLazyLoad) {
2316
return ( this.children.length > 0 ||
2317
(checkForLazyLoad && this.isDynamic() && !this.dynamicLoadComplete)
2323
* Expands if node is collapsed, collapses otherwise.
2326
toggle: function() {
2327
if (!this.tree.locked && ( this.hasChildren(true) || this.isDynamic()) ) {
2328
if (this.expanded) { this.collapse(); } else { this.expand(); }
2333
* Returns the markup for this node and its children.
2335
* @return {string} the markup for this node and its expanded children.
2337
getHtml: function() {
2339
this.childrenRendered = false;
2341
return ['<div class="ygtvitem" id="' , this.getElId() , '">' ,this.getNodeHtml() , this.getChildrenHtml() ,'</div>'].join("");
2345
* Called when first rendering the tree. We always build the div that will
2346
* contain this nodes children, but we don't render the children themselves
2347
* unless this node is expanded.
2348
* @method getChildrenHtml
2349
* @return {string} the children container div html and any expanded children
2352
getChildrenHtml: function() {
2356
sb[sb.length] = '<div class="ygtvchildren" id="' + this.getChildrenElId() + '"';
2358
// This is a workaround for an IE rendering issue, the child div has layout
2359
// in IE, creating extra space if a leaf node is created with the expanded
2360
// property set to true.
2361
if (!this.expanded || !this.hasChildren()) {
2362
sb[sb.length] = ' style="display:none;"';
2364
sb[sb.length] = '>';
2367
// Don't render the actual child node HTML unless this node is expanded.
2368
if ( (this.hasChildren(true) && this.expanded) ||
2369
(this.renderHidden && !this.isDynamic()) ) {
2370
sb[sb.length] = this.renderChildren();
2373
sb[sb.length] = '</div>';
2379
* Generates the markup for the child nodes. This is not done until the node
2381
* @method renderChildren
2382
* @return {string} the html for this node's children
2385
renderChildren: function() {
2390
if (this.isDynamic() && !this.dynamicLoadComplete) {
2391
this.isLoading = true;
2392
this.tree.locked = true;
2394
if (this.dataLoader) {
2398
node.dataLoader(node,
2400
node.loadComplete();
2404
} else if (this.tree.root.dataLoader) {
2408
node.tree.root.dataLoader(node,
2410
node.loadComplete();
2415
return "Error: data loader not found or not specified.";
2421
return this.completeRender();
2426
* Called when we know we have all the child data.
2427
* @method completeRender
2428
* @return {string} children html
2430
completeRender: function() {
2433
for (var i=0; i < this.children.length; ++i) {
2434
// this.children[i].childrenRendered = false;
2435
sb[sb.length] = this.children[i].getHtml();
2438
this.childrenRendered = true;
2444
* Load complete is the callback function we pass to the data provider
2445
* in dynamic load situations.
2446
* @method loadComplete
2448
loadComplete: function() {
2449
this.getChildrenEl().innerHTML = this.completeRender();
2450
if (this.propagateHighlightDown) {
2451
if (this.highlightState === 1 && !this.tree.singleNodeHighlight) {
2452
for (var i = 0; i < this.children.length; i++) {
2453
this.children[i].highlight(true);
2455
} else if (this.highlightState === 0 || this.tree.singleNodeHighlight) {
2456
for (i = 0; i < this.children.length; i++) {
2457
this.children[i].unhighlight(true);
2459
} // if (highlighState == 2) leave child nodes with whichever highlight state they are set
2462
this.dynamicLoadComplete = true;
2463
this.isLoading = false;
2465
this.tree.locked = false;
2469
* Returns this node's ancestor at the specified depth.
2470
* @method getAncestor
2471
* @param {int} depth the depth of the ancestor.
2472
* @return {Node} the ancestor
2474
getAncestor: function(depth) {
2475
if (depth >= this.depth || depth < 0) {
2479
var p = this.parent;
2481
while (p.depth > depth) {
2489
* Returns the css class for the spacer at the specified depth for
2490
* this node. If this node's ancestor at the specified depth
2491
* has a next sibling the presentation is different than if it
2492
* does not have a next sibling
2493
* @method getDepthStyle
2494
* @param {int} depth the depth of the ancestor.
2495
* @return {string} the css class for the spacer
2497
getDepthStyle: function(depth) {
2498
return (this.getAncestor(depth).nextSibling) ?
2499
"ygtvdepthcell" : "ygtvblankdepthcell";
2503
* Get the markup for the node. This may be overrided so that we can
2504
* support different types of nodes.
2505
* @method getNodeHtml
2506
* @return {string} The HTML that will render this node.
2508
getNodeHtml: function() {
2511
sb[sb.length] = '<table id="ygtvtableel' + this.index + '" border="0" cellpadding="0" cellspacing="0" class="ygtvtable ygtvdepth' + this.depth;
2512
sb[sb.length] = ' ygtv-' + (this.expanded?'expanded':'collapsed');
2513
if (this.enableHighlight) {
2514
sb[sb.length] = ' ygtv-highlight' + this.highlightState;
2516
if (this.className) {
2517
sb[sb.length] = ' ' + this.className;
2519
sb[sb.length] = '"><tr class="ygtvrow">';
2521
for (var i=0;i<this.depth;++i) {
2522
sb[sb.length] = '<td class="ygtvcell ' + this.getDepthStyle(i) + '"><div class="ygtvspacer"></div></td>';
2526
sb[sb.length] = '<td id="' + this.getToggleElId();
2527
sb[sb.length] = '" class="ygtvcell ';
2528
sb[sb.length] = this.getStyle() ;
2529
sb[sb.length] = '"><a href="#" class="ygtvspacer"> </a></td>';
2532
sb[sb.length] = '<td id="' + this.contentElId;
2533
sb[sb.length] = '" class="ygtvcell ';
2534
sb[sb.length] = this.contentStyle + ' ygtvcontent" ';
2535
sb[sb.length] = (this.nowrap) ? ' nowrap="nowrap" ' : '';
2536
sb[sb.length] = ' >';
2537
sb[sb.length] = this.getContentHtml();
2538
sb[sb.length] = '</td></tr></table>';
2544
* Get the markup for the contents of the node. This is designed to be overrided so that we can
2545
* support different types of nodes.
2546
* @method getContentHtml
2547
* @return {string} The HTML that will render the content of this node.
2549
getContentHtml: function () {
2554
* Regenerates the html for this node and its children. To be used when the
2555
* node is expanded and new children have been added.
2558
refresh: function() {
2559
// this.loadComplete();
2560
this.getChildrenEl().innerHTML = this.completeRender();
2563
var el = this.getToggleEl();
2565
el.className = el.className.replace(/\bygtv[lt][nmp]h*\b/gi,this.getStyle());
2573
* @return {string} string representation of the node
2575
toString: function() {
2576
return this._type + " (" + this.index + ")";
2579
* array of items that had the focus set on them
2580
* so that they can be cleaned when focus is lost
2581
* @property _focusHighlightedItems
2582
* @type Array of DOM elements
2585
_focusHighlightedItems: [],
2587
* DOM element that actually got the browser focus
2588
* @property _focusedItem
2595
* Returns true if there are any elements in the node that can
2596
* accept the real actual browser focus
2597
* @method _canHaveFocus
2598
* @return {boolean} success
2601
_canHaveFocus: function() {
2602
return this.getEl().getElementsByTagName('a').length > 0;
2605
* Removes the focus of previously selected Node
2606
* @method _removeFocus
2609
_removeFocus:function () {
2610
if (this._focusedItem) {
2611
Y.Event.detach('blur', this._focusedItem);
2612
this._focusedItem = null;
2615
while ((el = this._focusHighlightedItems.shift())) { // yes, it is meant as an assignment, really
2616
Dom.removeClass(el,YAHOO.widget.TreeView.FOCUS_CLASS_NAME);
2620
* Sets the focus on the node element.
2621
* It will only be able to set the focus on nodes that have anchor elements in it.
2622
* Toggle or branch icons have anchors and can be focused on.
2623
* If will fail in nodes that have no anchor
2625
* @return {boolean} success
2627
focus: function () {
2628
var focused = false, self = this;
2630
if (this.tree.currentFocus) {
2631
this.tree.currentFocus._removeFocus();
2634
var expandParent = function (node) {
2636
expandParent(node.parent);
2637
node.parent.expand();
2642
var root = new Y.Node(self.getEl().firstChild);
2644
var tds = root.queryAll('td');
2646
tds.each(function(node) {
2648
if ((/ygtv(([tl][pmn]h?)|(content))/).test(node.get('className'))) {
2650
node.addClass(YAHOO.widget.TreeView.FOCUS_CLASS_NAME);
2654
var a = node.query('a', null, true);
2658
self._focusedItem = aRef;
2659
a.on('blur', function () {
2660
self.tree.fire('focusChanged', {
2661
oldNode: self.tree.currentFocus,
2664
self.tree.currentFocus = null;
2665
self._removeFocus();
2670
self._focusHighlightedItems.push(node._node);
2675
this.tree.fire('focusChanged',{oldNode:this.tree.currentFocus,newNode:this});
2676
this.tree.currentFocus = this;
2678
this.tree.fire('focusChanged',{oldNode:self.tree.currentFocus,newNode:null});
2679
this.tree.currentFocus = null;
2680
this._removeFocus();
2686
* Count of nodes in a branch
2687
* @method getNodeCount
2688
* @return {int} number of nodes in the branch
2690
getNodeCount: function() {
2691
for (var i = 0, count = 0;i< this.children.length;i++) {
2692
count += this.children[i].getNodeCount();
2698
* Returns an object which could be used to build a tree out of this node and its children.
2699
* It can be passed to the tree constructor to reproduce this node as a tree.
2700
* It will return false if the node or any children loads dynamically, regardless of whether it is loaded or not.
2701
* @method getNodeDefinition
2702
* @return {Object | false} definition of the tree or false if the node or any children is defined as dynamic
2704
getNodeDefinition: function() {
2706
if (this.isDynamic()) { return false; }
2708
var def, defs = Lang.merge(this.data), children = [];
2712
if (this.expanded) {defs.expanded = this.expanded; }
2713
if (!this.multiExpand) { defs.multiExpand = this.multiExpand; }
2714
if (!this.renderHidden) { defs.renderHidden = this.renderHidden; }
2715
if (!this.hasIcon) { defs.hasIcon = this.hasIcon; }
2716
if (this.nowrap) { defs.nowrap = this.nowrap; }
2717
if (this.className) { defs.className = this.className; }
2718
if (this.editable) { defs.editable = this.editable; }
2719
if (this.enableHighlight) { defs.enableHighlight = this.enableHighlight; }
2720
if (this.highlightState) { defs.highlightState = this.highlightState; }
2721
if (this.propagateHighlightUp) { defs.propagateHighlightUp = this.propagateHighlightUp; }
2722
if (this.propagateHighlightDown) { defs.propagateHighlightDown = this.propagateHighlightDown; }
2723
defs.type = this._type;
2727
for (var i = 0; i < this.children.length;i++) {
2728
def = this.children[i].getNodeDefinition();
2729
if (def === false) { return false;}
2732
if (children.length) { defs.children = children; }
2738
* Generates the link that will invoke this node's toggle method
2739
* @method getToggleLink
2740
* @return {string} the javascript url for toggling this node
2742
getToggleLink: function() {
2743
return 'return false;';
2747
* Sets the value of property for this node and all loaded descendants.
2748
* Only public and defined properties can be set, not methods.
2749
* Values for unknown properties will be assigned to the refNode.data object
2750
* @method setNodesProperty
2751
* @param name {string} Name of the property to be set
2752
* @param value {any} value to be set
2753
* @param refresh {boolean} if present and true, it does a refresh
2755
setNodesProperty: function(name, value, refresh) {
2756
if (name.charAt(0) != '_' && !Lang.isUndefined(this[name]) && !Lang.isFunction(this[name]) ) {
2759
this.data[name] = value;
2761
for (var i = 0; i < this.children.length;i++) {
2762
this.children[i].setNodesProperty(name,value);
2769
* Toggles the highlighted state of a Node
2770
* @method toggleHighlight
2772
toggleHighlight: function() {
2773
if (this.enableHighlight) {
2774
// unhighlights only if fully highligthed. For not or partially highlighted it will highlight
2775
if (this.highlightState == 1) {
2784
* Turns highlighting on node.
2786
* @param _silent {boolean} optional, don't fire the highlightEvent
2788
highlight: function(_silent) {
2789
if (this.enableHighlight) {
2790
if (this.tree.singleNodeHighlight) {
2791
if (this.tree._currentlyHighlighted) {
2792
this.tree._currentlyHighlighted.unhighlight(_silent);
2794
this.tree._currentlyHighlighted = this;
2796
this.highlightState = 1;
2797
this._setHighlightClassName();
2798
if (!this.tree.singleNodeHighlight) {
2799
if (this.propagateHighlightDown) {
2800
for (var i = 0;i < this.children.length;i++) {
2801
this.children[i].highlight(true);
2804
if (this.propagateHighlightUp) {
2806
this.parent._childrenHighlighted();
2811
this.tree.fire('highlightEvent',this);
2816
* Turns highlighting off a node.
2817
* @method unhighlight
2818
* @param _silent {boolean} optional, don't fire the highlightEvent
2820
unhighlight: function(_silent) {
2821
if (this.enableHighlight) {
2822
// might have checked singleNodeHighlight but it wouldn't really matter either way
2823
this.tree._currentlyHighlighted = null;
2824
this.highlightState = 0;
2825
this._setHighlightClassName();
2826
if (!this.tree.singleNodeHighlight) {
2827
if (this.propagateHighlightDown) {
2828
for (var i = 0;i < this.children.length;i++) {
2829
this.children[i].unhighlight(true);
2832
if (this.propagateHighlightUp) {
2834
this.parent._childrenHighlighted();
2839
this.tree.fire('highlightEvent',this);
2844
* Checks whether all or part of the children of a node are highlighted and
2845
* sets the node highlight to full, none or partial highlight.
2846
* If set to propagate it will further call the parent
2847
* @method _childrenHighlighted
2850
_childrenHighlighted: function() {
2851
var yes = false, no = false;
2852
if (this.enableHighlight) {
2853
for (var i = 0;i < this.children.length;i++) {
2854
switch(this.children[i].highlightState) {
2867
this.highlightState = 2;
2869
this.highlightState = 1;
2871
this.highlightState = 0;
2873
this._setHighlightClassName();
2874
if (this.propagateHighlightUp) {
2876
this.parent._childrenHighlighted();
2883
* Changes the classNames on the toggle and content containers to reflect the current highlighting
2884
* @method _setHighlightClassName
2887
_setHighlightClassName: function() {
2888
var el = document.getElementById('ygtvtableel' + this.index);
2890
el.className = el.className.replace(/\bygtv-highlight\d\b/gi,'ygtv-highlight' + this.highlightState);
2898
var YAHOO = Y.Port();
2900
* A custom YAHOO.widget.Node that handles the unique nature of
2901
* the virtual, presentationless root node.
2902
* @namespace YAHOO.widget
2904
* @extends YAHOO.widget.Node
2905
* @param oTree {YAHOO.widget.TreeView} The tree instance this node belongs to
2908
var RootNode = function(oTree) {
2909
// Initialize the node with null params. The root node is a
2910
// special case where the node has no presentation. So we have
2911
// to alter the standard properties a bit.
2912
this._nodeinit(null, null, true);
2915
* For the root node, we get the tree reference from as a param
2916
* to the constructor instead of from the parent element.
2921
YAHOO.widget.RootNode = RootNode;
2923
Y.extend(RootNode, YAHOO.widget.Node, {
2930
* @default "RootNode"
2934
// overrides YAHOO.widget.Node
2935
getNodeHtml: function() {
2939
toString: function() {
2943
loadComplete: function() {
2948
* Count of nodes in tree.
2949
* It overrides Nodes.getNodeCount because the root node should not be counted.
2950
* @method getNodeCount
2951
* @return {int} number of nodes in the tree
2953
getNodeCount: function() {
2954
for (var i = 0, count = 0;i< this.children.length;i++) {
2955
count += this.children[i].getNodeCount();
2961
* Returns an object which could be used to build a tree out of this node and its children.
2962
* It can be passed to the tree constructor to reproduce this node as a tree.
2963
* Since the RootNode is automatically created by treeView,
2964
* its own definition is excluded from the returned node definition
2965
* which only contains its children.
2966
* @method getNodeDefinition
2967
* @return {Object | false} definition of the tree or false if any child node is defined as dynamic
2969
getNodeDefinition: function() {
2971
for (var def, defs = [], i = 0; i < this.children.length;i++) {
2972
def = this.children[i].getNodeDefinition();
2973
if (def === false) { return false;}
2979
collapse: function() {},
2980
expand: function() {},
2981
getSiblings: function() { return null; },
2982
focus: function () {}
2987
var YAHOO = Y.Port(),
2990
* The default node presentation. The first parameter should be
2991
* either a string that will be used as the node's label, or an object
2992
* that has at least a string property called label. By default, clicking the
2993
* label will toggle the expanded/collapsed state of the node. By
2994
* setting the href property of the instance, this behavior can be
2995
* changed so that the label will go to the specified href.
2996
* @namespace YAHOO.widget
2998
* @extends YAHOO.widget.Node
3000
* @param oData {object} a string or object containing the data that will
3001
* be used to render this node.
3002
* Providing a string is the same as providing an object with a single property named label.
3003
* All values in the oData will be used to set equally named properties in the node
3004
* as long as the node does have such properties, they are not undefined, private or functions.
3005
* All attributes are made available in noderef.data, which
3006
* can be used to store custom attributes. TreeView.getNode(s)ByProperty
3007
* can be used to retrieve a node by one of the attributes.
3008
* @param oParent {YAHOO.widget.Node} this node's parent node
3009
* @param expanded {boolean} the initial expanded/collapsed state (deprecated; use oData.expanded)
3011
YAHOO.widget.TextNode = function(oData, oParent, expanded) {
3014
if (Lang.isString(oData)) {
3015
oData = { label: oData };
3017
this._nodeinit(oData, oParent, expanded);
3018
this.setUpLabel(oData);
3022
Y.extend(YAHOO.widget.TextNode, YAHOO.widget.Node, {
3025
* The CSS class for the label href. Defaults to ygtvlabel, but can be
3026
* overridden to provide a custom presentation for a specific node.
3027
* @property labelStyle
3030
labelStyle: "ygtvlabel",
3033
* The derived element id of the label for this node
3034
* @property labelElId
3040
* The text for the label. It is assumed that the oData parameter will
3041
* either be a string that will be used as the label, or an object that
3042
* has a property called "label" that we will use.
3049
* The text for the title (tooltip) for the label element
3056
* The href for the node's label. If one is not specified, the href will
3057
* be set so that it toggles the node.
3064
* The label href target, defaults to current window
3075
* @default "TextNode"
3081
* Sets up the node label
3082
* @method setUpLabel
3083
* @param oData string containing the label, or an object with a label property
3085
setUpLabel: function(oData) {
3087
if (Lang.isString(oData)) {
3093
this.labelStyle = oData.style;
3097
this.label = oData.label;
3099
this.labelElId = "ygtvlabelel" + this.index;
3104
* Returns the label element
3105
* @for YAHOO.widget.TextNode
3106
* @method getLabelEl
3107
* @return {object} the element
3109
getLabelEl: function() {
3110
return document.getElementById(this.labelElId);
3113
// overrides YAHOO.widget.Node
3114
getContentHtml: function() {
3116
sb[sb.length] = this.href?'<a':'<span';
3117
sb[sb.length] = ' id="' + this.labelElId + '"';
3118
sb[sb.length] = ' class="' + this.labelStyle + '"';
3120
sb[sb.length] = ' href="' + this.href + '"';
3121
sb[sb.length] = ' target="' + this.target + '"';
3124
sb[sb.length] = ' title="' + this.title + '"';
3126
sb[sb.length] = ' >';
3127
sb[sb.length] = this.label;
3128
sb[sb.length] = this.href?'</a>':'</span>';
3135
* Returns an object which could be used to build a tree out of this node and its children.
3136
* It can be passed to the tree constructor to reproduce this node as a tree.
3137
* It will return false if the node or any descendant loads dynamically, regardless of whether it is loaded or not.
3138
* @method getNodeDefinition
3139
* @return {Object | false} definition of the tree or false if this node or any descendant is defined as dynamic
3141
getNodeDefinition: function() {
3142
var def = YAHOO.widget.TextNode.superclass.getNodeDefinition.call(this);
3143
if (def === false) { return false; }
3145
// Node specific properties
3146
def.label = this.label;
3147
if (this.labelStyle != 'ygtvlabel') { def.style = this.labelStyle; }
3148
if (this.title) { def.title = this.title; }
3149
if (this.href) { def.href = this.href; }
3150
if (this.target != '_self') { def.target = this.target; }
3156
toString: function() {
3157
return YAHOO.widget.TextNode.superclass.toString.call(this) + ": " + this.label;
3161
onLabelClick: function() {
3164
refresh: function() {
3165
YAHOO.widget.TextNode.superclass.refresh.call(this);
3166
var label = this.getLabelEl();
3167
label.innerHTML = this.label;
3168
if (label.tagName.toUpperCase() == 'A') {
3169
label.href = this.href;
3170
label.target = this.target;
3180
var YAHOO = Y.Port();
3182
* A menu-specific implementation that differs from TextNode in that only
3183
* one sibling can be expanded at a time.
3184
* @namespace YAHOO.widget
3186
* @extends YAHOO.widget.TextNode
3187
* @param oData {object} a string or object containing the data that will
3188
* be used to render this node.
3189
* Providing a string is the same as providing an object with a single property named label.
3190
* All values in the oData will be used to set equally named properties in the node
3191
* as long as the node does have such properties, they are not undefined, private or functions.
3192
* All attributes are made available in noderef.data, which
3193
* can be used to store custom attributes. TreeView.getNode(s)ByProperty
3194
* can be used to retrieve a node by one of the attributes.
3195
* @param oParent {YAHOO.widget.Node} this node's parent node
3196
* @param expanded {boolean} the initial expanded/collapsed state (deprecated; use oData.expanded)
3199
YAHOO.widget.MenuNode = function(oData, oParent, expanded) {
3200
YAHOO.widget.MenuNode.superclass.constructor.call(this,oData,oParent,expanded);
3203
* Menus usually allow only one branch to be open at a time.
3205
this.multiExpand = false;
3209
Y.extend(YAHOO.widget.MenuNode, YAHOO.widget.TextNode, {
3215
* @default "MenuNode"
3222
var YAHOO = Y.Port(),
3226
* This implementation takes either a string or object for the
3227
* oData argument. If is it a string, it will use it for the display
3228
* of this node (and it can contain any html code). If the parameter
3229
* is an object,it looks for a parameter called "html" that will be
3230
* used for this node's display.
3231
* @namespace YAHOO.widget
3233
* @extends YAHOO.widget.Node
3235
* @param oData {object} a string or object containing the data that will
3236
* be used to render this node.
3237
* Providing a string is the same as providing an object with a single property named html.
3238
* All values in the oData will be used to set equally named properties in the node
3239
* as long as the node does have such properties, they are not undefined, private or functions.
3240
* All other attributes are made available in noderef.data, which
3241
* can be used to store custom attributes. TreeView.getNode(s)ByProperty
3242
* can be used to retrieve a node by one of the attributes.
3243
* @param oParent {YAHOO.widget.Node} this node's parent node
3244
* @param expanded {boolean} the initial expanded/collapsed state (deprecated; use oData.expanded)
3245
* @param hasIcon {boolean} specifies whether or not leaf nodes should
3246
* be rendered with or without a horizontal line line and/or toggle icon. If the icon
3247
* is not displayed, the content fills the space it would have occupied.
3248
* This option operates independently of the leaf node presentation logic
3249
* for dynamic nodes.
3250
* (deprecated; use oData.hasIcon)
3252
YAHOO.widget.HTMLNode = function(oData, oParent, expanded, hasIcon) {
3254
this.init(oData, oParent, expanded);
3255
this.initContent(oData, hasIcon);
3259
Y.extend(YAHOO.widget.HTMLNode, YAHOO.widget.Node, {
3262
* The CSS class for the html content container. Defaults to ygtvhtml, but
3263
* can be overridden to provide a custom presentation for a specific node.
3264
* @property contentStyle
3267
contentStyle: "ygtvhtml",
3271
* The HTML content to use for this node's display
3282
* @default "HTMLNode"
3287
* Sets up the node label
3288
* @property initContent
3289
* @param oData {object} An html string or object containing an html property
3290
* @param hasIcon {boolean} determines if the node will be rendered with an
3293
initContent: function(oData, hasIcon) {
3294
this.setHtml(oData);
3295
this.contentElId = "ygtvcontentel" + this.index;
3296
if (!Lang.isUndefined(hasIcon)) { this.hasIcon = hasIcon; }
3301
* Synchronizes the node.html, and the node's content
3303
* @param o {object} An html string or object containing an html property
3305
setHtml: function(o) {
3307
this.html = (typeof o === "string") ? o : o.html;
3309
var el = this.getContentEl();
3311
el.innerHTML = this.html;
3316
// overrides YAHOO.widget.Node
3317
getContentHtml: function() {
3322
* Returns an object which could be used to build a tree out of this node and its children.
3323
* It can be passed to the tree constructor to reproduce this node as a tree.
3324
* It will return false if any node loads dynamically, regardless of whether it is loaded or not.
3325
* @method getNodeDefinition
3326
* @return {Object | false} definition of the tree or false if any node is defined as dynamic
3328
getNodeDefinition: function() {
3329
var def = YAHOO.widget.HTMLNode.superclass.getNodeDefinition.call(this);
3330
if (def === false) { return false; }
3331
def.html = this.html;
3338
var YAHOO = Y.Port(),
3339
Dom = YAHOO.util.Dom,
3341
Event = YAHOO.util.Event,
3342
TV = YAHOO.widget.TreeView,
3343
TVproto = TV.prototype;
3346
* An object to store information used for in-line editing
3347
* for all Nodes of all TreeViews. It contains:
3349
* <li>active {boolean}, whether there is an active cell editor </li>
3350
* <li>whoHasIt {YAHOO.widget.TreeView} TreeView instance that is currently using the editor</li>
3351
* <li>nodeType {string} value of static Node._type property, allows reuse of input element if node is of the same type.</li>
3352
* <li>editorPanel {HTMLelement (<div>)} element holding the in-line editor</li>
3353
* <li>inputContainer {HTMLelement (<div>)} element which will hold the type-specific input element(s) to be filled by the fillEditorContainer method</li>
3354
* <li>buttonsContainer {HTMLelement (<div>)} element which holds the <button> elements for Ok/Cancel. If you don't want any of the buttons, hide it via CSS styles, don't destroy it</li>
3355
* <li>node {YAHOO.widget.Node} reference to the Node being edited</li>
3356
* <li>saveOnEnter {boolean}, whether the Enter key should be accepted as a Save command (Esc. is always taken as Cancel), disable for multi-line input elements </li>
3357
* <li>oldValue {any} value before editing</li>
3359
* Editors are free to use this object to store additional data.
3360
* @property editorData
3362
* @for YAHOO.widget.TreeView
3366
whoHasIt:null, // which TreeView has it
3369
inputContainer:null,
3370
buttonsContainer:null,
3371
node:null, // which Node is being edited
3374
// Each node type is free to add its own properties to this as it sees fit.
3378
* Validator function for edited data, called from the TreeView instance scope,
3379
* receives the arguments (newValue, oldValue, nodeInstance)
3380
* and returns either the validated (or type-converted) value or undefined.
3381
* An undefined return will prevent the editor from closing
3382
* @property validator
3385
* @for YAHOO.widget.TreeView
3387
TVproto.validator = null;
3390
* Entry point for initializing the editing plug-in.
3391
* TreeView will call this method on initializing if it exists
3392
* @method _initEditor
3393
* @for YAHOO.widget.TreeView
3397
TVproto._initEditor = function () {
3399
* Fires when the user clicks on the ok button of a node editor
3400
* @event editorSaveEvent
3402
* @param oArgs.newValue {mixed} the new value just entered
3403
* @param oArgs.oldValue {mixed} the value originally in the tree
3404
* @param oArgs.node {YAHOO.widget.Node} the node that has the focus
3405
* @for YAHOO.widget.TreeView
3407
// this.publish("editorSaveEvent");
3410
* Fires when the user clicks on the cancel button of a node editor
3411
* @event editorCancelEvent
3413
* @param {YAHOO.widget.Node} node the node that has the focus
3414
* @for YAHOO.widget.TreeView
3416
// this.publish("editorCancelEvent");
3421
* Entry point of the editing plug-in.
3422
* TreeView will call this method if it exists when a node label is clicked
3423
* @method _nodeEditing
3424
* @param node {YAHOO.widget.Node} the node to be edited
3425
* @return {Boolean} true to indicate that the node is editable and prevent any further bubbling of the click.
3426
* @for YAHOO.widget.TreeView
3429
TVproto._nodeEditing = function (node) {
3431
if (node.fillEditorContainer && node.editable) {
3433
var ed, topLeft, buttons, button, editorData = TV.editorData;
3434
editorData.active = true;
3435
editorData.whoHasIt = this;
3436
if (!editorData.nodeType) {
3437
editorData.editorPanel = ed = document.body.appendChild(document.createElement('div'));
3438
Dom.addClass(ed,'ygtv-label-editor');
3440
buttons = editorData.buttonsContainer = ed.appendChild(document.createElement('div'));
3441
Dom.addClass(buttons,'ygtv-button-container');
3442
button = buttons.appendChild(document.createElement('button'));
3443
Dom.addClass(button,'ygtvok');
3444
button.innerHTML = ' ';
3445
button = buttons.appendChild(document.createElement('button'));
3446
Dom.addClass(button,'ygtvcancel');
3447
button.innerHTML = ' ';
3449
Y.on('click', function (ev) {
3450
var target = ev.target,
3451
targetEl = target._node,
3452
node = TV.editorData.node;
3454
// if (Dom.hasClass(target,'ygtvok')) {
3455
if (target.hasClass('ygtvok')) {
3457
this._closeEditor(true);
3459
// if (Dom.hasClass(target,'ygtvcancel')) {
3460
if (target.hasClass('ygtvcancel')) {
3462
this._closeEditor(false);
3466
editorData.inputContainer = ed.appendChild(document.createElement('div'));
3467
Dom.addClass(editorData.inputContainer,'ygtv-input');
3469
Y.on('keydown', function (ev) {
3470
var editorData = TV.editorData,
3471
KEY = Y.TreeView.KEY;
3472
switch (ev.keyCode) {
3475
if (editorData.saveOnEnter) {
3476
this._closeEditor(true);
3481
this._closeEditor(false);
3489
ed = editorData.editorPanel;
3491
editorData.node = node;
3492
if (editorData.nodeType) {
3493
Dom.removeClass(ed,'ygtv-edit-' + editorData.nodeType);
3495
Dom.addClass(ed,' ygtv-edit-' + node._type);
3496
topLeft = Dom.getXY(node.getContentEl());
3497
Dom.setStyle(ed,'left',topLeft[0] + 'px');
3498
Dom.setStyle(ed,'top',topLeft[1] + 'px');
3499
Dom.setStyle(ed,'display','block');
3501
node.fillEditorContainer(editorData);
3503
return true; // If inline editor available, don't do anything else.
3508
* Method to be associated with an event (clickEvent, dblClickEvent or enterKeyPressed) to pop up the contents editor
3509
* It calls the corresponding node editNode method.
3510
* @method onEventEditNode
3511
* @param oArgs {object} Object passed as arguments to TreeView event listeners
3512
* @for YAHOO.widget.TreeView
3514
TVproto.onEventEditNode = function (oArgs) {
3515
if (oArgs instanceof YAHOO.widget.Node) {
3517
} else if (oArgs.node instanceof YAHOO.widget.Node) {
3518
oArgs.node.editNode();
3523
* Method to be called when the inline editing is finished and the editor is to be closed
3524
* @method _closeEditor
3525
* @param save {Boolean} true if the edited value is to be saved, false if discarded
3527
* @for YAHOO.widget.TreeView
3529
TVproto._closeEditor = function (save) {
3530
var ed = TV.editorData,
3534
close = (ed.node.saveEditorValue(ed) !== false);
3536
this.fire('editorCancelEvent', node);
3540
Dom.setStyle(ed.editorPanel,'display','none');
3547
* Entry point for TreeView's destroy method to destroy whatever the editing plug-in has created
3548
* @method _destroyEditor
3550
* @for YAHOO.widget.TreeView
3552
TVproto._destroyEditor = function() {
3553
var ed = TV.editorData;
3554
if (ed && ed.nodeType && (!ed.active || ed.whoHasIt === this)) {
3555
Y.Event.detach('keydown', ed.editorPanel);
3556
Y.Event.detach('click', ed.buttonContainer);
3557
ed.node.destroyEditorContents(ed);
3558
document.body.removeChild(ed.editorPanel);
3559
ed.nodeType = ed.editorPanel = ed.inputContainer = ed.buttonsContainer = ed.whoHasIt = ed.node = null;
3564
var Nproto = YAHOO.widget.Node.prototype;
3567
* Signals if the label is editable. (Ignored on TextNodes with href set.)
3568
* @property editable
3570
* @for YAHOO.widget.Node
3572
Nproto.editable = false;
3575
* pops up the contents editor, if there is one and the node is declared editable
3577
* @for YAHOO.widget.Node
3579
Nproto.editNode = function () {
3580
this.tree._nodeEditing(this);
3583
/** Placeholder for a function that should provide the inline node label editor.
3584
* Leaving it set to null will indicate that this node type is not editable.
3585
* It should be overridden by nodes that provide inline editing.
3586
* The Node-specific editing element (input box, textarea or whatever) should be inserted into editorData.inputContainer.
3587
* @method fillEditorContainer
3588
* @param editorData {YAHOO.widget.TreeView.editorData} a shortcut to the static object holding editing information
3590
* @for YAHOO.widget.Node
3592
Nproto.fillEditorContainer = null;
3595
* Node-specific destroy function to empty the contents of the inline editor panel.
3596
* This function is the worst case alternative that will purge all possible events and remove the editor contents.
3597
* Method Event.purgeElement is somewhat costly so if it can be replaced by specifc Event.removeListeners, it is better to do so.
3598
* @method destroyEditorContents
3599
* @param editorData {YAHOO.widget.TreeView.editorData} a shortcut to the static object holding editing information
3600
* @for YAHOO.widget.Node
3602
Nproto.destroyEditorContents = function (editorData) {
3603
// In the worst case, if the input editor (such as the Calendar) has no destroy method
3604
// we can only try to remove all possible events on it.
3605
Event.purgeElement(editorData.inputContainer,true);
3606
editorData.inputContainer.innerHTML = '';
3610
* Saves the value entered into the editor.
3611
* @method saveEditorValue
3612
* @param editorData {YAHOO.widget.TreeView.editorData} a shortcut to the static object holding editing information
3613
* @return {false or none} a return of exactly false will prevent the editor from closing
3614
* @for YAHOO.widget.Node
3616
Nproto.saveEditorValue = function (editorData) {
3617
var node = editorData.node,
3619
validator = node.tree.validator;
3621
value = this.getEditorValue(editorData);
3623
if (Lang.isFunction(validator)) {
3624
value = validator(value,editorData.oldValue,node);
3625
if (Lang.isUndefined(value)) {
3630
var ret = this.tree.fire('editorSaveEvent', {
3632
oldValue:editorData.oldValue,
3637
if (ret !== false) {
3638
this.displayEditedValue(value, editorData);
3643
* Returns the value(s) from the input element(s) .
3644
* Should be overridden by each node type.
3645
* @method getEditorValue
3646
* @param editorData {YAHOO.widget.TreeView.editorData} a shortcut to the static object holding editing information
3647
* @return {any} value entered
3648
* @for YAHOO.widget.Node
3651
Nproto.getEditorValue = function (editorData) {
3655
* Finally displays the newly edited value(s) in the tree.
3656
* Should be overridden by each node type.
3657
* @method displayEditedValue
3658
* @param value {any} value to be displayed and stored in the node
3659
* @param editorData {YAHOO.widget.TreeView.editorData} a shortcut to the static object holding editing information
3660
* @for YAHOO.widget.Node
3662
Nproto.displayEditedValue = function (value,editorData) {
3665
var TNproto = YAHOO.widget.TextNode.prototype;
3668
* Places an <input> textbox in the input container and loads the label text into it.
3669
* @method fillEditorContainer
3670
* @param editorData {YAHOO.widget.TreeView.editorData} a shortcut to the static object holding editing information
3672
* @for YAHOO.widget.TextNode
3674
TNproto.fillEditorContainer = function (editorData) {
3677
// If last node edited is not of the same type as this one, delete it and fill it with our editor
3678
if (editorData.nodeType != this._type) {
3679
editorData.nodeType = this._type;
3680
editorData.saveOnEnter = true;
3681
editorData.node.destroyEditorContents(editorData);
3683
editorData.inputElement = input = editorData.inputContainer.appendChild(document.createElement('input'));
3686
// if the last node edited was of the same time, reuse the input element.
3687
input = editorData.inputElement;
3689
editorData.oldValue = this.label;
3690
input.value = this.label;
3696
* Returns the value from the input element.
3697
* Overrides Node.getEditorValue.
3698
* @method getEditorValue
3699
* @param editorData {YAHOO.widget.TreeView.editorData} a shortcut to the static object holding editing information
3700
* @return {string} value entered
3701
* @for YAHOO.widget.TextNode
3703
TNproto.getEditorValue = function (editorData) {
3704
return editorData.inputElement.value;
3708
* Finally displays the newly edited value in the tree.
3709
* Overrides Node.displayEditedValue.
3710
* @method displayEditedValue
3711
* @param value {string} value to be displayed and stored in the node
3712
* @param editorData {YAHOO.widget.TreeView.editorData} a shortcut to the static object holding editing information
3713
* @for YAHOO.widget.TextNode
3715
TNproto.displayEditedValue = function (value,editorData) {
3716
var node = editorData.node;
3718
node.getLabelEl().innerHTML = value;
3722
* Destroys the contents of the inline editor panel.
3723
* Overrides Node.destroyEditorContent.
3724
* Since we didn't set any event listeners on this inline editor, it is more efficient to avoid the generic method in Node.
3725
* @method destroyEditorContents
3726
* @param editorData {YAHOO.widget.TreeView.editorData} a shortcut to the static object holding editing information
3727
* @for YAHOO.widget.TextNode
3729
TNproto.destroyEditorContents = function (editorData) {
3730
editorData.inputContainer.innerHTML = '';
3735
}, '@VERSION@' ,{requires:['node','gallery-port']});