AutorÃa | Ultima modificación | Ver Log |
YUI.add('widget-parent', function (Y, NAME) {/*** Extension enabling a Widget to be a parent of another Widget.** @module widget-parent*/var Lang = Y.Lang,RENDERED = "rendered",BOUNDING_BOX = "boundingBox";/*** Widget extension providing functionality enabling a Widget to be a* parent of another Widget.** <p>In addition to the set of attributes supported by WidgetParent, the constructor* configuration object can also contain a <code>children</code> which can be used* to add child widgets to the parent during construction. The <code>children</code>* property is an array of either child widget instances or child widget configuration* objects, and is sugar for the <a href="#method_add">add</a> method. See the* <a href="#method_add">add</a> for details on the structure of the child widget* configuration object.* @class WidgetParent* @constructor* @uses ArrayList* @param {Object} config User configuration object.*/function Parent(config) {/*** Fires when a Widget is add as a child. The event object will have a* 'child' property that returns a reference to the child Widget, as well* as an 'index' property that returns a reference to the index specified* when the add() method was called.* <p>* Subscribers to the "on" moment of this event, will be notified* before a child is added.* </p>* <p>* Subscribers to the "after" moment of this event, will be notified* after a child is added.* </p>** @event addChild* @preventable _defAddChildFn* @param {EventFacade} e The Event Facade*/this.publish("addChild", {defaultTargetOnly: true,defaultFn: this._defAddChildFn});/*** Fires when a child Widget is removed. The event object will have a* 'child' property that returns a reference to the child Widget, as well* as an 'index' property that returns a reference child's ordinal position.* <p>* Subscribers to the "on" moment of this event, will be notified* before a child is removed.* </p>* <p>* Subscribers to the "after" moment of this event, will be notified* after a child is removed.* </p>** @event removeChild* @preventable _defRemoveChildFn* @param {EventFacade} e The Event Facade*/this.publish("removeChild", {defaultTargetOnly: true,defaultFn: this._defRemoveChildFn});this._items = [];var children,handle;if (config && config.children) {children = config.children;handle = this.after("initializedChange", function (e) {this._add(children);handle.detach();});}// Widget method overlapY.after(this._renderChildren, this, "renderUI");Y.after(this._bindUIParent, this, "bindUI");this.after("selectionChange", this._afterSelectionChange);this.after("selectedChange", this._afterParentSelectedChange);this.after("activeDescendantChange", this._afterActiveDescendantChange);this._hDestroyChild = this.after("*:destroy", this._afterDestroyChild);this.after("*:focusedChange", this._updateActiveDescendant);}Parent.ATTRS = {/*** @attribute defaultChildType* @type {String|Object}** @description String representing the default type of the children* managed by this Widget. Can also supply default type as a constructor* reference.*/defaultChildType: {setter: function (val) {var returnVal = Y.Attribute.INVALID_VALUE,FnConstructor = Lang.isString(val) ? Y[val] : val;if (Lang.isFunction(FnConstructor)) {returnVal = FnConstructor;}return returnVal;}},/*** @attribute activeDescendant* @type Widget* @readOnly** @description Returns the Widget's currently focused descendant Widget.*/activeDescendant: {readOnly: true},/*** @attribute multiple* @type Boolean* @default false* @writeOnce** @description Boolean indicating if multiple children can be selected at* once. Whether or not multiple selection is enabled is always delegated* to the value of the <code>multiple</code> attribute of the root widget* in the object hierarchy.*/multiple: {value: false,validator: Lang.isBoolean,writeOnce: true,getter: function (value) {var root = this.get("root");return (root && root != this) ? root.get("multiple") : value;}},/*** @attribute selection* @type {ArrayList|Widget}* @readOnly** @description Returns the currently selected child Widget. If the* <code>mulitple</code> attribte is set to <code>true</code> will* return an Y.ArrayList instance containing the currently selected* children. If no children are selected, will return null.*/selection: {readOnly: true,setter: "_setSelection",getter: function (value) {var selection = Lang.isArray(value) ?(new Y.ArrayList(value)) : value;return selection;}},selected: {setter: function (value) {// Enforces selection behavior on for parent Widgets. Parent's// selected attribute can be set to the following:// 0 - Not selected// 1 - Fully selected (all children are selected). In order for// all children to be selected, multiple selection must be// enabled. Therefore, you cannot set the "selected" attribute// on a parent Widget to 1 unless multiple selection is enabled.// 2 - Partially selected, meaning one ore more (but not all)// children are selected.var returnVal = value;if (value === 1 && !this.get("multiple")) {Y.log('The selected attribute can only be set to 1 if the "multiple" attribute is set to true.', "error", "widget");returnVal = Y.Attribute.INVALID_VALUE;}return returnVal;}}};Parent.prototype = {/*** The destructor implementation for Parent widgets. Destroys all children.* @method destructor*/destructor: function() {this._destroyChildren();},/*** Destroy event listener for each child Widget, responsible for removing* the destroyed child Widget from the parent's internal array of children* (_items property).** @method _afterDestroyChild* @protected* @param {EventFacade} event The event facade for the attribute change.*/_afterDestroyChild: function (event) {var child = event.target;if (child.get("parent") == this) {child.remove();}},/*** Attribute change listener for the <code>selection</code>* attribute, responsible for setting the value of the* parent's <code>selected</code> attribute.** @method _afterSelectionChange* @protected* @param {EventFacade} event The event facade for the attribute change.*/_afterSelectionChange: function (event) {if (event.target == this && event.src != this) {var selection = event.newVal,selectedVal = 0; // Not selectedif (selection) {selectedVal = 2; // Assume partially selected, confirm otherwiseif (Y.instanceOf(selection, Y.ArrayList) &&(selection.size() === this.size())) {selectedVal = 1; // Fully selected}}this.set("selected", selectedVal, { src: this });}},/*** Attribute change listener for the <code>activeDescendant</code>* attribute, responsible for setting the value of the* parent's <code>activeDescendant</code> attribute.** @method _afterActiveDescendantChange* @protected* @param {EventFacade} event The event facade for the attribute change.*/_afterActiveDescendantChange: function (event) {var parent = this.get("parent");if (parent) {parent._set("activeDescendant", event.newVal);}},/*** Attribute change listener for the <code>selected</code>* attribute, responsible for syncing the selected state of all children to* match that of their parent Widget.*** @method _afterParentSelectedChange* @protected* @param {EventFacade} event The event facade for the attribute change.*/_afterParentSelectedChange: function (event) {var value = event.newVal;if (this == event.target && event.src != this &&(value === 0 || value === 1)) {this.each(function (child) {// Specify the source of this change as the parent so that// value of the parent's "selection" attribute isn't// recalculatedchild.set("selected", value, { src: this });}, this);}},/*** Default setter for <code>selection</code> attribute changes.** @method _setSelection* @protected* @param child {Widget|Array} Widget or Array of Widget instances.* @return {Widget|Array} Widget or Array of Widget instances.*/_setSelection: function (child) {var selection = null,selected;if (this.get("multiple") && !this.isEmpty()) {selected = [];this.each(function (v) {if (v.get("selected") > 0) {selected.push(v);}});if (selected.length > 0) {selection = selected;}}else {if (child.get("selected") > 0) {selection = child;}}return selection;},/*** Attribute change listener for the <code>selected</code>* attribute of child Widgets, responsible for setting the value of the* parent's <code>selection</code> attribute.** @method _updateSelection* @protected* @param {EventFacade} event The event facade for the attribute change.*/_updateSelection: function (event) {var child = event.target,selection;if (child.get("parent") == this) {if (event.src != "_updateSelection") {selection = this.get("selection");if (!this.get("multiple") && selection && event.newVal > 0) {// Deselect the previously selected child.// Set src equal to the current context to prevent// unnecessary re-calculation of the selection.selection.set("selected", 0, { src: "_updateSelection" });}this._set("selection", child);}if (event.src == this) {this._set("selection", child, { src: this });}}},/*** Attribute change listener for the <code>focused</code>* attribute of child Widgets, responsible for setting the value of the* parent's <code>activeDescendant</code> attribute.** @method _updateActiveDescendant* @protected* @param {EventFacade} event The event facade for the attribute change.*/_updateActiveDescendant: function (event) {var activeDescendant = (event.newVal === true) ? event.target : null;this._set("activeDescendant", activeDescendant);},/*** Creates an instance of a child Widget using the specified configuration.* By default Widget instances will be created of the type specified* by the <code>defaultChildType</code> attribute. Types can be explicitly* defined via the <code>childType</code> property of the configuration object* literal. The use of the <code>type</code> property has been deprecated, but* will still be used as a fallback, if <code>childType</code> is not defined,* for backwards compatibility.** @method _createChild* @protected* @param config {Object} Object literal representing the configuration* used to create an instance of a Widget.*/_createChild: function (config) {var defaultType = this.get("defaultChildType"),altType = config.childType || config.type,child,Fn,FnConstructor;if (altType) {Fn = Lang.isString(altType) ? Y[altType] : altType;}if (Lang.isFunction(Fn)) {FnConstructor = Fn;} else if (defaultType) {// defaultType is normalized to a function in it's setterFnConstructor = defaultType;}if (FnConstructor) {child = new FnConstructor(config);} else {Y.error("Could not create a child instance because its constructor is either undefined or invalid.");}return child;},/*** Default addChild handler** @method _defAddChildFn* @protected* @param event {EventFacade} The Event object* @param child {Widget} The Widget instance, or configuration* object for the Widget to be added as a child.* @param index {Number} Number representing the position at* which the child will be inserted.*/_defAddChildFn: function (event) {var child = event.child,index = event.index,children = this._items;if (child.get("parent")) {child.remove();}if (Lang.isNumber(index)) {children.splice(index, 0, child);}else {children.push(child);}child._set("parent", this);child.addTarget(this);// Update index in case it got normalized after addition// (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).event.index = child.get("index");// TO DO: Remove in favor of using event bubblingchild.after("selectedChange", Y.bind(this._updateSelection, this));},/*** Default removeChild handler** @method _defRemoveChildFn* @protected* @param event {EventFacade} The Event object* @param child {Widget} The Widget instance to be removed.* @param index {Number} Number representing the index of the Widget to* be removed.*/_defRemoveChildFn: function (event) {var child = event.child,index = event.index,children = this._items;if (child.get("focused")) {child.blur(); // focused is readOnly, so use the public i/f to unset it}if (child.get("selected")) {child.set("selected", 0);}children.splice(index, 1);child.removeTarget(this);child._oldParent = child.get("parent");child._set("parent", null);},/*** @method _add* @protected* @param child {Widget|Object|Array} The Widget instance, or configuration* object for the Widget, or Array of Widget instances to be added as a child.* @param index {Number} Number representing the position at which the child* should be inserted.* @description Adds a Widget as a child. If the specified Widget already* has a parent it will be removed from its current parent before* being added as a child.* @return {Widget|Array} Successfully added Widget or Array containing the* successfully added Widget instance(s). If no children where added, will* will return undefined.*/_add: function (child, index) {var children,oChild,returnVal;if (Lang.isArray(child)) {children = [];Y.each(child, function (v, k) {oChild = this._add(v, (index + k));if (oChild) {children.push(oChild);}}, this);if (children.length > 0) {returnVal = children;}}else {if (Y.instanceOf(child, Y.Widget)) {oChild = child;}else {oChild = this._createChild(child);}if (oChild && this.fire("addChild", { child: oChild, index: index })) {returnVal = oChild;}}return returnVal;},/*** @method add* @param child {Widget|Object|Array} The Widget instance, or configuration* object for the Widget, or Array of Widget instances to be added as a child.* The configuration object for the child can include a <code>childType</code>* property, which is either a constructor function or a string which names* a constructor function on the Y instance (e.g. "Tab" would refer to Y.Tab).* <code>childType</code> used to be named <code>type</code>, support for* which has been deprecated, but is still maintained for backward compatibility.* <code>childType</code> takes precedence over <code>type</code> if both are defined.* @param index {Number} Number representing the position at which the child* should be inserted.* @description Adds a Widget as a child. If the specified Widget already* has a parent it will be removed from its current parent before* being added as a child.* @return {ArrayList} Y.ArrayList containing the successfully added* Widget instance(s). If no children where added, will return an empty* Y.ArrayList instance.*/add: function () {var added = this._add.apply(this, arguments),children = added ? (Lang.isArray(added) ? added : [added]) : [];return (new Y.ArrayList(children));},/*** @method remove* @param index {Number} (Optional.) Number representing the index of the* child to be removed.* @description Removes the Widget from its parent. Optionally, can remove* a child by specifying its index.* @return {Widget} Widget instance that was successfully removed, otherwise* undefined.*/remove: function (index) {var child = this._items[index],returnVal;if (child && this.fire("removeChild", { child: child, index: index })) {returnVal = child;}return returnVal;},/*** @method removeAll* @description Removes all of the children from the Widget.* @return {ArrayList} Y.ArrayList instance containing Widgets that were* successfully removed. If no children where removed, will return an empty* Y.ArrayList instance.*/removeAll: function () {var removed = [],child;Y.each(this._items.concat(), function () {child = this.remove(0);if (child) {removed.push(child);}}, this);return (new Y.ArrayList(removed));},/*** Selects the child at the given index (zero-based).** @method selectChild* @param {Number} i the index of the child to be selected*/selectChild: function(i) {this.item(i).set('selected', 1);},/*** Selects all children.** @method selectAll*/selectAll: function () {this.set("selected", 1);},/*** Deselects all children.** @method deselectAll*/deselectAll: function () {this.set("selected", 0);},/*** Updates the UI in response to a child being added.** @method _uiAddChild* @protected* @param child {Widget} The child Widget instance to render.* @param parentNode {Object} The Node under which the* child Widget is to be rendered.*/_uiAddChild: function (child, parentNode) {child.render(parentNode);// TODO: Ideally this should be in Child's render UI.var childBB = child.get("boundingBox"),siblingBB,nextSibling = child.next(false),prevSibling;// Insert or Append to last child.// Avoiding index, and using the current sibling// state (which should be accurate), means we don't have// to worry about decorator elements which may be added// to the _childContainer node.if (nextSibling && nextSibling.get(RENDERED)) {siblingBB = nextSibling.get(BOUNDING_BOX);siblingBB.insert(childBB, "before");} else {prevSibling = child.previous(false);if (prevSibling && prevSibling.get(RENDERED)) {siblingBB = prevSibling.get(BOUNDING_BOX);siblingBB.insert(childBB, "after");} else if (!parentNode.contains(childBB)) {// Based on pull request from andreas-karlsson// https://github.com/yui/yui3/pull/25#issuecomment-2103536// Account for case where a child was rendered independently of the// parent-child framework, to a node outside of the parentNode,// and there are no siblings.parentNode.appendChild(childBB);}}},/*** Updates the UI in response to a child being removed.** @method _uiRemoveChild* @protected* @param child {Widget} The child Widget instance to render.*/_uiRemoveChild: function (child) {child.get("boundingBox").remove();},_afterAddChild: function (event) {var child = event.child;if (child.get("parent") == this) {this._uiAddChild(child, this._childrenContainer);}},_afterRemoveChild: function (event) {var child = event.child;if (child._oldParent == this) {this._uiRemoveChild(child);}},/*** Sets up DOM and CustomEvent listeners for the parent widget.* <p>* This method in invoked after bindUI is invoked for the Widget class* using YUI's aop infrastructure.* </p>** @method _bindUIParent* @protected*/_bindUIParent: function () {this.after("addChild", this._afterAddChild);this.after("removeChild", this._afterRemoveChild);},/*** Renders all child Widgets for the parent.* <p>* This method in invoked after renderUI is invoked for the Widget class* using YUI's aop infrastructure.* </p>* @method _renderChildren* @protected*/_renderChildren: function () {/*** <p>By default WidgetParent will render it's children to the parent's content box.</p>** <p>If the children need to be rendered somewhere else, the _childrenContainer property* can be set to the Node which the children should be rendered to. This property should be* set before the _renderChildren method is invoked, ideally in your renderUI method,* as soon as you create the element to be rendered to.</p>** @protected* @property _childrenContainer* @value The content box* @type Node*/var renderTo = this._childrenContainer || this.get("contentBox");this._childrenContainer = renderTo;this.each(function (child) {child.render(renderTo);});},/*** Destroys all child Widgets for the parent.* <p>* This method is invoked before the destructor is invoked for the Widget* class using YUI's aop infrastructure.* </p>* @method _destroyChildren* @protected*/_destroyChildren: function () {// Detach the handler responsible for removing children in// response to destroying them since:// 1) It is unnecessary/inefficient at this point since we are doing// a batch destroy of all children.// 2) Removing each child will affect our ability to iterate the// children since the size of _items will be changing as we// iterate.this._hDestroyChild.detach();// Need to clone the _items array sincethis.each(function (child) {child.destroy();});}};Y.augment(Parent, Y.ArrayList);Y.WidgetParent = Parent;}, '3.18.1', {"requires": ["arraylist", "base-build", "widget"]});