/* Copyright (c) 2010, Yahoo! Inc. All rights reserved. Code licensed under the BSD License: http://developer.yahoo.com/yui/license.html version: 3.3.0 build: 3167 */ YUI.add('cache-base', function(Y) { /** * The Cache utility provides a common configurable interface for components to * cache and retrieve data from a local JavaScript struct. * * @module cache */ 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: *
*
entry (Object)
The cached entry.
*
* @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: *
*
request (Object)
The request object.
*
*/ /** * @event retrieve * @description Fired when an entry is retrieved from the cache. * @param e {Event.Facade} Event Facade with the following properties: *
*
entry (Object)
The retrieved entry.
*
*/ // Initialize internal values this._entries = []; }, /** * @method destructor * @description Internal destroy() handler. * @private */ destructor: function() { this._entries = []; }, ///////////////////////////////////////////////////////////////////////////// // // 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: *
*
entry (Object)
The cached entry.
*
* @protected */ _defAddFn: function(e) { var entries = this._entries, max = this.get("max"), entry = e.entry; if(this.get("uniqueKeys") && (this.retrieve(e.entry.request))) { entries.shift(); } // If the cache at or over capacity, make room by removing stalest element (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; }, /** * Flushes cache. * * @method _defFlushFn * @param e {Event.Facade} Event Facade object. * @protected */ _defFlushFn: function(e) { this._entries = []; }, /** * 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; }, ///////////////////////////////////////////////////////////////////////////// // // 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 { } }, /** * Flushes cache. * * @method flush */ flush: function() { this.fire("flush"); }, /** * 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, i = length-1; if((length > 0) && ((this.get("max") === null) || (this.get("max") > 0))) { this.fire("request", {request: request}); // Loop through each cached entry starting from the newest for(; i >= 0; i--) { entry = entries[i]; // Execute matching function if(this._isMatch(request, entry)) { this.fire("retrieve", {entry: entry}); // Refresh the position of the cache hit if(i < length-1) { // Remove element from its original location entries.splice(i,1); // Add as newest entries[entries.length] = entry; } return entry; } } } return null; } }); Y.Cache = Cache; }, '3.3.0' ,{requires:['base']}); YUI.add('cache-offline', function(Y) { /** * Extends Cache utility with offline functionality. * @class CacheOffline * @extends Cache * @constructor */ function CacheOffline() { CacheOffline.superclass.constructor.apply(this, arguments); } var localStorage = null, JSON = Y.JSON; // Bug 2529572 try { localStorage = Y.config.win.localStorage; } catch(e) { } ///////////////////////////////////////////////////////////////////////////// // // CacheOffline events // ///////////////////////////////////////////////////////////////////////////// /** * @event error * @description Fired when an entry could not be added, most likely due to * exceeded browser quota. *
*
error (Object)
The error object.
*
*/ ///////////////////////////////////////////////////////////////////////////// // // CacheOffline static // ///////////////////////////////////////////////////////////////////////////// Y.mix(CacheOffline, { /** * Class name. * * @property NAME * @type String * @static * @final * @value "cacheOffline" */ NAME: "cacheOffline", ATTRS: { ///////////////////////////////////////////////////////////////////////////// // // CacheOffline Attributes // ///////////////////////////////////////////////////////////////////////////// /** * @attribute sandbox * @description A string that must be passed in via the constructor. * This identifier is used to sandbox one cache instance's entries * from another. Calling the cache instance's flush and length methods * or get("entries") will apply to only these sandboxed entries. * @type String * @default "default" * @initOnly */ sandbox: { value: "default", writeOnce: "initOnly" }, /** * @attribute expires * @description Absolute Date when data expires or * relative number of milliseconds. Zero disables expiration. * @type Date | Number * @default 86400000 (one day) */ expires: { value: 86400000 }, /** * @attribute max * @description Disabled. * @readOnly * @default null */ max: { value: null, readOnly: true }, /** * @attribute uniqueKeys * @description Always true for CacheOffline. * @readOnly * @default true */ uniqueKeys: { value: true, readOnly: true, setter: function() { return true; } } }, /** * Removes all items from all sandboxes. Useful if localStorage has * exceeded quota. Only supported on browsers that implement HTML 5 * localStorage. * * @method flushAll * @static */ flushAll: function() { var store = localStorage, key; if(store) { if(store.clear) { store.clear(); } // FF2.x and FF3.0.x else { for (key in store) { if (store.hasOwnProperty(key)) { store.removeItem(key); delete store[key]; } } } } else { } } }); Y.extend(CacheOffline, Y.Cache, localStorage ? { ///////////////////////////////////////////////////////////////////////////// // // Offline is supported // ///////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////// // // CacheOffline protected methods // ///////////////////////////////////////////////////////////////////////////// /** * Always return null. * * @method _setMax * @protected */ _setMax: function(value) { return null; }, /** * Gets size. * * @method _getSize * @protected */ _getSize: function() { var count = 0, i=0, l=localStorage.length; for(; i *
entry (Object)
The cached entry.
* * @protected */ _defAddFn: function(e) { var entry = e.entry, request = entry.request, cached = entry.cached, expires = entry.expires; // Convert Dates to msecs on the way into localStorage entry.cached = cached.getTime(); entry.expires = expires ? expires.getTime() : expires; try { localStorage.setItem(this.get("sandbox")+JSON.stringify({"request":request}), JSON.stringify(entry)); } catch(error) { this.fire("error", {error:error}); } }, /** * Flushes cache. * * @method _defFlushFn * @param e {Event.Facade} Event Facade object. * @protected */ _defFlushFn: function(e) { var key, i=localStorage.length-1; for(; i>-1; --i) { // Match sandbox id key = localStorage.key(i); if(key.indexOf(this.get("sandbox")) === 0) { localStorage.removeItem(key); } } }, ///////////////////////////////////////////////////////////////////////////// // // CacheOffline public methods // ///////////////////////////////////////////////////////////////////////////// /** * Adds a new entry to the cache of the format * {request:request, response:response, cached:cached, expires: expires}. * * @method add * @param request {Object} Request value must be a String or JSON. * @param response {Object} Response value must be a String or JSON. */ /** * Retrieves cached object for given request, if available. * Returns null if there is no cache match. * * @method retrieve * @param request {Object} Request object. * @return {Object} Cached object with the properties request, response, * and expires, or null. */ retrieve: function(request) { this.fire("request", {request: request}); var entry, expires, sandboxedrequest; try { sandboxedrequest = this.get("sandbox")+JSON.stringify({"request":request}); try { entry = JSON.parse(localStorage.getItem(sandboxedrequest)); } catch(e) { } } catch(e2) { } if(entry) { // Convert msecs to Dates on the way out of localStorage entry.cached = new Date(entry.cached); expires = entry.expires; expires = !expires ? null : new Date(expires); entry.expires = expires; if(this._isMatch(request, entry)) { this.fire("retrieve", {entry: entry}); return entry; } } return null; } } : ///////////////////////////////////////////////////////////////////////////// // // Offline is not supported // ///////////////////////////////////////////////////////////////////////////// { /** * Always return null. * * @method _setMax * @protected */ _setMax: function(value) { return null; } }); Y.CacheOffline = CacheOffline; }, '3.3.0' ,{requires:['cache-base', 'json']}); YUI.add('cache-plugin', function(Y) { /** * Plugin.Cache adds pluginizability to Cache. * @class Plugin.Cache * @extends Cache * @uses Plugin.Base */ function CachePlugin(config) { var cache = config && config.cache ? config.cache : Y.Cache, tmpclass = Y.Base.create("dataSourceCache", cache, [Y.Plugin.Base]), tmpinstance = new tmpclass(config); tmpclass.NS = "tmpClass"; return tmpinstance; } Y.mix(CachePlugin, { /** * The namespace for the plugin. This will be the property on the host which * references the plugin instance. * * @property NS * @type String * @static * @final * @value "cache" */ NS: "cache", /** * Class name. * * @property NAME * @type String * @static * @final * @value "dataSourceCache" */ NAME: "cachePlugin" }); Y.namespace("Plugin").Cache = CachePlugin; }, '3.3.0' ,{requires:['plugin','cache-base']}); YUI.add('cache', function(Y){}, '3.3.0' ,{use:['cache-base','cache-offline','cache-plugin']});