Version 3.18.1

APIs

  • Begin typing in the search box above to see results.
Show:

File: widget-parent/js/Widget-Parent.js

 /**
 * 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 overlap
 Y.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 selected
 
 
 if (selection) {
 
 selectedVal = 2; // Assume partially selected, confirm otherwise
 
 
 if (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
 // recalculated
 
 child.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 setter
 FnConstructor = 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 bubbling
 child.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 since
 this.each(function (child) {
 child.destroy();
 });
 }
 
 };
 
 Y.augment(Parent, Y.ArrayList);
 
 Y.WidgetParent = Parent;
 
 

AltStyle によって変換されたページ (->オリジナル) /