Version 3.17.2
Show:

File: widget-child/js/Widget-Child.js

/**
 * Extension enabling a Widget to be a child of another Widget.
 *
 * @module widget-child
 */

var Lang = Y.Lang;

/**
 * Widget extension providing functionality enabling a Widget to be a
 * child of another Widget.
 *
 * @class WidgetChild
 * @param {Object} config User configuration object.
*/
function Child() {

    //  Widget method overlap
    Y.after(this._syncUIChild, this, "syncUI");
    Y.after(this._bindUIChild, this, "bindUI");

}

Child.ATTRS = {

    /**
     * @attribute selected
     * @type Number
     * @default 0
     *
     * @description Number indicating if the Widget is selected.  Possible
     * values are:
     * <dl>
     * <dt>0</dt> <dd>(Default) Not selected</dd>
     * <dt>1</dt> <dd>Fully selected</dd>
     * <dt>2</dt> <dd>Partially selected</dd>
     * </dl>
    */
    selected: {
        value: 0,
        validator: Lang.isNumber
    },


    /**
     * @attribute index
     * @type Number
     * @readOnly
     *
     * @description Number representing the Widget's ordinal position in its
     * parent Widget.
     */
    index: {
        readOnly: true,
        getter: function () {

            var parent = this.get("parent"),
                index = -1;

            if (parent) {
                index = parent.indexOf(this);
            }

            return index;

        }
    },


    /**
     * @attribute parent
     * @type Widget
     * @readOnly
     *
     * @description Retrieves the parent of the Widget in the object hierarchy.
    */
    parent: {
        readOnly: true
    },


    /**
     * @attribute depth
     * @type Number
     * @default -1
     * @readOnly
     *
     * @description Number representing the depth of this Widget relative to
     * the root Widget in the object heirarchy.
     */
    depth: {
        readOnly: true,
        getter: function () {

            var parent = this.get("parent"),
                root = this.get("root"),
                depth = -1;

            while (parent) {

                depth = (depth + 1);

                if (parent == root) {
                    break;
                }

                parent = parent.get("parent");

            }

            return depth;

        }
    },

    /**
     * @attribute root
     * @type Widget
     * @readOnly
     *
     * @description Returns the root Widget in the object hierarchy.  If the
     * ROOT_TYPE property is set, the search for the root Widget will be
     * constrained to parent Widgets of the specified type.
     */
    root: {
        readOnly: true,
        getter: function () {

            var getParent = function (child) {

                var parent = child.get("parent"),
                    FnRootType = child.ROOT_TYPE,
                    criteria = parent;

                if (FnRootType) {
                    criteria = (parent && Y.instanceOf(parent, FnRootType));
                }

                return (criteria ? getParent(parent) : child);

            };

            return getParent(this);

        }
    }

};

Child.prototype = {

    /**
     * Constructor reference used to determine the root of a Widget-based
     * object tree.
     * <p>
     * Currently used to control the behavior of the <code>root</code>
     * attribute so that recursing up the object heirarchy can be constrained
     * to a specific type of Widget.  Widget authors should set this property
     * to the constructor function for a given Widget implementation.
     * </p>
     *
     * @property ROOT_TYPE
     * @type Object
     */
    ROOT_TYPE: null,

    /**
     * Returns the node on which to bind delegate listeners.
     *
     * Override of Widget's implementation of _getUIEventNode() to ensure that
     * all event listeners are bound to the Widget's topmost DOM element.
     * This ensures that the firing of each type of Widget UI event (click,
     * mousedown, etc.) is facilitated by a single, top-level, delegated DOM
     * event listener.
     *
     * @method _getUIEventNode
     * @for Widget
     * @protected
     */
    _getUIEventNode: function () {
        var root = this.get("root"),
            returnVal;

        if (root) {
            returnVal = root.get("boundingBox");
        }

        return returnVal;
    },

    /**
    * @method next
    * @description Returns the Widget's next sibling.
    * @param {Boolean} circular Boolean indicating if the parent's first child
    * should be returned if the child has no next sibling.
    * @return {Widget} Widget instance.
    */
    next: function (circular) {

        var parent = this.get("parent"),
            sibling;

        if (parent) {
            sibling = parent.item((this.get("index")+1));
        }

        if (!sibling && circular) {
            sibling = parent.item(0);
        }

        return sibling;

    },


    /**
    * @method previous
    * @description Returns the Widget's previous sibling.
    * @param {Boolean} circular Boolean indicating if the parent's last child
    * should be returned if the child has no previous sibling.
    * @return {Widget} Widget instance.
    */
    previous: function (circular) {

        var parent = this.get("parent"),
            index = this.get("index"),
            sibling;

        if (parent && index > 0) {
            sibling = parent.item([(index-1)]);
        }

        if (!sibling && circular) {
            sibling = parent.item((parent.size() - 1));
        }

        return sibling;

    },


    //  Override of Y.WidgetParent.remove()
    //  Sugar implementation allowing a child to remove itself from its parent.
    remove: function (index) {

        var parent,
            removed;

        if (Lang.isNumber(index)) {
            removed = Y.WidgetParent.prototype.remove.apply(this, arguments);
        }
        else {

            parent = this.get("parent");

            if (parent) {
                removed = parent.remove(this.get("index"));
            }

        }

        return removed;

    },


    /**
    * @method isRoot
    * @description Determines if the Widget is the root Widget in the
    * object hierarchy.
    * @return {Boolean} Boolean indicating if Widget is the root Widget in the
    * object hierarchy.
    */
    isRoot: function () {
        return (this == this.get("root"));
    },


    /**
    * @method ancestor
    * @description Returns the Widget instance at the specified depth.
    * @param {number} depth Number representing the depth of the ancestor.
    * @return {Widget} Widget instance.
    */
    ancestor: function (depth) {

        var root = this.get("root"),
            parent;

        if (this.get("depth") > depth)  {

            parent = this.get("parent");

            while (parent != root && parent.get("depth") > depth) {
                parent = parent.get("parent");
            }

        }

        return parent;

    },


    /**
     * Updates the UI to reflect the <code>selected</code> attribute value.
     *
     * @method _uiSetChildSelected
     * @protected
     * @param {number} selected The selected value to be reflected in the UI.
     */
    _uiSetChildSelected: function (selected) {

        var box = this.get("boundingBox"),
            sClassName = this.getClassName("selected");

        if (selected === 0) {
            box.removeClass(sClassName);
        }
        else {
            box.addClass(sClassName);
        }

    },


    /**
     * Default attribute change listener for the <code>selected</code>
     * attribute, responsible for updating the UI, in response to
     * attribute changes.
     *
     * @method _afterChildSelectedChange
     * @protected
     * @param {EventFacade} event The event facade for the attribute change.
     */
    _afterChildSelectedChange: function (event) {
        this._uiSetChildSelected(event.newVal);
    },


    /**
     * Synchronizes the UI to match the WidgetChild state.
     * <p>
     * This method is invoked after bindUI is invoked for the Widget class
     * using YUI's aop infrastructure.
     * </p>
     *
     * @method _syncUIChild
     * @protected
     */
    _syncUIChild: function () {
        this._uiSetChildSelected(this.get("selected"));
    },


    /**
     * Binds event listeners responsible for updating the UI state in response
     * to WidgetChild related state changes.
     * <p>
     * This method is invoked after bindUI is invoked for the Widget class
     * using YUI's aop infrastructure.
     * </p>
     * @method _bindUIChild
     * @protected
     */
    _bindUIChild: function () {
        this.after("selectedChange", this._afterChildSelectedChange);
    }

};

Y.WidgetChild = Child;