Proyectos de Subversion Moodle

Rev

| Ultima modificación | Ver Log |

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