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