(function () { // Basil var Basil = function (options) { return Basil.utils.extend({}, Basil.plugins, new Basil.Storage().init(options)); }; // Version Basil.version = '0.4.2'; // Utils Basil.utils = { extend: function () { var destination = typeof arguments[0] === 'object' ? arguments[0] : {}; for (var i = 1; i < arguments.length; i++) { if (arguments[i] && typeof arguments[i] === 'object') for (var property in arguments[i]) destination[property] = arguments[i][property]; } return destination; }, each: function (obj, fnIterator, context) { if (this.isArray(obj)) { for (var i = 0; i < obj.length; i++) if (fnIterator.call(context, obj[i], i) === false) return; } else if (obj) { for (var key in obj) if (fnIterator.call(context, obj[key], key) === false) return; } }, tryEach: function (obj, fnIterator, fnError, context) { this.each(obj, function (value, key) { try { return fnIterator.call(context, value, key); } catch (error) { if (this.isFunction(fnError)) { try { fnError.call(context, value, key, error); } catch (error) {} } } }, this); }, registerPlugin: function (methods) { Basil.plugins = this.extend(methods, Basil.plugins); } }; // Add some isType methods: isArguments, isBoolean, isFunction, isString, isArray, isNumber, isDate, isRegExp. var types = ['Arguments', 'Boolean', 'Function', 'String', 'Array', 'Number', 'Date', 'RegExp'] for (var i = 0; i < types.length; i++) { Basil.utils['is' + types[i]] = (function (type) { return function (obj) { return Object.prototype.toString.call(obj) === '[object ' + type + ']'; }; })(types[i]); } // Plugins Basil.plugins = {}; // Options Basil.options = Basil.utils.extend({ namespace: 'b45i1', storages: ['local', 'cookie', 'session', 'memory'], expireDays: 365 }, window.Basil ? window.Basil.options : {}); // Storage Basil.Storage = function () { var _salt = 'b45i1' + (Math.random() + 1) .toString(36) .substring(7), _storages = {}, _toStoragesArray = function (storages) { if (Basil.utils.isArray(storages)) return storages; return Basil.utils.isString(storages) ? [storages] : []; }, _toStoredKey = function (namespace, path) { var key = ''; if (Basil.utils.isString(path) && path.length) path = [path]; if (Basil.utils.isArray(path) && path.length) key = path.join('.'); return key && namespace ? namespace + '.' + key : key; }, _toKeyName = function (namespace, key) { if (!namespace) return key; return key.replace(new RegExp('^' + namespace + '.'), ''); }, _toStoredValue = function (value) { return JSON.stringify(value); }, _fromStoredValue = function (value) { return value ? JSON.parse(value) : null; }; // HTML5 web storage interface var webStorageInterface = { engine: null, check: function () { try { window[this.engine].setItem(_salt, true); window[this.engine].removeItem(_salt); } catch (e) { return false; } return true; }, set: function (key, value, options) { if (!key) throw Error('invalid key'); window[this.engine].setItem(key, value); }, get: function (key) { return window[this.engine].getItem(key); }, remove: function (key) { window[this.engine].removeItem(key); }, reset: function (namespace) { for (var i = 0, key; i < window[this.engine].length; i++) { key = window[this.engine].key(i); if (!namespace || key.indexOf(namespace) === 0) { this.remove(key); i--; } } }, keys: function (namespace) { var keys = []; for (var i = 0, key; i < window[this.engine].length; i++) { key = window[this.engine].key(i); if (!namespace || key.indexOf(namespace) === 0) keys.push(_toKeyName(namespace, key)); } return keys; } }; // local storage _storages.local = Basil.utils.extend({}, webStorageInterface, { engine: 'localStorage' }); // session storage _storages.session = Basil.utils.extend({}, webStorageInterface, { engine: 'sessionStorage' }); // memory storage _storages.memory = { _hash: {}, check: function () { return true; }, set: function (key, value, options) { if (!key) throw Error('invalid key'); this._hash[key] = value; }, get: function (key) { return this._hash[key] || null; }, remove: function (key) { delete this._hash[key]; }, reset: function (namespace) { for (var key in this._hash) { if (!namespace || key.indexOf(namespace) === 0) this.remove(key); } }, keys: function (namespace) { var keys = []; for (var key in this._hash) if (!namespace || key.indexOf(namespace) === 0) keys.push(_toKeyName(namespace, key)); return keys; } }; // cookie storage _storages.cookie = { check: function () { return navigator.cookieEnabled; }, set: function (key, value, options) { if (!this.check()) throw Error('cookies are disabled'); options = options || {}; if (!key) throw Error('invalid key'); var cookie = encodeURIComponent(key) + '=' + encodeURIComponent(value); // handle expiration days if (options.expireDays) { var date = new Date(); date.setTime(date.getTime() + (options.expireDays * 24 * 60 * 60 * 1000)); cookie += '; expires=' + date.toGMTString(); } // handle domain if (options.domain && options.domain !== document.domain) { var _domain = options.domain.replace(/^\./, ''); if (document.domain.indexOf(_domain) === -1 || _domain.split('.').length <= 1) throw Error('invalid domain'); cookie += '; domain=' + options.domain; } // handle secure if (options.secure === true) { cookie += '; secure'; } document.cookie = cookie + '; path=/'; }, get: function (key) { if (!this.check()) throw Error('cookies are disabled'); var encodedKey = encodeURIComponent(key); var cookies = document.cookie ? document.cookie.split(';') : []; // retrieve last updated cookie first for (var i = cookies.length - 1, cookie; i >= 0; i--) { cookie = cookies[i].replace(/^\s*/, ''); if (cookie.indexOf(encodedKey + '=') === 0) return decodeURIComponent(cookie.substring(encodedKey.length + 1, cookie.length)); } return null; }, remove: function (key) { // remove cookie from main domain this.set(key, '', { expireDays: -1 }); // remove cookie from upper domains var domainParts = document.domain.split('.'); for (var i = domainParts.length; i >= 0; i--) { this.set(key, '', { expireDays: -1, domain: '.' + domainParts.slice(- i).join('.') }); } }, reset: function (namespace) { var cookies = document.cookie ? document.cookie.split(';') : []; for (var i = 0, cookie, key; i < cookies.length; i++) { cookie = cookies[i].replace(/^\s*/, ''); key = cookie.substr(0, cookie.indexOf('=')); if (!namespace || key.indexOf(namespace) === 0) this.remove(key); } }, keys: function (namespace) { if (!this.check()) throw Error('cookies are disabled'); var keys = [], cookies = document.cookie ? document.cookie.split(';') : []; for (var i = 0, cookie, key; i < cookies.length; i++) { cookie = cookies[i].replace(/^\s*/, ''); key = decodeURIComponent(cookie.substr(0, cookie.indexOf('='))); if (!namespace || key.indexOf(namespace) === 0) keys.push(_toKeyName(namespace, key)); } return keys; } }; return { init: function (options) { this.setOptions(options); return this; }, setOptions: function (options) { this.options = Basil.utils.extend({}, this.options || Basil.options, options); }, support: function (storage) { return _storages.hasOwnProperty(storage); }, check: function (storage) { if (this.support(storage)) return _storages[storage].check(); return false; }, set: function (key, value, options) { options = Basil.utils.extend({}, this.options, options); if (!(key = _toStoredKey(options.namespace, key))) return false; value = options.raw === true ? value : _toStoredValue(value); var where = null; // try to set key/value in first available storage Basil.utils.tryEach(_toStoragesArray(options.storages), function (storage, index) { _storages[storage].set(key, value, options); where = storage; return false; // break; }, null, this); if (!where) { // key has not been set anywhere return false; } // remove key from all other storages Basil.utils.tryEach(_toStoragesArray(options.storages), function (storage, index) { if (storage !== where) _storages[storage].remove(key); }, null, this); return true; }, get: function (key, options) { options = Basil.utils.extend({}, this.options, options); if (!(key = _toStoredKey(options.namespace, key))) return null; var value = null; Basil.utils.tryEach(_toStoragesArray(options.storages), function (storage, index) { if (value !== null) return false; // break if a value has already been found. value = _storages[storage].get(key, options) || null; value = options.raw === true ? value : _fromStoredValue(value); }, function (storage, index, error) { value = null; }, this); return value; }, remove: function (key, options) { options = Basil.utils.extend({}, this.options, options); if (!(key = _toStoredKey(options.namespace, key))) return; Basil.utils.tryEach(_toStoragesArray(options.storages), function (storage) { _storages[storage].remove(key); }, null, this); }, reset: function (options) { options = Basil.utils.extend({}, this.options, options); Basil.utils.tryEach(_toStoragesArray(options.storages), function (storage) { _storages[storage].reset(options.namespace); }, null, this); }, keys: function (options) { options = options || {}; var keys = []; for (var key in this.keysMap(options)) keys.push(key); return keys; }, keysMap: function (options) { options = Basil.utils.extend({}, this.options, options); var map = {}; Basil.utils.tryEach(_toStoragesArray(options.storages), function (storage) { Basil.utils.each(_storages[storage].keys(options.namespace), function (key) { map[key] = Basil.utils.isArray(map[key]) ? map[key] : []; map[key].push(storage); }, this); }, null, this); return map; } }; }; // Access to native storages, without namespace or basil value decoration Basil.memory = new Basil.Storage().init({ storages: 'memory', namespace: null, raw: true }); Basil.cookie = new Basil.Storage().init({ storages: 'cookie', namespace: null, raw: true }); Basil.localStorage = new Basil.Storage().init({ storages: 'local', namespace: null, raw: true }); Basil.sessionStorage = new Basil.Storage().init({ storages: 'session', namespace: null, raw: true }); // browser export window.Basil = Basil; // AMD export if (typeof define === 'function' && define.amd) { define(function() { return Basil; }); // commonjs export } else if (typeof module !== 'undefined' && module.exports) { module.exports = Basil; } })();