~bac/juju-gui/trunkcopy

« back to all changes in this revision

Viewing changes to lib/yui/build/widget-parent/widget-parent.js

  • Committer: kapil.foss at gmail
  • Date: 2012-07-13 18:45:59 UTC
  • Revision ID: kapil.foss@gmail.com-20120713184559-2xl7be17egsrz0c9
reshape

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
/*
2
 
YUI 3.5.1 (build 22)
3
 
Copyright 2012 Yahoo! Inc. All rights reserved.
4
 
Licensed under the BSD License.
5
 
http://yuilibrary.com/license/
6
 
*/
7
 
YUI.add('widget-parent', function(Y) {
8
 
 
9
 
/**
10
 
 * Extension enabling a Widget to be a parent of another Widget.
11
 
 *
12
 
 * @module widget-parent
13
 
 */
14
 
 
15
 
var Lang = Y.Lang,
16
 
    RENDERED = "rendered",
17
 
    BOUNDING_BOX = "boundingBox";
18
 
 
19
 
/**
20
 
 * Widget extension providing functionality enabling a Widget to be a 
21
 
 * parent of another Widget.
22
 
 *
23
 
 * <p>In addition to the set of attributes supported by WidgetParent, the constructor
24
 
 * configuration object can also contain a <code>children</code> which can be used
25
 
 * to add child widgets to the parent during construction. The <code>children</code>
26
 
 * property is an array of either child widget instances or child widget configuration 
27
 
 * objects, and is sugar for the <a href="#method_add">add</a> method. See the 
28
 
 * <a href="#method_add">add</a> for details on the structure of the child widget 
29
 
 * configuration object.
30
 
 * @class WidgetParent
31
 
 * @constructor
32
 
 * @uses ArrayList
33
 
 * @param {Object} config User configuration object.
34
 
 */
35
 
function Parent(config) {
36
 
 
37
 
    /**
38
 
    * Fires when a Widget is add as a child.  The event object will have a 
39
 
    * 'child' property that returns a reference to the child Widget, as well 
40
 
    * as an 'index' property that returns a reference to the index specified 
41
 
    * when the add() method was called.
42
 
    * <p>
43
 
    * Subscribers to the "on" moment of this event, will be notified 
44
 
    * before a child is added.
45
 
    * </p>
46
 
    * <p>
47
 
    * Subscribers to the "after" moment of this event, will be notified
48
 
    * after a child is added.
49
 
    * </p>
50
 
    *
51
 
    * @event addChild
52
 
    * @preventable _defAddChildFn
53
 
    * @param {EventFacade} e The Event Facade
54
 
    */
55
 
    this.publish("addChild", { 
56
 
        defaultTargetOnly: true,
57
 
        defaultFn: this._defAddChildFn 
58
 
    });
59
 
 
60
 
 
61
 
    /**
62
 
    * Fires when a child Widget is removed.  The event object will have a 
63
 
    * 'child' property that returns a reference to the child Widget, as well 
64
 
    * as an 'index' property that returns a reference child's ordinal position.
65
 
    * <p>
66
 
    * Subscribers to the "on" moment of this event, will be notified 
67
 
    * before a child is removed.
68
 
    * </p>
69
 
    * <p>
70
 
    * Subscribers to the "after" moment of this event, will be notified
71
 
    * after a child is removed.
72
 
    * </p>
73
 
    *
74
 
    * @event removeChild
75
 
    * @preventable _defRemoveChildFn
76
 
    * @param {EventFacade} e The Event Facade
77
 
    */
78
 
    this.publish("removeChild", { 
79
 
        defaultTargetOnly: true,
80
 
        defaultFn: this._defRemoveChildFn 
81
 
    });
82
 
 
83
 
    this._items = [];
84
 
 
85
 
    var children,
86
 
        handle;
87
 
 
88
 
    if (config && config.children) {
89
 
 
90
 
        children = config.children;
91
 
        
92
 
        handle = this.after("initializedChange", function (e) {
93
 
            this._add(children);
94
 
            handle.detach();
95
 
        });
96
 
 
97
 
    }
98
 
 
99
 
    //  Widget method overlap
100
 
    Y.after(this._renderChildren, this, "renderUI");
101
 
    Y.after(this._bindUIParent, this, "bindUI");
102
 
 
103
 
    this.after("selectionChange", this._afterSelectionChange);
104
 
    this.after("selectedChange", this._afterParentSelectedChange);
105
 
    this.after("activeDescendantChange", this._afterActiveDescendantChange);
106
 
 
107
 
    this._hDestroyChild = this.after("*:destroy", this._afterDestroyChild);
108
 
    this.after("*:focusedChange", this._updateActiveDescendant);
109
 
 
110
 
}
111
 
 
112
 
Parent.ATTRS = {
113
 
 
114
 
    /**
115
 
     * @attribute defaultChildType
116
 
     * @type {String|Object}
117
 
     *
118
 
     * @description String representing the default type of the children 
119
 
     * managed by this Widget.  Can also supply default type as a constructor
120
 
     * reference.
121
 
     */
122
 
    defaultChildType: {
123
 
        setter: function (val) {
124
 
            
125
 
            var returnVal = Y.Attribute.INVALID_VALUE,
126
 
                FnConstructor = Lang.isString(val) ? Y[val] : val;
127
 
            
128
 
            if (Lang.isFunction(FnConstructor)) {
129
 
                returnVal = FnConstructor;
130
 
            }
131
 
            
132
 
            return returnVal;
133
 
        }
134
 
    },
135
 
 
136
 
    /**
137
 
     * @attribute activeDescendant
138
 
     * @type Widget
139
 
     * @readOnly
140
 
     *
141
 
     * @description Returns the Widget's currently focused descendant Widget.
142
 
     */
143
 
    activeDescendant: {    
144
 
        readOnly: true
145
 
    },
146
 
 
147
 
    /**
148
 
     * @attribute multiple
149
 
     * @type Boolean
150
 
     * @default false
151
 
     * @writeOnce 
152
 
     *
153
 
     * @description Boolean indicating if multiple children can be selected at 
154
 
     * once.  Whether or not multiple selection is enabled is always delegated
155
 
     * to the value of the <code>multiple</code> attribute of the root widget
156
 
     * in the object hierarchy.
157
 
     */
158
 
    multiple: {
159
 
        value: false,
160
 
        validator: Lang.isBoolean,
161
 
        writeOnce: true,
162
 
        getter: function (value) {
163
 
            var root = this.get("root");
164
 
            return (root && root != this) ? root.get("multiple") : value;
165
 
        }
166
 
    },
167
 
 
168
 
 
169
 
    /**
170
 
     * @attribute selection
171
 
     * @type {ArrayList|Widget}
172
 
     * @readOnly  
173
 
     *
174
 
     * @description Returns the currently selected child Widget.  If the 
175
 
     * <code>mulitple</code> attribte is set to <code>true</code> will 
176
 
     * return an Y.ArrayList instance containing the currently selected 
177
 
     * children.  If no children are selected, will return null.
178
 
     */
179
 
    selection: {
180
 
        readOnly: true,
181
 
        setter: "_setSelection",
182
 
        getter: function (value) {
183
 
            var selection = Lang.isArray(value) ? 
184
 
                    (new Y.ArrayList(value)) : value;
185
 
            return selection;
186
 
        }
187
 
    },
188
 
 
189
 
    selected: {
190
 
        setter: function (value) {
191
 
 
192
 
            //  Enforces selection behavior on for parent Widgets.  Parent's 
193
 
            //  selected attribute can be set to the following:
194
 
            //  0 - Not selected
195
 
            //  1 - Fully selected (all children are selected).  In order for 
196
 
            //  all children to be selected, multiple selection must be 
197
 
            //  enabled.  Therefore, you cannot set the "selected" attribute 
198
 
            //  on a parent Widget to 1 unless multiple selection is enabled.
199
 
            //  2 - Partially selected, meaning one ore more (but not all) 
200
 
            //  children are selected.
201
 
 
202
 
            var returnVal = value;
203
 
 
204
 
            if (value === 1 && !this.get("multiple")) {
205
 
                returnVal = Y.Attribute.INVALID_VALUE;
206
 
            }
207
 
            
208
 
            return returnVal;
209
 
        }
210
 
    }
211
 
 
212
 
};
213
 
 
214
 
Parent.prototype = {
215
 
 
216
 
    /**
217
 
     * The destructor implementation for Parent widgets. Destroys all children.
218
 
     * @method destructor
219
 
     */
220
 
    destructor: function() {
221
 
        this._destroyChildren();
222
 
    },
223
 
 
224
 
    /**
225
 
     * Destroy event listener for each child Widget, responsible for removing 
226
 
     * the destroyed child Widget from the parent's internal array of children
227
 
     * (_items property).
228
 
     *
229
 
     * @method _afterDestroyChild
230
 
     * @protected
231
 
     * @param {EventFacade} event The event facade for the attribute change.
232
 
     */
233
 
    _afterDestroyChild: function (event) {
234
 
        var child = event.target;
235
 
 
236
 
        if (child.get("parent") == this) {
237
 
            child.remove();
238
 
        }        
239
 
    },
240
 
 
241
 
    /**
242
 
     * Attribute change listener for the <code>selection</code> 
243
 
     * attribute, responsible for setting the value of the 
244
 
     * parent's <code>selected</code> attribute.
245
 
     *
246
 
     * @method _afterSelectionChange
247
 
     * @protected
248
 
     * @param {EventFacade} event The event facade for the attribute change.
249
 
     */
250
 
    _afterSelectionChange: function (event) {
251
 
 
252
 
        if (event.target == this && event.src != this) {
253
 
 
254
 
            var selection = event.newVal,
255
 
                selectedVal = 0;    //  Not selected
256
 
 
257
 
 
258
 
            if (selection) {
259
 
 
260
 
                selectedVal = 2;    //  Assume partially selected, confirm otherwise
261
 
 
262
 
 
263
 
                if (Y.instanceOf(selection, Y.ArrayList) && 
264
 
                    (selection.size() === this.size())) {
265
 
 
266
 
                    selectedVal = 1;    //  Fully selected
267
 
 
268
 
                }
269
 
                
270
 
            }
271
 
 
272
 
            this.set("selected", selectedVal, { src: this });
273
 
        
274
 
        }
275
 
    },
276
 
 
277
 
 
278
 
    /**
279
 
     * Attribute change listener for the <code>activeDescendant</code> 
280
 
     * attribute, responsible for setting the value of the 
281
 
     * parent's <code>activeDescendant</code> attribute.
282
 
     *
283
 
     * @method _afterActiveDescendantChange
284
 
     * @protected
285
 
     * @param {EventFacade} event The event facade for the attribute change.
286
 
     */
287
 
    _afterActiveDescendantChange: function (event) {
288
 
        var parent = this.get("parent");
289
 
 
290
 
        if (parent) {
291
 
            parent._set("activeDescendant", event.newVal);
292
 
        }
293
 
    },
294
 
 
295
 
    /**
296
 
     * Attribute change listener for the <code>selected</code> 
297
 
     * attribute, responsible for syncing the selected state of all children to 
298
 
     * match that of their parent Widget.
299
 
     * 
300
 
     *
301
 
     * @method _afterParentSelectedChange
302
 
     * @protected
303
 
     * @param {EventFacade} event The event facade for the attribute change.
304
 
     */
305
 
    _afterParentSelectedChange: function (event) {
306
 
 
307
 
        var value = event.newVal;
308
 
 
309
 
        if (this == event.target && event.src != this && 
310
 
            (value === 0 || value === 1)) {
311
 
 
312
 
            this.each(function (child) {
313
 
 
314
 
                //  Specify the source of this change as the parent so that 
315
 
                //  value of the parent's "selection" attribute isn't 
316
 
                //  recalculated
317
 
 
318
 
                child.set("selected", value, { src: this });
319
 
 
320
 
            }, this);
321
 
            
322
 
        }
323
 
        
324
 
    },
325
 
 
326
 
 
327
 
    /**
328
 
     * Default setter for <code>selection</code> attribute changes.
329
 
     *
330
 
     * @method _setSelection
331
 
     * @protected
332
 
     * @param child {Widget|Array} Widget or Array of Widget instances.     
333
 
     * @return {Widget|Array} Widget or Array of Widget instances.
334
 
     */
335
 
    _setSelection: function (child) {
336
 
 
337
 
        var selection = null,
338
 
            selected;
339
 
 
340
 
        if (this.get("multiple") && !this.isEmpty()) {
341
 
 
342
 
            selected = [];
343
 
            
344
 
            this.each(function (v) {
345
 
 
346
 
               if (v.get("selected") > 0) {
347
 
                   selected.push(v);
348
 
               }
349
 
 
350
 
            });
351
 
 
352
 
            if (selected.length > 0) {
353
 
                selection = selected;
354
 
            }
355
 
 
356
 
        }
357
 
        else {
358
 
 
359
 
            if (child.get("selected") > 0) {
360
 
                selection = child;
361
 
            }
362
 
 
363
 
        }
364
 
        
365
 
        return selection;
366
 
            
367
 
    },
368
 
 
369
 
 
370
 
    /**
371
 
     * Attribute change listener for the <code>selected</code> 
372
 
     * attribute of child Widgets, responsible for setting the value of the 
373
 
     * parent's <code>selection</code> attribute.
374
 
     *
375
 
     * @method _updateSelection
376
 
     * @protected
377
 
     * @param {EventFacade} event The event facade for the attribute change.
378
 
     */
379
 
    _updateSelection: function (event) {
380
 
 
381
 
        var child = event.target,
382
 
            selection;
383
 
 
384
 
        if (child.get("parent") == this) {
385
 
 
386
 
            if (event.src != "_updateSelection") {
387
 
 
388
 
                selection = this.get("selection");
389
 
 
390
 
                if (!this.get("multiple") && selection && event.newVal > 0) {
391
 
 
392
 
                    //  Deselect the previously selected child.
393
 
                    //  Set src equal to the current context to prevent
394
 
                    //  unnecessary re-calculation of the selection.
395
 
 
396
 
                    selection.set("selected", 0, { src: "_updateSelection" });
397
 
 
398
 
                }
399
 
 
400
 
                this._set("selection", child);
401
 
 
402
 
            }
403
 
 
404
 
            if (event.src == this) {
405
 
                this._set("selection", child, { src: this });
406
 
            }
407
 
            
408
 
        }
409
 
 
410
 
    },
411
 
 
412
 
    /**
413
 
     * Attribute change listener for the <code>focused</code> 
414
 
     * attribute of child Widgets, responsible for setting the value of the 
415
 
     * parent's <code>activeDescendant</code> attribute.
416
 
     *
417
 
     * @method _updateActiveDescendant
418
 
     * @protected
419
 
     * @param {EventFacade} event The event facade for the attribute change.
420
 
     */
421
 
    _updateActiveDescendant: function (event) {
422
 
        var activeDescendant = (event.newVal === true) ? event.target : null;
423
 
        this._set("activeDescendant", activeDescendant);
424
 
    },
425
 
 
426
 
    /**
427
 
     * Creates an instance of a child Widget using the specified configuration.
428
 
     * By default Widget instances will be created of the type specified 
429
 
     * by the <code>defaultChildType</code> attribute.  Types can be explicitly
430
 
     * defined via the <code>childType</code> property of the configuration object
431
 
     * literal. The use of the <code>type</code> property has been deprecated, but 
432
 
     * will still be used as a fallback, if <code>childType</code> is not defined,
433
 
     * for backwards compatibility. 
434
 
     *
435
 
     * @method _createChild
436
 
     * @protected
437
 
     * @param config {Object} Object literal representing the configuration 
438
 
     * used to create an instance of a Widget.
439
 
     */
440
 
    _createChild: function (config) {
441
 
 
442
 
        var defaultType = this.get("defaultChildType"),
443
 
            altType = config.childType || config.type,
444
 
            child,
445
 
            Fn,
446
 
            FnConstructor;
447
 
 
448
 
        if (altType) {
449
 
            Fn = Lang.isString(altType) ? Y[altType] : altType;
450
 
        }
451
 
 
452
 
        if (Lang.isFunction(Fn)) {
453
 
            FnConstructor = Fn;
454
 
        } else if (defaultType) {
455
 
            // defaultType is normalized to a function in it's setter 
456
 
            FnConstructor = defaultType;
457
 
        }
458
 
 
459
 
        if (FnConstructor) {
460
 
            child = new FnConstructor(config);
461
 
        } else {
462
 
            Y.error("Could not create a child instance because its constructor is either undefined or invalid.");
463
 
        }
464
 
 
465
 
        return child;
466
 
        
467
 
    },
468
 
 
469
 
    /**
470
 
     * Default addChild handler
471
 
     *
472
 
     * @method _defAddChildFn
473
 
     * @protected
474
 
     * @param event {EventFacade} The Event object
475
 
     * @param child {Widget} The Widget instance, or configuration 
476
 
     * object for the Widget to be added as a child.
477
 
     * @param index {Number} Number representing the position at 
478
 
     * which the child will be inserted.
479
 
     */
480
 
    _defAddChildFn: function (event) {
481
 
 
482
 
        var child = event.child,
483
 
            index = event.index,
484
 
            children = this._items;
485
 
 
486
 
        if (child.get("parent")) {
487
 
            child.remove();
488
 
        }
489
 
 
490
 
        if (Lang.isNumber(index)) {
491
 
            children.splice(index, 0, child);
492
 
        }
493
 
        else {
494
 
            children.push(child);
495
 
        }
496
 
 
497
 
        child._set("parent", this);
498
 
        child.addTarget(this);
499
 
 
500
 
        // Update index in case it got normalized after addition
501
 
        // (e.g. user passed in 10, and there are only 3 items, the actual index would be 3. We don't want to pass 10 around in the event facade).
502
 
        event.index = child.get("index");
503
 
 
504
 
        //  TO DO: Remove in favor of using event bubbling
505
 
        child.after("selectedChange", Y.bind(this._updateSelection, this));
506
 
    },
507
 
 
508
 
 
509
 
    /**
510
 
     * Default removeChild handler
511
 
     *
512
 
     * @method _defRemoveChildFn
513
 
     * @protected
514
 
     * @param event {EventFacade} The Event object
515
 
     * @param child {Widget} The Widget instance to be removed.
516
 
     * @param index {Number} Number representing the index of the Widget to 
517
 
     * be removed.
518
 
     */    
519
 
    _defRemoveChildFn: function (event) {
520
 
 
521
 
        var child = event.child,
522
 
            index = event.index,
523
 
            children = this._items;
524
 
 
525
 
        if (child.get("focused")) {
526
 
            child.blur(); // focused is readOnly, so use the public i/f to unset it
527
 
        }
528
 
 
529
 
        if (child.get("selected")) {
530
 
            child.set("selected", 0);
531
 
        }
532
 
 
533
 
        children.splice(index, 1);
534
 
 
535
 
        child.removeTarget(this);
536
 
        child._oldParent = child.get("parent");
537
 
        child._set("parent", null);
538
 
    },
539
 
 
540
 
    /**
541
 
    * @method _add
542
 
    * @protected
543
 
    * @param child {Widget|Object} The Widget instance, or configuration 
544
 
    * object for the Widget to be added as a child.
545
 
    * @param child {Array} Array of Widget instances, or configuration 
546
 
    * objects for the Widgets to be added as a children.
547
 
    * @param index {Number} (Optional.)  Number representing the position at 
548
 
    * which the child should be inserted.
549
 
    * @description Adds a Widget as a child.  If the specified Widget already
550
 
    * has a parent it will be removed from its current parent before
551
 
    * being added as a child.
552
 
    * @return {Widget|Array} Successfully added Widget or Array containing the 
553
 
    * successfully added Widget instance(s). If no children where added, will 
554
 
    * will return undefined.
555
 
    */
556
 
    _add: function (child, index) {   
557
 
 
558
 
        var children,
559
 
            oChild,
560
 
            returnVal;
561
 
 
562
 
 
563
 
        if (Lang.isArray(child)) {
564
 
 
565
 
            children = [];
566
 
 
567
 
            Y.each(child, function (v, k) {
568
 
 
569
 
                oChild = this._add(v, (index + k));
570
 
 
571
 
                if (oChild) {
572
 
                    children.push(oChild);
573
 
                }
574
 
                
575
 
            }, this);
576
 
            
577
 
 
578
 
            if (children.length > 0) {
579
 
                returnVal = children;
580
 
            }
581
 
 
582
 
        }
583
 
        else {
584
 
 
585
 
            if (Y.instanceOf(child, Y.Widget)) {
586
 
                oChild = child;
587
 
            }
588
 
            else {
589
 
                oChild = this._createChild(child);
590
 
            }
591
 
 
592
 
            if (oChild && this.fire("addChild", { child: oChild, index: index })) {
593
 
                returnVal = oChild;
594
 
            }
595
 
 
596
 
        }
597
 
 
598
 
        return returnVal;
599
 
 
600
 
    },
601
 
 
602
 
 
603
 
    /**
604
 
    * @method add
605
 
    * @param child {Widget|Object} The Widget instance, or configuration 
606
 
    * object for the Widget to be added as a child. The configuration object
607
 
    * for the child can include a <code>childType</code> property, which is either
608
 
    * a constructor function or a string which names a constructor function on the 
609
 
    * Y instance (e.g. "Tab" would refer to Y.Tab) (<code>childType</code> used to be 
610
 
    * named <code>type</code>, support for which has been deprecated, but is still
611
 
    * maintained for backward compatibility. <code>childType</code> takes precedence
612
 
    * over <code>type</code> if both are defined.
613
 
    * @param child {Array} Array of Widget instances, or configuration 
614
 
    * objects for the Widgets to be added as a children.
615
 
    * @param index {Number} (Optional.)  Number representing the position at 
616
 
    * which the child should be inserted.
617
 
    * @description Adds a Widget as a child.  If the specified Widget already
618
 
    * has a parent it will be removed from its current parent before
619
 
    * being added as a child.
620
 
    * @return {ArrayList} Y.ArrayList containing the successfully added 
621
 
    * Widget instance(s).  If no children where added, will return an empty 
622
 
    * Y.ArrayList instance.
623
 
    */
624
 
    add: function () {
625
 
 
626
 
        var added = this._add.apply(this, arguments),
627
 
            children = added ? (Lang.isArray(added) ? added : [added]) : [];
628
 
 
629
 
        return (new Y.ArrayList(children));
630
 
 
631
 
    },
632
 
 
633
 
 
634
 
    /**
635
 
    * @method remove
636
 
    * @param index {Number} (Optional.)  Number representing the index of the 
637
 
    * child to be removed.
638
 
    * @description Removes the Widget from its parent.  Optionally, can remove
639
 
    * a child by specifying its index.
640
 
    * @return {Widget} Widget instance that was successfully removed, otherwise
641
 
    * undefined.
642
 
    */
643
 
    remove: function (index) {
644
 
 
645
 
        var child = this._items[index],
646
 
            returnVal;
647
 
 
648
 
        if (child && this.fire("removeChild", { child: child, index: index })) {
649
 
            returnVal = child;
650
 
        }
651
 
        
652
 
        return returnVal;
653
 
 
654
 
    },
655
 
 
656
 
 
657
 
    /**
658
 
    * @method removeAll
659
 
    * @description Removes all of the children from the Widget.
660
 
    * @return {ArrayList} Y.ArrayList instance containing Widgets that were 
661
 
    * successfully removed.  If no children where removed, will return an empty 
662
 
    * Y.ArrayList instance.
663
 
    */
664
 
    removeAll: function () {
665
 
 
666
 
        var removed = [],
667
 
            child;
668
 
 
669
 
        Y.each(this._items.concat(), function () {
670
 
 
671
 
            child = this.remove(0);
672
 
 
673
 
            if (child) {
674
 
                removed.push(child);
675
 
            }
676
 
 
677
 
        }, this);
678
 
 
679
 
        return (new Y.ArrayList(removed));
680
 
 
681
 
    },
682
 
    
683
 
    /**
684
 
     * Selects the child at the given index (zero-based).
685
 
     *
686
 
     * @method selectChild
687
 
     * @param {Number} i the index of the child to be selected
688
 
     */
689
 
    selectChild: function(i) {
690
 
        this.item(i).set('selected', 1);
691
 
    },
692
 
 
693
 
    /**
694
 
     * Selects all children.
695
 
     *
696
 
     * @method selectAll
697
 
     */
698
 
    selectAll: function () {
699
 
        this.set("selected", 1);
700
 
    },
701
 
 
702
 
    /**
703
 
     * Deselects all children.
704
 
     *
705
 
     * @method deselectAll
706
 
     */
707
 
    deselectAll: function () {
708
 
        this.set("selected", 0);
709
 
    },
710
 
 
711
 
    /**
712
 
     * Updates the UI in response to a child being added.
713
 
     *
714
 
     * @method _uiAddChild
715
 
     * @protected
716
 
     * @param child {Widget} The child Widget instance to render.
717
 
     * @param parentNode {Object} The Node under which the 
718
 
     * child Widget is to be rendered.
719
 
     */    
720
 
    _uiAddChild: function (child, parentNode) {
721
 
 
722
 
        child.render(parentNode);
723
 
 
724
 
        // TODO: Ideally this should be in Child's render UI. 
725
 
 
726
 
        var childBB = child.get("boundingBox"),
727
 
            siblingBB,
728
 
            nextSibling = child.next(false),
729
 
            prevSibling;
730
 
 
731
 
        // Insert or Append to last child.
732
 
 
733
 
        // Avoiding index, and using the current sibling 
734
 
        // state (which should be accurate), means we don't have 
735
 
        // to worry about decorator elements which may be added 
736
 
        // to the _childContainer node.
737
 
    
738
 
        if (nextSibling && nextSibling.get(RENDERED)) {
739
 
 
740
 
            siblingBB = nextSibling.get(BOUNDING_BOX);
741
 
            siblingBB.insert(childBB, "before");
742
 
 
743
 
        } else {
744
 
 
745
 
            prevSibling = child.previous(false);
746
 
 
747
 
            if (prevSibling && prevSibling.get(RENDERED)) {
748
 
 
749
 
                siblingBB = prevSibling.get(BOUNDING_BOX);
750
 
                siblingBB.insert(childBB, "after");
751
 
 
752
 
            } else if (!parentNode.contains(childBB)) {
753
 
 
754
 
                // Based on pull request from andreas-karlsson
755
 
                // https://github.com/yui/yui3/pull/25#issuecomment-2103536
756
 
 
757
 
                // Account for case where a child was rendered independently of the 
758
 
                // parent-child framework, to a node outside of the parentNode,
759
 
                // and there are no siblings.
760
 
 
761
 
                parentNode.appendChild(childBB);
762
 
            }
763
 
        }
764
 
 
765
 
    },
766
 
 
767
 
    /**
768
 
     * Updates the UI in response to a child being removed.
769
 
     *
770
 
     * @method _uiRemoveChild
771
 
     * @protected
772
 
     * @param child {Widget} The child Widget instance to render.
773
 
     */        
774
 
    _uiRemoveChild: function (child) {
775
 
        child.get("boundingBox").remove();
776
 
    },
777
 
 
778
 
    _afterAddChild: function (event) {
779
 
        var child = event.child;
780
 
 
781
 
        if (child.get("parent") == this) {
782
 
            this._uiAddChild(child, this._childrenContainer);
783
 
        }
784
 
    },
785
 
 
786
 
    _afterRemoveChild: function (event) {
787
 
        var child = event.child;
788
 
 
789
 
        if (child._oldParent == this) {
790
 
            this._uiRemoveChild(child);
791
 
        }
792
 
    },
793
 
 
794
 
    /**
795
 
     * Sets up DOM and CustomEvent listeners for the parent widget.
796
 
     * <p>
797
 
     * This method in invoked after bindUI is invoked for the Widget class
798
 
     * using YUI's aop infrastructure.
799
 
     * </p>
800
 
     *
801
 
     * @method _bindUIParent
802
 
     * @protected
803
 
     */
804
 
    _bindUIParent: function () {
805
 
        this.after("addChild", this._afterAddChild);
806
 
        this.after("removeChild", this._afterRemoveChild);
807
 
    },
808
 
 
809
 
    /**
810
 
     * Renders all child Widgets for the parent.
811
 
     * <p>
812
 
     * This method in invoked after renderUI is invoked for the Widget class
813
 
     * using YUI's aop infrastructure.
814
 
     * </p>
815
 
     * @method _renderChildren
816
 
     * @protected
817
 
     */
818
 
    _renderChildren: function () {
819
 
 
820
 
        /**
821
 
         * <p>By default WidgetParent will render it's children to the parent's content box.</p>
822
 
         *
823
 
         * <p>If the children need to be rendered somewhere else, the _childrenContainer property
824
 
         * can be set to the Node which the children should be rendered to. This property should be
825
 
         * set before the _renderChildren method is invoked, ideally in your renderUI method, 
826
 
         * as soon as you create the element to be rendered to.</p>
827
 
         *
828
 
         * @protected
829
 
         * @property _childrenContainer
830
 
         * @value The content box
831
 
         * @type Node
832
 
         */
833
 
        var renderTo = this._childrenContainer || this.get("contentBox");
834
 
 
835
 
        this._childrenContainer = renderTo;
836
 
 
837
 
        this.each(function (child) {
838
 
            child.render(renderTo);
839
 
        });
840
 
    },
841
 
 
842
 
    /**
843
 
     * Destroys all child Widgets for the parent.
844
 
     * <p>
845
 
     * This method is invoked before the destructor is invoked for the Widget 
846
 
     * class using YUI's aop infrastructure.
847
 
     * </p>
848
 
     * @method _destroyChildren
849
 
     * @protected
850
 
     */
851
 
    _destroyChildren: function () {
852
 
 
853
 
        //  Detach the handler responsible for removing children in 
854
 
        //  response to destroying them since:
855
 
        //  1)  It is unnecessary/inefficient at this point since we are doing 
856
 
        //      a batch destroy of all children.
857
 
        //  2)  Removing each child will affect our ability to iterate the 
858
 
        //      children since the size of _items will be changing as we 
859
 
        //      iterate.
860
 
        this._hDestroyChild.detach();
861
 
 
862
 
        //  Need to clone the _items array since 
863
 
        this.each(function (child) {
864
 
            child.destroy();
865
 
        });
866
 
    }
867
 
    
868
 
};
869
 
 
870
 
Y.augment(Parent, Y.ArrayList);
871
 
 
872
 
Y.WidgetParent = Parent;
873
 
 
874
 
 
875
 
}, '3.5.1' ,{requires:['base-build', 'arraylist', 'widget']});