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