lib/store/dom.js (181 lines of code) (raw):

/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ 'use strict'; /** @module store/dom */ var utils = require('./../utils.js'); // Imports. var throwErrorCallback = utils.throwErrorCallback; var delay = utils.delay; var localStorage = null; /** This method is used to override the Date.toJSON method and is called only by * JSON.stringify. It should never be called directly. * @summary Converts a Date object into an object representation friendly to JSON serialization. * @returns {Object} Object that represents the Date. */ function domStoreDateToJSON() { var newValue = { v: this.valueOf(), t: "[object Date]" }; // Date objects might have extra properties on them so we save them. for (var name in this) { newValue[name] = this[name]; } return newValue; } /** This method is used during JSON parsing and invoked only by the reviver function. * It should never be called directly. * @summary JSON reviver function for converting an object representing a Date in a JSON stream to a Date object * @param value _ * @param value - Object to convert. * @returns {Date} Date object. */ function domStoreJSONToDate(_, value) { if (value && value.t === "[object Date]") { var newValue = new Date(value.v); for (var name in value) { if (name !== "t" && name !== "v") { newValue[name] = value[name]; } } value = newValue; } return value; } /** Qualifies the key with the name of the store. * @param {Object} store - Store object whose name will be used for qualifying the key. * @param {String} key - Key string. * @returns {String} Fully qualified key string. */ function qualifyDomStoreKey(store, key) { return store.name + "#!#" + key; } /** Gets the key part of a fully qualified key string. * @param {Object} store - Store object whose name will be used for qualifying the key. * @param {String} key - Fully qualified key string. * @returns {String} Key part string */ function unqualifyDomStoreKey(store, key) { return key.replace(store.name + "#!#", ""); } /** Constructor for store objects that use DOM storage as the underlying mechanism. * @class DomStore * @constructor * @param {String} name - Store name. */ function DomStore(name) { this.name = name; } /** Creates a store object that uses DOM Storage as its underlying mechanism. * @method module:store/dom~DomStore.create * @param {String} name - Store name. * @returns {Object} Store object. */ DomStore.create = function (name) { if (DomStore.isSupported()) { localStorage = localStorage || window.localStorage; return new DomStore(name); } throw { message: "Web Storage not supported by the browser" }; }; /** Checks whether the underlying mechanism for this kind of store objects is supported by the browser. * @method DomStore.isSupported * @returns {Boolean} - True if the mechanism is supported by the browser; otherwise false. */ DomStore.isSupported = function () { return !!window.localStorage; }; /** Adds a new value identified by a key to the store. * @method module:store/dom~DomStore#add * @param {String} key - Key string. * @param value - Value that is going to be added to the store. * @param {Function} success - Callback for a successful add operation. * @param {Function} [error] - Callback for handling errors. If not specified then store.defaultError is invoked. * This method errors out if the store already contains the specified key. */ DomStore.prototype.add = function (key, value, success, error) { error = error || this.defaultError; var store = this; this.contains(key, function (contained) { if (!contained) { store.addOrUpdate(key, value, success, error); } else { delay(error, { message: "key already exists", key: key }); } }, error); }; /** This method will overwrite the key's current value if it already exists in the store; otherwise it simply adds the new key and value. * @summary Adds or updates a value identified by a key to the store. * @method module:store/dom~DomStore#addOrUpdate * @param {String} key - Key string. * @param value - Value that is going to be added or updated to the store. * @param {Function} success - Callback for a successful add or update operation. * @param {Function} [error] - Callback for handling errors. If not specified then store.defaultError is invoked. */ DomStore.prototype.addOrUpdate = function (key, value, success, error) { error = error || this.defaultError; if (key instanceof Array) { error({ message: "Array of keys not supported" }); } else { var fullKey = qualifyDomStoreKey(this, key); var oldDateToJSON = Date.prototype.toJSON; try { var storedValue = value; if (storedValue !== undefined) { // Dehydrate using json Date.prototype.toJSON = domStoreDateToJSON; storedValue = window.JSON.stringify(value); } // Save the json string. localStorage.setItem(fullKey, storedValue); delay(success, key, value); } catch (e) { if (e.code === 22 || e.number === 0x8007000E) { delay(error, { name: "QUOTA_EXCEEDED_ERR", error: e }); } else { delay(error, e); } } finally { Date.prototype.toJSON = oldDateToJSON; } } }; /** In case of an error, this method will not restore any keys that might have been deleted at that point. * @summary Removes all the data associated with this store object. * @method module:store/dom~DomStore#clear * @param {Function} success - Callback for a successful clear operation. * @param {Function} [error] - Callback for handling errors. If not specified then store.defaultError is invoked. */ DomStore.prototype.clear = function (success, error) { error = error || this.defaultError; try { var i = 0, len = localStorage.length; while (len > 0 && i < len) { var fullKey = localStorage.key(i); var key = unqualifyDomStoreKey(this, fullKey); if (fullKey !== key) { localStorage.removeItem(fullKey); len = localStorage.length; } else { i++; } } delay(success); } catch (e) { delay(error, e); } }; /** This function does nothing in DomStore as it does not have a connection model * @method module:store/dom~DomStore#close */ DomStore.prototype.close = function () { }; /** Checks whether a key exists in the store. * @method module:store/dom~DomStore#contains * @param {String} key - Key string. * @param {Function} success - Callback indicating whether the store contains the key or not. * @param {Function} [error] - Callback for handling errors. If not specified then store.defaultError is invoked. */ DomStore.prototype.contains = function (key, success, error) { error = error || this.defaultError; try { var fullKey = qualifyDomStoreKey(this, key); var value = localStorage.getItem(fullKey); delay(success, value !== null); } catch (e) { delay(error, e); } }; DomStore.prototype.defaultError = throwErrorCallback; /** Gets all the keys that exist in the store. * @method module:store/dom~DomStore#getAllKeys * @param {Function} success - Callback for a successful get operation. * @param {Function} [error] - Callback for handling errors. If not specified then store.defaultError is invoked. */ DomStore.prototype.getAllKeys = function (success, error) { error = error || this.defaultError; var results = []; var i, len; try { for (i = 0, len = localStorage.length; i < len; i++) { var fullKey = localStorage.key(i); var key = unqualifyDomStoreKey(this, fullKey); if (fullKey !== key) { results.push(key); } } delay(success, results); } catch (e) { delay(error, e); } }; /** Identifies the underlying mechanism used by the store.*/ DomStore.prototype.mechanism = "dom"; /** Reads the value associated to a key in the store. * @method module:store/dom~DomStore#read * @param {String} key - Key string. * @param {Function} success - Callback for a successful reads operation. * @param {Function} [error] - Callback for handling errors. If not specified then store.defaultError is invoked. */ DomStore.prototype.read = function (key, success, error) { error = error || this.defaultError; if (key instanceof Array) { error({ message: "Array of keys not supported" }); } else { try { var fullKey = qualifyDomStoreKey(this, key); var value = localStorage.getItem(fullKey); if (value !== null && value !== "undefined") { // Hydrate using json value = window.JSON.parse(value, domStoreJSONToDate); } else { value = undefined; } delay(success, key, value); } catch (e) { delay(error, e); } } }; /** Removes a key and its value from the store. * @method module:store/dom~DomStore#remove * @param {String} key - Key string. * @param {Function} success - Callback for a successful remove operation. * @param {Function} [error] - Callback for handling errors. If not specified then store.defaultError is invoked. */ DomStore.prototype.remove = function (key, success, error) { error = error || this.defaultError; if (key instanceof Array) { error({ message: "Batches not supported" }); } else { try { var fullKey = qualifyDomStoreKey(this, key); localStorage.removeItem(fullKey); delay(success); } catch (e) { delay(error, e); } } }; /** Updates the value associated to a key in the store. * @method module:store/dom~DomStore#update * @param {String} key - Key string. * @param value - New value. * @param {Function} success - Callback for a successful update operation. * @param {Function} [error] - Callback for handling errors. If not specified then store.defaultError is invoked * This method errors out if the specified key is not found in the store. */ DomStore.prototype.update = function (key, value, success, error) { error = error || this.defaultError; var store = this; this.contains(key, function (contained) { if (contained) { store.addOrUpdate(key, value, success, error); } else { delay(error, { message: "key not found", key: key }); } }, error); }; module.exports = DomStore;