· 9 years ago · Oct 26, 2016, 08:02 PM
1
2(function () {
3 var Dom = YAHOO.util.Dom,
4 Event = YAHOO.util.Event,
5 Lang = YAHOO.lang,
6 Widget = YAHOO.widget;
7
8/**
9 * The treeview widget is a generic tree building tool.
10 * @module treeview
11 * @title TreeView Widget
12 * @requires yahoo, event
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 * @uses YAHOO.util.EventProvider
22 * @constructor
23 * @param {string|HTMLElement} id The id of the element, or the element itself that the tree will be inserted into. Existing markup in this element, if valid, will be used to build the tree
24 * @param {Array|object|string} oConfig (optional) An array containing the definition of the tree. Objects will be converted to arrays of one element. A string will produce a single TextNode
25 *
26 */
27YAHOO.widget.TreeView = function(id, oConfig) {
28 if (id) { this.init(id); }
29 if (oConfig) {
30 if (!Lang.isArray(oConfig)) {
31 oConfig = [oConfig];
32 }
33 this.buildTreeFromObject(oConfig);
34 } else if (Lang.trim(this._el.innerHTML)) {
35 this.buildTreeFromMarkup(id);
36 }
37};
38
39var TV = Widget.TreeView;
40
41TV.prototype = {
42
43 /**
44 * The id of tree container element
45 * @property id
46 * @type String
47 */
48 id: null,
49
50 /**
51 * The host element for this tree
52 * @property _el
53 * @private
54 * @type HTMLelement
55 */
56 _el: null,
57
58 /**
59 * Flat collection of all nodes in this tree. This is a sparse
60 * array, so the length property can't be relied upon for a
61 * node count for the tree.
62 * @property _nodes
63 * @type Node[]
64 * @private
65 */
66 _nodes: null,
67
68 /**
69 * We lock the tree control while waiting for the dynamic loader to return
70 * @property locked
71 * @type boolean
72 */
73 locked: false,
74
75 /**
76 * The animation to use for expanding children, if any
77 * @property _expandAnim
78 * @type string
79 * @private
80 */
81 _expandAnim: null,
82
83 /**
84 * The animation to use for collapsing children, if any
85 * @property _collapseAnim
86 * @type string
87 * @private
88 */
89 _collapseAnim: null,
90
91 /**
92 * The current number of animations that are executing
93 * @property _animCount
94 * @type int
95 * @private
96 */
97 _animCount: 0,
98
99 /**
100 * The maximum number of animations to run at one time.
101 * @property maxAnim
102 * @type int
103 */
104 maxAnim: 2,
105
106 /**
107 * Whether there is any subscriber to dblClickEvent
108 * @property _hasDblClickSubscriber
109 * @type boolean
110 * @private
111 */
112 _hasDblClickSubscriber: false,
113
114 /**
115 * Stores the timer used to check for double clicks
116 * @property _dblClickTimer
117 * @type window.timer object
118 * @private
119 */
120 _dblClickTimer: null,
121
122
123 /**
124 * Sets up the animation for expanding children
125 * @method setExpandAnim
126 * @param {string} type the type of animation (acceptable values defined
127 * in YAHOO.widget.TVAnim)
128 */
129 setExpandAnim: function(type) {
130 this._expandAnim = (Widget.TVAnim.isValid(type)) ? type : null;
131 },
132
133 /**
134 * Sets up the animation for collapsing children
135 * @method setCollapseAnim
136 * @param {string} the type of animation (acceptable values defined in
137 * YAHOO.widget.TVAnim)
138 */
139 setCollapseAnim: function(type) {
140 this._collapseAnim = (Widget.TVAnim.isValid(type)) ? type : null;
141 },
142
143 /**
144 * Perform the expand animation if configured, or just show the
145 * element if not configured or too many animations are in progress
146 * @method animateExpand
147 * @param el {HTMLElement} the element to animate
148 * @param node {YAHOO.util.Node} the node that was expanded
149 * @return {boolean} true if animation could be invoked, false otherwise
150 */
151 animateExpand: function(el, node) {
152 this.logger.log("animating expand");
153
154 if (this._expandAnim && this._animCount < this.maxAnim) {
155 // this.locked = true;
156 var tree = this;
157 var a = Widget.TVAnim.getAnim(this._expandAnim, el,
158 function() { tree.expandComplete(node); });
159 if (a) {
160 ++this._animCount;
161 this.fireEvent("animStart", {
162 "node": node,
163 "type": "expand"
164 });
165 a.animate();
166 }
167
168 return true;
169 }
170
171 return false;
172 },
173
174 /**
175 * Perform the collapse animation if configured, or just show the
176 * element if not configured or too many animations are in progress
177 * @method animateCollapse
178 * @param el {HTMLElement} the element to animate
179 * @param node {YAHOO.util.Node} the node that was expanded
180 * @return {boolean} true if animation could be invoked, false otherwise
181 */
182 animateCollapse: function(el, node) {
183 this.logger.log("animating collapse");
184
185 if (this._collapseAnim && this._animCount < this.maxAnim) {
186 // this.locked = true;
187 var tree = this;
188 var a = Widget.TVAnim.getAnim(this._collapseAnim, el,
189 function() { tree.collapseComplete(node); });
190 if (a) {
191 ++this._animCount;
192 this.fireEvent("animStart", {
193 "node": node,
194 "type": "collapse"
195 });
196 a.animate();
197 }
198
199 return true;
200 }
201
202 return false;
203 },
204
205 /**
206 * Function executed when the expand animation completes
207 * @method expandComplete
208 */
209 expandComplete: function(node) {
210 this.logger.log("expand complete: " + this.id);
211 --this._animCount;
212 this.fireEvent("animComplete", {
213 "node": node,
214 "type": "expand"
215 });
216 // this.locked = false;
217 },
218
219 /**
220 * Function executed when the collapse animation completes
221 * @method collapseComplete
222 */
223 collapseComplete: function(node) {
224 this.logger.log("collapse complete: " + this.id);
225 --this._animCount;
226 this.fireEvent("animComplete", {
227 "node": node,
228 "type": "collapse"
229 });
230 // this.locked = false;
231 },
232
233 /**
234 * Initializes the tree
235 * @method init
236 * @parm {string|HTMLElement} id the id of the element that will hold the tree
237 * @private
238 */
239 init: function(id) {
240 this._el = Dom.get(id);
241 this.id = Dom.generateId(this._el,"yui-tv-auto-id-");
242
243 /**
244 * When animation is enabled, this event fires when the animation
245 * starts
246 * @event animStart
247 * @type CustomEvent
248 * @param {YAHOO.widget.Node} node the node that is expanding/collapsing
249 * @parm {String} type the type of animation ("expand" or "collapse")
250 */
251 this.createEvent("animStart", this);
252
253 /**
254 * When animation is enabled, this event fires when the animation
255 * completes
256 * @event animComplete
257 * @type CustomEvent
258 * @param {YAHOO.widget.Node} node the node that is expanding/collapsing
259 * @parm {String} type the type of animation ("expand" or "collapse")
260 */
261 this.createEvent("animComplete", this);
262
263 /**
264 * Fires when a node is going to be collapsed. Return false to stop
265 * the collapse.
266 * @event collapse
267 * @type CustomEvent
268 * @param {YAHOO.widget.Node} node the node that is collapsing
269 */
270 this.createEvent("collapse", this);
271
272 /**
273 * Fires after a node is successfully collapsed. This event will not fire
274 * if the "collapse" event was cancelled.
275 * @event collapseComplete
276 * @type CustomEvent
277 * @param {YAHOO.widget.Node} node the node that was collapsed
278 */
279 this.createEvent("collapseComplete", this);
280
281 /**
282 * Fires when a node is going to be expanded. Return false to stop
283 * the collapse.
284 * @event expand
285 * @type CustomEvent
286 * @param {YAHOO.widget.Node} node the node that is expanding
287 */
288 this.createEvent("expand", this);
289
290 /**
291 * Fires after a node is successfully expanded. This event will not fire
292 * if the "expand" event was cancelled.
293 * @event expandComplete
294 * @type CustomEvent
295 * @param {YAHOO.widget.Node} node the node that was expanded
296 */
297 this.createEvent("expandComplete", this);
298
299 /**
300 * Fires when the Enter key is pressed on a node that has the focus
301 * @event enterKeyPressed
302 * @type CustomEvent
303 * @param {YAHOO.widget.Node} node the node that has the focus
304 */
305 this.createEvent("enterKeyPressed", this);
306
307 /**
308 * Fires when the label in a TextNode or MenuNode or content in an HTMLNode receives a Click.
309 * The listener may return false to cancel toggling and focusing on the node.
310 * @event clickEvent
311 * @type CustomEvent
312 * @param oArgs.event {HTMLEvent} The event object
313 * @param oArgs.node {YAHOO.widget.Node} node the node that was clicked
314 */
315 this.createEvent("clickEvent", this);
316
317 /**
318 * Fires when the label in a TextNode or MenuNode or content in an HTMLNode receives a double Click
319 * @event dblClickEvent
320 * @type CustomEvent
321 * @param oArgs.event {HTMLEvent} The event object
322 * @param oArgs.node {YAHOO.widget.Node} node the node that was clicked
323 */
324 var self = this;
325 this.createEvent("dblClickEvent", {
326 scope:this,
327 onSubscribeCallback: function() {
328 self._hasDblClickSubscriber = true;
329 }
330 });
331
332 /**
333 * Custom event that is fired when the text node label is clicked.
334 * The node clicked is provided as an argument
335 *
336 * @event labelClick
337 * @type CustomEvent
338 * @param {YAHOO.widget.Node} node the node clicked
339 * @deprecated use clickEvent or dblClickEvent
340 */
341 this.createEvent("labelClick", this);
342
343
344 this._nodes = [];
345
346 // store a global reference
347 TV.trees[this.id] = this;
348
349 // Set up the root node
350 this.root = new Widget.RootNode(this);
351
352 var LW = Widget.LogWriter;
353
354 this.logger = (LW) ? new LW(this.toString()) : YAHOO;
355
356 this.logger.log("tree init: " + this.id);
357
358 // YAHOO.util.Event.onContentReady(this.id, this.handleAvailable, this, true);
359 // YAHOO.util.Event.on(this.id, "click", this.handleClick, this, true);
360 },
361
362 //handleAvailable: function() {
363 //var Event = YAHOO.util.Event;
364 //Event.on(this.id,
365 //},
366 /**
367 * Builds the TreeView from an object. This is the method called by the constructor to build the tree when it has a second argument.
368 * @method buildTreeFromObject
369 * @param oConfig {Array} array containing a full description of the tree
370 *
371 */
372 buildTreeFromObject: function (oConfig) {
373 this.logger.log('Building tree from object');
374 var build = function (parent, oConfig) {
375 var i, item, node, children, type, NodeType, ThisType;
376 for (i = 0; i < oConfig.length; i++) {
377 item = oConfig[i];
378 if (Lang.isString(item)) {
379 node = new Widget.TextNode(item, parent);
380 } else if (Lang.isObject(item)) {
381 children = item.children;
382 delete item.children;
383 type = item.type || 'text';
384 delete item.type;
385 switch (type.toLowerCase()) {
386 case 'text':
387 node = new Widget.TextNode(item, parent);
388 break;
389 case 'menu':
390 node = new Widget.MenuNode(item, parent);
391 break;
392 case 'html':
393 node = new Widget.HTMLNode(item, parent);
394 break;
395 default:
396 NodeType = Widget[type];
397 if (Lang.isObject(NodeType)) {
398 for (ThisType = NodeType; ThisType && ThisType !== Widget.Node; ThisType = ThisType.superclass.constructor) {}
399 if (ThisType) {
400 node = new NodeType(item, parent);
401 } else {
402 this.logger.log('Invalid type in node definition: ' + type,'error');
403 }
404 } else {
405 this.logger.log('Invalid type in node definition: ' + type,'error');
406 }
407 }
408 if (children) {
409 build(node,children);
410 }
411 } else {
412 this.logger.log('Invalid node definition','error');
413 }
414 }
415 };
416
417
418 build(this.root,oConfig);
419 },
420/**
421 * Builds the TreeView from existing markup. Markup should consist of <UL> or <OL> elements, possibly nested.
422 * Depending what the <LI> elements contain the following will be created: <ul>
423 * <li>plain text: a regular TextNode</li>
424 * <li>an (un-)ordered list: a nested branch</li>
425 * <li>anything else: an HTMLNode</li></ul>
426 * Only the first outermost (un-)ordered list in the markup and its children will be parsed.
427 * Tree will be fully collapsed.
428 * HTMLNodes have hasIcon set to true if the markup for that node has a className called hasIcon.
429 * @method buildTreeFromMarkup
430 * @param {string|HTMLElement} id the id of the element that contains the markup or a reference to it.
431 */
432 buildTreeFromMarkup: function (id) {
433 this.logger.log('Building tree from existing markup');
434 var build = function (parent,markup) {
435 var el, node, child, text;
436 for (el = Dom.getFirstChild(markup); el; el = Dom.getNextSibling(el)) {
437 if (el.nodeType == 1) {
438 switch (el.tagName.toUpperCase()) {
439 case 'LI':
440 for (child = el.firstChild; child; child = child.nextSibling) {
441 if (child.nodeType == 3) {
442 text = Lang.trim(child.nodeValue);
443 if (text.length) {
444 node = new Widget.TextNode(text, parent, false);
445 }
446 } else {
447 switch (child.tagName.toUpperCase()) {
448 case 'UL':
449 case 'OL':
450 build(node,child);
451 break;
452 case 'A':
453 node = new Widget.TextNode({
454 label:child.innerHTML,
455 href: child.href,
456 target:child.target,
457 title:child.title ||child.alt
458 },parent,false);
459 break;
460 default:
461 node = new Widget.HTMLNode(child.parentNode.innerHTML, parent, false, true);
462 break;
463 }
464 }
465 }
466 break;
467 case 'UL':
468 case 'OL':
469 this.logger.log('ULs or OLs can only contain LI elements, not other UL or OL. This will not work in some browsers','error');
470 build(node, el);
471 break;
472 }
473 }
474 }
475
476 };
477 var markup = Dom.getChildrenBy(Dom.get(id),function (el) {
478 var tag = el.tagName.toUpperCase();
479 return tag == 'UL' || tag == 'OL';
480 });
481 if (markup.length) {
482 build(this.root, markup[0]);
483 } else {
484 this.logger.log('Markup contains no UL or OL elements','warn');
485 }
486 },
487 /**
488 * Renders the tree boilerplate and visible nodes
489 * @method render
490 */
491 render: function() {
492 var html = this.root.getHtml();
493 this.getEl().innerHTML = html;
494 var getTarget = function (ev) {
495 var target = Event.getTarget(ev);
496 if (target.tagName.toUpperCase() != 'TD') { target = Dom.getAncestorByTagName(target,'td'); }
497 if (Lang.isNull(target)) { return null; }
498 if (target.className.length === 0) {
499 target = target.previousSibling;
500 if (Lang.isNull(target)) { return null; }
501 }
502 return target;
503 };
504 if (!this._hasEvents) {
505 Event.on(
506 this.getEl(),
507 'click',
508 function (ev) {
509 var self = this,
510 el = Event.getTarget(ev),
511 node = this.getNodeByElement(el);
512 if (!node) { return; }
513
514 var toggle = function () {
515 if (node.expanded) {
516 node.collapse();
517 } else {
518 node.expand();
519 }
520 node.focus();
521 };
522
523 if (Dom.hasClass(el, node.labelStyle) || Dom.getAncestorByClassName(el,node.labelStyle)) {
524 this.logger.log("onLabelClick " + node.label);
525 this.fireEvent('labelClick',node);
526 }
527 while (el && !Dom.hasClass(el.parentNode,'ygtvrow') && !/ygtv[tl][mp]h?h?/.test(el.className)) {
528 el = Dom.getAncestorByTagName(el,'td');
529 }
530 if (el) {
531 // If it is a spacer cell, do nothing
532 if (/ygtv(blank)?depthcell/.test(el.className)) { return;}
533 // If it is a toggle cell, toggle
534 if (/ygtv[tl][mp]h?h?/.test(el.className)) {
535 toggle();
536 } else {
537 if (this._dblClickTimer) {
538 window.clearTimeout(this._dblClickTimer);
539 this._dblClickTimer = null;
540 } else {
541 if (this._hasDblClickSubscriber) {
542 this._dblClickTimer = window.setTimeout(function () {
543 self._dblClickTimer = null;
544 if (self.fireEvent('clickEvent', {event:ev,node:node}) !== false) {
545 toggle();
546 }
547 }, 200);
548 } else {
549 if (self.fireEvent('clickEvent', {event:ev,node:node}) !== false) {
550 toggle();
551 }
552 }
553 }
554 }
555 }
556 },
557 this,
558 true
559 );
560
561 Event.on(
562 this.getEl(),
563 'dblclick',
564 function (ev) {
565 if (!this._hasDblClickSubscriber) { return; }
566 var el = Event.getTarget(ev);
567 while (!Dom.hasClass(el.parentNode,'ygtvrow')) {
568 el = Dom.getAncestorByTagName(el,'td');
569 }
570 if (/ygtv(blank)?depthcell/.test(el.className)) { return;}
571 if (!(/ygtv[tl][mp]h?h?/.test(el.className))) {
572 this.fireEvent('dblClickEvent', {event:ev, node:this.getNodeByElement(el)});
573 if (this._dblClickTimer) {
574 window.clearTimeout(this._dblClickTimer);
575 this._dblClickTimer = null;
576 }
577 }
578 },
579 this,
580 true
581 );
582 Event.on(
583 this.getEl(),
584 'mouseover',
585 function (ev) {
586 var target = getTarget(ev);
587 if (target) {
588target.className = target.className.replace(/ygtv([lt])([mp])/gi, 'ygtv$1$2h').replace(/h+/, 'h');
589 }
590 }
591 );
592 Event.on(
593 this.getEl(),
594 'mouseout',
595 function (ev) {
596 var target = getTarget(ev);
597 if (target) {
598 target.className = target.className.replace(/ygtv([lt])([mp])h/gi,'ygtv$1$2');
599 }
600 }
601 );
602 Event.on(
603 this.getEl(),
604 'keydown',
605 function (ev) {
606 var target = Event.getTarget(ev),
607 node = this.getNodeByElement(target),
608 newNode = node,
609 KEY = YAHOO.util.KeyListener.KEY;
610
611 switch(ev.keyCode) {
612 case KEY.UP:
613 this.logger.log('UP');
614 do {
615 if (newNode.previousSibling) {
616 newNode = newNode.previousSibling;
617 } else {
618 newNode = newNode.parent;
619 }
620 } while (newNode && !newNode.focus());
621 if (!newNode) { node.focus(); }
622 Event.preventDefault(ev);
623 break;
624 case KEY.DOWN:
625 this.logger.log('DOWN');
626 do {
627 if (newNode.nextSibling) {
628 newNode = newNode.nextSibling;
629 } else {
630 newNode.expand();
631 newNode = (newNode.children.length || null) && newNode.children[0];
632 }
633 } while (newNode && !newNode.focus());
634 if (!newNode) { node.focus(); }
635 Event.preventDefault(ev);
636 break;
637 case KEY.LEFT:
638 this.logger.log('LEFT');
639 do {
640 if (newNode.parent) {
641 newNode = newNode.parent;
642 } else {
643 newNode = newNode.previousSibling;
644 }
645 } while (newNode && !newNode.focus());
646 if (!newNode) { node.focus(); }
647 Event.preventDefault(ev);
648 break;
649 case KEY.RIGHT:
650 this.logger.log('RIGHT');
651 do {
652 newNode.expand();
653 if (newNode.children.length) {
654 newNode = newNode.children[0];
655 } else {
656 newNode = newNode.nextSibling;
657 }
658 } while (newNode && !newNode.focus());
659 if (!newNode) { node.focus(); }
660 Event.preventDefault(ev);
661 break;
662 case KEY.ENTER:
663 this.logger.log('ENTER: ' + newNode.href);
664 if (node.href) {
665 if (node.target) {
666 window.open(node.href,node.target);
667 } else {
668 window.location(node.href);
669 }
670 } else {
671 node.toggle();
672 }
673 this.fireEvent('enterKeyPressed',node);
674 Event.preventDefault(ev);
675 break;
676 case KEY.HOME:
677 this.logger.log('HOME');
678 newNode = this.getRoot();
679 if (newNode.children.length) {newNode = newNode.children[0];}
680 if (!newNode.focus()) { node.focus(); }
681 Event.preventDefault(ev);
682 break;
683 case KEY.END:
684 this.logger.log('END');
685 newNode = newNode.parent.children;
686 newNode = newNode[newNode.length -1];
687 if (!newNode.focus()) { node.focus(); }
688 Event.preventDefault(ev);
689 break;
690 // case KEY.PAGE_UP:
691 // this.logger.log('PAGE_UP');
692 // break;
693 // case KEY.PAGE_DOWN:
694 // this.logger.log('PAGE_DOWN');
695 // break;
696 case 107: // plus key
697 if (ev.shiftKey) {
698 this.logger.log('Shift-PLUS');
699 node.parent.expandAll();
700 } else {
701 this.logger.log('PLUS');
702 node.expand();
703 }
704 break;
705 case 109: // minus key
706 if (ev.shiftKey) {
707 this.logger.log('Shift-MINUS');
708 node.parent.collapseAll();
709 } else {
710 this.logger.log('MINUS');
711 node.collapse();
712 }
713 break;
714 default:
715 break;
716 }
717 },
718 this,
719 true
720 );
721 }
722 this._hasEvents = true;
723 },
724
725 /**
726 * Returns the tree's host element
727 * @method getEl
728 * @return {HTMLElement} the host element
729 */
730 getEl: function() {
731 if (! this._el) {
732 this._el = Dom.get(this.id);
733 }
734 return this._el;
735 },
736
737 /**
738 * Nodes register themselves with the tree instance when they are created.
739 * @method regNode
740 * @param node {Node} the node to register
741 * @private
742 */
743 regNode: function(node) {
744 this._nodes[node.index] = node;
745 },
746
747 /**
748 * Returns the root node of this tree
749 * @method getRoot
750 * @return {Node} the root node
751 */
752 getRoot: function() {
753 return this.root;
754 },
755
756 /**
757 * Configures this tree to dynamically load all child data
758 * @method setDynamicLoad
759 * @param {function} fnDataLoader the function that will be called to get the data
760 * @param iconMode {int} configures the icon that is displayed when a dynamic
761 * load node is expanded the first time without children. By default, the
762 * "collapse" icon will be used. If set to 1, the leaf node icon will be
763 * displayed.
764 */
765 setDynamicLoad: function(fnDataLoader, iconMode) {
766 this.root.setDynamicLoad(fnDataLoader, iconMode);
767 },
768
769 /**
770 * Expands all child nodes. Note: this conflicts with the "multiExpand"
771 * node property. If expand all is called in a tree with nodes that
772 * do not allow multiple siblings to be displayed, only the last sibling
773 * will be expanded.
774 * @method expandAll
775 */
776 expandAll: function() {
777 if (!this.locked) {
778 this.root.expandAll();
779 }
780 },
781
782 /**
783 * Collapses all expanded child nodes in the entire tree.
784 * @method collapseAll
785 */
786 collapseAll: function() {
787 if (!this.locked) {
788 this.root.collapseAll();
789 }
790 },
791
792 /**
793 * Returns a node in the tree that has the specified index (this index
794 * is created internally, so this function probably will only be used
795 * in html generated for a given node.)
796 * @method getNodeByIndex
797 * @param {int} nodeIndex the index of the node wanted
798 * @return {Node} the node with index=nodeIndex, null if no match
799 */
800 getNodeByIndex: function(nodeIndex) {
801 var n = this._nodes[nodeIndex];
802 return (n) ? n : null;
803 },
804
805 /**
806 * Returns a node that has a matching property and value in the data
807 * object that was passed into its constructor.
808 * @method getNodeByProperty
809 * @param {object} property the property to search (usually a string)
810 * @param {object} value the value we want to find (usuall an int or string)
811 * @return {Node} the matching node, null if no match
812 */
813 getNodeByProperty: function(property, value) {
814 for (var i in this._nodes) {
815 if (this._nodes.hasOwnProperty(i)) {
816 var n = this._nodes[i];
817 if (n.data && value == n.data[property]) {
818 return n;
819 }
820 }
821 }
822
823 return null;
824 },
825
826 /**
827 * Returns a collection of nodes that have a matching property
828 * and value in the data object that was passed into its constructor.
829 * @method getNodesByProperty
830 * @param {object} property the property to search (usually a string)
831 * @param {object} value the value we want to find (usuall an int or string)
832 * @return {Array} the matching collection of nodes, null if no match
833 */
834 getNodesByProperty: function(property, value) {
835 var values = [];
836 for (var i in this._nodes) {
837 if (this._nodes.hasOwnProperty(i)) {
838 var n = this._nodes[i];
839 if (n.data && value == n.data[property]) {
840 values.push(n);
841 }
842 }
843 }
844
845 return (values.length) ? values : null;
846 },
847
848 /**
849 * Returns the treeview node reference for an anscestor element
850 * of the node, or null if it is not contained within any node
851 * in this tree.
852 * @method getNodeByElement
853 * @param {HTMLElement} the element to test
854 * @return {YAHOO.widget.Node} a node reference or null
855 */
856 getNodeByElement: function(el) {
857
858 var p=el, m, re=/ygtv([^\d]*)(.*)/;
859
860 do {
861
862 if (p && p.id) {
863 m = p.id.match(re);
864 if (m && m[2]) {
865 return this.getNodeByIndex(m[2]);
866 }
867 }
868
869 p = p.parentNode;
870
871 if (!p || !p.tagName) {
872 break;
873 }
874
875 }
876 while (p.id !== this.id && p.tagName.toLowerCase() !== "body");
877
878 return null;
879 },
880
881 /**
882 * Removes the node and its children, and optionally refreshes the
883 * branch of the tree that was affected.
884 * @method removeNode
885 * @param {Node} The node to remove
886 * @param {boolean} autoRefresh automatically refreshes branch if true
887 * @return {boolean} False is there was a problem, true otherwise.
888 */
889 removeNode: function(node, autoRefresh) {
890
891 // Don't delete the root node
892 if (node.isRoot()) {
893 return false;
894 }
895
896 // Get the branch that we may need to refresh
897 var p = node.parent;
898 if (p.parent) {
899 p = p.parent;
900 }
901
902 // Delete the node and its children
903 this._deleteNode(node);
904
905 // Refresh the parent of the parent
906 if (autoRefresh && p && p.childrenRendered) {
907 p.refresh();
908 }
909
910 return true;
911 },
912
913 /**
914 * wait until the animation is complete before deleting
915 * to avoid javascript errors
916 * @method _removeChildren_animComplete
917 * @param o the custom event payload
918 * @private
919 */
920 _removeChildren_animComplete: function(o) {
921 this.unsubscribe(this._removeChildren_animComplete);
922 this.removeChildren(o.node);
923 },
924
925 /**
926 * Deletes this nodes child collection, recursively. Also collapses
927 * the node, and resets the dynamic load flag. The primary use for
928 * this method is to purge a node and allow it to fetch its data
929 * dynamically again.
930 * @method removeChildren
931 * @param {Node} node the node to purge
932 */
933 removeChildren: function(node) {
934
935 if (node.expanded) {
936 // wait until the animation is complete before deleting to
937 // avoid javascript errors
938 if (this._collapseAnim) {
939 this.subscribe("animComplete",
940 this._removeChildren_animComplete, this, true);
941 Widget.Node.prototype.collapse.call(node);
942 return;
943 }
944
945 node.collapse();
946 }
947
948 this.logger.log("Removing children for " + node);
949 while (node.children.length) {
950 this._deleteNode(node.children[0]);
951 }
952
953 if (node.isRoot()) {
954 Widget.Node.prototype.expand.call(node);
955 }
956
957 node.childrenRendered = false;
958 node.dynamicLoadComplete = false;
959
960 node.updateIcon();
961 },
962
963 /**
964 * Deletes the node and recurses children
965 * @method _deleteNode
966 * @private
967 */
968 _deleteNode: function(node) {
969 // Remove all the child nodes first
970 this.removeChildren(node);
971
972 // Remove the node from the tree
973 this.popNode(node);
974 },
975
976 /**
977 * Removes the node from the tree, preserving the child collection
978 * to make it possible to insert the branch into another part of the
979 * tree, or another tree.
980 * @method popNode
981 * @param {Node} the node to remove
982 */
983 popNode: function(node) {
984 var p = node.parent;
985
986 // Update the parent's collection of children
987 var a = [];
988
989 for (var i=0, len=p.children.length;i<len;++i) {
990 if (p.children[i] != node) {
991 a[a.length] = p.children[i];
992 }
993 }
994
995 p.children = a;
996
997 // reset the childrenRendered flag for the parent
998 p.childrenRendered = false;
999
1000 // Update the sibling relationship
1001 if (node.previousSibling) {
1002 node.previousSibling.nextSibling = node.nextSibling;
1003 }
1004
1005 if (node.nextSibling) {
1006 node.nextSibling.previousSibling = node.previousSibling;
1007 }
1008
1009 node.parent = null;
1010 node.previousSibling = null;
1011 node.nextSibling = null;
1012 node.tree = null;
1013
1014 // Update the tree's node collection
1015 delete this._nodes[node.index];
1016 },
1017
1018 /**
1019 * Nulls out the entire TreeView instance and related objects, removes attached
1020 * event listeners, and clears out DOM elements inside the container. After
1021 * calling this method, the instance reference should be expliclitly nulled by
1022 * implementer, as in myDataTable = null. Use with caution!
1023 *
1024 * @method destroy
1025 */
1026 destroy : function() {
1027 // Since the label editor can be separated from the main TreeView control
1028 // the destroy method for it might not be there.
1029 if (this._destroyEditor) { this._destroyEditor(); }
1030 var el = this.getEl();
1031 Event.removeListener(el,'click');
1032 Event.removeListener(el,'dblclick');
1033 Event.removeListener(el,'mouseover');
1034 Event.removeListener(el,'mouseout');
1035 Event.removeListener(el,'keydown');
1036 for (var i = 0 ; i < this._nodes.length; i++) {
1037 var node = this._nodes[i];
1038 if (node && node.destroy) {node.destroy(); }
1039 }
1040 el.parentNode.removeChild(el);
1041 this._hasEvents = false;
1042 },
1043
1044
1045
1046
1047 /**
1048 * TreeView instance toString
1049 * @method toString
1050 * @return {string} string representation of the tree
1051 */
1052 toString: function() {
1053 return "TreeView " + this.id;
1054 },
1055
1056 /**
1057 * Count of nodes in tree
1058 * @method getNodeCount
1059 * @return {int} number of nodes in the tree
1060 */
1061 getNodeCount: function() {
1062 return this.getRoot().getNodeCount();
1063 },
1064
1065 /**
1066 * Returns an object which could be used to rebuild the tree.
1067 * It can be passed to the tree constructor to reproduce the same tree.
1068 * It will return false if any node loads dynamically, regardless of whether it is loaded or not.
1069 * @method getTreeDefinition
1070 * @return {Object | false} definition of the tree or false if any node is defined as dynamic
1071 */
1072 getTreeDefinition: function() {
1073 return this.getRoot().getNodeDefinition();
1074 },
1075
1076 /**
1077 * Abstract method that is executed when a node is expanded
1078 * @method onExpand
1079 * @param node {Node} the node that was expanded
1080 * @deprecated use treeobj.subscribe("expand") instead
1081 */
1082 onExpand: function(node) { },
1083
1084 /**
1085 * Abstract method that is executed when a node is collapsed.
1086 * @method onCollapse
1087 * @param node {Node} the node that was collapsed.
1088 * @deprecated use treeobj.subscribe("collapse") instead
1089 */
1090 onCollapse: function(node) { }
1091
1092};
1093
1094/* Backwards compatibility aliases */
1095var PROT = TV.prototype;
1096 /**
1097 * Renders the tree boilerplate and visible nodes.
1098 * Alias for render
1099 * @method draw
1100 * @deprecated Use render instead
1101 */
1102PROT.draw = PROT.render;
1103
1104/* end backwards compatibility aliases */
1105
1106YAHOO.augment(TV, YAHOO.util.EventProvider);
1107
1108/**
1109 * Running count of all nodes created in all trees. This is
1110 * used to provide unique identifies for all nodes. Deleting
1111 * nodes does not change the nodeCount.
1112 * @property YAHOO.widget.TreeView.nodeCount
1113 * @type int
1114 * @static
1115 */
1116TV.nodeCount = 0;
1117
1118/**
1119 * Global cache of tree instances
1120 * @property YAHOO.widget.TreeView.trees
1121 * @type Array
1122 * @static
1123 * @private
1124 */
1125TV.trees = [];
1126
1127/**
1128 * Global method for getting a tree by its id. Used in the generated
1129 * tree html.
1130 * @method YAHOO.widget.TreeView.getTree
1131 * @param treeId {String} the id of the tree instance
1132 * @return {TreeView} the tree instance requested, null if not found.
1133 * @static
1134 */
1135TV.getTree = function(treeId) {
1136 var t = TV.trees[treeId];
1137 return (t) ? t : null;
1138};
1139
1140
1141/**
1142 * Global method for getting a node by its id. Used in the generated
1143 * tree html.
1144 * @method YAHOO.widget.TreeView.getNode
1145 * @param treeId {String} the id of the tree instance
1146 * @param nodeIndex {String} the index of the node to return
1147 * @return {Node} the node instance requested, null if not found
1148 * @static
1149 */
1150TV.getNode = function(treeId, nodeIndex) {
1151 var t = TV.getTree(treeId);
1152 return (t) ? t.getNodeByIndex(nodeIndex) : null;
1153};
1154
1155
1156/**
1157 * Class name assigned to elements that have the focus
1158 *
1159 * @property TreeView.FOCUS_CLASS_NAME
1160 * @type String
1161 * @static
1162 * @final
1163 * @default "ygtvfocus"
1164
1165 */
1166TV.FOCUS_CLASS_NAME = 'ygtvfocus';
1167
1168/**
1169 * Attempts to preload the images defined in the styles used to draw the tree by
1170 * rendering off-screen elements that use the styles.
1171 * @method YAHOO.widget.TreeView.preload
1172 * @param {string} prefix the prefix to use to generate the names of the
1173 * images to preload, default is ygtv
1174 * @static
1175 */
1176TV.preload = function(e, prefix) {
1177 prefix = prefix || "ygtv";
1178
1179 YAHOO.log("Preloading images: " + prefix, "info", "TreeView");
1180
1181 var styles = ["tn","tm","tmh","tp","tph","ln","lm","lmh","lp","lph","loading"];
1182 // var styles = ["tp"];
1183
1184 var sb = [];
1185
1186 // save the first one for the outer container
1187 for (var i=1; i < styles.length; i=i+1) {
1188 sb[sb.length] = '<span class="' + prefix + styles[i] + '"> </span>';
1189 }
1190
1191 var f = document.createElement("div");
1192 var s = f.style;
1193 s.className = prefix + styles[0];
1194 s.position = "absolute";
1195 s.height = "1px";
1196 s.width = "1px";
1197 s.top = "-1000px";
1198 s.left = "-1000px";
1199 f.innerHTML = sb.join("");
1200
1201 document.body.appendChild(f);
1202
1203 Event.removeListener(window, "load", TV.preload);
1204
1205};
1206
1207Event.addListener(window,"load", TV.preload);
1208})();
1209(function () {
1210 var Dom = YAHOO.util.Dom,
1211 Lang = YAHOO.lang,
1212 Event = YAHOO.util.Event;
1213/**
1214 * The base class for all tree nodes. The node's presentation and behavior in
1215 * response to mouse events is handled in Node subclasses.
1216 * @namespace YAHOO.widget
1217 * @class Node
1218 * @uses YAHOO.util.EventProvider
1219 * @param oData {object} a string or object containing the data that will
1220 * be used to render this node, and any custom attributes that should be
1221 * stored with the node (which is available in noderef.data).
1222 * All values in oData will be used to set equally named properties in the node
1223 * as long as the node does have such properties, they are not undefined, private or functions.
1224 * @param oParent {Node} this node's parent node
1225 * @param expanded {boolean} the initial expanded/collapsed state (deprecated, use oData.expanded)
1226 * @constructor
1227 */
1228YAHOO.widget.Node = function(oData, oParent, expanded) {
1229 if (oData) { this.init(oData, oParent, expanded); }
1230};
1231
1232YAHOO.widget.Node.prototype = {
1233
1234 /**
1235 * The index for this instance obtained from global counter in YAHOO.widget.TreeView.
1236 * @property index
1237 * @type int
1238 */
1239 index: 0,
1240
1241 /**
1242 * This node's child node collection.
1243 * @property children
1244 * @type Node[]
1245 */
1246 children: null,
1247
1248 /**
1249 * Tree instance this node is part of
1250 * @property tree
1251 * @type TreeView
1252 */
1253 tree: null,
1254
1255 /**
1256 * The data linked to this node. This can be any object or primitive
1257 * value, and the data can be used in getNodeHtml().
1258 * @property data
1259 * @type object
1260 */
1261 data: null,
1262
1263 /**
1264 * Parent node
1265 * @property parent
1266 * @type Node
1267 */
1268 parent: null,
1269
1270 /**
1271 * The depth of this node. We start at -1 for the root node.
1272 * @property depth
1273 * @type int
1274 */
1275 depth: -1,
1276
1277 /**
1278 * The href for the node's label. If one is not specified, the href will
1279 * be set so that it toggles the node.
1280 * @property href
1281 * @type string
1282 */
1283 href: null,
1284
1285 /**
1286 * The label href target, defaults to current window
1287 * @property target
1288 * @type string
1289 */
1290 target: "_self",
1291
1292 /**
1293 * The node's expanded/collapsed state
1294 * @property expanded
1295 * @type boolean
1296 */
1297 expanded: false,
1298
1299 /**
1300 * Can multiple children be expanded at once?
1301 * @property multiExpand
1302 * @type boolean
1303 */
1304 multiExpand: true,
1305
1306 /**
1307 * Should we render children for a collapsed node? It is possible that the
1308 * implementer will want to render the hidden data... @todo verify that we
1309 * need this, and implement it if we do.
1310 * @property renderHidden
1311 * @type boolean
1312 */
1313 renderHidden: false,
1314
1315 /**
1316 * This flag is set to true when the html is generated for this node's
1317 * children, and set to false when new children are added.
1318 * @property childrenRendered
1319 * @type boolean
1320 */
1321 childrenRendered: false,
1322
1323 /**
1324 * Dynamically loaded nodes only fetch the data the first time they are
1325 * expanded. This flag is set to true once the data has been fetched.
1326 * @property dynamicLoadComplete
1327 * @type boolean
1328 */
1329 dynamicLoadComplete: false,
1330
1331 /**
1332 * This node's previous sibling
1333 * @property previousSibling
1334 * @type Node
1335 */
1336 previousSibling: null,
1337
1338 /**
1339 * This node's next sibling
1340 * @property nextSibling
1341 * @type Node
1342 */
1343 nextSibling: null,
1344
1345 /**
1346 * We can set the node up to call an external method to get the child
1347 * data dynamically.
1348 * @property _dynLoad
1349 * @type boolean
1350 * @private
1351 */
1352 _dynLoad: false,
1353
1354 /**
1355 * Function to execute when we need to get this node's child data.
1356 * @property dataLoader
1357 * @type function
1358 */
1359 dataLoader: null,
1360
1361 /**
1362 * This is true for dynamically loading nodes while waiting for the
1363 * callback to return.
1364 * @property isLoading
1365 * @type boolean
1366 */
1367 isLoading: false,
1368
1369 /**
1370 * The toggle/branch icon will not show if this is set to false. This
1371 * could be useful if the implementer wants to have the child contain
1372 * extra info about the parent, rather than an actual node.
1373 * @property hasIcon
1374 * @type boolean
1375 */
1376 hasIcon: true,
1377
1378 /**
1379 * Used to configure what happens when a dynamic load node is expanded
1380 * and we discover that it does not have children. By default, it is
1381 * treated as if it still could have children (plus/minus icon). Set
1382 * iconMode to have it display like a leaf node instead.
1383 * @property iconMode
1384 * @type int
1385 */
1386 iconMode: 0,
1387
1388 /**
1389 * Specifies whether or not the content area of the node should be allowed
1390 * to wrap.
1391 * @property nowrap
1392 * @type boolean
1393 * @default false
1394 */
1395 nowrap: false,
1396
1397 /**
1398 * If true, the node will alway be rendered as a leaf node. This can be
1399 * used to override the presentation when dynamically loading the entire
1400 * tree. Setting this to true also disables the dynamic load call for the
1401 * node.
1402 * @property isLeaf
1403 * @type boolean
1404 * @default false
1405 */
1406 isLeaf: false,
1407
1408/**
1409 * The CSS class for the html content container. Defaults to ygtvhtml, but
1410 * can be overridden to provide a custom presentation for a specific node.
1411 * @property contentStyle
1412 * @type string
1413 */
1414 contentStyle: "",
1415
1416 /**
1417 * The generated id that will contain the data passed in by the implementer.
1418 * @property contentElId
1419 * @type string
1420 */
1421 contentElId: null,
1422 /**
1423 * The node type
1424 * @property _type
1425 * @private
1426 * @type string
1427 * @default "Node"
1428*/
1429 _type: "Node",
1430
1431 /*
1432 spacerPath: "http://us.i1.yimg.com/us.yimg.com/i/space.gif",
1433 expandedText: "Expanded",
1434 collapsedText: "Collapsed",
1435 loadingText: "Loading",
1436 */
1437
1438 /**
1439 * Initializes this node, gets some of the properties from the parent
1440 * @method init
1441 * @param oData {object} a string or object containing the data that will
1442 * be used to render this node
1443 * @param oParent {Node} this node's parent node
1444 * @param expanded {boolean} the initial expanded/collapsed state
1445 */
1446 init: function(oData, oParent, expanded) {
1447
1448 this.data = oData;
1449 this.children = [];
1450 this.index = YAHOO.widget.TreeView.nodeCount;
1451 ++YAHOO.widget.TreeView.nodeCount;
1452 this.contentElId = "ygtvcontentel" + this.index;
1453
1454 if (Lang.isObject(oData)) {
1455 for (var property in oData) {
1456 if (property.charAt(0) != '_' && oData.hasOwnProperty(property) && !Lang.isUndefined(this[property]) && !Lang.isFunction(this[property]) ) {
1457 this[property] = oData[property];
1458 }
1459 }
1460 }
1461 if (!Lang.isUndefined(expanded) ) { this.expanded = expanded; }
1462
1463 this.logger = new YAHOO.widget.LogWriter(this.toString());
1464
1465 /**
1466 * The parentChange event is fired when a parent element is applied
1467 * to the node. This is useful if you need to apply tree-level
1468 * properties to a tree that need to happen if a node is moved from
1469 * one tree to another.
1470 *
1471 * @event parentChange
1472 * @type CustomEvent
1473 */
1474 this.createEvent("parentChange", this);
1475
1476 // oParent should never be null except when we create the root node.
1477 if (oParent) {
1478 oParent.appendChild(this);
1479 }
1480 },
1481
1482 /**
1483 * Certain properties for the node cannot be set until the parent
1484 * is known. This is called after the node is inserted into a tree.
1485 * the parent is also applied to this node's children in order to
1486 * make it possible to move a branch from one tree to another.
1487 * @method applyParent
1488 * @param {Node} parentNode this node's parent node
1489 * @return {boolean} true if the application was successful
1490 */
1491 applyParent: function(parentNode) {
1492 if (!parentNode) {
1493 return false;
1494 }
1495
1496 this.tree = parentNode.tree;
1497 this.parent = parentNode;
1498 this.depth = parentNode.depth + 1;
1499
1500 // @todo why was this put here. This causes new nodes added at the
1501 // root level to lose the menu behavior.
1502 // if (! this.multiExpand) {
1503 // this.multiExpand = parentNode.multiExpand;
1504 // }
1505
1506 this.tree.regNode(this);
1507 parentNode.childrenRendered = false;
1508
1509 // cascade update existing children
1510 for (var i=0, len=this.children.length;i<len;++i) {
1511 this.children[i].applyParent(this);
1512 }
1513
1514 this.fireEvent("parentChange");
1515
1516 return true;
1517 },
1518
1519 /**
1520 * Appends a node to the child collection.
1521 * @method appendChild
1522 * @param childNode {Node} the new node
1523 * @return {Node} the child node
1524 * @private
1525 */
1526 appendChild: function(childNode) {
1527 if (this.hasChildren()) {
1528 var sib = this.children[this.children.length - 1];
1529 sib.nextSibling = childNode;
1530 childNode.previousSibling = sib;
1531 }
1532 this.children[this.children.length] = childNode;
1533 childNode.applyParent(this);
1534
1535 // part of the IE display issue workaround. If child nodes
1536 // are added after the initial render, and the node was
1537 // instantiated with expanded = true, we need to show the
1538 // children div now that the node has a child.
1539 if (this.childrenRendered && this.expanded) {
1540 this.getChildrenEl().style.display = "";
1541 }
1542
1543 return childNode;
1544 },
1545
1546 /**
1547 * Appends this node to the supplied node's child collection
1548 * @method appendTo
1549 * @param parentNode {Node} the node to append to.
1550 * @return {Node} The appended node
1551 */
1552 appendTo: function(parentNode) {
1553 return parentNode.appendChild(this);
1554 },
1555
1556 /**
1557 * Inserts this node before this supplied node
1558 * @method insertBefore
1559 * @param node {Node} the node to insert this node before
1560 * @return {Node} the inserted node
1561 */
1562 insertBefore: function(node) {
1563 this.logger.log("insertBefore: " + node);
1564 var p = node.parent;
1565 if (p) {
1566
1567 if (this.tree) {
1568 this.tree.popNode(this);
1569 }
1570
1571 var refIndex = node.isChildOf(p);
1572 //this.logger.log(refIndex);
1573 p.children.splice(refIndex, 0, this);
1574 if (node.previousSibling) {
1575 node.previousSibling.nextSibling = this;
1576 }
1577 this.previousSibling = node.previousSibling;
1578 this.nextSibling = node;
1579 node.previousSibling = this;
1580
1581 this.applyParent(p);
1582 }
1583
1584 return this;
1585 },
1586
1587 /**
1588 * Inserts this node after the supplied node
1589 * @method insertAfter
1590 * @param node {Node} the node to insert after
1591 * @return {Node} the inserted node
1592 */
1593 insertAfter: function(node) {
1594 this.logger.log("insertAfter: " + node);
1595 var p = node.parent;
1596 if (p) {
1597
1598 if (this.tree) {
1599 this.tree.popNode(this);
1600 }
1601
1602 var refIndex = node.isChildOf(p);
1603 this.logger.log(refIndex);
1604
1605 if (!node.nextSibling) {
1606 this.nextSibling = null;
1607 return this.appendTo(p);
1608 }
1609
1610 p.children.splice(refIndex + 1, 0, this);
1611
1612 node.nextSibling.previousSibling = this;
1613 this.previousSibling = node;
1614 this.nextSibling = node.nextSibling;
1615 node.nextSibling = this;
1616
1617 this.applyParent(p);
1618 }
1619
1620 return this;
1621 },
1622
1623 /**
1624 * Returns true if the Node is a child of supplied Node
1625 * @method isChildOf
1626 * @param parentNode {Node} the Node to check
1627 * @return {boolean} The node index if this Node is a child of
1628 * supplied Node, else -1.
1629 * @private
1630 */
1631 isChildOf: function(parentNode) {
1632 if (parentNode && parentNode.children) {
1633 for (var i=0, len=parentNode.children.length; i<len ; ++i) {
1634 if (parentNode.children[i] === this) {
1635 return i;
1636 }
1637 }
1638 }
1639
1640 return -1;
1641 },
1642
1643 /**
1644 * Returns a node array of this node's siblings, null if none.
1645 * @method getSiblings
1646 * @return Node[]
1647 */
1648 getSiblings: function() {
1649 var sib = this.parent.children.slice(0);
1650 for (var i=0;i < sib.length && sib[i] != this;i++) {}
1651 sib.splice(i,1);
1652 if (sib.length) { return sib; }
1653 return null;
1654 },
1655
1656 /**
1657 * Shows this node's children
1658 * @method showChildren
1659 */
1660 showChildren: function() {
1661 if (!this.tree.animateExpand(this.getChildrenEl(), this)) {
1662 if (this.hasChildren()) {
1663 this.getChildrenEl().style.display = "";
1664 }
1665 }
1666 },
1667
1668 /**
1669 * Hides this node's children
1670 * @method hideChildren
1671 */
1672 hideChildren: function() {
1673 this.logger.log("hiding " + this.index);
1674
1675 if (!this.tree.animateCollapse(this.getChildrenEl(), this)) {
1676 this.getChildrenEl().style.display = "none";
1677 }
1678 },
1679
1680 /**
1681 * Returns the id for this node's container div
1682 * @method getElId
1683 * @return {string} the element id
1684 */
1685 getElId: function() {
1686 return "ygtv" + this.index;
1687 },
1688
1689 /**
1690 * Returns the id for this node's children div
1691 * @method getChildrenElId
1692 * @return {string} the element id for this node's children div
1693 */
1694 getChildrenElId: function() {
1695 return "ygtvc" + this.index;
1696 },
1697
1698 /**
1699 * Returns the id for this node's toggle element
1700 * @method getToggleElId
1701 * @return {string} the toggel element id
1702 */
1703 getToggleElId: function() {
1704 return "ygtvt" + this.index;
1705 },
1706
1707
1708 /*
1709 * Returns the id for this node's spacer image. The spacer is positioned
1710 * over the toggle and provides feedback for screen readers.
1711 * @method getSpacerId
1712 * @return {string} the id for the spacer image
1713 */
1714 /*
1715 getSpacerId: function() {
1716 return "ygtvspacer" + this.index;
1717 },
1718 */
1719
1720 /**
1721 * Returns this node's container html element
1722 * @method getEl
1723 * @return {HTMLElement} the container html element
1724 */
1725 getEl: function() {
1726 return Dom.get(this.getElId());
1727 },
1728
1729 /**
1730 * Returns the div that was generated for this node's children
1731 * @method getChildrenEl
1732 * @return {HTMLElement} this node's children div
1733 */
1734 getChildrenEl: function() {
1735 return Dom.get(this.getChildrenElId());
1736 },
1737
1738 /**
1739 * Returns the element that is being used for this node's toggle.
1740 * @method getToggleEl
1741 * @return {HTMLElement} this node's toggle html element
1742 */
1743 getToggleEl: function() {
1744 return Dom.get(this.getToggleElId());
1745 },
1746 /**
1747 * Returns the outer html element for this node's content
1748 * @method getContentEl
1749 * @return {HTMLElement} the element
1750 */
1751 getContentEl: function() {
1752 return Dom.get(this.contentElId);
1753 },
1754
1755
1756 /*
1757 * Returns the element that is being used for this node's spacer.
1758 * @method getSpacer
1759 * @return {HTMLElement} this node's spacer html element
1760 */
1761 /*
1762 getSpacer: function() {
1763 return document.getElementById( this.getSpacerId() ) || {};
1764 },
1765 */
1766
1767 /*
1768 getStateText: function() {
1769 if (this.isLoading) {
1770 return this.loadingText;
1771 } else if (this.hasChildren(true)) {
1772 if (this.expanded) {
1773 return this.expandedText;
1774 } else {
1775 return this.collapsedText;
1776 }
1777 } else {
1778 return "";
1779 }
1780 },
1781 */
1782
1783 /**
1784 * Hides this nodes children (creating them if necessary), changes the toggle style.
1785 * @method collapse
1786 */
1787 collapse: function() {
1788 // Only collapse if currently expanded
1789 if (!this.expanded) { return; }
1790
1791 // fire the collapse event handler
1792 var ret = this.tree.onCollapse(this);
1793
1794 if (false === ret) {
1795 this.logger.log("Collapse was stopped by the abstract onCollapse");
1796 return;
1797 }
1798
1799 ret = this.tree.fireEvent("collapse", this);
1800
1801 if (false === ret) {
1802 this.logger.log("Collapse was stopped by a custom event handler");
1803 return;
1804 }
1805
1806
1807 if (!this.getEl()) {
1808 this.expanded = false;
1809 } else {
1810 // hide the child div
1811 this.hideChildren();
1812 this.expanded = false;
1813
1814 this.updateIcon();
1815 }
1816
1817 // this.getSpacer().title = this.getStateText();
1818
1819 ret = this.tree.fireEvent("collapseComplete", this);
1820
1821 },
1822
1823 /**
1824 * Shows this nodes children (creating them if necessary), changes the
1825 * toggle style, and collapses its siblings if multiExpand is not set.
1826 * @method expand
1827 */
1828 expand: function(lazySource) {
1829 // Only expand if currently collapsed.
1830 if (this.expanded && !lazySource) {
1831 return;
1832 }
1833
1834 var ret = true;
1835
1836 // When returning from the lazy load handler, expand is called again
1837 // in order to render the new children. The "expand" event already
1838 // fired before fething the new data, so we need to skip it now.
1839 if (!lazySource) {
1840 // fire the expand event handler
1841 ret = this.tree.onExpand(this);
1842
1843 if (false === ret) {
1844 this.logger.log("Expand was stopped by the abstract onExpand");
1845 return;
1846 }
1847
1848 ret = this.tree.fireEvent("expand", this);
1849 }
1850
1851 if (false === ret) {
1852 this.logger.log("Expand was stopped by the custom event handler");
1853 return;
1854 }
1855
1856 if (!this.getEl()) {
1857 this.expanded = true;
1858 return;
1859 }
1860
1861 if (!this.childrenRendered) {
1862 this.logger.log("children not rendered yet");
1863 this.getChildrenEl().innerHTML = this.renderChildren();
1864 } else {
1865 this.logger.log("children already rendered");
1866 }
1867
1868 this.expanded = true;
1869
1870 this.updateIcon();
1871
1872 // this.getSpacer().title = this.getStateText();
1873
1874 // We do an extra check for children here because the lazy
1875 // load feature can expose nodes that have no children.
1876
1877 // if (!this.hasChildren()) {
1878 if (this.isLoading) {
1879 this.expanded = false;
1880 return;
1881 }
1882
1883 if (! this.multiExpand) {
1884 var sibs = this.getSiblings();
1885 for (var i=0; sibs && i<sibs.length; ++i) {
1886 if (sibs[i] != this && sibs[i].expanded) {
1887 sibs[i].collapse();
1888 }
1889 }
1890 }
1891
1892 this.showChildren();
1893
1894 ret = this.tree.fireEvent("expandComplete", this);
1895 },
1896
1897 updateIcon: function() {
1898 if (this.hasIcon) {
1899 var el = this.getToggleEl();
1900 if (el) {
1901 el.className = el.className.replace(/ygtv(([tl][pmn]h?)|(loading))/,this.getStyle());
1902 }
1903 }
1904 },
1905
1906 /**
1907 * Returns the css style name for the toggle
1908 * @method getStyle
1909 * @return {string} the css class for this node's toggle
1910 */
1911 getStyle: function() {
1912 // this.logger.log("No children, " + " isDyanmic: " + this.isDynamic() + " expanded: " + this.expanded);
1913 if (this.isLoading) {
1914 this.logger.log("returning the loading icon");
1915 return "ygtvloading";
1916 } else {
1917 // location top or bottom, middle nodes also get the top style
1918 var loc = (this.nextSibling) ? "t" : "l";
1919
1920 // type p=plus(expand), m=minus(collapase), n=none(no children)
1921 var type = "n";
1922 if (this.hasChildren(true) || (this.isDynamic() && !this.getIconMode())) {
1923 // if (this.hasChildren(true)) {
1924 type = (this.expanded) ? "m" : "p";
1925 }
1926
1927 // this.logger.log("ygtv" + loc + type);
1928 return "ygtv" + loc + type;
1929 }
1930 },
1931
1932 /**
1933 * Returns the hover style for the icon
1934 * @return {string} the css class hover state
1935 * @method getHoverStyle
1936 */
1937 getHoverStyle: function() {
1938 var s = this.getStyle();
1939 if (this.hasChildren(true) && !this.isLoading) {
1940 s += "h";
1941 }
1942 return s;
1943 },
1944
1945 /**
1946 * Recursively expands all of this node's children.
1947 * @method expandAll
1948 */
1949 expandAll: function() {
1950 for (var i=0;i<this.children.length;++i) {
1951 var c = this.children[i];
1952 if (c.isDynamic()) {
1953 this.logger.log("Not supported (lazy load + expand all)");
1954 break;
1955 } else if (! c.multiExpand) {
1956 this.logger.log("Not supported (no multi-expand + expand all)");
1957 break;
1958 } else {
1959 c.expand();
1960 c.expandAll();
1961 }
1962 }
1963 },
1964
1965 /**
1966 * Recursively collapses all of this node's children.
1967 * @method collapseAll
1968 */
1969 collapseAll: function() {
1970 for (var i=0;i<this.children.length;++i) {
1971 this.children[i].collapse();
1972 this.children[i].collapseAll();
1973 }
1974 },
1975
1976 /**
1977 * Configures this node for dynamically obtaining the child data
1978 * when the node is first expanded. Calling it without the callback
1979 * will turn off dynamic load for the node.
1980 * @method setDynamicLoad
1981 * @param fmDataLoader {function} the function that will be used to get the data.
1982 * @param iconMode {int} configures the icon that is displayed when a dynamic
1983 * load node is expanded the first time without children. By default, the
1984 * "collapse" icon will be used. If set to 1, the leaf node icon will be
1985 * displayed.
1986 */
1987 setDynamicLoad: function(fnDataLoader, iconMode) {
1988 if (fnDataLoader) {
1989 this.dataLoader = fnDataLoader;
1990 this._dynLoad = true;
1991 } else {
1992 this.dataLoader = null;
1993 this._dynLoad = false;
1994 }
1995
1996 if (iconMode) {
1997 this.iconMode = iconMode;
1998 }
1999 },
2000
2001 /**
2002 * Evaluates if this node is the root node of the tree
2003 * @method isRoot
2004 * @return {boolean} true if this is the root node
2005 */
2006 isRoot: function() {
2007 return (this == this.tree.root);
2008 },
2009
2010 /**
2011 * Evaluates if this node's children should be loaded dynamically. Looks for
2012 * the property both in this instance and the root node. If the tree is
2013 * defined to load all children dynamically, the data callback function is
2014 * defined in the root node
2015 * @method isDynamic
2016 * @return {boolean} true if this node's children are to be loaded dynamically
2017 */
2018 isDynamic: function() {
2019 if (this.isLeaf) {
2020 return false;
2021 } else {
2022 return (!this.isRoot() && (this._dynLoad || this.tree.root._dynLoad));
2023 // this.logger.log("isDynamic: " + lazy);
2024 // return lazy;
2025 }
2026 },
2027
2028 /**
2029 * Returns the current icon mode. This refers to the way childless dynamic
2030 * load nodes appear (this comes into play only after the initial dynamic
2031 * load request produced no children).
2032 * @method getIconMode
2033 * @return {int} 0 for collapse style, 1 for leaf node style
2034 */
2035 getIconMode: function() {
2036 return (this.iconMode || this.tree.root.iconMode);
2037 },
2038
2039 /**
2040 * Checks if this node has children. If this node is lazy-loading and the
2041 * children have not been rendered, we do not know whether or not there
2042 * are actual children. In most cases, we need to assume that there are
2043 * children (for instance, the toggle needs to show the expandable
2044 * presentation state). In other times we want to know if there are rendered
2045 * children. For the latter, "checkForLazyLoad" should be false.
2046 * @method hasChildren
2047 * @param checkForLazyLoad {boolean} should we check for unloaded children?
2048 * @return {boolean} true if this has children or if it might and we are
2049 * checking for this condition.
2050 */
2051 hasChildren: function(checkForLazyLoad) {
2052 if (this.isLeaf) {
2053 return false;
2054 } else {
2055 return ( this.children.length > 0 ||
2056(checkForLazyLoad && this.isDynamic() && !this.dynamicLoadComplete) );
2057 }
2058 },
2059
2060 /**
2061 * Expands if node is collapsed, collapses otherwise.
2062 * @method toggle
2063 */
2064 toggle: function() {
2065 if (!this.tree.locked && ( this.hasChildren(true) || this.isDynamic()) ) {
2066 if (this.expanded) { this.collapse(); } else { this.expand(); }
2067 }
2068 },
2069
2070 /**
2071 * Returns the markup for this node and its children.
2072 * @method getHtml
2073 * @return {string} the markup for this node and its expanded children.
2074 */
2075 getHtml: function() {
2076
2077 this.childrenRendered = false;
2078
2079 var sb = [];
2080 sb[sb.length] = '<div class="ygtvitem" id="' + this.getElId() + '">';
2081 sb[sb.length] = this.getNodeHtml();
2082 sb[sb.length] = this.getChildrenHtml();
2083 sb[sb.length] = '</div>';
2084 return sb.join("");
2085 },
2086
2087 /**
2088 * Called when first rendering the tree. We always build the div that will
2089 * contain this nodes children, but we don't render the children themselves
2090 * unless this node is expanded.
2091 * @method getChildrenHtml
2092 * @return {string} the children container div html and any expanded children
2093 * @private
2094 */
2095 getChildrenHtml: function() {
2096
2097
2098 var sb = [];
2099 sb[sb.length] = '<div class="ygtvchildren"';
2100 sb[sb.length] = ' id="' + this.getChildrenElId() + '"';
2101
2102 // This is a workaround for an IE rendering issue, the child div has layout
2103 // in IE, creating extra space if a leaf node is created with the expanded
2104 // property set to true.
2105 if (!this.expanded || !this.hasChildren()) {
2106 sb[sb.length] = ' style="display:none;"';
2107 }
2108 sb[sb.length] = '>';
2109
2110 // this.logger.log(["index", this.index,
2111 // "hasChildren", this.hasChildren(true),
2112 // "expanded", this.expanded,
2113 // "renderHidden", this.renderHidden,
2114 // "isDynamic", this.isDynamic()]);
2115
2116 // Don't render the actual child node HTML unless this node is expanded.
2117 if ( (this.hasChildren(true) && this.expanded) ||
2118 (this.renderHidden && !this.isDynamic()) ) {
2119 sb[sb.length] = this.renderChildren();
2120 }
2121
2122 sb[sb.length] = '</div>';
2123
2124 return sb.join("");
2125 },
2126
2127 /**
2128 * Generates the markup for the child nodes. This is not done until the node
2129 * is expanded.
2130 * @method renderChildren
2131 * @return {string} the html for this node's children
2132 * @private
2133 */
2134 renderChildren: function() {
2135
2136 this.logger.log("rendering children for " + this.index);
2137
2138 var node = this;
2139
2140 if (this.isDynamic() && !this.dynamicLoadComplete) {
2141 this.isLoading = true;
2142 this.tree.locked = true;
2143
2144 if (this.dataLoader) {
2145 this.logger.log("Using dynamic loader defined for this node");
2146
2147 setTimeout(
2148 function() {
2149 node.dataLoader(node,
2150 function() {
2151 node.loadComplete();
2152 });
2153 }, 10);
2154
2155 } else if (this.tree.root.dataLoader) {
2156 this.logger.log("Using the tree-level dynamic loader");
2157
2158 setTimeout(
2159 function() {
2160 node.tree.root.dataLoader(node,
2161 function() {
2162 node.loadComplete();
2163 });
2164 }, 10);
2165
2166 } else {
2167 this.logger.log("no loader found");
2168 return "Error: data loader not found or not specified.";
2169 }
2170
2171 return "";
2172
2173 } else {
2174 return this.completeRender();
2175 }
2176 },
2177
2178 /**
2179 * Called when we know we have all the child data.
2180 * @method completeRender
2181 * @return {string} children html
2182 */
2183 completeRender: function() {
2184 this.logger.log("completeRender: " + this.index + ", # of children: " + this.children.length);
2185 var sb = [];
2186
2187 for (var i=0; i < this.children.length; ++i) {
2188 // this.children[i].childrenRendered = false;
2189 sb[sb.length] = this.children[i].getHtml();
2190 }
2191
2192 this.childrenRendered = true;
2193
2194 return sb.join("");
2195 },
2196
2197 /**
2198 * Load complete is the callback function we pass to the data provider
2199 * in dynamic load situations.
2200 * @method loadComplete
2201 */
2202 loadComplete: function() {
2203 this.logger.log(this.index + " loadComplete, children: " + this.children.length);
2204 this.getChildrenEl().innerHTML = this.completeRender();
2205 this.dynamicLoadComplete = true;
2206 this.isLoading = false;
2207 this.expand(true);
2208 this.tree.locked = false;
2209 },
2210
2211 /**
2212 * Returns this node's ancestor at the specified depth.
2213 * @method getAncestor
2214 * @param {int} depth the depth of the ancestor.
2215 * @return {Node} the ancestor
2216 */
2217 getAncestor: function(depth) {
2218 if (depth >= this.depth || depth < 0) {
2219 this.logger.log("illegal getAncestor depth: " + depth);
2220 return null;
2221 }
2222
2223 var p = this.parent;
2224
2225 while (p.depth > depth) {
2226 p = p.parent;
2227 }
2228
2229 return p;
2230 },
2231
2232 /**
2233 * Returns the css class for the spacer at the specified depth for
2234 * this node. If this node's ancestor at the specified depth
2235 * has a next sibling the presentation is different than if it
2236 * does not have a next sibling
2237 * @method getDepthStyle
2238 * @param {int} depth the depth of the ancestor.
2239 * @return {string} the css class for the spacer
2240 */
2241 getDepthStyle: function(depth) {
2242 return (this.getAncestor(depth).nextSibling) ?
2243 "ygtvdepthcell" : "ygtvblankdepthcell";
2244 },
2245
2246 /**
2247 * Get the markup for the node. This may be overrided so that we can
2248 * support different types of nodes.
2249 * @method getNodeHtml
2250 * @return {string} The HTML that will render this node.
2251 */
2252 getNodeHtml: function() {
2253 this.logger.log("Generating html");
2254 var sb = [];
2255
2256 sb[sb.length] = '<table border="0" cellpadding="0" cellspacing="0" class="ygtvdepth' + this.depth + '">';
2257 sb[sb.length] = '<tr class="ygtvrow">';
2258
2259 for (var i=0;i<this.depth;++i) {
2260 sb[sb.length] = '<td class="' + this.getDepthStyle(i) + '"><div class="ygtvspacer"></div></td>';
2261 }
2262
2263 if (this.hasIcon) {
2264 sb[sb.length] = '<td';
2265 sb[sb.length] = ' id="' + this.getToggleElId() + '"';
2266 sb[sb.length] = ' class="' + this.getStyle() + '"';
2267 sb[sb.length] = '><a href="#" class="ygtvspacer"> </a></td>';
2268 }
2269
2270 sb[sb.length] = '<td';
2271 sb[sb.length] = ' id="' + this.contentElId + '"';
2272 sb[sb.length] = ' class="' + this.contentStyle + ' ygtvcontent" ';
2273 sb[sb.length] = (this.nowrap) ? ' nowrap="nowrap" ' : '';
2274 sb[sb.length] = ' >';
2275 sb[sb.length] = this.getContentHtml();
2276 sb[sb.length] = '</td>';
2277 sb[sb.length] = '</tr>';
2278 sb[sb.length] = '</table>';
2279
2280 return sb.join("");
2281
2282 },
2283 /**
2284 * Get the markup for the contents of the node. This is designed to be overrided so that we can
2285 * support different types of nodes.
2286 * @method getContentHtml
2287 * @return {string} The HTML that will render the content of this node.
2288 */
2289 getContentHtml: function () {
2290 return "";
2291 },
2292
2293 /**
2294 * Regenerates the html for this node and its children. To be used when the
2295 * node is expanded and new children have been added.
2296 * @method refresh
2297 */
2298 refresh: function() {
2299 // this.loadComplete();
2300 this.getChildrenEl().innerHTML = this.completeRender();
2301
2302 if (this.hasIcon) {
2303 var el = this.getToggleEl();
2304 if (el) {
2305 el.className = this.getStyle();
2306 }
2307 }
2308 },
2309
2310 /**
2311 * Node toString
2312 * @method toString
2313 * @return {string} string representation of the node
2314 */
2315 toString: function() {
2316 return this._type + " (" + this.index + ")";
2317 },
2318 /**
2319 * array of items that had the focus set on them
2320 * so that they can be cleaned when focus is lost
2321 * @property _focusHighlightedItems
2322 * @type Array of DOM elements
2323 * @private
2324 */
2325 _focusHighlightedItems: [],
2326 _focusedItem: null,
2327 /**
2328 * Sets the focus on the node element.
2329 * It will only be able to set the focus on nodes that have anchor elements in it.
2330 * Toggle or branch icons have anchors and can be focused on.
2331 * If will fail in nodes that have no anchor
2332 * @method focus
2333 * @return {boolean} success
2334 */
2335 focus: function () {
2336 var focused = false, self = this;
2337
2338 var removeListeners = function () {
2339 var el;
2340 if (self._focusedItem) {
2341 Event.removeListener(self._focusedItem,'blur');
2342 self._focusedItem = null;
2343 }
2344
2345 while ((el = self._focusHighlightedItems.shift())) { // yes, it is meant as an assignment, really
2346 Dom.removeClass(el,YAHOO.widget.TreeView.FOCUS_CLASS_NAME );
2347 }
2348 };
2349 removeListeners();
2350
2351 Dom.getElementsBy (
2352 function (el) {
2353 return /ygtv(([tl][pmn]h?)|(content))/.test(el.className);
2354 } ,
2355 'td' ,
2356 this.getEl().firstChild ,
2357 function (el) {
2358 Dom.addClass(el, YAHOO.widget.TreeView.FOCUS_CLASS_NAME );
2359 if (!focused) {
2360 var aEl = el.getElementsByTagName('a');
2361 if (aEl.length) {
2362 aEl = aEl[0];
2363 aEl.focus();
2364 self._focusedItem = aEl;
2365 Event.on(aEl,'blur',removeListeners);
2366 focused = true;
2367 }
2368 }
2369 self._focusHighlightedItems.push(el);
2370 }
2371 );
2372 if (!focused) { removeListeners(); }
2373 return focused;
2374 },
2375
2376 /**
2377 * Count of nodes in tree
2378 * @method getNodeCount
2379 * @return {int} number of nodes in the tree
2380 */
2381 getNodeCount: function() {
2382 for (var i = 0, count = 0;i< this.children.length;i++) {
2383 count += this.children[i].getNodeCount();
2384 }
2385 return count + 1;
2386 },
2387
2388 /**
2389 * Returns an object which could be used to build a tree out of this node and its children.
2390 * It can be passed to the tree constructor to reproduce this node as a tree.
2391 * It will return false if the node or any children loads dynamically, regardless of whether it is loaded or not.
2392 * @method getNodeDefinition
2393 * @return {Object | false} definition of the tree or false if the node or any children is defined as dynamic
2394 */
2395 getNodeDefinition: function() {
2396
2397 if (this.isDynamic()) { return false; }
2398
2399 var def, defs = this.data, children = [];
2400
2401
2402 if (this.href) { defs.href = this.href; }
2403 if (this.target != '_self') { defs.target = this.target; }
2404 if (this.expanded) {defs.expanded = this.expanded; }
2405 if (!this.multiExpand) { defs.multiExpand = this.multiExpand; }
2406 if (!this.hasIcon) { defs.hasIcon = this.hasIcon; }
2407 if (this.nowrap) { defs.nowrap = this.nowrap; }
2408 defs.type = this._type;
2409
2410
2411
2412 for (var i = 0; i < this.children.length;i++) {
2413 def = this.children[i].getNodeDefinition();
2414 if (def === false) { return false;}
2415 children.push(def);
2416 }
2417 if (children.length) { defs.children = children; }
2418 return defs;
2419 },
2420
2421
2422 /**
2423 * Generates the link that will invoke this node's toggle method
2424 * @method getToggleLink
2425 * @return {string} the javascript url for toggling this node
2426 */
2427 getToggleLink: function() {
2428 return 'return false;';
2429 }
2430
2431};
2432
2433YAHOO.augment(YAHOO.widget.Node, YAHOO.util.EventProvider);
2434})();
2435(function () {
2436 var Dom = YAHOO.util.Dom,
2437 Lang = YAHOO.lang,
2438 Event = YAHOO.util.Event;
2439/**
2440 * The default node presentation. The first parameter should be
2441 * either a string that will be used as the node's label, or an object
2442 * that has at least a string property called label. By default, clicking the
2443 * label will toggle the expanded/collapsed state of the node. By
2444 * setting the href property of the instance, this behavior can be
2445 * changed so that the label will go to the specified href.
2446 * @namespace YAHOO.widget
2447 * @class TextNode
2448 * @extends YAHOO.widget.Node
2449 * @constructor
2450 * @param oData {object} a string or object containing the data that will
2451 * be used to render this node.
2452 * Providing a string is the same as providing an object with a single property named label.
2453 * All values in the oData will be used to set equally named properties in the node
2454 * as long as the node does have such properties, they are not undefined, private or functions.
2455 * All attributes are made available in noderef.data, which
2456 * can be used to store custom attributes. TreeView.getNode(s)ByProperty
2457 * can be used to retrieve a node by one of the attributes.
2458 * @param oParent {YAHOO.widget.Node} this node's parent node
2459 * @param expanded {boolean} the initial expanded/collapsed state (deprecated; use oData.expanded)
2460 */
2461YAHOO.widget.TextNode = function(oData, oParent, expanded) {
2462
2463 if (oData) {
2464 if (Lang.isString(oData)) {
2465 oData = { label: oData };
2466 }
2467 this.init(oData, oParent, expanded);
2468 this.setUpLabel(oData);
2469 }
2470
2471 this.logger = new YAHOO.widget.LogWriter(this.toString());
2472};
2473
2474YAHOO.extend(YAHOO.widget.TextNode, YAHOO.widget.Node, {
2475
2476 /**
2477 * The CSS class for the label href. Defaults to ygtvlabel, but can be
2478 * overridden to provide a custom presentation for a specific node.
2479 * @property labelStyle
2480 * @type string
2481 */
2482 labelStyle: "ygtvlabel",
2483
2484 /**
2485 * The derived element id of the label for this node
2486 * @property labelElId
2487 * @type string
2488 */
2489 labelElId: null,
2490
2491 /**
2492 * The text for the label. It is assumed that the oData parameter will
2493 * either be a string that will be used as the label, or an object that
2494 * has a property called "label" that we will use.
2495 * @property label
2496 * @type string
2497 */
2498 label: null,
2499
2500 /**
2501 * The text for the title (tooltip) for the label element
2502 * @property title
2503 * @type string
2504 */
2505 title: null,
2506
2507/**
2508 * The node type
2509 * @property _type
2510 * @private
2511 * @type string
2512 * @default "TextNode"
2513 */
2514 _type: "TextNode",
2515
2516
2517 /**
2518 * Sets up the node label
2519 * @method setUpLabel
2520 * @param oData string containing the label, or an object with a label property
2521 */
2522 setUpLabel: function(oData) {
2523
2524 if (Lang.isString(oData)) {
2525 oData = {
2526 label: oData
2527 };
2528 } else {
2529 if (oData.style) {
2530 this.labelStyle = oData.style;
2531 }
2532 }
2533
2534 this.label = oData.label;
2535
2536 this.labelElId = "ygtvlabelel" + this.index;
2537
2538 },
2539
2540 /**
2541 * Returns the label element
2542 * @for YAHOO.widget.TextNode
2543 * @method getLabelEl
2544 * @return {object} the element
2545 */
2546 getLabelEl: function() {
2547 return Dom.get(this.labelElId);
2548 },
2549
2550 // overrides YAHOO.widget.Node
2551 getContentHtml: function() {
2552 var sb = [];
2553 sb[sb.length] = this.href?'<a':'<span';
2554 sb[sb.length] = ' id="' + this.labelElId + '"';
2555 if (this.title) {
2556 sb[sb.length] = ' title="' + this.title + '"';
2557 }
2558 sb[sb.length] = ' class="' + this.labelStyle + '"';
2559 if (this.href) {
2560 sb[sb.length] = ' href="' + this.href + '"';
2561 sb[sb.length] = ' target="' + this.target + '"';
2562 }
2563 sb[sb.length] = ' >';
2564 sb[sb.length] = this.label;
2565 sb[sb.length] = this.href?'</a>':'</span>';
2566 return sb.join("");
2567 },
2568
2569
2570
2571 /**
2572 * Returns an object which could be used to build a tree out of this node and its children.
2573 * It can be passed to the tree constructor to reproduce this node as a tree.
2574 * It will return false if the node or any descendant loads dynamically, regardless of whether it is loaded or not.
2575 * @method getNodeDefinition
2576 * @return {Object | false} definition of the tree or false if this node or any descendant is defined as dynamic
2577 */
2578 getNodeDefinition: function() {
2579 var def = YAHOO.widget.TextNode.superclass.getNodeDefinition.call(this);
2580 if (def === false) { return false; }
2581
2582 // Node specific properties
2583 def.label = this.label;
2584 if (this.labelStyle != 'ygtvlabel') { def.style = this.labelStyle; }
2585 if (this.title) { def.title = this.title ; }
2586
2587 return def;
2588
2589 },
2590
2591 toString: function() {
2592 return YAHOO.widget.TextNode.superclass.toString.call(this) + ": " + this.label;
2593 },
2594
2595 // deprecated
2596 onLabelClick: function() {
2597 return false;
2598 }
2599});
2600})();
2601/**
2602 * A custom YAHOO.widget.Node that handles the unique nature of
2603 * the virtual, presentationless root node.
2604 * @namespace YAHOO.widget
2605 * @class RootNode
2606 * @extends YAHOO.widget.Node
2607 * @param oTree {YAHOO.widget.TreeView} The tree instance this node belongs to
2608 * @constructor
2609 */
2610YAHOO.widget.RootNode = function(oTree) {
2611 // Initialize the node with null params. The root node is a
2612 // special case where the node has no presentation. So we have
2613 // to alter the standard properties a bit.
2614 this.init(null, null, true);
2615
2616 /*
2617 * For the root node, we get the tree reference from as a param
2618 * to the constructor instead of from the parent element.
2619 */
2620 this.tree = oTree;
2621};
2622
2623YAHOO.extend(YAHOO.widget.RootNode, YAHOO.widget.Node, {
2624
2625 /**
2626 * The node type
2627 * @property _type
2628 * @type string
2629 * @private
2630 * @default "RootNode"
2631 */
2632 _type: "RootNode",
2633
2634 // overrides YAHOO.widget.Node
2635 getNodeHtml: function() {
2636 return "";
2637 },
2638
2639 toString: function() {
2640 return this._type;
2641 },
2642
2643 loadComplete: function() {
2644 this.tree.draw();
2645 },
2646
2647 /**
2648 * Count of nodes in tree.
2649 * It overrides Nodes.getNodeCount because the root node should not be counted.
2650 * @method getNodeCount
2651 * @return {int} number of nodes in the tree
2652 */
2653 getNodeCount: function() {
2654 for (var i = 0, count = 0;i< this.children.length;i++) {
2655 count += this.children[i].getNodeCount();
2656 }
2657 return count;
2658 },
2659
2660 /**
2661 * Returns an object which could be used to build a tree out of this node and its children.
2662 * It can be passed to the tree constructor to reproduce this node as a tree.
2663 * Since the RootNode is automatically created by treeView,
2664 * its own definition is excluded from the returned node definition
2665 * which only contains its children.
2666 * @method getNodeDefinition
2667 * @return {Object | false} definition of the tree or false if any child node is defined as dynamic
2668 */
2669 getNodeDefinition: function() {
2670
2671 for (var def, defs = [], i = 0; i < this.children.length;i++) {
2672 def = this.children[i].getNodeDefinition();
2673 if (def === false) { return false;}
2674 defs.push(def);
2675 }
2676 return defs;
2677 },
2678
2679 collapse: function() {},
2680 expand: function() {},
2681 getSiblings: function() { return null; },
2682 focus: function () {}
2683
2684});
2685(function () {
2686 var Dom = YAHOO.util.Dom,
2687 Lang = YAHOO.lang,
2688 Event = YAHOO.util.Event;
2689
2690/**
2691 * This implementation takes either a string or object for the
2692 * oData argument. If is it a string, it will use it for the display
2693 * of this node (and it can contain any html code). If the parameter
2694 * is an object,it looks for a parameter called "html" that will be
2695 * used for this node's display.
2696 * @namespace YAHOO.widget
2697 * @class HTMLNode
2698 * @extends YAHOO.widget.Node
2699 * @constructor
2700 * @param oData {object} a string or object containing the data that will
2701 * be used to render this node.
2702 * Providing a string is the same as providing an object with a single property named html.
2703 * All values in the oData will be used to set equally named properties in the node
2704 * as long as the node does have such properties, they are not undefined, private or functions.
2705 * All other attributes are made available in noderef.data, which
2706 * can be used to store custom attributes. TreeView.getNode(s)ByProperty
2707 * can be used to retrieve a node by one of the attributes.
2708 * @param oParent {YAHOO.widget.Node} this node's parent node
2709 * @param expanded {boolean} the initial expanded/collapsed state (deprecated; use oData.expanded)
2710 * @param hasIcon {boolean} specifies whether or not leaf nodes should
2711 * be rendered with or without a horizontal line line and/or toggle icon. If the icon
2712 * is not displayed, the content fills the space it would have occupied.
2713 * This option operates independently of the leaf node presentation logic
2714 * for dynamic nodes.
2715 * (deprecated; use oData.hasIcon)
2716 */
2717YAHOO.widget.HTMLNode = function(oData, oParent, expanded, hasIcon) {
2718 if (oData) {
2719 this.init(oData, oParent, expanded);
2720 this.initContent(oData, hasIcon);
2721 }
2722};
2723
2724YAHOO.extend(YAHOO.widget.HTMLNode, YAHOO.widget.Node, {
2725
2726 /**
2727 * The CSS class for the html content container. Defaults to ygtvhtml, but
2728 * can be overridden to provide a custom presentation for a specific node.
2729 * @property contentStyle
2730 * @type string
2731 */
2732 contentStyle: "ygtvhtml",
2733
2734
2735 /**
2736 * The HTML content to use for this node's display
2737 * @property html
2738 * @type string
2739 */
2740 html: null,
2741
2742/**
2743 * The node type
2744 * @property _type
2745 * @private
2746 * @type string
2747 * @default "HTMLNode"
2748 */
2749 _type: "HTMLNode",
2750
2751 /**
2752 * Sets up the node label
2753 * @property initContent
2754 * @param oData {object} An html string or object containing an html property
2755 * @param hasIcon {boolean} determines if the node will be rendered with an
2756 * icon or not
2757 */
2758 initContent: function(oData, hasIcon) {
2759 this.setHtml(oData);
2760 this.contentElId = "ygtvcontentel" + this.index;
2761 if (!Lang.isUndefined(hasIcon)) { this.hasIcon = hasIcon; }
2762
2763 this.logger = new YAHOO.widget.LogWriter(this.toString());
2764 },
2765
2766 /**
2767 * Synchronizes the node.data, node.html, and the node's content
2768 * @property setHtml
2769 * @param o {object} An html string or object containing an html property
2770 */
2771 setHtml: function(o) {
2772
2773 this.data = o;
2774 this.html = (typeof o === "string") ? o : o.html;
2775
2776 var el = this.getContentEl();
2777 if (el) {
2778 el.innerHTML = this.html;
2779 }
2780
2781 },
2782
2783 // overrides YAHOO.widget.Node
2784 getContentHtml: function() {
2785 return this.html;
2786 },
2787
2788 /**
2789 * Returns an object which could be used to build a tree out of this node and its children.
2790 * It can be passed to the tree constructor to reproduce this node as a tree.
2791 * It will return false if any node loads dynamically, regardless of whether it is loaded or not.
2792 * @method getNodeDefinition
2793 * @return {Object | false} definition of the tree or false if any node is defined as dynamic
2794 */
2795 getNodeDefinition: function() {
2796 var def = YAHOO.widget.HTMLNode.superclass.getNodeDefinition.call(this);
2797 if (def === false) { return false; }
2798 def.html = this.html;
2799 return def;
2800
2801 }
2802});
2803})();
2804/**
2805 * A menu-specific implementation that differs from TextNode in that only
2806 * one sibling can be expanded at a time.
2807 * @namespace YAHOO.widget
2808 * @class MenuNode
2809 * @extends YAHOO.widget.TextNode
2810 * @param oData {object} a string or object containing the data that will
2811 * be used to render this node.
2812 * Providing a string is the same as providing an object with a single property named label.
2813 * All values in the oData will be used to set equally named properties in the node
2814 * as long as the node does have such properties, they are not undefined, private or functions.
2815 * All attributes are made available in noderef.data, which
2816 * can be used to store custom attributes. TreeView.getNode(s)ByProperty
2817 * can be used to retrieve a node by one of the attributes.
2818 * @param oParent {YAHOO.widget.Node} this node's parent node
2819 * @param expanded {boolean} the initial expanded/collapsed state (deprecated; use oData.expanded)
2820 * @constructor
2821 */
2822YAHOO.widget.MenuNode = function(oData, oParent, expanded) {
2823 YAHOO.widget.MenuNode.superclass.constructor.call(this,oData,oParent,expanded);
2824
2825 /*
2826 * Menus usually allow only one branch to be open at a time.
2827 */
2828 this.multiExpand = false;
2829
2830};
2831
2832YAHOO.extend(YAHOO.widget.MenuNode, YAHOO.widget.TextNode, {
2833
2834 /**
2835 * The node type
2836 * @property _type
2837 * @private
2838 * @default "MenuNode"
2839 */
2840 _type: "MenuNode"
2841
2842});
2843(function () {
2844 var Dom = YAHOO.util.Dom,
2845 Lang = YAHOO.lang,
2846 Event = YAHOO.util.Event,
2847 Calendar = YAHOO.widget.Calendar;
2848
2849/**
2850 * A Date-specific implementation that differs from TextNode in that it uses
2851 * YAHOO.widget.Calendar as an in-line editor, if available
2852 * If Calendar is not available, it behaves as a plain TextNode.
2853 * @namespace YAHOO.widget
2854 * @class DateNode
2855 * @extends YAHOO.widget.TextNode
2856 * @param oData {object} a string or object containing the data that will
2857 * be used to render this node.
2858 * Providing a string is the same as providing an object with a single property named label.
2859 * All values in the oData will be used to set equally named properties in the node
2860 * as long as the node does have such properties, they are not undefined, private nor functions.
2861 * All attributes are made available in noderef.data, which
2862 * can be used to store custom attributes. TreeView.getNode(s)ByProperty
2863 * can be used to retrieve a node by one of the attributes.
2864 * @param oParent {YAHOO.widget.Node} this node's parent node
2865 * @param expanded {boolean} the initial expanded/collapsed state (deprecated; use oData.expanded)
2866 * @constructor
2867 */
2868YAHOO.widget.DateNode = function(oData, oParent, expanded) {
2869 YAHOO.widget.DateNode.superclass.constructor.call(this,oData, oParent, expanded);
2870};
2871
2872YAHOO.extend(YAHOO.widget.DateNode, YAHOO.widget.TextNode, {
2873
2874 /**
2875 * The node type
2876 * @property _type
2877 * @type string
2878 * @private
2879 * @default "DateNode"
2880 */
2881 _type: "DateNode",
2882
2883 /**
2884 * Configuration object for the Calendar editor, if used.
2885 * See <a href="http://developer.yahoo.com/yui/calendar/#internationalization">http://developer.yahoo.com/yui/calendar/#internationalization</a>
2886 * @property calendarConfig
2887 */
2888 calendarConfig: null,
2889
2890
2891
2892 /**
2893 * If YAHOO.widget.Calendar is available, it will pop up a Calendar to enter a new date. Otherwise, it falls back to a plain <input> textbox
2894 * @method fillEditorContainer
2895 * @param editorData {YAHOO.widget.TreeView.editorData} a shortcut to the static object holding editing information
2896 * @return void
2897 */
2898 fillEditorContainer: function (editorData) {
2899
2900 var cal, container = editorData.inputContainer;
2901
2902 if (Lang.isUndefined(Calendar)) {
2903 Dom.replaceClass(editorData.editorPanel,'ygtv-edit-DateNode','ygtv-edit-TextNode');
2904 YAHOO.widget.DateNode.superclass.fillEditorContainer.call(this, editorData);
2905 return;
2906 }
2907
2908 if (editorData.nodeType != this._type) {
2909 editorData.nodeType = this._type;
2910 editorData.saveOnEnter = false;
2911
2912 editorData.node.destroyEditorContents(editorData);
2913
2914 editorData.inputObject = cal = new Calendar(container.appendChild(document.createElement('div')));
2915 if (this.calendarConfig) {
2916 cal.cfg.applyConfig(this.calendarConfig,true);
2917 cal.cfg.fireQueue();
2918 }
2919 cal.selectEvent.subscribe(function () {
2920 this.tree._closeEditor(true);
2921 },this,true);
2922 } else {
2923 cal = editorData.inputObject;
2924 }
2925
2926 cal.cfg.setProperty("selected",this.label, false);
2927
2928 var delim = cal.cfg.getProperty('DATE_FIELD_DELIMITER');
2929 var pageDate = this.label.split(delim);
2930 cal.cfg.setProperty('pagedate',pageDate[cal.cfg.getProperty('MDY_MONTH_POSITION') -1] + delim + pageDate[cal.cfg.getProperty('MDY_YEAR_POSITION') -1]);
2931 cal.cfg.fireQueue();
2932
2933 cal.render();
2934 cal.oDomContainer.focus();
2935 },
2936 /**
2937 * Saves the date entered in the editor into the DateNode label property and displays it.
2938 * Overrides Node.saveEditorValue
2939 * @method saveEditorValue
2940 * @param editorData {YAHOO.widget.TreeView.editorData} a shortcut to the static object holding editing information
2941 */
2942 saveEditorValue: function (editorData) {
2943 var node = editorData.node, value;
2944 if (Lang.isUndefined(Calendar)) {
2945 value = editorData.inputElement.value;
2946 } else {
2947 var cal = editorData.inputObject,
2948 date = cal.getSelectedDates()[0],
2949 dd = [];
2950
2951 dd[cal.cfg.getProperty('MDY_DAY_POSITION') -1] = date.getDate();
2952 dd[cal.cfg.getProperty('MDY_MONTH_POSITION') -1] = date.getMonth() + 1;
2953 dd[cal.cfg.getProperty('MDY_YEAR_POSITION') -1] = date.getFullYear();
2954 value = dd.join(cal.cfg.getProperty('DATE_FIELD_DELIMITER'));
2955 }
2956
2957 node.label = value;
2958 node.data.label = value;
2959 node.getLabelEl().innerHTML = value;
2960 }
2961
2962});
2963})();
2964(function () {
2965 var Dom = YAHOO.util.Dom,
2966 Lang = YAHOO.lang,
2967 Event = YAHOO.util.Event,
2968 TV = YAHOO.widget.TreeView,
2969 TVproto = TV.prototype;
2970
2971 /**
2972 * An object to store information used for in-line editing
2973 * for all Nodes of all TreeViews. It contains:
2974 * <ul>
2975 * <li>active {boolean}, whether there is an active cell editor </li>
2976 * <li>whoHasIt {YAHOO.widget.TreeView} TreeView instance that is currently using the editor</li>
2977 * <li>nodeType {string} value of static Node._type property, allows reuse of input element if node is of the same type.</li>
2978 * <li>editorPanel {HTMLelement (<div>)} element holding the in-line editor</li>
2979 * <li>inputContainer {HTMLelement (<div>)} element which will hold the type-specific input element(s) to be filled by the fillEditorContainer method</li>
2980 * <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>
2981 * <li>node {YAHOO.widget.Node} reference to the Node being edited</li>
2982 * <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>
2983 * </ul>
2984 * Editors are free to use this object to store additional data.
2985 * @property editorData
2986 * @static
2987 * @for YAHOO.widget.TreeView
2988 */
2989 TV.editorData = {
2990 active:false,
2991 whoHasIt:null, // which TreeView has it
2992 nodeType:null,
2993 editorPanel:null,
2994 inputContainer:null,
2995 buttonsContainer:null,
2996 node:null, // which Node is being edited
2997 saveOnEnter:true
2998 // Each node type is free to add its own properties to this as it sees fit.
2999 };
3000
3001 /**
3002 * Entry point of the editing plug-in.
3003 * TreeView will call this method if it exists when a node label is clicked
3004 * @method _nodeEditing
3005 * @param node {YAHOO.widget.Node} the node to be edited
3006 * @return {Boolean} true to indicate that the node is editable and prevent any further bubbling of the click.
3007 * @for YAHOO.widget.TreeView
3008 */
3009
3010
3011 TVproto._nodeEditing = function (node) {
3012 if (node.fillEditorContainer && node.editable) {
3013 var ed, topLeft, buttons, button, editorData = TV.editorData;
3014 editorData.active = true;
3015 editorData.whoHasIt = this;
3016 if (!editorData.nodeType) {
3017 editorData.editorPanel = ed = document.body.appendChild(document.createElement('div'));
3018 Dom.addClass(ed,'ygtv-label-editor');
3019
3020 buttons = editorData.buttonsContainer = ed.appendChild(document.createElement('div'));
3021 Dom.addClass(buttons,'ygtv-button-container');
3022 button = buttons.appendChild(document.createElement('button'));
3023 Dom.addClass(button,'ygtvok');
3024 button.innerHTML = ' ';
3025 button = buttons.appendChild(document.createElement('button'));
3026 Dom.addClass(button,'ygtvcancel');
3027 button.innerHTML = ' ';
3028 Event.on(buttons, 'click', function (ev) {
3029 this.logger.log('click on editor');
3030 var target = Event.getTarget(ev);
3031 var node = TV.editorData.node;
3032 if (Dom.hasClass(target,'ygtvok')) {
3033 node.logger.log('ygtvok');
3034 Event.stopEvent(ev);
3035 this._closeEditor(true);
3036 }
3037 if (Dom.hasClass(target,'ygtvcancel')) {
3038 node.logger.log('ygtvcancel');
3039 Event.stopEvent(ev);
3040 this._closeEditor(false);
3041 }
3042 }, this, true);
3043
3044 editorData.inputContainer = ed.appendChild(document.createElement('div'));
3045 Dom.addClass(editorData.inputContainer,'ygtv-input');
3046
3047 Event.on(ed,'keydown',function (ev) {
3048 var editorData = TV.editorData,
3049 KEY = YAHOO.util.KeyListener.KEY;
3050 switch (ev.keyCode) {
3051 case KEY.ENTER:
3052 this.logger.log('ENTER');
3053 Event.stopEvent(ev);
3054 if (editorData.saveOnEnter) {
3055 this._closeEditor(true);
3056 }
3057 break;
3058 case KEY.ESCAPE:
3059 this.logger.log('ESC');
3060 Event.stopEvent(ev);
3061 this._closeEditor(false);
3062 break;
3063 }
3064 },this,true);
3065
3066
3067
3068 } else {
3069 ed = editorData.editorPanel;
3070 }
3071 editorData.node = node;
3072 if (editorData.nodeType) {
3073 Dom.removeClass(ed,'ygtv-edit-' + editorData.nodeType);
3074 }
3075 Dom.addClass(ed,' ygtv-edit-' + node._type);
3076 topLeft = Dom.getXY(node.getContentEl());
3077 Dom.setStyle(ed,'left',topLeft[0] + 'px');
3078 Dom.setStyle(ed,'top',topLeft[1] + 'px');
3079 Dom.setStyle(ed,'display','block');
3080 ed.focus();
3081 node.fillEditorContainer(editorData);
3082
3083 return true; // If inline editor available, don't do anything else.
3084 }
3085 };
3086
3087 /**
3088 * Method to be associated with an event (clickEvent, dblClickEvent or enterKeyPressed) to pop up the contents editor
3089 * It calls the corresponding node editNode method.
3090 * @method onEventEditNode
3091 * @param oArgs {object} Object passed as arguments to TreeView event listeners
3092 * @for YAHOO.widget.TreeView
3093 */
3094
3095 TVproto.onEventEditNode = function (oArgs) {
3096 if (oArgs instanceof YAHOO.widget.Node) {
3097 oArgs.editNode();
3098 } else if (oArgs.node instanceof YAHOO.widget.Node) {
3099 oArgs.node.editNode();
3100 }
3101 };
3102
3103 /**
3104 * Method to be called when the inline editing is finished and the editor is to be closed
3105 * @method _closeEditor
3106 * @param save {Boolean} true if the edited value is to be saved, false if discarded
3107 * @private
3108 * @for YAHOO.widget.TreeView
3109 */
3110
3111 TVproto._closeEditor = function (save) {
3112 var ed = TV.editorData,
3113 node = ed.node;
3114 if (save) {
3115 ed.node.saveEditorValue(ed);
3116 }
3117 Dom.setStyle(ed.editorPanel,'display','none');
3118 ed.active = false;
3119 node.focus();
3120 };
3121
3122 /**
3123 * Entry point for TreeView's destroy method to destroy whatever the editing plug-in has created
3124 * @method _destroyEditor
3125 * @private
3126 * @for YAHOO.widget.TreeView
3127 */
3128 TVproto._destroyEditor = function() {
3129 var ed = TV.editorData;
3130 if (ed && ed.nodeType && (!ed.active || ed.whoHasIt === this)) {
3131 Event.removeListener(ed.editorPanel,'keydown');
3132 Event.removeListener(ed.buttonContainer,'click');
3133 ed.node.destroyEditorContents(ed);
3134 document.body.removeChild(ed.editorPanel);
3135 ed.nodeType = ed.editorPanel = ed.inputContainer = ed.buttonsContainer = ed.whoHasIt = ed.node = null;
3136 ed.active = false;
3137 }
3138 };
3139
3140 var Nproto = YAHOO.widget.Node.prototype;
3141
3142 /**
3143 * Signals if the label is editable. (Ignored on TextNodes with href set.)
3144 * @property editable
3145 * @type boolean
3146 * @for YAHOO.widget.Node
3147 */
3148 Nproto.editable = false;
3149
3150 /**
3151 * pops up the contents editor, if there is one and the node is declared editable
3152 * @method editNode
3153 * @for YAHOO.widget.Node
3154 */
3155
3156 Nproto.editNode = function () {
3157 this.tree._nodeEditing(this);
3158 };
3159
3160
3161
3162
3163 /** Placeholder for a function that should provide the inline node label editor.
3164 * Leaving it set to null will indicate that this node type is not editable.
3165 * It should be overridden by nodes that provide inline editing.
3166 * The Node-specific editing element (input box, textarea or whatever) should be inserted into editorData.inputContainer.
3167 * @method fillEditorContainer
3168 * @param editorData {YAHOO.widget.TreeView.editorData} a shortcut to the static object holding editing information
3169 * @return void
3170 * @for YAHOO.widget.Node
3171 */
3172 Nproto.fillEditorContainer = null;
3173
3174
3175 /**
3176 * Node-specific destroy function to empty the contents of the inline editor panel
3177 * This function is the worst case alternative that will purge all possible events and remove the editor contents
3178 * Method Event.purgeElement is somewhat costly so if it can be replaced by specifc Event.removeListeners, it is better to do so.
3179 * @method destroyEditorContents
3180 * @param editorData {YAHOO.widget.TreeView.editorData} a shortcut to the static object holding editing information
3181 * @for YAHOO.widget.Node
3182 */
3183 Nproto.destroyEditorContents = function (editorData) {
3184 // In the worst case, if the input editor (such as the Calendar) has no destroy method
3185 // we can only try to remove all possible events on it.
3186 Event.purgeElement(editorData.inputContainer,true);
3187 editorData.inputContainer.innerHTML = '';
3188 };
3189
3190 /**
3191 * Saves the value entered into the editor.
3192 * Should be overridden by each node type
3193 * @method saveEditorValue
3194 * @param editorData {YAHOO.widget.TreeView.editorData} a shortcut to the static object holding editing information
3195 * @for YAHOO.widget.Node
3196 */
3197 Nproto.saveEditorValue = function (editorData) {
3198 };
3199
3200 var TNproto = YAHOO.widget.TextNode.prototype;
3201
3202
3203
3204 /**
3205 * Places an <input> textbox in the input container and loads the label text into it
3206 * @method fillEditorContainer
3207 * @param editorData {YAHOO.widget.TreeView.editorData} a shortcut to the static object holding editing information
3208 * @return void
3209 * @for YAHOO.widget.TextNode
3210 */
3211 TNproto.fillEditorContainer = function (editorData) {
3212
3213 var input;
3214 // If last node edited is not of the same type as this one, delete it and fill it with our editor
3215 if (editorData.nodeType != this._type) {
3216 editorData.nodeType = this._type;
3217 editorData.saveOnEnter = true;
3218 editorData.node.destroyEditorContents(editorData);
3219
3220 editorData.inputElement = input = editorData.inputContainer.appendChild(document.createElement('input'));
3221
3222 } else {
3223 // if the last node edited was of the same time, reuse the input element.
3224 input = editorData.inputElement;
3225 }
3226
3227 input.value = this.label;
3228 input.focus();
3229 input.select();
3230 };
3231
3232 /**
3233 * Saves the value entered in the editor into the TextNode label property and displays it
3234 * Overrides Node.saveEditorValue
3235 * @method saveEditorValue
3236 * @param editorData {YAHOO.widget.TreeView.editorData} a shortcut to the static object holding editing information
3237 * @for YAHOO.widget.TextNode
3238 */
3239 TNproto.saveEditorValue = function (editorData) {
3240 var node = editorData.node, value = editorData.inputElement.value;
3241 node.label = value;
3242 node.data.label = value;
3243 node.getLabelEl().innerHTML = value;
3244 };
3245
3246 /**
3247 * Destroys the contents of the inline editor panel
3248 * Overrides Node.destroyEditorContent
3249 * Since we didn't set any event listeners on this inline editor, it is more efficient to avoid the generic method in Node
3250 * @method destroyEditorContents
3251 * @param editorData {YAHOO.widget.TreeView.editorData} a shortcut to the static object holding editing information
3252 * @for YAHOO.widget.TextNode
3253 */
3254 TNproto.destroyEditorContents = function (editorData) {
3255 editorData.inputContainer.innerHTML = '';
3256 };
3257})();
3258/**
3259 * A static factory class for tree view expand/collapse animations
3260 * @class TVAnim
3261 * @static
3262 */
3263YAHOO.widget.TVAnim = function() {
3264 return {
3265 /**
3266 * Constant for the fade in animation
3267 * @property FADE_IN
3268 * @type string
3269 * @static
3270 */
3271 FADE_IN: "TVFadeIn",
3272
3273 /**
3274 * Constant for the fade out animation
3275 * @property FADE_OUT
3276 * @type string
3277 * @static
3278 */
3279 FADE_OUT: "TVFadeOut",
3280
3281 /**
3282 * Returns a ygAnim instance of the given type
3283 * @method getAnim
3284 * @param type {string} the type of animation
3285 * @param el {HTMLElement} the element to element (probably the children div)
3286 * @param callback {function} function to invoke when the animation is done.
3287 * @return {YAHOO.util.Animation} the animation instance
3288 * @static
3289 */
3290 getAnim: function(type, el, callback) {
3291 if (YAHOO.widget[type]) {
3292 return new YAHOO.widget[type](el, callback);
3293 } else {
3294 return null;
3295 }
3296 },
3297
3298 /**
3299 * Returns true if the specified animation class is available
3300 * @method isValid
3301 * @param type {string} the type of animation
3302 * @return {boolean} true if valid, false if not
3303 * @static
3304 */
3305 isValid: function(type) {
3306 return (YAHOO.widget[type]);
3307 }
3308 };
3309} ();
3310/**
3311 * A 1/2 second fade-in animation.
3312 * @class TVFadeIn
3313 * @constructor
3314 * @param el {HTMLElement} the element to animate
3315 * @param callback {function} function to invoke when the animation is finished
3316 */
3317YAHOO.widget.TVFadeIn = function(el, callback) {
3318 /**
3319 * The element to animate
3320 * @property el
3321 * @type HTMLElement
3322 */
3323 this.el = el;
3324
3325 /**
3326 * the callback to invoke when the animation is complete
3327 * @property callback
3328 * @type function
3329 */
3330 this.callback = callback;
3331
3332 this.logger = new YAHOO.widget.LogWriter(this.toString());
3333};
3334
3335YAHOO.widget.TVFadeIn.prototype = {
3336 /**
3337 * Performs the animation
3338 * @method animate
3339 */
3340 animate: function() {
3341 var tvanim = this;
3342
3343 var s = this.el.style;
3344 s.opacity = 0.1;
3345 s.filter = "alpha(opacity=10)";
3346 s.display = "";
3347
3348 var dur = 0.4;
3349 var a = new YAHOO.util.Anim(this.el, {opacity: {from: 0.1, to: 1, unit:""}}, dur);
3350 a.onComplete.subscribe( function() { tvanim.onComplete(); } );
3351 a.animate();
3352 },
3353
3354 /**
3355 * Clean up and invoke callback
3356 * @method onComplete
3357 */
3358 onComplete: function() {
3359 this.callback();
3360 },
3361
3362 /**
3363 * toString
3364 * @method toString
3365 * @return {string} the string representation of the instance
3366 */
3367 toString: function() {
3368 return "TVFadeIn";
3369 }
3370};
3371/**
3372 * A 1/2 second fade out animation.
3373 * @class TVFadeOut
3374 * @constructor
3375 * @param el {HTMLElement} the element to animate
3376 * @param callback {Function} function to invoke when the animation is finished
3377 */
3378YAHOO.widget.TVFadeOut = function(el, callback) {
3379 /**
3380 * The element to animate
3381 * @property el
3382 * @type HTMLElement
3383 */
3384 this.el = el;
3385
3386 /**
3387 * the callback to invoke when the animation is complete
3388 * @property callback
3389 * @type function
3390 */
3391 this.callback = callback;
3392
3393 this.logger = new YAHOO.widget.LogWriter(this.toString());
3394};
3395
3396YAHOO.widget.TVFadeOut.prototype = {
3397 /**
3398 * Performs the animation
3399 * @method animate
3400 */
3401 animate: function() {
3402 var tvanim = this;
3403 var dur = 0.4;
3404 var a = new YAHOO.util.Anim(this.el, {opacity: {from: 1, to: 0.1, unit:""}}, dur);
3405 a.onComplete.subscribe( function() { tvanim.onComplete(); } );
3406 a.animate();
3407 },
3408
3409 /**
3410 * Clean up and invoke callback
3411 * @method onComplete
3412 */
3413 onComplete: function() {
3414 var s = this.el.style;
3415 s.display = "none";
3416 // s.opacity = 1;
3417 s.filter = "alpha(opacity=100)";
3418 this.callback();
3419 },
3420
3421 /**
3422 * toString
3423 * @method toString
3424 * @return {string} the string representation of the instance
3425 */
3426 toString: function() {
3427 return "TVFadeOut";
3428 }
3429};
3430YAHOO.register("treeview", YAHOO.widget.TreeView, {version: "2.6.0", build: "1321"});