|
- /*
- * JS Storage Plugin
- *
- * Copyright (c) 2019 Julien Maurel
- *
- * Licensed under the MIT license:
- * http://www.opensource.org/licenses/mit-license.php
- *
- * Project home:
- * https://github.com/julien-maurel/js-storage
- *
- * Version: 1.1.0
- */
- (function (factory) {
- var registeredInModuleLoader = false;
- if (typeof define === 'function' && define.amd) {
- define(['jquery', 'js-cookie/cookie-wrapper'], factory);
- registeredInModuleLoader = true;
- }
- if (typeof exports === 'object') {
- module.exports = factory();
- registeredInModuleLoader = true;
- }
- if (!registeredInModuleLoader) {
- var OldStorages = window.Storages;
- var api = window.Storages = factory();
- api.noConflict = function () {
- window.Storages = OldStorages;
- return api;
- };
- }
- }(function () {
- // Variables used by utilities functions (like isPlainObject...)
- var class2type = {};
- var toString = class2type.toString;
- var hasOwn = class2type.hasOwnProperty;
- var fnToString = hasOwn.toString;
- var ObjectFunctionString = fnToString.call(Object);
- var getProto = Object.getPrototypeOf;
- var apis = {};
-
- // Prefix to use with cookie fallback
- var cookie_local_prefix = "ls_";
- var cookie_session_prefix = "ss_";
-
- // Get items from a storage
- function _get() {
- var storage = this._type, l = arguments.length, s = window[storage], a = arguments, a0 = a[0], vi, ret, tmp, i, j;
- if (l < 1) {
- throw new Error('Minimum 1 argument must be given');
- } else if (Array.isArray(a0)) {
- // If second argument is an array, return an object with value of storage for each item in this array
- ret = {};
- for (i in a0) {
- if (a0.hasOwnProperty(i)) {
- vi = a0[i];
- try {
- ret[vi] = JSON.parse(s.getItem(vi));
- } catch (e) {
- ret[vi] = s.getItem(vi);
- }
- }
- }
- return ret;
- } else if (l == 1) {
- // If only 1 argument, return value directly
- try {
- return JSON.parse(s.getItem(a0));
- } catch (e) {
- return s.getItem(a0);
- }
- } else {
- // If more than 1 argument, parse storage to retrieve final value to return it
- // Get first level
- try {
- ret = JSON.parse(s.getItem(a0));
- if (!ret) {
- throw new ReferenceError(a0 + ' is not defined in this storage');
- }
- } catch (e) {
- throw new ReferenceError(a0 + ' is not defined in this storage');
- }
- // Parse next levels
- for (i = 1; i < l - 1; i++) {
- ret = ret[a[i]];
- if (ret === undefined) {
- throw new ReferenceError([].slice.call(a, 0, i + 1).join('.') + ' is not defined in this storage');
- }
- }
- // If last argument is an array, return an object with value for each item in this array
- // Else return value normally
- if (Array.isArray(a[i])) {
- tmp = ret;
- ret = {};
- for (j in a[i]) {
- if (a[i].hasOwnProperty(j)) {
- ret[a[i][j]] = tmp[a[i][j]];
- }
- }
- return ret;
- } else {
- return ret[a[i]];
- }
- }
- }
-
- // Set items of a storage
- function _set() {
- var storage = this._type, l = arguments.length, s = window[storage], a = arguments, a0 = a[0], a1 = a[1], vi, to_store = isNaN(a1) ? {} : [], type, tmp, i;
- if (l < 1 || !_isPlainObject(a0) && l < 2) {
- throw new Error('Minimum 2 arguments must be given or first parameter must be an object');
- } else if (_isPlainObject(a0)) {
- // If first argument is an object, set values of storage for each property of this object
- for (i in a0) {
- if (a0.hasOwnProperty(i)) {
- vi = a0[i];
- if (!_isPlainObject(vi) && !this.alwaysUseJson) {
- s.setItem(i, vi);
- } else {
- s.setItem(i, JSON.stringify(vi));
- }
- }
- }
- return a0;
- } else if (l == 2) {
- // If only 2 arguments, set value of storage directly
- if (typeof a1 === 'object' || this.alwaysUseJson) {
- s.setItem(a0, JSON.stringify(a1));
- } else {
- s.setItem(a0, a1);
- }
- return a1;
- } else {
- // If more than 3 arguments, parse storage to retrieve final node and set value
- // Get first level
- try {
- tmp = s.getItem(a0);
- if (tmp != null) {
- to_store = JSON.parse(tmp);
- }
- } catch (e) {
- }
- tmp = to_store;
- // Parse next levels and set value
- for (i = 1; i < l - 2; i++) {
- vi = a[i];
- type = isNaN(a[i + 1]) ? "object" : "array";
- if (!tmp[vi] || type == "object" && !_isPlainObject(tmp[vi]) || type == "array" && !Array.isArray(tmp[vi])) {
- if (type == "array") tmp[vi] = [];
- else tmp[vi] = {};
- }
- tmp = tmp[vi];
- }
- tmp[a[i]] = a[i + 1];
- s.setItem(a0, JSON.stringify(to_store));
- return to_store;
- }
- }
-
- // Remove items from a storage
- function _remove() {
- var storage = this._type, l = arguments.length, s = window[storage], a = arguments, a0 = a[0], to_store, tmp, i, j;
- if (l < 1) {
- throw new Error('Minimum 1 argument must be given');
- } else if (Array.isArray(a0)) {
- // If first argument is an array, remove values from storage for each item of this array
- for (i in a0) {
- if (a0.hasOwnProperty(i)) {
- s.removeItem(a0[i]);
- }
- }
- return true;
- } else if (l == 1) {
- // If only 2 arguments, remove value from storage directly
- s.removeItem(a0);
- return true;
- } else {
- // If more than 2 arguments, parse storage to retrieve final node and remove value
- // Get first level
- try {
- to_store = tmp = JSON.parse(s.getItem(a0));
- } catch (e) {
- throw new ReferenceError(a0 + ' is not defined in this storage');
- }
- // Parse next levels and remove value
- for (i = 1; i < l - 1; i++) {
- tmp = tmp[a[i]];
- if (tmp === undefined) {
- throw new ReferenceError([].slice.call(a, 1, i).join('.') + ' is not defined in this storage');
- }
- }
- // If last argument is an array,remove value for each item in this array
- // Else remove value normally
- if (Array.isArray(a[i])) {
- for (j in a[i]) {
- if (a[i].hasOwnProperty(j)) {
- delete tmp[a[i][j]];
- }
- }
- } else {
- delete tmp[a[i]];
- }
- s.setItem(a0, JSON.stringify(to_store));
- return true;
- }
- }
-
- // Remove all items from a storage
- function _removeAll(reinit_ns) {
- var keys = _keys.call(this), i;
- for (i in keys) {
- if (keys.hasOwnProperty(i)) {
- _remove.call(this, keys[i]);
- }
- }
- // Reinitialize all namespace storages
- if (reinit_ns) {
- for (i in apis.namespaceStorages) {
- if (apis.namespaceStorages.hasOwnProperty(i)) {
- _createNamespace(i);
- }
- }
- }
- }
-
- // Check if items of a storage are empty
- function _isEmpty() {
- var l = arguments.length, a = arguments, a0 = a[0], i;
- if (l == 0) {
- // If no argument, test if storage is empty
- return (_keys.call(this).length == 0);
- } else if (Array.isArray(a0)) {
- // If first argument is an array, test each item of this array and return true only if all items are empty
- for (i = 0; i < a0.length; i++) {
- if (!_isEmpty.call(this, a0[i])) {
- return false;
- }
- }
- return true;
- } else {
- // If at least 1 argument, try to get value and test it
- try {
- var v = _get.apply(this, arguments);
- // Convert result to an object (if last argument is an array, _get return already an object) and test each item
- if (!Array.isArray(a[l - 1])) {
- v = {'totest': v};
- }
- for (i in v) {
- if (v.hasOwnProperty(i) && !(
- (_isPlainObject(v[i]) && _isEmptyObject(v[i])) ||
- (Array.isArray(v[i]) && !v[i].length) ||
- (typeof v[i] !== 'boolean' && !v[i])
- )) {
- return false;
- }
- }
- return true;
- } catch (e) {
- return true;
- }
- }
- }
-
- // Check if items of a storage exist
- function _isSet() {
- var l = arguments.length, a = arguments, a0 = a[0], i;
- if (l < 1) {
- throw new Error('Minimum 1 argument must be given');
- }
- if (Array.isArray(a0)) {
- // If first argument is an array, test each item of this array and return true only if all items exist
- for (i = 0; i < a0.length; i++) {
- if (!_isSet.call(this, a0[i])) {
- return false;
- }
- }
- return true;
- } else {
- // For other case, try to get value and test it
- try {
- var v = _get.apply(this, arguments);
- // Convert result to an object (if last argument is an array, _get return already an object) and test each item
- if (!Array.isArray(a[l - 1])) {
- v = {'totest': v};
- }
- for (i in v) {
- if (v.hasOwnProperty(i) && !(v[i] !== undefined && v[i] !== null)) {
- return false;
- }
- }
- return true;
- } catch (e) {
- return false;
- }
- }
- }
-
- // Get keys of a storage or of an item of the storage
- function _keys() {
- var storage = this._type, l = arguments.length, s = window[storage], keys = [], o = {};
- // If at least 1 argument, get value from storage to retrieve keys
- // Else, use storage to retrieve keys
- if (l > 0) {
- o = _get.apply(this, arguments);
- } else {
- o = s;
- }
- if (o && o._cookie) {
- // If storage is a cookie, use js-cookie to retrieve keys
- var cookies = Cookies.get();
- for (var key in cookies) {
- if (cookies.hasOwnProperty(key) && key != '') {
- keys.push(key.replace(o._prefix, ''));
- }
- }
- } else {
- for (var i in o) {
- if (o.hasOwnProperty(i)) {
- keys.push(i);
- }
- }
- }
- return keys;
- }
-
- // Create new namespace storage
- function _createNamespace(name) {
- if (!name || typeof name != "string") {
- throw new Error('First parameter must be a string');
- }
- if (storage_available) {
- if (!window.localStorage.getItem(name)) {
- window.localStorage.setItem(name, '{}');
- }
- if (!window.sessionStorage.getItem(name)) {
- window.sessionStorage.setItem(name, '{}');
- }
- } else {
- if (!window.localCookieStorage.getItem(name)) {
- window.localCookieStorage.setItem(name, '{}');
- }
- if (!window.sessionCookieStorage.getItem(name)) {
- window.sessionCookieStorage.setItem(name, '{}');
- }
- }
- var ns = {
- localStorage: _extend({}, apis.localStorage, {_ns: name}),
- sessionStorage: _extend({}, apis.sessionStorage, {_ns: name})
- };
- if (cookies_available) {
- if (!window.cookieStorage.getItem(name)) {
- window.cookieStorage.setItem(name, '{}');
- }
- ns.cookieStorage = _extend({}, apis.cookieStorage, {_ns: name});
- }
- apis.namespaceStorages[name] = ns;
- return ns;
- }
-
- // Test if storage is natively available on browser
- function _testStorage(name) {
- var foo = 'jsapi';
- try {
- if (!window[name]) {
- return false;
- }
- window[name].setItem(foo, foo);
- window[name].removeItem(foo);
- return true;
- } catch (e) {
- return false;
- }
- }
-
- // Test if a variable is a plain object (from jQuery)
- function _isPlainObject(obj) {
- var proto, Ctor;
-
- // Detect obvious negatives
- // Use toString instead of jQuery.type to catch host objects
- if (!obj || toString.call(obj) !== "[object Object]") {
- return false;
- }
-
- proto = getProto(obj);
-
- // Objects with no prototype (e.g., `Object.create( null )`) are plain
- if (!proto) {
- return true;
- }
-
- // Objects with prototype are plain iff they were constructed by a global Object function
- Ctor = hasOwn.call(proto, "constructor") && proto.constructor;
- return typeof Ctor === "function" && fnToString.call(Ctor) === ObjectFunctionString;
- }
-
- // Test if a variable is an empty object (from jQuery)
- function _isEmptyObject(obj) {
- var name;
-
- for (name in obj) {
- return false;
- }
- return true;
- }
-
- // Merge objects
- function _extend() {
- var i = 1;
- var result = arguments[0];
- for (; i < arguments.length; i++) {
- var attributes = arguments[i];
- for (var key in attributes) {
- if (attributes.hasOwnProperty(key)) {
- result[key] = attributes[key];
- }
- }
- }
- return result;
- }
-
- // Check if storages are natively available on browser and check is js-cookie is present
- var storage_available = _testStorage('localStorage');
- var cookies_available = typeof Cookies !== 'undefined';
-
- // Namespace object
- var storage = {
- _type: '',
- _ns: '',
- _callMethod: function (f, a) {
- a = Array.prototype.slice.call(a);
- var p = [], a0 = a[0];
- if (this._ns) {
- p.push(this._ns);
- }
- if (typeof a0 === 'string' && a0.indexOf('.') !== -1) {
- a.shift();
- [].unshift.apply(a, a0.split('.'));
- }
- [].push.apply(p, a);
- return f.apply(this, p);
- },
- // Define if plugin always use JSON to store values (even to store simple values like string, int...) or not
- alwaysUseJson: false,
- // Get items. If no parameters and storage have a namespace, return all namespace
- get: function () {
- if (!storage_available && !cookies_available){
- return null;
- }
- return this._callMethod(_get, arguments);
- },
- // Set items
- set: function () {
- var l = arguments.length, a = arguments, a0 = a[0];
- if (l < 1 || !_isPlainObject(a0) && l < 2) {
- throw new Error('Minimum 2 arguments must be given or first parameter must be an object');
- }
- if (!storage_available && !cookies_available){
- return null;
- }
- // If first argument is an object and storage is a namespace storage, set values individually
- if (_isPlainObject(a0) && this._ns) {
- for (var i in a0) {
- if (a0.hasOwnProperty(i)) {
- this._callMethod(_set, [i, a0[i]]);
- }
- }
- return a0;
- } else {
- var r = this._callMethod(_set, a);
- if (this._ns) {
- return r[a0.split('.')[0]];
- } else {
- return r;
- }
- }
- },
- // Delete items
- remove: function () {
- if (arguments.length < 1) {
- throw new Error('Minimum 1 argument must be given');
- }
- if (!storage_available && !cookies_available){
- return null;
- }
- return this._callMethod(_remove, arguments);
- },
- // Delete all items
- removeAll: function (reinit_ns) {
- if (!storage_available && !cookies_available){
- return null;
- }
- if (this._ns) {
- this._callMethod(_set, [{}]);
- return true;
- } else {
- return this._callMethod(_removeAll, [reinit_ns]);
- }
- },
- // Items empty
- isEmpty: function () {
- if (!storage_available && !cookies_available){
- return null;
- }
- return this._callMethod(_isEmpty, arguments);
- },
- // Items exists
- isSet: function () {
- if (arguments.length < 1) {
- throw new Error('Minimum 1 argument must be given');
- }
- if (!storage_available && !cookies_available){
- return null;
- }
- return this._callMethod(_isSet, arguments);
- },
- // Get keys of items
- keys: function () {
- if (!storage_available && !cookies_available){
- return null;
- }
- return this._callMethod(_keys, arguments);
- }
- };
-
- // Use js-cookie for compatibility with old browsers and give access to cookieStorage
- if (cookies_available) {
- // sessionStorage is valid for one window/tab. To simulate that with cookie, we set a name for the window and use it for the name of the cookie
- if (!window.name) {
- window.name = Math.floor(Math.random() * 100000000);
- }
- var cookie_storage = {
- _cookie: true,
- _prefix: '',
- _expires: null,
- _path: null,
- _domain: null,
- _secure: false,
- setItem: function (n, v) {
- Cookies.set(this._prefix + n, v, {expires: this._expires, path: this._path, domain: this._domain, secure: this._secure});
- },
- getItem: function (n) {
- return Cookies.get(this._prefix + n);
- },
- removeItem: function (n) {
- return Cookies.remove(this._prefix + n, {path: this._path});
- },
- clear: function () {
- var cookies = Cookies.get();
- for (var key in cookies) {
- if (cookies.hasOwnProperty(key) && key != '') {
- if (!this._prefix && key.indexOf(cookie_local_prefix) === -1 && key.indexOf(cookie_session_prefix) === -1 || this._prefix && key.indexOf(this._prefix) === 0) {
- Cookies.remove(key);
- }
- }
- }
- },
- setExpires: function (e) {
- this._expires = e;
- return this;
- },
- setPath: function (p) {
- this._path = p;
- return this;
- },
- setDomain: function (d) {
- this._domain = d;
- return this;
- },
- setSecure: function (s) {
- this._secure = s;
- return this;
- },
- setConf: function (c) {
- if (c.path) {
- this._path = c.path;
- }
- if (c.domain) {
- this._domain = c.domain;
- }
- if (c.secure) {
- this._secure = c.secure;
- }
- if (c.expires) {
- this._expires = c.expires;
- }
- return this;
- },
- setDefaultConf: function () {
- this._path = this._domain = this._expires = null;
- this._secure = false;
- }
- };
- if (!storage_available) {
- window.localCookieStorage = _extend({}, cookie_storage, {
- _prefix: cookie_local_prefix,
- _expires: 365 * 10,
- _secure: true
- });
- window.sessionCookieStorage = _extend({}, cookie_storage, {
- _prefix: cookie_session_prefix + window.name + '_',
- _secure: true
- });
- }
- window.cookieStorage = _extend({}, cookie_storage);
- // cookieStorage API
- apis.cookieStorage = _extend({}, storage, {
- _type: 'cookieStorage',
- setExpires: function (e) {
- window.cookieStorage.setExpires(e);
- return this;
- },
- setPath: function (p) {
- window.cookieStorage.setPath(p);
- return this;
- },
- setDomain: function (d) {
- window.cookieStorage.setDomain(d);
- return this;
- },
- setSecure: function (s) {
- window.cookieStorage.setSecure(s);
- return this;
- },
- setConf: function (c) {
- window.cookieStorage.setConf(c);
- return this;
- },
- setDefaultConf: function () {
- window.cookieStorage.setDefaultConf();
- return this;
- }
- });
- }
-
- // Get a new API on a namespace
- apis.initNamespaceStorage = function (ns) {
- return _createNamespace(ns);
- };
- if (storage_available) {
- // localStorage API
- apis.localStorage = _extend({}, storage, {_type: 'localStorage'});
- // sessionStorage API
- apis.sessionStorage = _extend({}, storage, {_type: 'sessionStorage'});
- } else {
- // localStorage API
- apis.localStorage = _extend({}, storage, {_type: 'localCookieStorage'});
- // sessionStorage API
- apis.sessionStorage = _extend({}, storage, {_type: 'sessionCookieStorage'});
- }
- // List of all namespace storage
- apis.namespaceStorages = {};
- // Remove all items in all storages
- apis.removeAllStorages = function (reinit_ns) {
- apis.localStorage.removeAll(reinit_ns);
- apis.sessionStorage.removeAll(reinit_ns);
- if (apis.cookieStorage) {
- apis.cookieStorage.removeAll(reinit_ns);
- }
- if (!reinit_ns) {
- apis.namespaceStorages = {};
- }
- };
- // About alwaysUseJson
- // By default, all values are string on html storages and the plugin don't use json to store simple values (strings, int, float...)
- // So by default, if you do storage.setItem('test',2), value in storage will be "2", not 2
- // If you set this property to true, all values set with the plugin will be stored as json to have typed values in any cases
- apis.alwaysUseJsonInStorage = function (value) {
- storage.alwaysUseJson = value;
- apis.localStorage.alwaysUseJson = value;
- apis.sessionStorage.alwaysUseJson = value;
- if (apis.cookieStorage) {
- apis.cookieStorage.alwaysUseJson = value;
- }
- };
-
- return apis;
- }));
|