Widget-Parent.js revision 0159bcf230ef375f776f086fbee832e538128737
57775f39a19dc392e5882d5217d5a9148688aa4aMatt Sweeney/**
57775f39a19dc392e5882d5217d5a9148688aa4aMatt Sweeney * Extension enabling a Widget to be a parent of another Widget.
69ce664b1e66d48ae3ae5be08488f38b4855fc8bSatyen Desai *
69ce664b1e66d48ae3ae5be08488f38b4855fc8bSatyen Desai * @module widget-parent
69ce664b1e66d48ae3ae5be08488f38b4855fc8bSatyen Desai */
69ce664b1e66d48ae3ae5be08488f38b4855fc8bSatyen Desai
69ce664b1e66d48ae3ae5be08488f38b4855fc8bSatyen Desaivar Lang = Y.Lang;
69ce664b1e66d48ae3ae5be08488f38b4855fc8bSatyen Desai
69ce664b1e66d48ae3ae5be08488f38b4855fc8bSatyen Desai/**
69ce664b1e66d48ae3ae5be08488f38b4855fc8bSatyen Desai * Widget extension providing functionality enabling a Widget to be a
69ce664b1e66d48ae3ae5be08488f38b4855fc8bSatyen Desai * parent of another Widget.
69ce664b1e66d48ae3ae5be08488f38b4855fc8bSatyen Desai *
69ce664b1e66d48ae3ae5be08488f38b4855fc8bSatyen Desai * @class WidgetParent
eaa9719567e2ec55d96f9298077f5fc0effe1ed3Satyen Desai * @param {Object} config User configuration object.
69ce664b1e66d48ae3ae5be08488f38b4855fc8bSatyen Desai */
69ce664b1e66d48ae3ae5be08488f38b4855fc8bSatyen Desaifunction Parent(config) {
86fa3fa071113dfe732100d1a15cb149f67ffc83Jenny Donnelly
86fa3fa071113dfe732100d1a15cb149f67ffc83Jenny Donnelly /**
86fa3fa071113dfe732100d1a15cb149f67ffc83Jenny Donnelly * Fires when a Widget is add as a child. The event object will have a
86fa3fa071113dfe732100d1a15cb149f67ffc83Jenny Donnelly * 'child' property that returns a reference to the child Widget, as well
86fa3fa071113dfe732100d1a15cb149f67ffc83Jenny Donnelly * as an 'index' property that returns a reference to the index specified
69ce664b1e66d48ae3ae5be08488f38b4855fc8bSatyen Desai * when the add() method was called.
69ce664b1e66d48ae3ae5be08488f38b4855fc8bSatyen Desai * <p>
69ce664b1e66d48ae3ae5be08488f38b4855fc8bSatyen Desai * Subscribers to the "on" moment of this event, will be notified
69ce664b1e66d48ae3ae5be08488f38b4855fc8bSatyen Desai * before a child is added.
69ce664b1e66d48ae3ae5be08488f38b4855fc8bSatyen Desai * </p>
10d8bafc5c24f3a4285cf6060a1935ba5cfc4b85Luke Smith * <p>
69ce664b1e66d48ae3ae5be08488f38b4855fc8bSatyen Desai * Subscribers to the "after" moment of this event, will be notified
69ce664b1e66d48ae3ae5be08488f38b4855fc8bSatyen Desai * after a child is added.
69ce664b1e66d48ae3ae5be08488f38b4855fc8bSatyen Desai * </p>
69ce664b1e66d48ae3ae5be08488f38b4855fc8bSatyen Desai *
57775f39a19dc392e5882d5217d5a9148688aa4aMatt Sweeney * @event childAdded
57775f39a19dc392e5882d5217d5a9148688aa4aMatt Sweeney * @preventable _defAddChildFn
69ce664b1e66d48ae3ae5be08488f38b4855fc8bSatyen Desai * @param {EventFacade} e The Event Facade
feaab31990f7893c59257f33cd9f708a6c0e7ddbSatyen Desai */
69ce664b1e66d48ae3ae5be08488f38b4855fc8bSatyen Desai this.publish("addChild", {
69ce664b1e66d48ae3ae5be08488f38b4855fc8bSatyen Desai defaultTargetOnly: true,
69ce664b1e66d48ae3ae5be08488f38b4855fc8bSatyen Desai defaultFn: this._defAddChildFn
57775f39a19dc392e5882d5217d5a9148688aa4aMatt Sweeney });
69ce664b1e66d48ae3ae5be08488f38b4855fc8bSatyen Desai
69ce664b1e66d48ae3ae5be08488f38b4855fc8bSatyen Desai
57775f39a19dc392e5882d5217d5a9148688aa4aMatt Sweeney /**
69ce664b1e66d48ae3ae5be08488f38b4855fc8bSatyen Desai * Fires when a child Widget is removed. The event object will have a
69ce664b1e66d48ae3ae5be08488f38b4855fc8bSatyen Desai * 'child' property that returns a reference to the child Widget, as well
69ce664b1e66d48ae3ae5be08488f38b4855fc8bSatyen Desai * as an 'index' property that returns a reference child's ordinal position.
69ce664b1e66d48ae3ae5be08488f38b4855fc8bSatyen Desai * <p>
69ce664b1e66d48ae3ae5be08488f38b4855fc8bSatyen Desai * Subscribers to the "on" moment of this event, will be notified
69ce664b1e66d48ae3ae5be08488f38b4855fc8bSatyen Desai * before a child is removed.
10d8bafc5c24f3a4285cf6060a1935ba5cfc4b85Luke Smith * </p>
eaa9719567e2ec55d96f9298077f5fc0effe1ed3Satyen Desai * <p>
69ce664b1e66d48ae3ae5be08488f38b4855fc8bSatyen Desai * Subscribers to the "after" moment of this event, will be notified
69ce664b1e66d48ae3ae5be08488f38b4855fc8bSatyen Desai * after a child is removed.
69ce664b1e66d48ae3ae5be08488f38b4855fc8bSatyen Desai * </p>
69ce664b1e66d48ae3ae5be08488f38b4855fc8bSatyen Desai *
69ce664b1e66d48ae3ae5be08488f38b4855fc8bSatyen Desai * @event childRemoved
69ce664b1e66d48ae3ae5be08488f38b4855fc8bSatyen Desai * @preventable _defRemoveChildFn
69ce664b1e66d48ae3ae5be08488f38b4855fc8bSatyen Desai * @param {EventFacade} e The Event Facade
69ce664b1e66d48ae3ae5be08488f38b4855fc8bSatyen Desai */
69ce664b1e66d48ae3ae5be08488f38b4855fc8bSatyen Desai this.publish("removeChild", {
10d8bafc5c24f3a4285cf6060a1935ba5cfc4b85Luke Smith defaultTargetOnly: true,
eaa9719567e2ec55d96f9298077f5fc0effe1ed3Satyen Desai defaultFn: this._defRemoveChildFn
69ce664b1e66d48ae3ae5be08488f38b4855fc8bSatyen Desai });
69ce664b1e66d48ae3ae5be08488f38b4855fc8bSatyen Desai
69ce664b1e66d48ae3ae5be08488f38b4855fc8bSatyen Desai
69ce664b1e66d48ae3ae5be08488f38b4855fc8bSatyen Desai // TO DO: Document ability to populate children via the constructor
69ce664b1e66d48ae3ae5be08488f38b4855fc8bSatyen Desai
57775f39a19dc392e5882d5217d5a9148688aa4aMatt Sweeney this._items = [];
69ce664b1e66d48ae3ae5be08488f38b4855fc8bSatyen Desai
69ce664b1e66d48ae3ae5be08488f38b4855fc8bSatyen Desai var children,
69ce664b1e66d48ae3ae5be08488f38b4855fc8bSatyen Desai handle;
69ce664b1e66d48ae3ae5be08488f38b4855fc8bSatyen Desai
69ce664b1e66d48ae3ae5be08488f38b4855fc8bSatyen Desai if (config && config.children) {
69ce664b1e66d48ae3ae5be08488f38b4855fc8bSatyen Desai
69ce664b1e66d48ae3ae5be08488f38b4855fc8bSatyen Desai children = config.children;
69ce664b1e66d48ae3ae5be08488f38b4855fc8bSatyen Desai
69ce664b1e66d48ae3ae5be08488f38b4855fc8bSatyen Desai handle = this.after("initializedChange", function (e) {
69ce664b1e66d48ae3ae5be08488f38b4855fc8bSatyen Desai this._add(children);
1233f432158f66c7692b2d4a9ad206d6ca27e4b6Matt Sweeney handle.detach();
57775f39a19dc392e5882d5217d5a9148688aa4aMatt Sweeney });
69ce664b1e66d48ae3ae5be08488f38b4855fc8bSatyen Desai
57775f39a19dc392e5882d5217d5a9148688aa4aMatt Sweeney }
69ce664b1e66d48ae3ae5be08488f38b4855fc8bSatyen Desai
eaa9719567e2ec55d96f9298077f5fc0effe1ed3Satyen Desai // Widget method overlap
57775f39a19dc392e5882d5217d5a9148688aa4aMatt Sweeney Y.after(this._renderChildren, this, "renderUI");
69ce664b1e66d48ae3ae5be08488f38b4855fc8bSatyen Desai Y.after(this._bindUIParent, this, "bindUI");
69ce664b1e66d48ae3ae5be08488f38b4855fc8bSatyen Desai Y.before(this._destroyChildren, this, "destructor");
75013e52e14f366e77d0a078bc1bfedfa421fe70Satyen Desai
69ce664b1e66d48ae3ae5be08488f38b4855fc8bSatyen Desai this.after("selectionChange", this._afterSelectionChange);
69ce664b1e66d48ae3ae5be08488f38b4855fc8bSatyen Desai this.after("selectedChange", this._afterParentSelectedChange);
57775f39a19dc392e5882d5217d5a9148688aa4aMatt Sweeney this.after("activeDescendantChange", this._afterActiveDescendantChange);
57775f39a19dc392e5882d5217d5a9148688aa4aMatt Sweeney
69ce664b1e66d48ae3ae5be08488f38b4855fc8bSatyen Desai this._hDestroyChild = this.after("*:destroy", this._afterDestroyChild);
69ce664b1e66d48ae3ae5be08488f38b4855fc8bSatyen Desai this.after("*:focusedChange", this._updateActiveDescendant);
69ce664b1e66d48ae3ae5be08488f38b4855fc8bSatyen Desai
57775f39a19dc392e5882d5217d5a9148688aa4aMatt Sweeney}
69ce664b1e66d48ae3ae5be08488f38b4855fc8bSatyen Desai
57775f39a19dc392e5882d5217d5a9148688aa4aMatt SweeneyParent.ATTRS = {
69ce664b1e66d48ae3ae5be08488f38b4855fc8bSatyen Desai
69ce664b1e66d48ae3ae5be08488f38b4855fc8bSatyen Desai /**
69ce664b1e66d48ae3ae5be08488f38b4855fc8bSatyen Desai * @attribute defaultChildType
69ce664b1e66d48ae3ae5be08488f38b4855fc8bSatyen Desai * @type {String|Object}
69ce664b1e66d48ae3ae5be08488f38b4855fc8bSatyen Desai *
57775f39a19dc392e5882d5217d5a9148688aa4aMatt Sweeney * @description String representing the default type of the children
69ce664b1e66d48ae3ae5be08488f38b4855fc8bSatyen Desai * managed by this Widget. Can also supply default type as a constructor
69ce664b1e66d48ae3ae5be08488f38b4855fc8bSatyen Desai * reference.
57775f39a19dc392e5882d5217d5a9148688aa4aMatt Sweeney */
69ce664b1e66d48ae3ae5be08488f38b4855fc8bSatyen Desai defaultChildType: {
69ce664b1e66d48ae3ae5be08488f38b4855fc8bSatyen Desai setter: function (val) {
69ce664b1e66d48ae3ae5be08488f38b4855fc8bSatyen Desai
69ce664b1e66d48ae3ae5be08488f38b4855fc8bSatyen Desai var returnVal = Y.Attribute.INVALID_VALUE,
69ce664b1e66d48ae3ae5be08488f38b4855fc8bSatyen Desai FnConstructor = Lang.isString(val) ? Y[val] : val;
69ce664b1e66d48ae3ae5be08488f38b4855fc8bSatyen Desai
75013e52e14f366e77d0a078bc1bfedfa421fe70Satyen Desai if (Lang.isFunction(FnConstructor)) {
69ce664b1e66d48ae3ae5be08488f38b4855fc8bSatyen Desai returnVal = FnConstructor;
69ce664b1e66d48ae3ae5be08488f38b4855fc8bSatyen Desai }
982119a5f53b6531610b5d29c631550a6222da1fSatyen Desai
69ce664b1e66d48ae3ae5be08488f38b4855fc8bSatyen Desai return returnVal;
75013e52e14f366e77d0a078bc1bfedfa421fe70Satyen Desai }
75013e52e14f366e77d0a078bc1bfedfa421fe70Satyen Desai },
57775f39a19dc392e5882d5217d5a9148688aa4aMatt Sweeney
75013e52e14f366e77d0a078bc1bfedfa421fe70Satyen Desai /**
75013e52e14f366e77d0a078bc1bfedfa421fe70Satyen Desai * @attribute activeDescendant
69ce664b1e66d48ae3ae5be08488f38b4855fc8bSatyen Desai * @type Widget
75013e52e14f366e77d0a078bc1bfedfa421fe70Satyen Desai * @readOnly
57775f39a19dc392e5882d5217d5a9148688aa4aMatt Sweeney *
57775f39a19dc392e5882d5217d5a9148688aa4aMatt Sweeney * @description Returns the Widget's currently focused descendant Widget.
69ce664b1e66d48ae3ae5be08488f38b4855fc8bSatyen Desai */
69ce664b1e66d48ae3ae5be08488f38b4855fc8bSatyen Desai activeDescendant: {
69ce664b1e66d48ae3ae5be08488f38b4855fc8bSatyen Desai readOnly: true
69ce664b1e66d48ae3ae5be08488f38b4855fc8bSatyen Desai },
69ce664b1e66d48ae3ae5be08488f38b4855fc8bSatyen Desai
69ce664b1e66d48ae3ae5be08488f38b4855fc8bSatyen Desai /**
69ce664b1e66d48ae3ae5be08488f38b4855fc8bSatyen Desai * @attribute multiple
69ce664b1e66d48ae3ae5be08488f38b4855fc8bSatyen Desai * @type Boolean
69ce664b1e66d48ae3ae5be08488f38b4855fc8bSatyen Desai * @default false
75013e52e14f366e77d0a078bc1bfedfa421fe70Satyen Desai * @writeOnce
69ce664b1e66d48ae3ae5be08488f38b4855fc8bSatyen Desai *
69ce664b1e66d48ae3ae5be08488f38b4855fc8bSatyen Desai * @description Boolean indicating if multiple children can be selected at
75013e52e14f366e77d0a078bc1bfedfa421fe70Satyen Desai * once. Whether or not multiple selection is enabled is always delegated
69ce664b1e66d48ae3ae5be08488f38b4855fc8bSatyen Desai * to the value of the <code>multiple</code> attribute of the root widget
75013e52e14f366e77d0a078bc1bfedfa421fe70Satyen Desai * in the object hierarchy.
75013e52e14f366e77d0a078bc1bfedfa421fe70Satyen Desai */
69ce664b1e66d48ae3ae5be08488f38b4855fc8bSatyen Desai multiple: {
75013e52e14f366e77d0a078bc1bfedfa421fe70Satyen Desai value: false,
75013e52e14f366e77d0a078bc1bfedfa421fe70Satyen Desai validator: Lang.isBoolean,
69ce664b1e66d48ae3ae5be08488f38b4855fc8bSatyen Desai writeOnce: true,
75013e52e14f366e77d0a078bc1bfedfa421fe70Satyen Desai getter: function (value) {
69ce664b1e66d48ae3ae5be08488f38b4855fc8bSatyen Desai var root = this.get("root");
69ce664b1e66d48ae3ae5be08488f38b4855fc8bSatyen Desai return (root && root != this) ? root.get("multiple") : value;
75013e52e14f366e77d0a078bc1bfedfa421fe70Satyen Desai }
75013e52e14f366e77d0a078bc1bfedfa421fe70Satyen Desai },
75013e52e14f366e77d0a078bc1bfedfa421fe70Satyen Desai
75013e52e14f366e77d0a078bc1bfedfa421fe70Satyen Desai
75013e52e14f366e77d0a078bc1bfedfa421fe70Satyen Desai /**
75013e52e14f366e77d0a078bc1bfedfa421fe70Satyen Desai * @attribute selection
75013e52e14f366e77d0a078bc1bfedfa421fe70Satyen Desai * @type {Y.ArrayList|Widget}
75013e52e14f366e77d0a078bc1bfedfa421fe70Satyen Desai * @readOnly
75013e52e14f366e77d0a078bc1bfedfa421fe70Satyen Desai *
75013e52e14f366e77d0a078bc1bfedfa421fe70Satyen Desai * @description Returns the currently selected child Widget. If the
75013e52e14f366e77d0a078bc1bfedfa421fe70Satyen Desai * <code>mulitple</code> attribte is set to <code>true</code> will
75013e52e14f366e77d0a078bc1bfedfa421fe70Satyen Desai * return an Y.ArrayList instance containing the currently selected
75013e52e14f366e77d0a078bc1bfedfa421fe70Satyen Desai * children. If no children are selected, will return null.
75013e52e14f366e77d0a078bc1bfedfa421fe70Satyen Desai */
75013e52e14f366e77d0a078bc1bfedfa421fe70Satyen Desai selection: {
14039ac20172671b6c453a0b6571795429fbe3b3Satyen Desai readOnly: true,
75013e52e14f366e77d0a078bc1bfedfa421fe70Satyen Desai setter: "_setSelection",
75013e52e14f366e77d0a078bc1bfedfa421fe70Satyen Desai getter: function (value) {
75013e52e14f366e77d0a078bc1bfedfa421fe70Satyen Desai var selection = Lang.isArray(value) ?
75013e52e14f366e77d0a078bc1bfedfa421fe70Satyen Desai (new Y.ArrayList(value)) : value;
75013e52e14f366e77d0a078bc1bfedfa421fe70Satyen Desai return selection;
75013e52e14f366e77d0a078bc1bfedfa421fe70Satyen Desai }
75013e52e14f366e77d0a078bc1bfedfa421fe70Satyen Desai },
75013e52e14f366e77d0a078bc1bfedfa421fe70Satyen Desai
75013e52e14f366e77d0a078bc1bfedfa421fe70Satyen Desai selected: {
75013e52e14f366e77d0a078bc1bfedfa421fe70Satyen Desai setter: function (value) {
75013e52e14f366e77d0a078bc1bfedfa421fe70Satyen Desai
75013e52e14f366e77d0a078bc1bfedfa421fe70Satyen Desai // Enforces selection behavior on for parent Widgets. Parent's
75013e52e14f366e77d0a078bc1bfedfa421fe70Satyen Desai // selected attribute can be set to the following:
75013e52e14f366e77d0a078bc1bfedfa421fe70Satyen Desai // 0 - Not selected
75013e52e14f366e77d0a078bc1bfedfa421fe70Satyen Desai // 1 - Fully selected (all children are selected). In order for
75013e52e14f366e77d0a078bc1bfedfa421fe70Satyen Desai // all children to be selected, multiple selection must be
75013e52e14f366e77d0a078bc1bfedfa421fe70Satyen Desai // enabled. Therefore, you cannot set the "selected" attribute
75013e52e14f366e77d0a078bc1bfedfa421fe70Satyen Desai // on a parent Widget to 1 unless multiple selection is enabled.
75013e52e14f366e77d0a078bc1bfedfa421fe70Satyen Desai // 2 - Partially selected, meaning one ore more (but not all)
75013e52e14f366e77d0a078bc1bfedfa421fe70Satyen Desai // children are selected.
75013e52e14f366e77d0a078bc1bfedfa421fe70Satyen Desai
75013e52e14f366e77d0a078bc1bfedfa421fe70Satyen Desai var returnVal = value;
75013e52e14f366e77d0a078bc1bfedfa421fe70Satyen Desai
75013e52e14f366e77d0a078bc1bfedfa421fe70Satyen Desai if (value === 1 && !this.get("multiple")) {
75013e52e14f366e77d0a078bc1bfedfa421fe70Satyen Desai Y.log('The selected attribute can only be set to 1 if the "multiple" attribute is set to true.', "error", "widget");
75013e52e14f366e77d0a078bc1bfedfa421fe70Satyen Desai returnVal = Y.Attribute.INVALID_VALUE;
75013e52e14f366e77d0a078bc1bfedfa421fe70Satyen Desai }
ca8fd8dbdf317ed1d8965afc7a2c7cc1ba9df034Luke Smith
75013e52e14f366e77d0a078bc1bfedfa421fe70Satyen Desai return returnVal;
75013e52e14f366e77d0a078bc1bfedfa421fe70Satyen Desai }
75013e52e14f366e77d0a078bc1bfedfa421fe70Satyen Desai }
75013e52e14f366e77d0a078bc1bfedfa421fe70Satyen Desai
75013e52e14f366e77d0a078bc1bfedfa421fe70Satyen Desai};
75013e52e14f366e77d0a078bc1bfedfa421fe70Satyen Desai
75013e52e14f366e77d0a078bc1bfedfa421fe70Satyen DesaiParent.prototype = {
75013e52e14f366e77d0a078bc1bfedfa421fe70Satyen Desai
75013e52e14f366e77d0a078bc1bfedfa421fe70Satyen Desai /**
75013e52e14f366e77d0a078bc1bfedfa421fe70Satyen Desai * Destroy event listener for each child Widget, responsible for removing
75013e52e14f366e77d0a078bc1bfedfa421fe70Satyen Desai * the destroyed child Widget from the parent's internal array of children
75013e52e14f366e77d0a078bc1bfedfa421fe70Satyen Desai * (_items property).
75013e52e14f366e77d0a078bc1bfedfa421fe70Satyen Desai *
75013e52e14f366e77d0a078bc1bfedfa421fe70Satyen Desai * @method _afterDestroyChild
75013e52e14f366e77d0a078bc1bfedfa421fe70Satyen Desai * @protected
75013e52e14f366e77d0a078bc1bfedfa421fe70Satyen Desai * @param {EventFacade} event The event facade for the attribute change.
ca8fd8dbdf317ed1d8965afc7a2c7cc1ba9df034Luke Smith */
75013e52e14f366e77d0a078bc1bfedfa421fe70Satyen Desai _afterDestroyChild: function (event) {
75013e52e14f366e77d0a078bc1bfedfa421fe70Satyen Desai var child = event.target;
75013e52e14f366e77d0a078bc1bfedfa421fe70Satyen Desai
75013e52e14f366e77d0a078bc1bfedfa421fe70Satyen Desai if (child.get("parent") == this) {
75013e52e14f366e77d0a078bc1bfedfa421fe70Satyen Desai child.remove();
75013e52e14f366e77d0a078bc1bfedfa421fe70Satyen Desai }
69ce664b1e66d48ae3ae5be08488f38b4855fc8bSatyen Desai },
69ce664b1e66d48ae3ae5be08488f38b4855fc8bSatyen Desai
69ce664b1e66d48ae3ae5be08488f38b4855fc8bSatyen Desai
69ce664b1e66d48ae3ae5be08488f38b4855fc8bSatyen Desai /**
69ce664b1e66d48ae3ae5be08488f38b4855fc8bSatyen Desai * Attribute change listener for the <code>selection</code>
69ce664b1e66d48ae3ae5be08488f38b4855fc8bSatyen Desai * attribute, responsible for setting the value of the
69ce664b1e66d48ae3ae5be08488f38b4855fc8bSatyen Desai * parent's <code>selected</code> attribute.
69ce664b1e66d48ae3ae5be08488f38b4855fc8bSatyen Desai *
69ce664b1e66d48ae3ae5be08488f38b4855fc8bSatyen Desai * @method _afterSelectionChange
69ce664b1e66d48ae3ae5be08488f38b4855fc8bSatyen Desai * @protected
57775f39a19dc392e5882d5217d5a9148688aa4aMatt Sweeney * @param {EventFacade} event The event facade for the attribute change.
57775f39a19dc392e5882d5217d5a9148688aa4aMatt Sweeney */
aca18adfbc860a76fb2e4bee2f2e1d3b89c52b1bSatyen Desai _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 ((selection instanceof 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>type</code> property of the configuration object
* literal.
*
* @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.type,
child,
Fn,
FnConstructor;
if (altType) {
Fn = Lang.isString(altType) ? Y[altType] : altType;
if (Lang.isFunction(Fn)) {
FnConstructor = Fn;
}
}
else if (defaultType) {
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 childAdded 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 childRemoved 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.set("focused", false);
}
if (child.get("selected")) {
child.set("selected", 0);
}
children.splice(index, 1);
child.removeTarget(this);
child._set("parent", null);
},
/**
* @method _add
* @protected
* @param child {Widget|Object} The Widget instance, or configuration
* object for the Widget to be added as a child.
* @param child {Array} Array of Widget instances, or configuration
* objects for the Widgets to be added as a children.
* @param index {Number} (Optional.) 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 (child instanceof 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} The Widget instance, or configuration
* object for the Widget to be added as a child.
* @param child {Array} Array of Widget instances, or configuration
* objects for the Widgets to be added as a children.
* @param index {Number} (Optional.) 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 {Y.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 {Y.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) {
siblingBB = nextSibling.get("boundingBox");
siblingBB.insert(childBB, "before");
} else {
prevSibling = child.previous(false);
if (prevSibling) {
siblingBB = prevSibling.get("boundingBox");
siblingBB.insert(childBB, "after");
}
}
},
/**
* 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.get("parent") == 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;