# Yahoo! UI Library

### Container 2.8.0r4

Yahoo! UI Library > container > Config.js (source view)
Search:
Filters Show Private Show Protected Show Deprecated

    (function () {

        /**
        * Config is a utility used within an Object to allow the implementer to
        * maintain a list of local configuration properties and listen for changes
        * to those properties dynamically using CustomEvent. The initial values are
        * also maintained so that the configuration can be reset at any given point
        * to its initial state.
        * @namespace YAHOO.util
        * @class Config
        * @constructor
        * @param {Object} owner The owner Object to which this Config Object belongs
        */
        YAHOO.util.Config = function (owner) {

            if (owner) {
                this.init(owner);
            }

            if (!owner) {  YAHOO.log("No owner specified for Config object", "error", "Config"); }

        };

        var Lang = YAHOO.lang,
            CustomEvent = YAHOO.util.CustomEvent,
            Config = YAHOO.util.Config;

        /**
         * Constant representing the CustomEvent type for the config changed event.
         * @property YAHOO.util.Config.CONFIG_CHANGED_EVENT
         * @private
         * @static
         * @final
         */
        Config.CONFIG_CHANGED_EVENT = "configChanged";

        /**
         * Constant representing the boolean type string
         * @property YAHOO.util.Config.BOOLEAN_TYPE
         * @private
         * @static
         * @final
         */
        Config.BOOLEAN_TYPE = "boolean";

        Config.prototype = {

            /**
            * Object reference to the owner of this Config Object
            * @property owner
            * @type Object
            */
            owner: null,

            /**
            * Boolean flag that specifies whether a queue is currently
            * being executed
            * @property queueInProgress
            * @type Boolean
            */
            queueInProgress: false,

            /**
            * Maintains the local collection of configuration property objects and
            * their specified values
            * @property config
            * @private
            * @type Object
            */
            config: null,

            /**
            * Maintains the local collection of configuration property objects as
            * they were initially applied.
            * This object is used when resetting a property.
            * @property initialConfig
            * @private
            * @type Object
            */
            initialConfig: null,

            /**
            * Maintains the local, normalized CustomEvent queue
            * @property eventQueue
            * @private
            * @type Object
            */
            eventQueue: null,

            /**
            * Custom Event, notifying subscribers when Config properties are set
            * (setProperty is called without the silent flag
            * @event configChangedEvent
            */
            configChangedEvent: null,

            /**
            * Initializes the configuration Object and all of its local members.
            * @method init
            * @param {Object} owner The owner Object to which this Config
            * Object belongs
            */
            init: function (owner) {

                this.owner = owner;

                this.configChangedEvent =
                    this.createEvent(Config.CONFIG_CHANGED_EVENT);

                this.configChangedEvent.signature = CustomEvent.LIST;
                this.queueInProgress = false;
                this.config = {};
                this.initialConfig = {};
                this.eventQueue = [];

            },

            /**
            * Validates that the value passed in is a Boolean.
            * @method checkBoolean
            * @param {Object} val The value to validate
            * @return {Boolean} true, if the value is valid
            */
            checkBoolean: function (val) {
                return (typeof val == Config.BOOLEAN_TYPE);
            },

            /**
            * Validates that the value passed in is a number.
            * @method checkNumber
            * @param {Object} val The value to validate
            * @return {Boolean} true, if the value is valid
            */
            checkNumber: function (val) {
                return (!isNaN(val));
            },

            /**
            * Fires a configuration property event using the specified value.
            * @method fireEvent
            * @private
            * @param {String} key The configuration property's name
            * @param {value} Object The value of the correct type for the property
            */
            fireEvent: function ( key, value ) {
                YAHOO.log("Firing Config event: " + key + "=" + value, "info", "Config");
                var property = this.config[key];

                if (property && property.event) {
                    property.event.fire(value);
                }
            },

            /**
            * Adds a property to the Config Object's private config hash.
            * @method addProperty
            * @param {String} key The configuration property's name
            * @param {Object} propertyObject The Object containing all of this
            * property's arguments
            */
            addProperty: function ( key, propertyObject ) {
                key = key.toLowerCase();
                YAHOO.log("Added property: " + key, "info", "Config");

                this.config[key] = propertyObject;

                propertyObject.event = this.createEvent(key, { scope: this.owner });
                propertyObject.event.signature = CustomEvent.LIST;

                propertyObject.key = key;

                if (propertyObject.handler) {
                    propertyObject.event.subscribe(propertyObject.handler,
                        this.owner);
                }

                this.setProperty(key, propertyObject.value, true);

                if (! propertyObject.suppressEvent) {
                    this.queueProperty(key, propertyObject.value);
                }

            },

            /**
            * Returns a key-value configuration map of the values currently set in
            * the Config Object.
            * @method getConfig
            * @return {Object} The current config, represented in a key-value map
            */
            getConfig: function () {

                var cfg = {},
                    currCfg = this.config,
                    prop,
                    property;

                for (prop in currCfg) {
                    if (Lang.hasOwnProperty(currCfg, prop)) {
                        property = currCfg[prop];
                        if (property && property.event) {
                            cfg[prop] = property.value;
                        }
                    }
                }

                return cfg;
            },

            /**
            * Returns the value of specified property.
            * @method getProperty
            * @param {String} key The name of the property
            * @return {Object}  The value of the specified property
            */
            getProperty: function (key) {
                var property = this.config[key.toLowerCase()];
                if (property && property.event) {
                    return property.value;
                } else {
                    return undefined;
                }
            },

            /**
            * Resets the specified property's value to its initial value.
            * @method resetProperty
            * @param {String} key The name of the property
            * @return {Boolean} True is the property was reset, false if not
            */
            resetProperty: function (key) {

                key = key.toLowerCase();

                var property = this.config[key];

                if (property && property.event) {

                    if (this.initialConfig[key] &&
                        !Lang.isUndefined(this.initialConfig[key])) {

                        this.setProperty(key, this.initialConfig[key]);

                        return true;

                    }

                } else {

                    return false;
                }

            },

            /**
            * Sets the value of a property. If the silent property is passed as
            * true, the property's event will not be fired.
            * @method setProperty
            * @param {String} key The name of the property
            * @param {String} value The value to set the property to
            * @param {Boolean} silent Whether the value should be set silently,
            * without firing the property event.
            * @return {Boolean} True, if the set was successful, false if it failed.
            */
            setProperty: function (key, value, silent) {

                var property;

                key = key.toLowerCase();
                YAHOO.log("setProperty: " + key + "=" + value, "info", "Config");

                if (this.queueInProgress && ! silent) {
                    // Currently running through a queue...
                    this.queueProperty(key,value);
                    return true;

                } else {
                    property = this.config[key];
                    if (property && property.event) {
                        if (property.validator && !property.validator(value)) {
                            return false;
                        } else {
                            property.value = value;
                            if (! silent) {
                                this.fireEvent(key, value);
                                this.configChangedEvent.fire([key, value]);
                            }
                            return true;
                        }
                    } else {
                        return false;
                    }
                }
            },

            /**
            * Sets the value of a property and queues its event to execute. If the
            * event is already scheduled to execute, it is
            * moved from its current position to the end of the queue.
            * @method queueProperty
            * @param {String} key The name of the property
            * @param {String} value The value to set the property to
            * @return {Boolean}  true, if the set was successful, false if
            * it failed.
            */
            queueProperty: function (key, value) {

                key = key.toLowerCase();
                YAHOO.log("queueProperty: " + key + "=" + value, "info", "Config");

                var property = this.config[key],
                    foundDuplicate = false,
                    iLen,
                    queueItem,
                    queueItemKey,
                    queueItemValue,
                    sLen,
                    supercedesCheck,
                    qLen,
                    queueItemCheck,
                    queueItemCheckKey,
                    queueItemCheckValue,
                    i,
                    s,
                    q;

                if (property && property.event) {

                    if (!Lang.isUndefined(value) && property.validator &&
                        !property.validator(value)) { // validator
                        return false;
                    } else {

                        if (!Lang.isUndefined(value)) {
                            property.value = value;
                        } else {
                            value = property.value;
                        }

                        foundDuplicate = false;
                        iLen = this.eventQueue.length;

                        for (i = 0; i < iLen; i++) {
                            queueItem = this.eventQueue[i];

                            if (queueItem) {
                                queueItemKey = queueItem[0];
                                queueItemValue = queueItem[1];

                                if (queueItemKey == key) {

                                    /*
                                        found a dupe... push to end of queue, null
                                        current item, and break
                                    */

                                    this.eventQueue[i] = null;

                                    this.eventQueue.push(
                                        [key, (!Lang.isUndefined(value) ?
                                        value : queueItemValue)]);

                                    foundDuplicate = true;
                                    break;
                                }
                            }
                        }

                        // this is a refire, or a new property in the queue

                        if (! foundDuplicate && !Lang.isUndefined(value)) {
                            this.eventQueue.push([key, value]);
                        }
                    }

                    if (property.supercedes) {

                        sLen = property.supercedes.length;

                        for (s = 0; s < sLen; s++) {

                            supercedesCheck = property.supercedes[s];
                            qLen = this.eventQueue.length;

                            for (q = 0; q < qLen; q++) {
                                queueItemCheck = this.eventQueue[q];

                                if (queueItemCheck) {
                                    queueItemCheckKey = queueItemCheck[0];
                                    queueItemCheckValue = queueItemCheck[1];

                                    if (queueItemCheckKey ==
                                        supercedesCheck.toLowerCase() ) {

                                        this.eventQueue.push([queueItemCheckKey,
                                            queueItemCheckValue]);

                                        this.eventQueue[q] = null;
                                        break;

                                    }
                                }
                            }
                        }
                    }

                    YAHOO.log("Config event queue: " + this.outputEventQueue(), "info", "Config");

                    return true;
                } else {
                    return false;
                }
            },

            /**
            * Fires the event for a property using the property's current value.
            * @method refireEvent
            * @param {String} key The name of the property
            */
            refireEvent: function (key) {

                key = key.toLowerCase();

                var property = this.config[key];

                if (property && property.event &&

                    !Lang.isUndefined(property.value)) {

                    if (this.queueInProgress) {

                        this.queueProperty(key);

                    } else {

                        this.fireEvent(key, property.value);

                    }

                }
            },

            /**
            * Applies a key-value Object literal to the configuration, replacing
            * any existing values, and queueing the property events.
            * Although the values will be set, fireQueue() must be called for their
            * associated events to execute.
            * @method applyConfig
            * @param {Object} userConfig The configuration Object literal
            * @param {Boolean} init  When set to true, the initialConfig will
            * be set to the userConfig passed in, so that calling a reset will
            * reset the properties to the passed values.
            */
            applyConfig: function (userConfig, init) {

                var sKey,
                    oConfig;

                if (init) {
                    oConfig = {};
                    for (sKey in userConfig) {
                        if (Lang.hasOwnProperty(userConfig, sKey)) {
                            oConfig[sKey.toLowerCase()] = userConfig[sKey];
                        }
                    }
                    this.initialConfig = oConfig;
                }

                for (sKey in userConfig) {
                    if (Lang.hasOwnProperty(userConfig, sKey)) {
                        this.queueProperty(sKey, userConfig[sKey]);
                    }
                }
            },

            /**
            * Refires the events for all configuration properties using their
            * current values.
            * @method refresh
            */
            refresh: function () {

                var prop;

                for (prop in this.config) {
                    if (Lang.hasOwnProperty(this.config, prop)) {
                        this.refireEvent(prop);
                    }
                }
            },

            /**
            * Fires the normalized list of queued property change events
            * @method fireQueue
            */
            fireQueue: function () {

                var i,
                    queueItem,
                    key,
                    value,
                    property;

                this.queueInProgress = true;
                for (i = 0;i < this.eventQueue.length; i++) {
                    queueItem = this.eventQueue[i];
                    if (queueItem) {

                        key = queueItem[0];
                        value = queueItem[1];
                        property = this.config[key];

                        property.value = value;

                        // Clear out queue entry, to avoid it being
                        // re-added to the queue by any queueProperty/supercedes
                        // calls which are invoked during fireEvent
                        this.eventQueue[i] = null;

                        this.fireEvent(key,value);
                    }
                }

                this.queueInProgress = false;
                this.eventQueue = [];
            },

            /**
            * Subscribes an external handler to the change event for any
            * given property.
            * @method subscribeToConfigEvent
            * @param {String} key The property name
            * @param {Function} handler The handler function to use subscribe to
            * the property's event
            * @param {Object} obj The Object to use for scoping the event handler
            * (see CustomEvent documentation)
            * @param {Boolean} overrideContext Optional. If true, will override
            * "this" within the handler to map to the scope Object passed into the
            * method.
            * @return {Boolean} True, if the subscription was successful,
            * otherwise false.
            */
            subscribeToConfigEvent: function (key, handler, obj, overrideContext) {

                var property = this.config[key.toLowerCase()];

                if (property && property.event) {
                    if (!Config.alreadySubscribed(property.event, handler, obj)) {
                        property.event.subscribe(handler, obj, overrideContext);
                    }
                    return true;
                } else {
                    return false;
                }

            },

            /**
            * Unsubscribes an external handler from the change event for any
            * given property.
            * @method unsubscribeFromConfigEvent
            * @param {String} key The property name
            * @param {Function} handler The handler function to use subscribe to
            * the property's event
            * @param {Object} obj The Object to use for scoping the event
            * handler (see CustomEvent documentation)
            * @return {Boolean} True, if the unsubscription was successful,
            * otherwise false.
            */
            unsubscribeFromConfigEvent: function (key, handler, obj) {
                var property = this.config[key.toLowerCase()];
                if (property && property.event) {
                    return property.event.unsubscribe(handler, obj);
                } else {
                    return false;
                }
            },

            /**
            * Returns a string representation of the Config object
            * @method toString
            * @return {String} The Config object in string format.
            */
            toString: function () {
                var output = "Config";
                if (this.owner) {
                    output += " [" + this.owner.toString() + "]";
                }
                return output;
            },

            /**
            * Returns a string representation of the Config object's current
            * CustomEvent queue
            * @method outputEventQueue
            * @return {String} The string list of CustomEvents currently queued
            * for execution
            */
            outputEventQueue: function () {

                var output = "",
                    queueItem,
                    q,
                    nQueue = this.eventQueue.length;

                for (q = 0; q < nQueue; q++) {
                    queueItem = this.eventQueue[q];
                    if (queueItem) {
                        output += queueItem[0] + "=" + queueItem[1] + ", ";
                    }
                }
                return output;
            },

            /**
            * Sets all properties to null, unsubscribes all listeners from each
            * property's change event and all listeners from the configChangedEvent.
            * @method destroy
            */
            destroy: function () {

                var oConfig = this.config,
                    sProperty,
                    oProperty;

                for (sProperty in oConfig) {

                    if (Lang.hasOwnProperty(oConfig, sProperty)) {

                        oProperty = oConfig[sProperty];

                        oProperty.event.unsubscribeAll();
                        oProperty.event = null;

                    }

                }

                this.configChangedEvent.unsubscribeAll();

                this.configChangedEvent = null;
                this.owner = null;
                this.config = null;
                this.initialConfig = null;
                this.eventQueue = null;

            }

        };

        /**
        * Checks to determine if a particular function/Object pair are already
        * subscribed to the specified CustomEvent
        * @method YAHOO.util.Config.alreadySubscribed
        * @static
        * @param {YAHOO.util.CustomEvent} evt The CustomEvent for which to check
        * the subscriptions
        * @param {Function} fn The function to look for in the subscribers list
        * @param {Object} obj The execution scope Object for the subscription
        * @return {Boolean} true, if the function/Object pair is already subscribed
        * to the CustomEvent passed in
        */
        Config.alreadySubscribed = function (evt, fn, obj) {

            var nSubscribers = evt.subscribers.length,
                subsc,
                i;

            if (nSubscribers > 0) {
                i = nSubscribers - 1;
                do {
                    subsc = evt.subscribers[i];
                    if (subsc && subsc.obj == obj && subsc.fn == fn) {
                        return true;
                    }
                }
                while (i--);
            }

            return false;

        };

        YAHOO.lang.augmentProto(Config, YAHOO.util.EventProvider);

    }());

Copyright © 2009 Yahoo! Inc. All rights reserved.