summaryrefslogtreecommitdiff
path: root/frontend/beta/js/YUI/treeview.js
Unidiff
Diffstat (limited to 'frontend/beta/js/YUI/treeview.js') (more/less context) (show whitespace changes)
-rw-r--r--frontend/beta/js/YUI/treeview.js2182
1 files changed, 2182 insertions, 0 deletions
diff --git a/frontend/beta/js/YUI/treeview.js b/frontend/beta/js/YUI/treeview.js
new file mode 100644
index 0000000..ea6b6ef
--- a/dev/null
+++ b/frontend/beta/js/YUI/treeview.js
@@ -0,0 +1,2182 @@
1/*
2Copyright (c) 2006, Yahoo! Inc. All rights reserved.
3Code licensed under the BSD License:
4http://developer.yahoo.net/yui/license.txt
5version: 0.12.0
6*/
7
8/**
9 * The treeview widget is a generic tree building tool.
10 * @module treeview
11 * @title TreeView Widget
12 * @requires yahoo
13 * @optional animation
14 * @namespace YAHOO.widget
15 */
16
17/**
18 * Contains the tree view state data and the root node.
19 *
20 * @class TreeView
21 * @constructor
22 * @param {string|HTMLElement} id The id of the element, or the element
23 * itself that the tree will be inserted into.
24 */
25YAHOO.widget.TreeView = function(id) {
26 if (id) { this.init(id); }
27};
28
29YAHOO.widget.TreeView.prototype = {
30
31 /**
32 * The id of tree container element
33 * @property id
34 * @type String
35 */
36 id: null,
37
38 /**
39 * The host element for this tree
40 * @property _el
41 * @private
42 */
43 _el: null,
44
45 /**
46 * Flat collection of all nodes in this tree
47 * @property _nodes
48 * @type Node[]
49 * @private
50 */
51 _nodes: null,
52
53 /**
54 * We lock the tree control while waiting for the dynamic loader to return
55 * @property locked
56 * @type boolean
57 */
58 locked: false,
59
60 /**
61 * The animation to use for expanding children, if any
62 * @property _expandAnim
63 * @type string
64 * @private
65 */
66 _expandAnim: null,
67
68 /**
69 * The animation to use for collapsing children, if any
70 * @property _collapseAnim
71 * @type string
72 * @private
73 */
74 _collapseAnim: null,
75
76 /**
77 * The current number of animations that are executing
78 * @property _animCount
79 * @type int
80 * @private
81 */
82 _animCount: 0,
83
84 /**
85 * The maximum number of animations to run at one time.
86 * @property maxAnim
87 * @type int
88 */
89 maxAnim: 2,
90
91 /**
92 * Sets up the animation for expanding children
93 * @method setExpandAnim
94 * @param {string} type the type of animation (acceptable values defined
95 * in YAHOO.widget.TVAnim)
96 */
97 setExpandAnim: function(type) {
98 if (YAHOO.widget.TVAnim.isValid(type)) {
99 this._expandAnim = type;
100 }
101 },
102
103 /**
104 * Sets up the animation for collapsing children
105 * @method setCollapseAnim
106 * @param {string} the type of animation (acceptable values defined in
107 * YAHOO.widget.TVAnim)
108 */
109 setCollapseAnim: function(type) {
110 if (YAHOO.widget.TVAnim.isValid(type)) {
111 this._collapseAnim = type;
112 }
113 },
114
115 /**
116 * Perform the expand animation if configured, or just show the
117 * element if not configured or too many animations are in progress
118 * @method animateExpand
119 * @param el {HTMLElement} the element to animate
120 * @param node {YAHOO.util.Node} the node that was expanded
121 * @return {boolean} true if animation could be invoked, false otherwise
122 */
123 animateExpand: function(el, node) {
124
125 if (this._expandAnim && this._animCount < this.maxAnim) {
126 // this.locked = true;
127 var tree = this;
128 var a = YAHOO.widget.TVAnim.getAnim(this._expandAnim, el,
129 function() { tree.expandComplete(node); });
130 if (a) {
131 ++this._animCount;
132 this.fireEvent("animStart", {
133 "node": node,
134 "type": "expand"
135 });
136 a.animate();
137 }
138
139 return true;
140 }
141
142 return false;
143 },
144
145 /**
146 * Perform the collapse animation if configured, or just show the
147 * element if not configured or too many animations are in progress
148 * @method animateCollapse
149 * @param el {HTMLElement} the element to animate
150 * @param node {YAHOO.util.Node} the node that was expanded
151 * @return {boolean} true if animation could be invoked, false otherwise
152 */
153 animateCollapse: function(el, node) {
154
155 if (this._collapseAnim && this._animCount < this.maxAnim) {
156 // this.locked = true;
157 var tree = this;
158 var a = YAHOO.widget.TVAnim.getAnim(this._collapseAnim, el,
159 function() { tree.collapseComplete(node); });
160 if (a) {
161 ++this._animCount;
162 this.fireEvent("animStart", {
163 "node": node,
164 "type": "collapse"
165 });
166 a.animate();
167 }
168
169 return true;
170 }
171
172 return false;
173 },
174
175 /**
176 * Function executed when the expand animation completes
177 * @method expandComplete
178 */
179 expandComplete: function(node) {
180 --this._animCount;
181 this.fireEvent("animComplete", {
182 "node": node,
183 "type": "expand"
184 });
185 // this.locked = false;
186 },
187
188 /**
189 * Function executed when the collapse animation completes
190 * @method collapseComplete
191 */
192 collapseComplete: function(node) {
193 --this._animCount;
194 this.fireEvent("animComplete", {
195 "node": node,
196 "type": "collapse"
197 });
198 // this.locked = false;
199 },
200
201 /**
202 * Initializes the tree
203 * @method init
204 * @parm {string|HTMLElement} id the id of the element that will hold the tree
205 * @private
206 */
207 init: function(id) {
208
209 this.id = id;
210
211 if ("string" !== typeof id) {
212 this._el = id;
213 this.id = this.generateId(id);
214 }
215
216 /**
217 * When animation is enabled, this event fires when the animation
218 * starts
219 * @event animStart
220 * @type CustomEvent
221 * @param {YAHOO.widget.Node} node the node that is expanding/collapsing
222 * @parm {String} type the type of animation ("expand" or "collapse")
223 */
224 this.createEvent("animStart", this);
225
226 /**
227 * When animation is enabled, this event fires when the animation
228 * completes
229 * @event animComplete
230 * @type CustomEvent
231 * @param {YAHOO.widget.Node} node the node that is expanding/collapsing
232 * @parm {String} type the type of animation ("expand" or "collapse")
233 */
234 this.createEvent("animComplete", this);
235
236 /**
237 * Fires when a node is going to be expanded. Return false to stop
238 * the expand.
239 * @event collapse
240 * @type CustomEvent
241 * @param {YAHOO.widget.Node} node the node that is expanding/collapsing
242 */
243 this.createEvent("collapse", this);
244
245 /**
246 * Fires when a node is going to be collapsed. Return false to stop
247 * the collapse.
248 * @event expand
249 * @type CustomEvent
250 * @param {YAHOO.widget.Node} node the node that is expanding/collapsing
251 */
252 this.createEvent("expand", this);
253
254 this._nodes = [];
255
256 // store a global reference
257 YAHOO.widget.TreeView.trees[this.id] = this;
258
259 // Set up the root node
260 this.root = new YAHOO.widget.RootNode(this);
261
262
263 },
264
265 /**
266 * Renders the tree boilerplate and visible nodes
267 * @method draw
268 */
269 draw: function() {
270 var html = this.root.getHtml();
271 this.getEl().innerHTML = html;
272 this.firstDraw = false;
273 },
274
275 /**
276 * Returns the tree's host element
277 * @method getEl
278 * @return {HTMLElement} the host element
279 */
280 getEl: function() {
281 if (! this._el) {
282 this._el = document.getElementById(this.id);
283 }
284 return this._el;
285 },
286
287 /**
288 * Nodes register themselves with the tree instance when they are created.
289 * @method regNode
290 * @param node {Node} the node to register
291 * @private
292 */
293 regNode: function(node) {
294 this._nodes[node.index] = node;
295 },
296
297 /**
298 * Returns the root node of this tree
299 * @method getRoot
300 * @return {Node} the root node
301 */
302 getRoot: function() {
303 return this.root;
304 },
305
306 /**
307 * Configures this tree to dynamically load all child data
308 * @method setDynamicLoad
309 * @param {function} fnDataLoader the function that will be called to get the data
310 * @param iconMode {int} configures the icon that is displayed when a dynamic
311 * load node is expanded the first time without children. By default, the
312 * "collapse" icon will be used. If set to 1, the leaf node icon will be
313 * displayed.
314 */
315 setDynamicLoad: function(fnDataLoader, iconMode) {
316 this.root.setDynamicLoad(fnDataLoader, iconMode);
317 },
318
319 /**
320 * Expands all child nodes. Note: this conflicts with the "multiExpand"
321 * node property. If expand all is called in a tree with nodes that
322 * do not allow multiple siblings to be displayed, only the last sibling
323 * will be expanded.
324 * @method expandAll
325 */
326 expandAll: function() {
327 if (!this.locked) {
328 this.root.expandAll();
329 }
330 },
331
332 /**
333 * Collapses all expanded child nodes in the entire tree.
334 * @method collapseAll
335 */
336 collapseAll: function() {
337 if (!this.locked) {
338 this.root.collapseAll();
339 }
340 },
341
342 /**
343 * Returns a node in the tree that has the specified index (this index
344 * is created internally, so this function probably will only be used
345 * in html generated for a given node.)
346 * @method getNodeByIndex
347 * @param {int} nodeIndex the index of the node wanted
348 * @return {Node} the node with index=nodeIndex, null if no match
349 */
350 getNodeByIndex: function(nodeIndex) {
351 var n = this._nodes[nodeIndex];
352 return (n) ? n : null;
353 },
354
355 /**
356 * Returns a node that has a matching property and value in the data
357 * object that was passed into its constructor.
358 * @method getNodeByProperty
359 * @param {object} property the property to search (usually a string)
360 * @param {object} value the value we want to find (usuall an int or string)
361 * @return {Node} the matching node, null if no match
362 */
363 getNodeByProperty: function(property, value) {
364 for (var i in this._nodes) {
365 var n = this._nodes[i];
366 if (n.data && value == n.data[property]) {
367 return n;
368 }
369 }
370
371 return null;
372 },
373
374 /**
375 * Returns a collection of nodes that have a matching property
376 * and value in the data object that was passed into its constructor.
377 * @method getNodesByProperty
378 * @param {object} property the property to search (usually a string)
379 * @param {object} value the value we want to find (usuall an int or string)
380 * @return {Array} the matching collection of nodes, null if no match
381 */
382 getNodesByProperty: function(property, value) {
383 var values = [];
384 for (var i in this._nodes) {
385 var n = this._nodes[i];
386 if (n.data && value == n.data[property]) {
387 values.push(n);
388 }
389 }
390
391 return (values.length) ? values : null;
392 },
393
394 /**
395 * Removes the node and its children, and optionally refreshes the
396 * branch of the tree that was affected.
397 * @method removeNode
398 * @param {Node} The node to remove
399 * @param {boolean} autoRefresh automatically refreshes branch if true
400 * @return {boolean} False is there was a problem, true otherwise.
401 */
402 removeNode: function(node, autoRefresh) {
403
404 // Don't delete the root node
405 if (node.isRoot()) {
406 return false;
407 }
408
409 // Get the branch that we may need to refresh
410 var p = node.parent;
411 if (p.parent) {
412 p = p.parent;
413 }
414
415 // Delete the node and its children
416 this._deleteNode(node);
417
418 // Refresh the parent of the parent
419 if (autoRefresh && p && p.childrenRendered) {
420 p.refresh();
421 }
422
423 return true;
424 },
425
426 /**
427 * Deletes this nodes child collection, recursively. Also collapses
428 * the node, and resets the dynamic load flag. The primary use for
429 * this method is to purge a node and allow it to fetch its data
430 * dynamically again.
431 * @method removeChildren
432 * @param {Node} node the node to purge
433 */
434 removeChildren: function(node) {
435 while (node.children.length) {
436 this._deleteNode(node.children[0]);
437 }
438
439 node.childrenRendered = false;
440 node.dynamicLoadComplete = false;
441 if (node.expanded) {
442 node.collapse();
443 } else {
444 node.updateIcon();
445 }
446 },
447
448 /**
449 * Deletes the node and recurses children
450 * @method _deleteNode
451 * @private
452 */
453 _deleteNode: function(node) {
454 // Remove all the child nodes first
455 this.removeChildren(node);
456
457 // Remove the node from the tree
458 this.popNode(node);
459 },
460
461 /**
462 * Removes the node from the tree, preserving the child collection
463 * to make it possible to insert the branch into another part of the
464 * tree, or another tree.
465 * @method popNode
466 * @param {Node} the node to remove
467 */
468 popNode: function(node) {
469 var p = node.parent;
470
471 // Update the parent's collection of children
472 var a = [];
473
474 for (var i=0, len=p.children.length;i<len;++i) {
475 if (p.children[i] != node) {
476 a[a.length] = p.children[i];
477 }
478 }
479
480 p.children = a;
481
482 // reset the childrenRendered flag for the parent
483 p.childrenRendered = false;
484
485 // Update the sibling relationship
486 if (node.previousSibling) {
487 node.previousSibling.nextSibling = node.nextSibling;
488 }
489
490 if (node.nextSibling) {
491 node.nextSibling.previousSibling = node.previousSibling;
492 }
493
494 node.parent = null;
495 node.previousSibling = null;
496 node.nextSibling = null;
497 node.tree = null;
498
499 // Update the tree's node collection
500 delete this._nodes[node.index];
501 },
502
503 /**
504 * TreeView instance toString
505 * @method toString
506 * @return {string} string representation of the tree
507 */
508 toString: function() {
509 return "TreeView " + this.id;
510 },
511
512 /**
513 * Generates an unique id for an element if it doesn't yet have one
514 * @method generateId
515 * @private
516 */
517 generateId: function(el) {
518 var id = el.id;
519
520 if (!id) {
521 id = "yui-tv-auto-id-" + YAHOO.widget.TreeView.counter;
522 ++YAHOO.widget.TreeView.counter;
523 }
524
525 return id;
526 },
527
528 /**
529 * Abstract method that is executed when a node is expanded
530 * @method onExpand
531 * @param node {Node} the node that was expanded
532 * @deprecated use treeobj.subscribe("expand") instead
533 */
534 onExpand: function(node) { },
535
536 /**
537 * Abstract method that is executed when a node is collapsed.
538 * @method onCollapse
539 * @param node {Node} the node that was collapsed.
540 * @deprecated use treeobj.subscribe("collapse") instead
541 */
542 onCollapse: function(node) { }
543
544};
545
546YAHOO.augment(YAHOO.widget.TreeView, YAHOO.util.EventProvider);
547
548/**
549 * Count of all nodes in all trees
550 * @property YAHOO.widget.TreeView.nodeCount
551 * @type int
552 * @static
553 */
554YAHOO.widget.TreeView.nodeCount = 0;
555
556/**
557 * Global cache of tree instances
558 * @property YAHOO.widget.TreeView.trees
559 * @type Array
560 * @static
561 * @private
562 */
563YAHOO.widget.TreeView.trees = [];
564
565/**
566 * Counter for generating a new unique element id
567 * @property YAHOO.widget.TreeView.counter
568 * @static
569 * @private
570 */
571YAHOO.widget.TreeView.counter = 0;
572
573/**
574 * Global method for getting a tree by its id. Used in the generated
575 * tree html.
576 * @method YAHOO.widget.TreeView.getTree
577 * @param treeId {String} the id of the tree instance
578 * @return {TreeView} the tree instance requested, null if not found.
579 * @static
580 */
581YAHOO.widget.TreeView.getTree = function(treeId) {
582 var t = YAHOO.widget.TreeView.trees[treeId];
583 return (t) ? t : null;
584};
585
586/**
587 * Global method for getting a node by its id. Used in the generated
588 * tree html.
589 * @method YAHOO.widget.TreeView.getNode
590 * @param treeId {String} the id of the tree instance
591 * @param nodeIndex {String} the index of the node to return
592 * @return {Node} the node instance requested, null if not found
593 * @static
594 */
595YAHOO.widget.TreeView.getNode = function(treeId, nodeIndex) {
596 var t = YAHOO.widget.TreeView.getTree(treeId);
597 return (t) ? t.getNodeByIndex(nodeIndex) : null;
598};
599
600/**
601 * Add a DOM event
602 * @method YAHOO.widget.TreeView.addHandler
603 * @param el the elment to bind the handler to
604 * @param {string} sType the type of event handler
605 * @param {function} fn the callback to invoke
606 * @static
607 */
608YAHOO.widget.TreeView.addHandler = function (el, sType, fn) {
609 if (el.addEventListener) {
610 el.addEventListener(sType, fn, false);
611 } else if (el.attachEvent) {
612 el.attachEvent("on" + sType, fn);
613 }
614};
615
616/**
617 * Remove a DOM event
618 * @method YAHOO.widget.TreeView.removeHandler
619 * @param el the elment to bind the handler to
620 * @param {string} sType the type of event handler
621 * @param {function} fn the callback to invoke
622 * @static
623 */
624
625YAHOO.widget.TreeView.removeHandler = function (el, sType, fn) {
626 if (el.removeEventListener) {
627 el.removeEventListener(sType, fn, false);
628 } else if (el.detachEvent) {
629 el.detachEvent("on" + sType, fn);
630 }
631};
632
633/**
634 * Attempts to preload the images defined in the styles used to draw the tree by
635 * rendering off-screen elements that use the styles.
636 * @method YAHOO.widget.TreeView.preload
637 * @param {string} prefix the prefix to use to generate the names of the
638 * images to preload, default is ygtv
639 * @static
640 */
641YAHOO.widget.TreeView.preload = function(prefix) {
642 prefix = prefix || "ygtv";
643 var styles = ["tn","tm","tmh","tp","tph","ln","lm","lmh","lp","lph","loading"];
644
645 var sb = [];
646
647 for (var i = 0; i < styles.length; ++i) {
648 sb[sb.length] = '<span class="' + prefix + styles[i] + '">&#160;</span>';
649 }
650
651 var f = document.createElement("div");
652 var s = f.style;
653 s.position = "absolute";
654 s.top = "-1000px";
655 s.left = "-1000px";
656 f.innerHTML = sb.join("");
657
658 document.body.appendChild(f);
659
660 YAHOO.widget.TreeView.removeHandler(window,
661 "load", YAHOO.widget.TreeView.preload);
662
663};
664
665YAHOO.widget.TreeView.addHandler(window,
666 "load", YAHOO.widget.TreeView.preload);
667
668/**
669 * The base class for all tree nodes. The node's presentation and behavior in
670 * response to mouse events is handled in Node subclasses.
671 * @namespace YAHOO.widget
672 * @class Node
673 * @param oData {object} a string or object containing the data that will
674 * be used to render this node
675 * @param oParent {Node} this node's parent node
676 * @param expanded {boolean} the initial expanded/collapsed state
677 * @constructor
678 */
679YAHOO.widget.Node = function(oData, oParent, expanded) {
680 if (oData) { this.init(oData, oParent, expanded); }
681};
682
683YAHOO.widget.Node.prototype = {
684
685 /**
686 * The index for this instance obtained from global counter in YAHOO.widget.TreeView.
687 * @property index
688 * @type int
689 */
690 index: 0,
691
692 /**
693 * This node's child node collection.
694 * @property children
695 * @type Node[]
696 */
697 children: null,
698
699 /**
700 * Tree instance this node is part of
701 * @property tree
702 * @type TreeView
703 */
704 tree: null,
705
706 /**
707 * The data linked to this node. This can be any object or primitive
708 * value, and the data can be used in getNodeHtml().
709 * @property data
710 * @type object
711 */
712 data: null,
713
714 /**
715 * Parent node
716 * @property parent
717 * @type Node
718 */
719 parent: null,
720
721 /**
722 * The depth of this node. We start at -1 for the root node.
723 * @property depth
724 * @type int
725 */
726 depth: -1,
727
728 /**
729 * The href for the node's label. If one is not specified, the href will
730 * be set so that it toggles the node.
731 * @property href
732 * @type string
733 */
734 href: null,
735
736 /**
737 * The label href target, defaults to current window
738 * @property target
739 * @type string
740 */
741 target: "_self",
742
743 /**
744 * The node's expanded/collapsed state
745 * @property expanded
746 * @type boolean
747 */
748 expanded: false,
749
750 /**
751 * Can multiple children be expanded at once?
752 * @property multiExpand
753 * @type boolean
754 */
755 multiExpand: true,
756
757 /**
758 * Should we render children for a collapsed node? It is possible that the
759 * implementer will want to render the hidden data... @todo verify that we
760 * need this, and implement it if we do.
761 * @property renderHidden
762 * @type boolean
763 */
764 renderHidden: false,
765
766 /**
767 * This flag is set to true when the html is generated for this node's
768 * children, and set to false when new children are added.
769 * @property childrenRendered
770 * @type boolean
771 */
772 childrenRendered: false,
773
774 /**
775 * Dynamically loaded nodes only fetch the data the first time they are
776 * expanded. This flag is set to true once the data has been fetched.
777 * @property dynamicLoadComplete
778 * @type boolean
779 */
780 dynamicLoadComplete: false,
781
782 /**
783 * This node's previous sibling
784 * @property previousSibling
785 * @type Node
786 */
787 previousSibling: null,
788
789 /**
790 * This node's next sibling
791 * @property nextSibling
792 * @type Node
793 */
794 nextSibling: null,
795
796 /**
797 * We can set the node up to call an external method to get the child
798 * data dynamically.
799 * @property _dynLoad
800 * @type boolean
801 * @private
802 */
803 _dynLoad: false,
804
805 /**
806 * Function to execute when we need to get this node's child data.
807 * @property dataLoader
808 * @type function
809 */
810 dataLoader: null,
811
812 /**
813 * This is true for dynamically loading nodes while waiting for the
814 * callback to return.
815 * @property isLoading
816 * @type boolean
817 */
818 isLoading: false,
819
820 /**
821 * The toggle/branch icon will not show if this is set to false. This
822 * could be useful if the implementer wants to have the child contain
823 * extra info about the parent, rather than an actual node.
824 * @property hasIcon
825 * @type boolean
826 */
827 hasIcon: true,
828
829 /**
830 * Used to configure what happens when a dynamic load node is expanded
831 * and we discover that it does not have children. By default, it is
832 * treated as if it still could have children (plus/minus icon). Set
833 * iconMode to have it display like a leaf node instead.
834 * @property iconMode
835 * @type int
836 */
837 iconMode: 0,
838
839 /**
840 * The node type
841 * @property _type
842 * @private
843 */
844 _type: "Node",
845
846 /*
847 spacerPath: "http://us.i1.yimg.com/us.yimg.com/i/space.gif",
848 expandedText: "Expanded",
849 collapsedText: "Collapsed",
850 loadingText: "Loading",
851 */
852
853 /**
854 * Initializes this node, gets some of the properties from the parent
855 * @method init
856 * @param oData {object} a string or object containing the data that will
857 * be used to render this node
858 * @param oParent {Node} this node's parent node
859 * @param expanded {boolean} the initial expanded/collapsed state
860 */
861 init: function(oData, oParent, expanded) {
862
863 this.data = oData;
864 this.children = [];
865 this.index = YAHOO.widget.TreeView.nodeCount;
866 ++YAHOO.widget.TreeView.nodeCount;
867 this.expanded = expanded;
868
869 /**
870 * The parentChange event is fired when a parent element is applied
871 * to the node. This is useful if you need to apply tree-level
872 * properties to a tree that need to happen if a node is moved from
873 * one tre to another.
874 *
875 * @event parentChange
876 * @type CustomEvent
877 */
878 this.createEvent("parentChange", this);
879
880 // oParent should never be null except when we create the root node.
881 if (oParent) {
882 oParent.appendChild(this);
883 }
884 },
885
886 /**
887 * Certain properties for the node cannot be set until the parent
888 * is known. This is called after the node is inserted into a tree.
889 * the parent is also applied to this node's children in order to
890 * make it possible to move a branch from one tree to another.
891 * @method applyParent
892 * @param {Node} parentNode this node's parent node
893 * @return {boolean} true if the application was successful
894 */
895 applyParent: function(parentNode) {
896 if (!parentNode) {
897 return false;
898 }
899
900 this.tree = parentNode.tree;
901 this.parent = parentNode;
902 this.depth = parentNode.depth + 1;
903
904 if (!this.href) {
905 this.href = "javascript:" + this.getToggleLink();
906 }
907
908 if (! this.multiExpand) {
909 this.multiExpand = parentNode.multiExpand;
910 }
911
912 this.tree.regNode(this);
913 parentNode.childrenRendered = false;
914
915 // cascade update existing children
916 for (var i=0, len=this.children.length;i<len;++i) {
917 this.children[i].applyParent(this);
918 }
919
920 this.fireEvent("parentChange");
921
922 return true;
923 },
924
925 /**
926 * Appends a node to the child collection.
927 * @method appendChild
928 * @param childNode {Node} the new node
929 * @return {Node} the child node
930 * @private
931 */
932 appendChild: function(childNode) {
933 if (this.hasChildren()) {
934 var sib = this.children[this.children.length - 1];
935 sib.nextSibling = childNode;
936 childNode.previousSibling = sib;
937 }
938 this.children[this.children.length] = childNode;
939 childNode.applyParent(this);
940
941 return childNode;
942 },
943
944 /**
945 * Appends this node to the supplied node's child collection
946 * @method appendTo
947 * @param parentNode {Node} the node to append to.
948 * @return {Node} The appended node
949 */
950 appendTo: function(parentNode) {
951 return parentNode.appendChild(this);
952 },
953
954 /**
955 * Inserts this node before this supplied node
956 * @method insertBefore
957 * @param node {Node} the node to insert this node before
958 * @return {Node} the inserted node
959 */
960 insertBefore: function(node) {
961 var p = node.parent;
962 if (p) {
963
964 if (this.tree) {
965 this.tree.popNode(this);
966 }
967
968 var refIndex = node.isChildOf(p);
969 p.children.splice(refIndex, 0, this);
970 if (node.previousSibling) {
971 node.previousSibling.nextSibling = this;
972 }
973 this.previousSibling = node.previousSibling;
974 this.nextSibling = node;
975 node.previousSibling = this;
976
977 this.applyParent(p);
978 }
979
980 return this;
981 },
982
983 /**
984 * Inserts this node after the supplied node
985 * @method insertAfter
986 * @param node {Node} the node to insert after
987 * @return {Node} the inserted node
988 */
989 insertAfter: function(node) {
990 var p = node.parent;
991 if (p) {
992
993 if (this.tree) {
994 this.tree.popNode(this);
995 }
996
997 var refIndex = node.isChildOf(p);
998
999 if (!node.nextSibling) {
1000 return this.appendTo(p);
1001 }
1002
1003 p.children.splice(refIndex + 1, 0, this);
1004
1005 node.nextSibling.previousSibling = this;
1006 this.previousSibling = node;
1007 this.nextSibling = node.nextSibling;
1008 node.nextSibling = this;
1009
1010 this.applyParent(p);
1011 }
1012
1013 return this;
1014 },
1015
1016 /**
1017 * Returns true if the Node is a child of supplied Node
1018 * @method isChildOf
1019 * @param parentNode {Node} the Node to check
1020 * @return {boolean} The node index if this Node is a child of
1021 * supplied Node, else -1.
1022 * @private
1023 */
1024 isChildOf: function(parentNode) {
1025 if (parentNode && parentNode.children) {
1026 for (var i=0, len=parentNode.children.length; i<len ; ++i) {
1027 if (parentNode.children[i] === this) {
1028 return i;
1029 }
1030 }
1031 }
1032
1033 return -1;
1034 },
1035
1036 /**
1037 * Returns a node array of this node's siblings, null if none.
1038 * @method getSiblings
1039 * @return Node[]
1040 */
1041 getSiblings: function() {
1042 return this.parent.children;
1043 },
1044
1045 /**
1046 * Shows this node's children
1047 * @method showChildren
1048 */
1049 showChildren: function() {
1050 if (!this.tree.animateExpand(this.getChildrenEl(), this)) {
1051 if (this.hasChildren()) {
1052 this.getChildrenEl().style.display = "";
1053 }
1054 }
1055 },
1056
1057 /**
1058 * Hides this node's children
1059 * @method hideChildren
1060 */
1061 hideChildren: function() {
1062
1063 if (!this.tree.animateCollapse(this.getChildrenEl(), this)) {
1064 this.getChildrenEl().style.display = "none";
1065 }
1066 },
1067
1068 /**
1069 * Returns the id for this node's container div
1070 * @method getElId
1071 * @return {string} the element id
1072 */
1073 getElId: function() {
1074 return "ygtv" + this.index;
1075 },
1076
1077 /**
1078 * Returns the id for this node's children div
1079 * @method getChildrenElId
1080 * @return {string} the element id for this node's children div
1081 */
1082 getChildrenElId: function() {
1083 return "ygtvc" + this.index;
1084 },
1085
1086 /**
1087 * Returns the id for this node's toggle element
1088 * @method getToggleElId
1089 * @return {string} the toggel element id
1090 */
1091 getToggleElId: function() {
1092 return "ygtvt" + this.index;
1093 },
1094
1095 /*
1096 * Returns the id for this node's spacer image. The spacer is positioned
1097 * over the toggle and provides feedback for screen readers.
1098 * @method getSpacerId
1099 * @return {string} the id for the spacer image
1100 */
1101 /*
1102 getSpacerId: function() {
1103 return "ygtvspacer" + this.index;
1104 },
1105 */
1106
1107 /**
1108 * Returns this node's container html element
1109 * @method getEl
1110 * @return {HTMLElement} the container html element
1111 */
1112 getEl: function() {
1113 return document.getElementById(this.getElId());
1114 },
1115
1116 /**
1117 * Returns the div that was generated for this node's children
1118 * @method getChildrenEl
1119 * @return {HTMLElement} this node's children div
1120 */
1121 getChildrenEl: function() {
1122 return document.getElementById(this.getChildrenElId());
1123 },
1124
1125 /**
1126 * Returns the element that is being used for this node's toggle.
1127 * @method getToggleEl
1128 * @return {HTMLElement} this node's toggle html element
1129 */
1130 getToggleEl: function() {
1131 return document.getElementById(this.getToggleElId());
1132 },
1133
1134 /*
1135 * Returns the element that is being used for this node's spacer.
1136 * @method getSpacer
1137 * @return {HTMLElement} this node's spacer html element
1138 */
1139 /*
1140 getSpacer: function() {
1141 return document.getElementById( this.getSpacerId() ) || {};
1142 },
1143 */
1144
1145 /*
1146 getStateText: function() {
1147 if (this.isLoading) {
1148 return this.loadingText;
1149 } else if (this.hasChildren(true)) {
1150 if (this.expanded) {
1151 return this.expandedText;
1152 } else {
1153 return this.collapsedText;
1154 }
1155 } else {
1156 return "";
1157 }
1158 },
1159 */
1160
1161 /**
1162 * Generates the link that will invoke this node's toggle method
1163 * @method getToggleLink
1164 * @return {string} the javascript url for toggling this node
1165 */
1166 getToggleLink: function() {
1167 return "YAHOO.widget.TreeView.getNode(\'" + this.tree.id + "\'," +
1168 this.index + ").toggle()";
1169 },
1170
1171 /**
1172 * Hides this nodes children (creating them if necessary), changes the
1173 * @method collapse
1174 * toggle style.
1175 */
1176 collapse: function() {
1177 // Only collapse if currently expanded
1178 if (!this.expanded) { return; }
1179
1180 // fire the collapse event handler
1181 var ret = this.tree.onCollapse(this);
1182
1183 if (false === ret) {
1184 return;
1185 }
1186
1187 ret = this.tree.fireEvent("collapse", this);
1188
1189 if (false === ret) {
1190 return;
1191 }
1192
1193 if (!this.getEl()) {
1194 this.expanded = false;
1195 return;
1196 }
1197
1198 // hide the child div
1199 this.hideChildren();
1200 this.expanded = false;
1201
1202 this.updateIcon();
1203
1204 // this.getSpacer().title = this.getStateText();
1205
1206 },
1207
1208 /**
1209 * Shows this nodes children (creating them if necessary), changes the
1210 * toggle style, and collapses its siblings if multiExpand is not set.
1211 * @method expand
1212 */
1213 expand: function() {
1214 // Only expand if currently collapsed.
1215 if (this.expanded) { return; }
1216
1217 // fire the expand event handler
1218 var ret = this.tree.onExpand(this);
1219
1220 if (false === ret) {
1221 return;
1222 }
1223
1224 ret = this.tree.fireEvent("expand", this);
1225
1226 if (false === ret) {
1227 return;
1228 }
1229
1230 if (!this.getEl()) {
1231 this.expanded = true;
1232 return;
1233 }
1234
1235 if (! this.childrenRendered) {
1236 this.getChildrenEl().innerHTML = this.renderChildren();
1237 } else {
1238 }
1239
1240 this.expanded = true;
1241
1242 this.updateIcon();
1243
1244 // this.getSpacer().title = this.getStateText();
1245
1246 // We do an extra check for children here because the lazy
1247 // load feature can expose nodes that have no children.
1248
1249 // if (!this.hasChildren()) {
1250 if (this.isLoading) {
1251 this.expanded = false;
1252 return;
1253 }
1254
1255 if (! this.multiExpand) {
1256 var sibs = this.getSiblings();
1257 for (var i=0; i<sibs.length; ++i) {
1258 if (sibs[i] != this && sibs[i].expanded) {
1259 sibs[i].collapse();
1260 }
1261 }
1262 }
1263
1264 this.showChildren();
1265 },
1266
1267 updateIcon: function() {
1268 if (this.hasIcon) {
1269 var el = this.getToggleEl();
1270 if (el) {
1271 el.className = this.getStyle();
1272 }
1273 }
1274 },
1275
1276 /**
1277 * Returns the css style name for the toggle
1278 * @method getStyle
1279 * @return {string} the css class for this node's toggle
1280 */
1281 getStyle: function() {
1282 if (this.isLoading) {
1283 return "ygtvloading";
1284 } else {
1285 // location top or bottom, middle nodes also get the top style
1286 var loc = (this.nextSibling) ? "t" : "l";
1287
1288 // type p=plus(expand), m=minus(collapase), n=none(no children)
1289 var type = "n";
1290 if (this.hasChildren(true) || (this.isDynamic() && !this.getIconMode())) {
1291 // if (this.hasChildren(true)) {
1292 type = (this.expanded) ? "m" : "p";
1293 }
1294
1295 return "ygtv" + loc + type;
1296 }
1297 },
1298
1299 /**
1300 * Returns the hover style for the icon
1301 * @return {string} the css class hover state
1302 * @method getHoverStyle
1303 */
1304 getHoverStyle: function() {
1305 var s = this.getStyle();
1306 if (this.hasChildren(true) && !this.isLoading) {
1307 s += "h";
1308 }
1309 return s;
1310 },
1311
1312 /**
1313 * Recursively expands all of this node's children.
1314 * @method expandAll
1315 */
1316 expandAll: function() {
1317 for (var i=0;i<this.children.length;++i) {
1318 var c = this.children[i];
1319 if (c.isDynamic()) {
1320 alert("Not supported (lazy load + expand all)");
1321 break;
1322 } else if (! c.multiExpand) {
1323 alert("Not supported (no multi-expand + expand all)");
1324 break;
1325 } else {
1326 c.expand();
1327 c.expandAll();
1328 }
1329 }
1330 },
1331
1332 /**
1333 * Recursively collapses all of this node's children.
1334 * @method collapseAll
1335 */
1336 collapseAll: function() {
1337 for (var i=0;i<this.children.length;++i) {
1338 this.children[i].collapse();
1339 this.children[i].collapseAll();
1340 }
1341 },
1342
1343 /**
1344 * Configures this node for dynamically obtaining the child data
1345 * when the node is first expanded. Calling it without the callback
1346 * will turn off dynamic load for the node.
1347 * @method setDynamicLoad
1348 * @param fmDataLoader {function} the function that will be used to get the data.
1349 * @param iconMode {int} configures the icon that is displayed when a dynamic
1350 * load node is expanded the first time without children. By default, the
1351 * "collapse" icon will be used. If set to 1, the leaf node icon will be
1352 * displayed.
1353 */
1354 setDynamicLoad: function(fnDataLoader, iconMode) {
1355 if (fnDataLoader) {
1356 this.dataLoader = fnDataLoader;
1357 this._dynLoad = true;
1358 } else {
1359 this.dataLoader = null;
1360 this._dynLoad = false;
1361 }
1362
1363 if (iconMode) {
1364 this.iconMode = iconMode;
1365 }
1366 },
1367
1368 /**
1369 * Evaluates if this node is the root node of the tree
1370 * @method isRoot
1371 * @return {boolean} true if this is the root node
1372 */
1373 isRoot: function() {
1374 return (this == this.tree.root);
1375 },
1376
1377 /**
1378 * Evaluates if this node's children should be loaded dynamically. Looks for
1379 * the property both in this instance and the root node. If the tree is
1380 * defined to load all children dynamically, the data callback function is
1381 * defined in the root node
1382 * @method isDynamic
1383 * @return {boolean} true if this node's children are to be loaded dynamically
1384 */
1385 isDynamic: function() {
1386 var lazy = (!this.isRoot() && (this._dynLoad || this.tree.root._dynLoad));
1387 return lazy;
1388 },
1389
1390 /**
1391 * Returns the current icon mode. This refers to the way childless dynamic
1392 * load nodes appear.
1393 * @method getIconMode
1394 * @return {int} 0 for collapse style, 1 for leaf node style
1395 */
1396 getIconMode: function() {
1397 return (this.iconMode || this.tree.root.iconMode);
1398 },
1399
1400 /**
1401 * Checks if this node has children. If this node is lazy-loading and the
1402 * children have not been rendered, we do not know whether or not there
1403 * are actual children. In most cases, we need to assume that there are
1404 * children (for instance, the toggle needs to show the expandable
1405 * presentation state). In other times we want to know if there are rendered
1406 * children. For the latter, "checkForLazyLoad" should be false.
1407 * @method hasChildren
1408 * @param checkForLazyLoad {boolean} should we check for unloaded children?
1409 * @return {boolean} true if this has children or if it might and we are
1410 * checking for this condition.
1411 */
1412 hasChildren: function(checkForLazyLoad) {
1413 return ( this.children.length > 0 ||
1414 (checkForLazyLoad && this.isDynamic() && !this.dynamicLoadComplete) );
1415 },
1416
1417 /**
1418 * Expands if node is collapsed, collapses otherwise.
1419 * @method toggle
1420 */
1421 toggle: function() {
1422 if (!this.tree.locked && ( this.hasChildren(true) || this.isDynamic()) ) {
1423 if (this.expanded) { this.collapse(); } else { this.expand(); }
1424 }
1425 },
1426
1427 /**
1428 * Returns the markup for this node and its children.
1429 * @method getHtml
1430 * @return {string} the markup for this node and its expanded children.
1431 */
1432 getHtml: function() {
1433
1434 this.childrenRendered = false;
1435
1436 var sb = [];
1437 sb[sb.length] = '<div class="ygtvitem" id="' + this.getElId() + '">';
1438 sb[sb.length] = this.getNodeHtml();
1439 sb[sb.length] = this.getChildrenHtml();
1440 sb[sb.length] = '</div>';
1441 return sb.join("");
1442 },
1443
1444 /**
1445 * Called when first rendering the tree. We always build the div that will
1446 * contain this nodes children, but we don't render the children themselves
1447 * unless this node is expanded.
1448 * @method getChildrenHtml
1449 * @return {string} the children container div html and any expanded children
1450 * @private
1451 */
1452 getChildrenHtml: function() {
1453
1454 var sb = [];
1455 sb[sb.length] = '<div class="ygtvchildren"';
1456 sb[sb.length] = ' id="' + this.getChildrenElId() + '"';
1457 if (!this.expanded) {
1458 sb[sb.length] = ' style="display:none;"';
1459 }
1460 sb[sb.length] = '>';
1461
1462 // Don't render the actual child node HTML unless this node is expanded.
1463 if ( (this.hasChildren(true) && this.expanded) ||
1464 (this.renderHidden && !this.isDynamic()) ) {
1465 sb[sb.length] = this.renderChildren();
1466 }
1467
1468 sb[sb.length] = '</div>';
1469
1470 return sb.join("");
1471 },
1472
1473 /**
1474 * Generates the markup for the child nodes. This is not done until the node
1475 * is expanded.
1476 * @method renderChildren
1477 * @return {string} the html for this node's children
1478 * @private
1479 */
1480 renderChildren: function() {
1481
1482
1483 var node = this;
1484
1485 if (this.isDynamic() && !this.dynamicLoadComplete) {
1486 this.isLoading = true;
1487 this.tree.locked = true;
1488
1489 if (this.dataLoader) {
1490
1491 setTimeout(
1492 function() {
1493 node.dataLoader(node,
1494 function() {
1495 node.loadComplete();
1496 });
1497 }, 10);
1498
1499 } else if (this.tree.root.dataLoader) {
1500
1501 setTimeout(
1502 function() {
1503 node.tree.root.dataLoader(node,
1504 function() {
1505 node.loadComplete();
1506 });
1507 }, 10);
1508
1509 } else {
1510 return "Error: data loader not found or not specified.";
1511 }
1512
1513 return "";
1514
1515 } else {
1516 return this.completeRender();
1517 }
1518 },
1519
1520 /**
1521 * Called when we know we have all the child data.
1522 * @method completeRender
1523 * @return {string} children html
1524 */
1525 completeRender: function() {
1526 var sb = [];
1527
1528 for (var i=0; i < this.children.length; ++i) {
1529 // this.children[i].childrenRendered = false;
1530 sb[sb.length] = this.children[i].getHtml();
1531 }
1532
1533 this.childrenRendered = true;
1534
1535 return sb.join("");
1536 },
1537
1538 /**
1539 * Load complete is the callback function we pass to the data provider
1540 * in dynamic load situations.
1541 * @method loadComplete
1542 */
1543 loadComplete: function() {
1544 this.getChildrenEl().innerHTML = this.completeRender();
1545 this.dynamicLoadComplete = true;
1546 this.isLoading = false;
1547 this.expand();
1548 this.tree.locked = false;
1549 },
1550
1551 /**
1552 * Returns this node's ancestor at the specified depth.
1553 * @method getAncestor
1554 * @param {int} depth the depth of the ancestor.
1555 * @return {Node} the ancestor
1556 */
1557 getAncestor: function(depth) {
1558 if (depth >= this.depth || depth < 0) {
1559 return null;
1560 }
1561
1562 var p = this.parent;
1563
1564 while (p.depth > depth) {
1565 p = p.parent;
1566 }
1567
1568 return p;
1569 },
1570
1571 /**
1572 * Returns the css class for the spacer at the specified depth for
1573 * this node. If this node's ancestor at the specified depth
1574 * has a next sibling the presentation is different than if it
1575 * does not have a next sibling
1576 * @method getDepthStyle
1577 * @param {int} depth the depth of the ancestor.
1578 * @return {string} the css class for the spacer
1579 */
1580 getDepthStyle: function(depth) {
1581 return (this.getAncestor(depth).nextSibling) ?
1582 "ygtvdepthcell" : "ygtvblankdepthcell";
1583 },
1584
1585 /**
1586 * Get the markup for the node. This is designed to be overrided so that we can
1587 * support different types of nodes.
1588 * @method getNodeHtml
1589 * @return {string} The HTML that will render this node.
1590 */
1591 getNodeHtml: function() {
1592 return "";
1593 },
1594
1595 /**
1596 * Regenerates the html for this node and its children. To be used when the
1597 * node is expanded and new children have been added.
1598 * @method refresh
1599 */
1600 refresh: function() {
1601 // this.loadComplete();
1602 this.getChildrenEl().innerHTML = this.completeRender();
1603
1604 if (this.hasIcon) {
1605 var el = this.getToggleEl();
1606 if (el) {
1607 el.className = this.getStyle();
1608 }
1609 }
1610 },
1611
1612 /**
1613 * Node toString
1614 * @method toString
1615 * @return {string} string representation of the node
1616 */
1617 toString: function() {
1618 return "Node (" + this.index + ")";
1619 }
1620
1621};
1622
1623YAHOO.augment(YAHOO.widget.Node, YAHOO.util.EventProvider);
1624
1625/**
1626 * A custom YAHOO.widget.Node that handles the unique nature of
1627 * the virtual, presentationless root node.
1628 * @namespace YAHOO.widget
1629 * @class RootNode
1630 * @extends YAHOO.widget.Node
1631 * @param oTree {YAHOO.widget.TreeView} The tree instance this node belongs to
1632 * @constructor
1633 */
1634YAHOO.widget.RootNode = function(oTree) {
1635 // Initialize the node with null params. The root node is a
1636 // special case where the node has no presentation. So we have
1637 // to alter the standard properties a bit.
1638 this.init(null, null, true);
1639
1640 /*
1641 * For the root node, we get the tree reference from as a param
1642 * to the constructor instead of from the parent element.
1643 */
1644 this.tree = oTree;
1645};
1646
1647YAHOO.extend(YAHOO.widget.RootNode, YAHOO.widget.Node, {
1648
1649 // overrides YAHOO.widget.Node
1650 getNodeHtml: function() {
1651 return "";
1652 },
1653
1654 toString: function() {
1655 return "RootNode";
1656 },
1657
1658 loadComplete: function() {
1659 this.tree.draw();
1660 }
1661
1662});
1663/**
1664 * The default node presentation. The first parameter should be
1665 * either a string that will be used as the node's label, or an object
1666 * that has a string propery called label. By default, the clicking the
1667 * label will toggle the expanded/collapsed state of the node. By
1668 * changing the href property of the instance, this behavior can be
1669 * changed so that the label will go to the specified href.
1670 * @namespace YAHOO.widget
1671 * @class TextNode
1672 * @extends YAHOO.widget.Node
1673 * @constructor
1674 * @param oData {object} a string or object containing the data that will
1675 * be used to render this node
1676 * @param oParent {YAHOO.widget.Node} this node's parent node
1677 * @param expanded {boolean} the initial expanded/collapsed state
1678 */
1679YAHOO.widget.TextNode = function(oData, oParent, expanded) {
1680
1681 if (oData) {
1682 this.init(oData, oParent, expanded);
1683 this.setUpLabel(oData);
1684 }
1685
1686};
1687
1688YAHOO.extend(YAHOO.widget.TextNode, YAHOO.widget.Node, {
1689
1690 /**
1691 * The CSS class for the label href. Defaults to ygtvlabel, but can be
1692 * overridden to provide a custom presentation for a specific node.
1693 * @property labelStyle
1694 * @type string
1695 */
1696 labelStyle: "ygtvlabel",
1697
1698 /**
1699 * The derived element id of the label for this node
1700 * @property labelElId
1701 * @type string
1702 */
1703 labelElId: null,
1704
1705 /**
1706 * The text for the label. It is assumed that the oData parameter will
1707 * either be a string that will be used as the label, or an object that
1708 * has a property called "label" that we will use.
1709 * @property label
1710 * @type string
1711 */
1712 label: null,
1713
1714 textNodeParentChange: function() {
1715
1716 /**
1717 * Custom event that is fired when the text node label is clicked. The
1718 * custom event is defined on the tree instance, so there is a single
1719 * event that handles all nodes in the tree. The node clicked is
1720 * provided as an argument
1721 *
1722 * @event labelClick
1723 * @for YAHOO.widget.TreeView
1724 * @param {YAHOO.widget.Node} node the node clicked
1725 */
1726 if (this.tree && !this.tree.hasEvent("labelClick")) {
1727 this.tree.createEvent("labelClick", this.tree);
1728 }
1729
1730 },
1731
1732 /**
1733 * Sets up the node label
1734 * @method setUpLabel
1735 * @param oData string containing the label, or an object with a label property
1736 */
1737 setUpLabel: function(oData) {
1738
1739 // set up the custom event on the tree
1740 this.textNodeParentChange();
1741 this.subscribe("parentChange", this.textNodeParentChange);
1742
1743 if (typeof oData == "string") {
1744 oData = { label: oData };
1745 }
1746 this.label = oData.label;
1747
1748 // update the link
1749 if (oData.href) {
1750 this.href = oData.href;
1751 }
1752
1753 // set the target
1754 if (oData.target) {
1755 this.target = oData.target;
1756 }
1757
1758 if (oData.style) {
1759 this.labelStyle = oData.style;
1760 }
1761
1762 this.labelElId = "ygtvlabelel" + this.index;
1763 },
1764
1765 /**
1766 * Returns the label element
1767 * @for YAHOO.widget.TextNode
1768 * @method getLabelEl
1769 * @return {object} the element
1770 */
1771 getLabelEl: function() {
1772 return document.getElementById(this.labelElId);
1773 },
1774
1775 // overrides YAHOO.widget.Node
1776 getNodeHtml: function() {
1777 var sb = [];
1778
1779 sb[sb.length] = '<table border="0" cellpadding="0" cellspacing="0">';
1780 sb[sb.length] = '<tr>';
1781
1782 for (var i=0;i<this.depth;++i) {
1783 // sb[sb.length] = '<td class="ygtvdepthcell">&#160;</td>';
1784 sb[sb.length] = '<td class="' + this.getDepthStyle(i) + '">&#160;</td>';
1785 }
1786
1787 var getNode = 'YAHOO.widget.TreeView.getNode(\'' +
1788 this.tree.id + '\',' + this.index + ')';
1789
1790 sb[sb.length] = '<td';
1791 // sb[sb.length] = ' onselectstart="return false"';
1792 sb[sb.length] = ' id="' + this.getToggleElId() + '"';
1793 sb[sb.length] = ' class="' + this.getStyle() + '"';
1794 if (this.hasChildren(true)) {
1795 sb[sb.length] = ' onmouseover="this.className=';
1796 sb[sb.length] = getNode + '.getHoverStyle()"';
1797 sb[sb.length] = ' onmouseout="this.className=';
1798 sb[sb.length] = getNode + '.getStyle()"';
1799 }
1800 sb[sb.length] = ' onclick="javascript:' + this.getToggleLink() + '">';
1801
1802 /*
1803 sb[sb.length] = '<img id="' + this.getSpacerId() + '"';
1804 sb[sb.length] = ' alt=""';
1805 sb[sb.length] = ' tabindex=0';
1806 sb[sb.length] = ' src="' + this.spacerPath + '"';
1807 sb[sb.length] = ' title="' + this.getStateText() + '"';
1808 sb[sb.length] = ' class="ygtvspacer"';
1809 // sb[sb.length] = ' onkeypress="return ' + getNode + '".onKeyPress()"';
1810 sb[sb.length] = ' />';
1811 */
1812
1813 sb[sb.length] = '&#160;';
1814
1815 sb[sb.length] = '</td>';
1816 sb[sb.length] = '<td>';
1817 sb[sb.length] = '<a';
1818 sb[sb.length] = ' id="' + this.labelElId + '"';
1819 sb[sb.length] = ' class="' + this.labelStyle + '"';
1820 sb[sb.length] = ' href="' + this.href + '"';
1821 sb[sb.length] = ' target="' + this.target + '"';
1822 sb[sb.length] = ' onclick="return ' + getNode + '.onLabelClick(' + getNode +')"';
1823 if (this.hasChildren(true)) {
1824 sb[sb.length] = ' onmouseover="document.getElementById(\'';
1825 sb[sb.length] = this.getToggleElId() + '\').className=';
1826 sb[sb.length] = getNode + '.getHoverStyle()"';
1827 sb[sb.length] = ' onmouseout="document.getElementById(\'';
1828 sb[sb.length] = this.getToggleElId() + '\').className=';
1829 sb[sb.length] = getNode + '.getStyle()"';
1830 }
1831 sb[sb.length] = ' >';
1832 sb[sb.length] = this.label;
1833 sb[sb.length] = '</a>';
1834 sb[sb.length] = '</td>';
1835 sb[sb.length] = '</tr>';
1836 sb[sb.length] = '</table>';
1837
1838 return sb.join("");
1839 },
1840
1841 /**
1842 * Executed when the label is clicked. Fires the labelClick custom event.
1843 * @method onLabelClick
1844 * @param me {Node} this node
1845 * @scope the anchor tag clicked
1846 * @return false to cancel the anchor click
1847 */
1848 onLabelClick: function(me) {
1849 return me.tree.fireEvent("labelClick", me);
1850 //return true;
1851 },
1852
1853 toString: function() {
1854 return "TextNode (" + this.index + ") " + this.label;
1855 }
1856
1857});
1858/**
1859 * A menu-specific implementation that differs from TextNode in that only
1860 * one sibling can be expanded at a time.
1861 * @namespace YAHOO.widget
1862 * @class MenuNode
1863 * @extends YAHOO.widget.TextNode
1864 * @param oData {object} a string or object containing the data that will
1865 * be used to render this node
1866 * @param oParent {YAHOO.widget.Node} this node's parent node
1867 * @param expanded {boolean} the initial expanded/collapsed state
1868 * @constructor
1869 */
1870YAHOO.widget.MenuNode = function(oData, oParent, expanded) {
1871 if (oData) {
1872 this.init(oData, oParent, expanded);
1873 this.setUpLabel(oData);
1874 }
1875
1876 /*
1877 * Menus usually allow only one branch to be open at a time.
1878 */
1879 this.multiExpand = false;
1880
1881
1882};
1883
1884YAHOO.extend(YAHOO.widget.MenuNode, YAHOO.widget.TextNode, {
1885
1886 toString: function() {
1887 return "MenuNode (" + this.index + ") " + this.label;
1888 }
1889
1890});
1891/**
1892 * This implementation takes either a string or object for the
1893 * oData argument. If is it a string, we will use it for the display
1894 * of this node (and it can contain any html code). If the parameter
1895 * is an object, we look for a parameter called "html" that will be
1896 * used for this node's display.
1897 * @namespace YAHOO.widget
1898 * @class HTMLNode
1899 * @extends YAHOO.widget.Node
1900 * @constructor
1901 * @param oData {object} a string or object containing the data that will
1902 * be used to render this node
1903 * @param oParent {YAHOO.widget.Node} this node's parent node
1904 * @param expanded {boolean} the initial expanded/collapsed state
1905 * @param hasIcon {boolean} specifies whether or not leaf nodes should
1906 * have an icon
1907 */
1908YAHOO.widget.HTMLNode = function(oData, oParent, expanded, hasIcon) {
1909 if (oData) {
1910 this.init(oData, oParent, expanded);
1911 this.initContent(oData, hasIcon);
1912 }
1913};
1914
1915YAHOO.extend(YAHOO.widget.HTMLNode, YAHOO.widget.Node, {
1916
1917 /**
1918 * The CSS class for the html content container. Defaults to ygtvhtml, but
1919 * can be overridden to provide a custom presentation for a specific node.
1920 * @property contentStyle
1921 * @type string
1922 */
1923 contentStyle: "ygtvhtml",
1924
1925 /**
1926 * The generated id that will contain the data passed in by the implementer.
1927 * @property contentElId
1928 * @type string
1929 */
1930 contentElId: null,
1931
1932 /**
1933 * The HTML content to use for this node's display
1934 * @property content
1935 * @type string
1936 */
1937 content: null,
1938
1939 /**
1940 * Sets up the node label
1941 * @property initContent
1942 * @param {object} An html string or object containing an html property
1943 * @param {boolean} hasIcon determines if the node will be rendered with an
1944 * icon or not
1945 */
1946 initContent: function(oData, hasIcon) {
1947 if (typeof oData == "string") {
1948 oData = { html: oData };
1949 }
1950
1951 this.html = oData.html;
1952 this.contentElId = "ygtvcontentel" + this.index;
1953 this.hasIcon = hasIcon;
1954
1955 },
1956
1957 /**
1958 * Returns the outer html element for this node's content
1959 * @method getContentEl
1960 * @return {HTMLElement} the element
1961 */
1962 getContentEl: function() {
1963 return document.getElementById(this.contentElId);
1964 },
1965
1966 // overrides YAHOO.widget.Node
1967 getNodeHtml: function() {
1968 var sb = [];
1969
1970 sb[sb.length] = '<table border="0" cellpadding="0" cellspacing="0">';
1971 sb[sb.length] = '<tr>';
1972
1973 for (var i=0;i<this.depth;++i) {
1974 sb[sb.length] = '<td class="' + this.getDepthStyle(i) + '">&#160;</td>';
1975 }
1976
1977 if (this.hasIcon) {
1978 sb[sb.length] = '<td';
1979 sb[sb.length] = ' id="' + this.getToggleElId() + '"';
1980 sb[sb.length] = ' class="' + this.getStyle() + '"';
1981 sb[sb.length] = ' onclick="javascript:' + this.getToggleLink() + '"';
1982 if (this.hasChildren(true)) {
1983 sb[sb.length] = ' onmouseover="this.className=';
1984 sb[sb.length] = 'YAHOO.widget.TreeView.getNode(\'';
1985 sb[sb.length] = this.tree.id + '\',' + this.index + ').getHoverStyle()"';
1986 sb[sb.length] = ' onmouseout="this.className=';
1987 sb[sb.length] = 'YAHOO.widget.TreeView.getNode(\'';
1988 sb[sb.length] = this.tree.id + '\',' + this.index + ').getStyle()"';
1989 }
1990 sb[sb.length] = '>&#160;</td>';
1991 }
1992
1993 sb[sb.length] = '<td';
1994 sb[sb.length] = ' id="' + this.contentElId + '"';
1995 sb[sb.length] = ' class="' + this.contentStyle + '"';
1996 sb[sb.length] = ' >';
1997 sb[sb.length] = this.html;
1998 sb[sb.length] = '</td>';
1999 sb[sb.length] = '</tr>';
2000 sb[sb.length] = '</table>';
2001
2002 return sb.join("");
2003 },
2004
2005 toString: function() {
2006 return "HTMLNode (" + this.index + ")";
2007 }
2008
2009});
2010/**
2011 * A static factory class for tree view expand/collapse animations
2012 * @class TVAnim
2013 * @static
2014 */
2015YAHOO.widget.TVAnim = function() {
2016 return {
2017 /**
2018 * Constant for the fade in animation
2019 * @property FADE_IN
2020 * @type string
2021 * @static
2022 */
2023 FADE_IN: "TVFadeIn",
2024
2025 /**
2026 * Constant for the fade out animation
2027 * @property FADE_OUT
2028 * @type string
2029 * @static
2030 */
2031 FADE_OUT: "TVFadeOut",
2032
2033 /**
2034 * Returns a ygAnim instance of the given type
2035 * @method getAnim
2036 * @param type {string} the type of animation
2037 * @param el {HTMLElement} the element to element (probably the children div)
2038 * @param callback {function} function to invoke when the animation is done.
2039 * @return {YAHOO.util.Animation} the animation instance
2040 * @static
2041 */
2042 getAnim: function(type, el, callback) {
2043 if (YAHOO.widget[type]) {
2044 return new YAHOO.widget[type](el, callback);
2045 } else {
2046 return null;
2047 }
2048 },
2049
2050 /**
2051 * Returns true if the specified animation class is available
2052 * @method isValid
2053 * @param type {string} the type of animation
2054 * @return {boolean} true if valid, false if not
2055 * @static
2056 */
2057 isValid: function(type) {
2058 return (YAHOO.widget[type]);
2059 }
2060 };
2061} ();
2062
2063/**
2064 * A 1/2 second fade-in animation.
2065 * @class TVFadeIn
2066 * @constructor
2067 * @param el {HTMLElement} the element to animate
2068 * @param callback {function} function to invoke when the animation is finished
2069 */
2070YAHOO.widget.TVFadeIn = function(el, callback) {
2071 /**
2072 * The element to animate
2073 * @property el
2074 * @type HTMLElement
2075 */
2076 this.el = el;
2077
2078 /**
2079 * the callback to invoke when the animation is complete
2080 * @property callback
2081 * @type function
2082 */
2083 this.callback = callback;
2084
2085};
2086
2087YAHOO.widget.TVFadeIn.prototype = {
2088 /**
2089 * Performs the animation
2090 * @method animate
2091 */
2092 animate: function() {
2093 var tvanim = this;
2094
2095 var s = this.el.style;
2096 s.opacity = 0.1;
2097 s.filter = "alpha(opacity=10)";
2098 s.display = "";
2099
2100 var dur = 0.4;
2101 var a = new YAHOO.util.Anim(this.el, {opacity: {from: 0.1, to: 1, unit:""}}, dur);
2102 a.onComplete.subscribe( function() { tvanim.onComplete(); } );
2103 a.animate();
2104 },
2105
2106 /**
2107 * Clean up and invoke callback
2108 * @method onComplete
2109 */
2110 onComplete: function() {
2111 this.callback();
2112 },
2113
2114 /**
2115 * toString
2116 * @method toString
2117 * @return {string} the string representation of the instance
2118 */
2119 toString: function() {
2120 return "TVFadeIn";
2121 }
2122};
2123
2124/**
2125 * A 1/2 second fade out animation.
2126 * @class TVFadeOut
2127 * @constructor
2128 * @param el {HTMLElement} the element to animate
2129 * @param callback {Function} function to invoke when the animation is finished
2130 */
2131YAHOO.widget.TVFadeOut = function(el, callback) {
2132 /**
2133 * The element to animate
2134 * @property el
2135 * @type HTMLElement
2136 */
2137 this.el = el;
2138
2139 /**
2140 * the callback to invoke when the animation is complete
2141 * @property callback
2142 * @type function
2143 */
2144 this.callback = callback;
2145
2146};
2147
2148YAHOO.widget.TVFadeOut.prototype = {
2149 /**
2150 * Performs the animation
2151 * @method animate
2152 */
2153 animate: function() {
2154 var tvanim = this;
2155 var dur = 0.4;
2156 var a = new YAHOO.util.Anim(this.el, {opacity: {from: 1, to: 0.1, unit:""}}, dur);
2157 a.onComplete.subscribe( function() { tvanim.onComplete(); } );
2158 a.animate();
2159 },
2160
2161 /**
2162 * Clean up and invoke callback
2163 * @method onComplete
2164 */
2165 onComplete: function() {
2166 var s = this.el.style;
2167 s.display = "none";
2168 // s.opacity = 1;
2169 s.filter = "alpha(opacity=100)";
2170 this.callback();
2171 },
2172
2173 /**
2174 * toString
2175 * @method toString
2176 * @return {string} the string representation of the instance
2177 */
2178 toString: function() {
2179 return "TVFadeOut";
2180 }
2181};
2182