#

API Docs for: 3.10.3
Show: Inherited Protected Private Deprecated

# File: cache/js/cache-base.js

    /**
     * The Cache utility provides a common configurable interface for components to
     * cache and retrieve data from a local JavaScript struct.
     *
     * @module cache
     * @main
     */

    /**
     * Provides the base class for the YUI Cache utility.
     *
     * @submodule cache-base
     */
    var LANG = Y.Lang,
        isDate = Y.Lang.isDate,

    /**
     * Base class for the YUI Cache utility.
     * @class Cache
     * @extends Base
     * @constructor
     */
    Cache = function() {
        Cache.superclass.constructor.apply(this, arguments);
    };

        /////////////////////////////////////////////////////////////////////////////
        //
        // Cache static properties
        //
        /////////////////////////////////////////////////////////////////////////////
    Y.mix(Cache, {
        /**
         * Class name.
         *
         * @property NAME
         * @type String
         * @static
         * @final
         * @value "cache"
         */
        NAME: "cache",

        ATTRS: {
            /////////////////////////////////////////////////////////////////////////////
            //
            // Cache Attributes
            //
            /////////////////////////////////////////////////////////////////////////////

            /**
            * @attribute max
            * @description Maximum number of entries the Cache can hold.
            * Set to 0 to turn off caching.
            * @type Number
            * @default 0
            */
            max: {
                value: 0,
                setter: "_setMax"
            },

            /**
            * @attribute size
            * @description Number of entries currently cached.
            * @type Number
            */
            size: {
                readOnly: true,
                getter: "_getSize"
            },

            /**
            * @attribute uniqueKeys
            * @description Validate uniqueness of stored keys. Default is false and
            * is more performant.
            * @type Boolean
            */
            uniqueKeys: {
                value: false
            },

            /**
            * @attribute expires
            * @description Absolute Date when data expires or
            * relative number of milliseconds. Zero disables expiration.
            * @type Date | Number
            * @default 0
            */
            expires: {
                value: 0,
                validator: function(v) {
                    return Y.Lang.isDate(v) || (Y.Lang.isNumber(v) && v >= 0);
                }
            },

            /**
             * @attribute entries
             * @description Cached entries.
             * @type Array
             */
            entries: {
                readOnly: true,
                getter: "_getEntries"
            }
        }
    });

    Y.extend(Cache, Y.Base, {
        /////////////////////////////////////////////////////////////////////////////
        //
        // Cache private properties
        //
        /////////////////////////////////////////////////////////////////////////////

        /**
         * Array of request/response objects indexed chronologically.
         *
         * @property _entries
         * @type Object[]
         * @private
         */
        _entries: null,

        /////////////////////////////////////////////////////////////////////////////
        //
        // Cache private methods
        //
        /////////////////////////////////////////////////////////////////////////////

        /**
        * @method initializer
        * @description Internal init() handler.
        * @param config {Object} Config object.
        * @private
        */
        initializer: function(config) {

            /**
            * @event add
            * @description Fired when an entry is added.
            * @param e {Event.Facade} Event Facade with the following properties:
             * <dl>
             * <dt>entry (Object)</dt> <dd>The cached entry.</dd>
             * </dl>
            * @preventable _defAddFn
            */
            this.publish("add", {defaultFn: this._defAddFn});

            /**
            * @event flush
            * @description Fired when the cache is flushed.
            * @param e {Event.Facade} Event Facade object.
            * @preventable _defFlushFn
            */
            this.publish("flush", {defaultFn: this._defFlushFn});

            /**
            * @event request
            * @description Fired when an entry is requested from the cache.
            * @param e {Event.Facade} Event Facade with the following properties:
            * <dl>
            * <dt>request (Object)</dt> <dd>The request object.</dd>
            * </dl>
            */

            /**
            * @event retrieve
            * @description Fired when an entry is retrieved from the cache.
            * @param e {Event.Facade} Event Facade with the following properties:
            * <dl>
            * <dt>entry (Object)</dt> <dd>The retrieved entry.</dd>
            * </dl>
            */

            // Initialize internal values
            this._entries = [];
            Y.log("Cache initialized", "info", "cache");
        },

        /**
        * @method destructor
        * @description Internal destroy() handler.
        * @private
        */
        destructor: function() {
            this._entries = [];
            Y.log("Cache destroyed", "info", "cache");
        },

        /////////////////////////////////////////////////////////////////////////////
        //
        // Cache protected methods
        //
        /////////////////////////////////////////////////////////////////////////////

        /**
         * Sets max.
         *
         * @method _setMax
         * @protected
         */
        _setMax: function(value) {
            // If the cache is full, make room by removing stalest element (index=0)
            var entries = this._entries;
            if(value > 0) {
                if(entries) {
                    while(entries.length > value) {
                        entries.shift();
                    }
                }
            }
            else {
                value = 0;
                this._entries = [];
            }
            return value;
        },

        /**
         * Gets size.
         *
         * @method _getSize
         * @protected
         */
        _getSize: function() {
            return this._entries.length;
        },

        /**
         * Gets all entries.
         *
         * @method _getEntries
         * @protected
         */
        _getEntries: function() {
            return this._entries;
        },

        /**
         * Adds entry to cache.
         *
         * @method _defAddFn
         * @param e {Event.Facade} Event Facade with the following properties:
         * <dl>
         * <dt>entry (Object)</dt> <dd>The cached entry.</dd>
         * </dl>
         * @protected
         */
        _defAddFn: function(e) {
            var entries = this._entries,
                entry   = e.entry,
                max     = this.get("max"),
                pos;

            // If uniqueKeys is true and item exists with this key, then remove it.
            if (this.get("uniqueKeys")) {
                pos = this._position(e.entry.request);
                if (LANG.isValue(pos)) {
                    entries.splice(pos, 1);
                }
            }

            // If the cache at or over capacity, make room by removing stalest
            // element(s) starting at index-0.
            while (max && entries.length >= max) {
                entries.shift();
            }

            // Add entry to cache in the newest position, at the end of the array
            entries[entries.length] = entry;
            Y.log("Cached entry: " + Y.dump(entry), "info", "cache");
        },

        /**
         * Flushes cache.
         *
         * @method _defFlushFn
         * @param e {Event.Facade} Event Facade object.
         * @protected
         */
        _defFlushFn: function(e) {
            var entries = this._entries,
                details = e.details[0],
                pos;

            //passed an item, flush only that
            if(details && LANG.isValue(details.request)) {
                pos = this._position(details.request);

                if(LANG.isValue(pos)) {
                    entries.splice(pos,1);

                    Y.log("Flushed cache item " + Y.dump(details.request), "info", "cache");
                }
            }
            //no item, flush everything
            else {
                this._entries = [];
                Y.log("Cache flushed", "info", "cache");
            }
        },

        /**
         * Default overridable method compares current request with given cache entry.
         * Returns true if current request matches the cached request, otherwise
         * false. Implementers should override this method to customize the
         * cache-matching algorithm.
         *
         * @method _isMatch
         * @param request {Object} Request object.
         * @param entry {Object} Cached entry.
         * @return {Boolean} True if current request matches given cached request, false otherwise.
         * @protected
         */
        _isMatch: function(request, entry) {
            if(!entry.expires || new Date() < entry.expires) {
                return (request === entry.request);
            }
            return false;
        },

        /**
         * Returns position of a request in the entries array, otherwise null.
         *
         * @method _position
         * @param request {Object} Request object.
         * @return {Number} Array position if found, null otherwise.
         * @protected
         */
        _position: function(request) {
            // If cache is enabled...
            var entries = this._entries,
                length = entries.length,
                i = length-1;

            if((this.get("max") === null) || this.get("max") > 0) {
                // Loop through each cached entry starting from the newest
                for(; i >= 0; i--) {
                    // Execute matching function
                    if(this._isMatch(request, entries[i])) {
                        return i;
                    }
                }
            }

            return null;
        },

        /////////////////////////////////////////////////////////////////////////////
        //
        // Cache public methods
        //
        /////////////////////////////////////////////////////////////////////////////

        /**
         * Adds a new entry to the cache of the format
         * {request:request, response:response, cached:cached, expires:expires}.
         * If cache is full, evicts the stalest entry before adding the new one.
         *
         * @method add
         * @param request {Object} Request value.
         * @param response {Object} Response value.
         */
        add: function(request, response) {
            var expires = this.get("expires");
            if(this.get("initialized") && ((this.get("max") === null) || this.get("max") > 0) &&
                    (LANG.isValue(request) || LANG.isNull(request) || LANG.isUndefined(request))) {
                this.fire("add", {entry: {
                    request:request,
                    response:response,
                    cached: new Date(),
                    expires: isDate(expires) ? expires :
                (expires ? new Date(new Date().getTime() + this.get("expires")) : null)
                }});
            }
            else {
                Y.log("Could not add " + Y.dump(response) + " to cache for " + Y.dump(request), "info", "cache");
            }
        },

        /**
         * Flushes cache.
         *
         * @method flush
         */
        flush: function(request) {
            this.fire("flush", { request: (LANG.isValue(request) ? request : null) });
        },

        /**
         * Retrieves cached object for given request, if available, and refreshes
         * entry in the cache. Returns null if there is no cache match.
         *
         * @method retrieve
         * @param request {Object} Request object.
         * @return {Object} Cached object with the properties request and response, or null.
         */
        retrieve: function(request) {
            // If cache is enabled...
            var entries = this._entries,
                length = entries.length,
                entry = null,
                pos;

            if((length > 0) && ((this.get("max") === null) || (this.get("max") > 0))) {
                this.fire("request", {request: request});

                pos = this._position(request);

                if(LANG.isValue(pos)) {
                    entry = entries[pos];

                    this.fire("retrieve", {entry: entry});

                    // Refresh the position of the cache hit
                    if(pos < length-1) {
                        // Remove element from its original location
                        entries.splice(pos,1);
                        // Add as newest
                        entries[entries.length] = entry;
                        Y.log("Refreshed cache entry: " + Y.dump(entry) +
                                " for request: " +  Y.dump(request), "info", "cache");
                    }

                    Y.log("Retrieved cached response: " + Y.dump(entry) +
                            " for request: " + Y.dump(request), "info", "cache");
                    return entry;
                }
            }
            return null;
        }
    });

    Y.Cache = Cache;