- /*
- An extension which provides a sync implementation through locally stored
- key value pairs, either through the HTML localStorage API or falling back
- onto an in-memory cache, that can be mixed into a Model or ModelList subclass.
-
- @module app
- @submodule model-sync-local
- @since @VERSION@
- **/
-
- /**
- An extension which provides a sync implementation through locally stored
- key value pairs, either through the HTML localStorage API or falling back
- onto an in-memory cache, that can be mixed into a Model or ModelList subclass.
-
- A group of Models/ModelLists is serialized in localStorage by either its
- class name, or a specified 'root' that is provided.
-
- var User = Y.Base.create('user', Y.Model, [Y.ModelSync.Local], {
- root: 'user'
- });
-
- var Users = Y.Base.create('users', Y.ModelList, [Y.ModelSync.Local], {
- model: User,
- });
-
- @class ModelSync.Local
- @extensionfor Model
- @extensionfor ModelList
- @since @VERSION@
- **/
- function LocalSync() {}
-
- /**
- Properties that shouldn't be turned into ad-hoc attributes when passed to a
- Model or ModelList constructor.
-
- @property _NON_ATTRS_CFG
- @type Array
- @default ['root']
- @static
- @protected
- @since @VERSION@
- **/
- LocalSync._NON_ATTRS_CFG = ['root'];
-
- /**
- Feature testing for `localStorage` availability.
- Will return falsey for browsers with `localStorage`, but that don't
- actually work, such as iOS Safari in private browsing mode.
-
- @property _hasLocalStorage
- @type Boolean
- @private
- **/
- LocalSync._hasLocalStorage = (function () {
- var LS = Y.config.win.localStorage,
- test = Y.guid();
-
- try {
- LS.setItem(test, test);
- LS.removeItem(test);
- return true;
- } catch (e) {
- return false;
- }
- })(),
-
- /**
- Object of key/value pairs to fall back on when localStorage is not available.
-
- @property _data
- @type Object
- @private
- **/
- LocalSync._data = {};
-
- /**
- Cache to quickly access a specific object with a given ID.
- This maps a model's ID to its reference inside of `LocalSync._data`.
-
- @property _idMap
- @type Object
- @private
- **/
-
- LocalSync._idMap = {};
-
- LocalSync.prototype = {
-
- // -- Public Methods -------------------------------------------------------
-
- /**
- Root used as the key inside of localStorage and/or the in-memory store.
-
- @property root
- @type String
- @default ""
- @since @VERSION@
- **/
- root: '',
-
- /**
- Shortcut for access to localStorage.
-
- @property storage
- @type Storage
- @default null
- @since @VERSION@
- **/
- storage: null,
-
- // -- Lifecycle Methods -----------------------------------------------------
- initializer: function (config) {
- var store, data;
-
- config || (config = {});
-
- if ('root' in config) {
- this.root = config.root || '';
- }
-
- // This is checking to see if the sync layer is being applied to
- // a ModelList, and if so, is looking for a `root` property on its
- // Model's prototype instead.
- if (!this.root && this.model && this.model.prototype.root) {
- this.root = this.model.prototype.root;
- }
-
- if (LocalSync._hasLocalStorage) {
- this.storage = Y.config.win.localStorage;
- store = this.storage.getItem(this.root);
- } else {
- Y.log("Could not access localStorage.", "warn");
- }
-
- // Pull in existing data from localStorage, if possible.
- // Otherwise, see if there's existing data on the local cache.
- if (store) {
- try {
- LocalSync._data[this.root] = Y.JSON.parse(store);
- } catch (e) {
- LocalSync._data[this.root] = [];
- }
- } else {
- LocalSync._data[this.root] || (LocalSync._data[this.root] = []);
- }
-
- // Map each model's ID to its reference inside of data, if there
- // are already existing models inside of `localStorage`.
- LocalSync._idMap[this.root] || (LocalSync._idMap[this.root] = {});
- Y.Array.each(LocalSync._data[this.root], function (item) {
- var id = item.id;
- if (id) {
- LocalSync._idMap[this.root][id] = item;
- }
- }, this);
- },
-
- // -- Public Methods -----------------------------------------------------------
-
- /**
- Creates a synchronization layer with the localStorage API, if available.
- Otherwise, falls back to a in-memory data store.
-
- This method is called internally by load(), save(), and destroy().
-
- @method sync
- @param {String} action Sync action to perform. May be one of the following:
-
- * **create**: Store a newly-created model for the first time.
- * **read** : Load an existing model.
- * **update**: Update an existing model.
- * **delete**: Delete an existing model.
-
- @param {Object} [options] Sync options
- @param {callback} [callback] Called when the sync operation finishes.
- @param {Error|null} callback.err If an error occurred, this parameter will
- contain the error. If the sync operation succeeded, _err_ will be
- falsey.
- @param {Any} [callback.response] The response from our sync. This value will
- be passed to the parse() method, which is expected to parse it and
- return an attribute hash.
- **/
- sync: function (action, options, callback) {
- options || (options = {});
- var response, errorInfo;
-
- try {
- switch (action) {
- case 'read':
- if (this._isYUIModelList) {
- response = this._index(options);
- } else {
- response = this._show(options);
- }
- break;
- case 'create':
- response = this._create(options);
- break;
- case 'update':
- response = this._update(options);
- break;
- case 'delete':
- response = this._destroy(options);
- break;
- }
- } catch (error) {
- errorInfo = error.message;
- }
-
- if (response) {
- callback(null, response);
- } else if (errorInfo) {
- callback(errorInfo);
- } else {
- callback("Data not found in LocalStorage");
- }
- },
-
- /**
- Generate a random GUID for our Models. This can be overriden if you have
- another method of generating different IDs.
-
- @method generateID
- @protected
- @param {String} pre Optional GUID prefix
- **/
- generateID: function (pre) {
- return Y.guid(pre + '_');
- },
-
- // -- Protected Methods ----------------------------------------------------
-
- /**
- Sync method correlating to the "read" operation, for a Model List
-
- @method _index
- @return {Object[]} Array of objects found for that root key
- @protected
- @since @VERSION@
- **/
- _index: function () {
- return LocalSync._data[this.root];
- },
-
- /**
- Sync method correlating to the "read" operation, for a Model
-
- @method _show
- @return {Object} Object found for that root key and model ID
- @protected
- @since @VERSION@
- **/
- _show: function () {
- return LocalSync._idMap[this.root][this.get('id')] || null;
- },
-
- /**
- Sync method correlating to the "create" operation
-
- @method _show
- @return {Object} The new object created.
- @protected
- @since @VERSION@
- **/
- _create: function () {
- var hash = this.toJSON(),
- data = LocalSync._data[this.root],
- idMap = LocalSync._idMap[this.root];
-
- hash.id = this.generateID(this.root);
- data.push(hash);
- idMap[hash.id] = hash;
-
- this._save();
- return hash;
- },
-
- /**
- Sync method correlating to the "update" operation
-
- @method _update
- @return {Object} The updated object.
- @protected
- @since @VERSION@
- **/
- _update: function () {
- var hash = Y.merge(this.toJSON());
- LocalSync._idMap[this.get('id')] = hash;
-
- this._save();
- return hash;
- },
-
- /**
- Sync method correlating to the "delete" operation. Deletes the data
- from the in-memory object, and saves into localStorage if available.
-
- @method _destroy
- @return {Object} The deleted object.
- @protected
- @since @VERSION@
- **/
- _destroy: function () {
- delete LocalSync._idMap[this.get('id')];
- this._save();
- return this.toJSON();
- },
-
- /**
- Saves the current in-memory store into a localStorage key/value pair
- if localStorage is available; otherwise, does nothing.
-
- @method _save
- @protected
- @since @VERSION@
- **/
- _save: function () {
- if (LocalSync._hasLocalStorage) {
- this.storage && this.storage.setItem(
- this.root,
- Y.JSON.stringify(LocalSync._data[this.root])
- );
- }
- }
- };
-
- // -- Namespace ---------------------------------------------------------------
-
- Y.namespace('ModelSync').Local = LocalSync;
-
-