From 2945d72e22dfe811d2efbc097459ee3f12348ac2 Mon Sep 17 00:00:00 2001 From: Clarence Leung Date: Fri, 13 Jul 2012 14:13:28 -0400 Subject: [PATCH 1/6] Updating app.html for ModelSync.Local --- src/app/tests/app.html | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/app/tests/app.html b/src/app/tests/app.html index a4b0d547bc0..d992083747a 100644 --- a/src/app/tests/app.html +++ b/src/app/tests/app.html @@ -23,6 +23,7 @@ 'lazy-model-list-test', 'model-test', 'model-list-test', + 'model-sync-local-test', 'router-test', 'view-test', 'view-node-map-test', @@ -45,6 +46,11 @@ requires: ['model-list', 'test'] }, + 'model-rest-test': { + fullpath: 'model-sync-local-test.js', + requires: ['model', 'model-list', 'model-sync-local', 'test'] + }, + 'router-test': { fullpath: 'router-test.js', requires: ['router', 'test'] From ef6a2987e5a0232d2b934ff640d743d0a45a2181 Mon Sep 17 00:00:00 2001 From: Clarence Leung Date: Fri, 13 Jul 2012 14:23:10 -0400 Subject: [PATCH 2/6] First drop of ModelSync.Local --- src/app/build-model-sync-local.properties | 8 + src/app/build-model-sync-local.xml | 7 + .../js/model-extensions/model-sync-local.js | 258 ++++++++++++++++++ 3 files changed, 273 insertions(+) create mode 100644 src/app/build-model-sync-local.properties create mode 100644 src/app/build-model-sync-local.xml create mode 100644 src/app/js/model-extensions/model-sync-local.js diff --git a/src/app/build-model-sync-local.properties b/src/app/build-model-sync-local.properties new file mode 100644 index 00000000000..7200007aa73 --- /dev/null +++ b/src/app/build-model-sync-local.properties @@ -0,0 +1,8 @@ +builddir=../../../builder/componentbuild +srcdir=../.. + +#global.build.component=${srcdir}/build/app + +component=model-sync-local +component.jsfiles=model-extensions/model-sync-local.js +component.requires=model, model-list, json-stringify diff --git a/src/app/build-model-sync-local.xml b/src/app/build-model-sync-local.xml new file mode 100644 index 00000000000..2a13daf16b5 --- /dev/null +++ b/src/app/build-model-sync-local.xml @@ -0,0 +1,7 @@ + + + + + + diff --git a/src/app/js/model-extensions/model-sync-local.js b/src/app/js/model-extensions/model-sync-local.js new file mode 100644 index 00000000000..eaf6cf21985 --- /dev/null +++ b/src/app/js/model-extensions/model-sync-local.js @@ -0,0 +1,258 @@ +/* +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 3.6.0 +**/ + +/** +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.REST], { + root: 'user' + }); + + var Users = Y.Base.create('users', Y.ModelList, [Y.ModelSync.REST], { + model: User, + root : 'user' + }); + +@class ModelSync.Local +@extensionfor Model +@extensionfor ModelList +@since 3.6.0 +**/ +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 3.6.0 +**/ +LocalSync._NON_ATTRS_CFG = ['root']; + +/** +Object of key/value pairs to fall back on when localStorage is not available. + +@property _data +@type Object +@private +**/ +LocalSync._data = {}; + +LocalSync.prototype = { + + // -- Public Methods ------------------------------------------------------- + + /** + Root used as the key inside of localStorage and/or the in-memory store. + + @property root + @type String + @default "" + @since 3.6.0 + **/ + root: '', + + /** + Shortcut for access to localStorage. + + @property storage + @type Storage + @default null + @since 3.6.0 + **/ + storage: null, + + // -- Lifecycle Methods ----------------------------------------------------- + initializer: function (config) { + var store; + + config || (config = {}); + + ('root' in config) && (this.root = config.root || this.constructor.NAME); + try { + this.storage = Y.config.win.localStorage; + store = this.storage.getItem(this.root); + } catch (e) { + Y.log("Could not access localStorage.", "warn"); + } + + // Pull in existing data from localStorage, if possible + LocalSync._data[this.root] = (store && Y.JSON.parse(store)) || {}; + }, + + // -- 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 + falsy. + @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; + + 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; + } + + if (response) { + callback(null, response); + } else { + callback('Data not found'); + } + }, + + // -- 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 3.6.0 + **/ + _index: function (options) { + return Y.Object.values(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 3.6.0 + **/ + _show: function (options) { + return Y.JSON.parse(LocalSync._data[this.root][this.get('id')]); + }, + + /** + Sync method correlating to the "create" operation + + @method _show + @return {Object} The new object created. + @protected + @since 3.6.0 + **/ + _create: function (options) { + var hash = this.toJSON(); + hash.id = this._generateID(this.root); + LocalSync._data[this.root][hash.id] = hash; + + this._save(); + return hash; + }, + + /** + Sync method correlating to the "update" operation. Merges + + @method _update + @return {Object} The updated object. + @protected + @since 3.6.0 + **/ + _update: function (options) { + var hash = this.toJSON(); + LocalSync._data[this.root][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 3.6.0 + **/ + _destroy: function (options) { + delete LocalSync._data[this.root][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 3.6.0 + **/ + _save: function () { + this.storage && this.storage.setItem( + this.root, + Y.JSON.stringify(LocalSync._data[this.root]) + ); + }, + + /** + 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 + '_'); + } +}; + +// -- Namespace --------------------------------------------------------------- + +Y.namespace('ModelSync').Local = LocalSync; From 3750aaa56b14dcd462c577e86caa33f6b7f6b83f Mon Sep 17 00:00:00 2001 From: Clarence Leung Date: Fri, 13 Jul 2012 17:57:29 -0400 Subject: [PATCH 3/6] Updated ModelSync.Local tests --- src/app/tests/model-sync-local-test.js | 170 +++++++++++++++++++++++++ 1 file changed, 170 insertions(+) create mode 100644 src/app/tests/model-sync-local-test.js diff --git a/src/app/tests/model-sync-local-test.js b/src/app/tests/model-sync-local-test.js new file mode 100644 index 00000000000..284da5d10b6 --- /dev/null +++ b/src/app/tests/model-sync-local-test.js @@ -0,0 +1,170 @@ +YUI.add('model-sync-local-test', function (Y) { + +var ArrayAssert = Y.ArrayAssert, + Assert = Y.Assert, + ObjectAssert = Y.ObjectAssert, + + suite, + modelSyncLocalSuite; + +// -- Global Suite ------------------------------------------------------------- +suite = Y.AppTestSuite || (Y.AppTestSuite = new Y.Test.Suite('App Framework')); + +// -- ModelSync.Local Suite ---------------------------------------------------- +modelSyncLocalSuite = new Y.Test.Suite('ModelSync.Local'); + +// -- ModelSync.Local: Lifecycle ----------------------------------------------- +modelSyncLocalSuite.add(new Y.Test.Case({ + name: 'Lifecycle', + + setUp: function () { + Y.TestModel = Y.Base.create('customModel', Y.Model, [Y.ModelSync.Local]); + + Y.TestModelList = Y.Base.create('testModelList', Y.ModelList, [Y.ModelSync.Local], { + model: Y.TestModel + }) + }, + + tearDown: function () { + delete Y.TestModel; + delete Y.TestModelList; + }, + + 'initializer should set the `root` property on the instance': function () { + var model = new Y.TestModel({root: 'model'}), + modelList = new Y.TestModelList({root: 'list'}); + + Assert.areSame('model', model.root); + Assert.areSame('list', modelList.root); + }, + + '`root` property should be the class name by default': function () { + var model = new Y.TestModel(), + modelList = new Y.TestModelList(); + + Assert.areSame('customModel', model.root); + Assert.areSame('testModelList', modelList.root); + }, + + '`localStorage` should be set to the `storage` property': function () { + var model = new Y.TestModel(), + modelList = new Y.TestModelList(); + test = 'test', + hasStorage = function(context) { + try { + context.storage.setItem(test, test); + context.storage.removeItem(test); + return true; + } catch(e) { + return false; + } + }; + + Assert.isTrue(hasStorage(model), 'Model storage not properly set'); + Assert.isTrue(hasStorage(modelList), 'List storage not properly set'); + }, + + '`data` property should be filled with any existing `localStorage` data': function () { + var testStore; + try { + testStore = Y.config.win.localStorage; + testStore.setItem('users', '{"users-1":{"id":"users-1","name":"clarle"},"users-2":{"id":"users-2","name":"eric"}}'); + } catch (e) { + Y.log("Could not access localStorage.", "warn"); + } + + var model = new Y.TestModel({root: 'users', id: 'users-1'}), + modelList = new Y.TestModelList({ root: 'users'}), + data = Y.ModelSync.Local._data; + + Assert.areSame('clarle', data['users']['users-1']['name']); + } +})); + +// -- ModelSync.Local: Sync ---------------------------------------------------- +modelSyncLocalSuite.add(new Y.Test.Case({ + name: 'Sync', + + setUp: function () { + try { + testStore = Y.config.win.localStorage; + testStore.setItem('users', '{"users-1":{"id":"users-1","name":"clarle"},"users-2":{"id":"users-2","name":"eric"}}'); + + } catch (e) { + Y.ModelSync._data = {"users": {"users-1":{"id":"users-1","name":"clarle"},"users-2":{"id":"users-2","name":"eric"}}}; + } + + Y.TestModel = Y.Base.create('users', Y.Model, [Y.ModelSync.Local]); + + Y.TestModelList = Y.Base.create('users', Y.ModelList, [Y.ModelSync.Local], { + model: Y.TestModel + }) + }, + + tearDown: function () { + delete Y.TestModel; + delete Y.TestModelList; + try { + Y.config.win.storage.clear(); + } catch (e) { + Y.log("Could not access localStorage.", "warn"); + } + }, + + 'load() of Model should get the stored local object': function () { + var model = new Y.TestModel({id: 'users-1'}); + model.load(); + Assert.areSame('clarle', model.get('name')); + }, + + 'load() of ModelList should get all stored local objects': function () { + var modelList = new Y.TestModelList(); + modelList.load(); + Assert.areSame(2, modelList.size()); + Assert.areSame('users-1', modelList.item(0).get('id')); + Assert.areSame('clarle', modelList.item(0).get('name')); + }, + + 'save() of a new Model should create a new object with an ID': function () { + var model = new Y.TestModel({name: 'dav'}); + + Assert.isUndefined(model.get('id'), 'Initial model ID should be undefined.'); + model.save(); + Assert.isNotNull(model.get('id'), 'Model ID should not be null'); + Assert.areSame('dav', model.get('name'), 'Model should have correct name'); + }, + + 'save() of an existing Model should update the object': function () { + var model = new Y.TestModel({id: 'users-2'}); + model.load(); + Assert.areSame('eric', model.get('name'), 'Model should have correct name'); + model.set('name', 'satyen'); + model.save(); + Assert.areSame('satyen', model.get('name'), 'Model should have updated name'); + }, + + 'destroy({remove: true}) of an existing Model should delete the object': function () { + var model = new Y.TestModel({id: 'users-1'}), + data; + + model.load(); + Assert.areSame('clarle', model.get('name'), 'Model should have correct name'); + data = Y.ModelSync.Local._data; + Assert.isUndefined(data['users-1'], 'Data should be deleted'); + }, + + 'Failed sync() calls should pass an error message to the callback': function () { + var model = new Y.TestModel({id: 'users-3'}); + + model.sync('read', {}, function (err, res) { + Assert.areSame('Data not found', err); + }); + } +})); + + +suite.add(modelSyncLocalSuite); + +}, '@VERSION@', { + requires: ['model-sync-local', 'model', 'model-list', 'test'] +}); From a782e3c19946ce9764498232665af72a44513209 Mon Sep 17 00:00:00 2001 From: Clarence Leung Date: Fri, 13 Jul 2012 18:01:23 -0400 Subject: [PATCH 4/6] Naming changes for constructor and metadata --- src/app/js/model-extensions/model-sync-local.js | 7 ++++--- src/app/meta/app.json | 9 +++++++++ src/app/tests/app.html | 5 +---- 3 files changed, 14 insertions(+), 7 deletions(-) diff --git a/src/app/js/model-extensions/model-sync-local.js b/src/app/js/model-extensions/model-sync-local.js index eaf6cf21985..ab1ffd1f6b3 100644 --- a/src/app/js/model-extensions/model-sync-local.js +++ b/src/app/js/model-extensions/model-sync-local.js @@ -84,14 +84,15 @@ LocalSync.prototype = { config || (config = {}); - ('root' in config) && (this.root = config.root || this.constructor.NAME); + this.root = config.root || this.constructor.NAME; + try { this.storage = Y.config.win.localStorage; store = this.storage.getItem(this.root); } catch (e) { Y.log("Could not access localStorage.", "warn"); } - + // Pull in existing data from localStorage, if possible LocalSync._data[this.root] = (store && Y.JSON.parse(store)) || {}; }, @@ -174,7 +175,7 @@ LocalSync.prototype = { @since 3.6.0 **/ _show: function (options) { - return Y.JSON.parse(LocalSync._data[this.root][this.get('id')]); + return LocalSync._data[this.root][this.get('id')]; }, /** diff --git a/src/app/meta/app.json b/src/app/meta/app.json index 038247195a1..81d6baee51a 100644 --- a/src/app/meta/app.json +++ b/src/app/meta/app.json @@ -7,6 +7,7 @@ "model", "model-list", "model-sync-rest", + "model-sync-local", "router", "view", "view-node-map" @@ -63,6 +64,14 @@ ] }, + "model-sync-local": { + "requires": [ + "model", + "model-list", + "json-stringify" + ] + }, + "model-sync-rest": { "requires": [ "model", diff --git a/src/app/tests/app.html b/src/app/tests/app.html index 120ffb1299a..efc3c0816ff 100644 --- a/src/app/tests/app.html +++ b/src/app/tests/app.html @@ -26,11 +26,8 @@ 'lazy-model-list-test', 'model-test', 'model-list-test', -<<<<<<< HEAD 'model-sync-local-test', -======= 'model-sync-rest-test', ->>>>>>> 7e7ee6614e70060ba64e6f062a3acf10be925457 'router-test', 'view-test', 'view-node-map-test', @@ -54,7 +51,7 @@ }, 'model-sync-local-test': { - fullpath: 'model-sync-local-test.js', + fullpath: 'model-sync-local-test.js', requires: ['model', 'model-list', 'model-sync-local', 'test'] }, From 04abf405b2faf8ce61eec7ab40e16a91df17ef20 Mon Sep 17 00:00:00 2001 From: Clarence Leung Date: Fri, 13 Jul 2012 18:02:34 -0400 Subject: [PATCH 5/6] Build ModelSync.Local --- .../model-sync-local-debug.js | 264 ++++++++++++++++++ .../model-sync-local/model-sync-local-min.js | 1 + build/model-sync-local/model-sync-local.js | 263 +++++++++++++++++ 3 files changed, 528 insertions(+) create mode 100644 build/model-sync-local/model-sync-local-debug.js create mode 100644 build/model-sync-local/model-sync-local-min.js create mode 100644 build/model-sync-local/model-sync-local.js diff --git a/build/model-sync-local/model-sync-local-debug.js b/build/model-sync-local/model-sync-local-debug.js new file mode 100644 index 00000000000..7e85cde3d93 --- /dev/null +++ b/build/model-sync-local/model-sync-local-debug.js @@ -0,0 +1,264 @@ +YUI.add('model-sync-local', function(Y) { + +/* +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 3.6.0 +**/ + +/** +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.REST], { + root: 'user' + }); + + var Users = Y.Base.create('users', Y.ModelList, [Y.ModelSync.REST], { + model: User, + root : 'user' + }); + +@class ModelSync.Local +@extensionfor Model +@extensionfor ModelList +@since 3.6.0 +**/ +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 3.6.0 +**/ +LocalSync._NON_ATTRS_CFG = ['root']; + +/** +Object of key/value pairs to fall back on when localStorage is not available. + +@property _data +@type Object +@private +**/ +LocalSync._data = {}; + +LocalSync.prototype = { + + // -- Public Methods ------------------------------------------------------- + + /** + Root used as the key inside of localStorage and/or the in-memory store. + + @property root + @type String + @default "" + @since 3.6.0 + **/ + root: '', + + /** + Shortcut for access to localStorage. + + @property storage + @type Storage + @default null + @since 3.6.0 + **/ + storage: null, + + // -- Lifecycle Methods ----------------------------------------------------- + initializer: function (config) { + var store; + + config || (config = {}); + + this.root = config.root || this.constructor.NAME; + + try { + this.storage = Y.config.win.localStorage; + store = this.storage.getItem(this.root); + } catch (e) { + Y.log("Could not access localStorage.", "warn"); + } + + // Pull in existing data from localStorage, if possible + LocalSync._data[this.root] = (store && Y.JSON.parse(store)) || {}; + }, + + // -- 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 + falsy. + @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; + + 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; + } + + if (response) { + callback(null, response); + } else { + callback('Data not found'); + } + }, + + // -- 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 3.6.0 + **/ + _index: function (options) { + return Y.Object.values(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 3.6.0 + **/ + _show: function (options) { + return LocalSync._data[this.root][this.get('id')]; + }, + + /** + Sync method correlating to the "create" operation + + @method _show + @return {Object} The new object created. + @protected + @since 3.6.0 + **/ + _create: function (options) { + var hash = this.toJSON(); + hash.id = this._generateID(this.root); + LocalSync._data[this.root][hash.id] = hash; + + this._save(); + return hash; + }, + + /** + Sync method correlating to the "update" operation. Merges + + @method _update + @return {Object} The updated object. + @protected + @since 3.6.0 + **/ + _update: function (options) { + var hash = this.toJSON(); + LocalSync._data[this.root][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 3.6.0 + **/ + _destroy: function (options) { + delete LocalSync._data[this.root][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 3.6.0 + **/ + _save: function () { + this.storage && this.storage.setItem( + this.root, + Y.JSON.stringify(LocalSync._data[this.root]) + ); + }, + + /** + 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 + '_'); + } +}; + +// -- Namespace --------------------------------------------------------------- + +Y.namespace('ModelSync').Local = LocalSync; + + +}, '@VERSION@' ,{requires:['model', 'model-list', 'json-stringify']}); diff --git a/build/model-sync-local/model-sync-local-min.js b/build/model-sync-local/model-sync-local-min.js new file mode 100644 index 00000000000..3bebe279cc8 --- /dev/null +++ b/build/model-sync-local/model-sync-local-min.js @@ -0,0 +1 @@ +YUI.add("model-sync-local",function(b){function a(){}a._NON_ATTRS_CFG=["root"];a._data={};a.prototype={root:"",storage:null,initializer:function(d){var c;d||(d={});this.root=d.root||this.constructor.NAME;try{this.storage=b.config.win.localStorage;c=this.storage.getItem(this.root);}catch(f){}a._data[this.root]=(c&&b.JSON.parse(c))||{};},sync:function(e,d,f){d||(d={});var c;switch(e){case"read":if(this._isYUIModelList){c=this._index(d);}else{c=this._show(d);}break;case"create":c=this._create(d);break;case"update":c=this._update(d);break;case"delete":c=this._destroy(d);break;}if(c){f(null,c);}else{f("Data not found");}},_index:function(c){return b.Object.values(a._data[this.root]);},_show:function(c){return a._data[this.root][this.get("id")];},_create:function(c){var d=this.toJSON();d.id=this._generateID(this.root);a._data[this.root][d.id]=d;this._save();return d;},_update:function(c){var d=this.toJSON();a._data[this.root][this.get("id")]=d;this._save();return d;},_destroy:function(c){delete a._data[this.root][this.get("id")];this._save();return this.toJSON();},_save:function(){this.storage&&this.storage.setItem(this.root,b.JSON.stringify(a._data[this.root]));},_generateID:function(c){return b.guid(c+"_");}};b.namespace("ModelSync").Local=a;},"@VERSION@",{requires:["model","model-list","json-stringify"]}); \ No newline at end of file diff --git a/build/model-sync-local/model-sync-local.js b/build/model-sync-local/model-sync-local.js new file mode 100644 index 00000000000..614169267f8 --- /dev/null +++ b/build/model-sync-local/model-sync-local.js @@ -0,0 +1,263 @@ +YUI.add('model-sync-local', function(Y) { + +/* +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 3.6.0 +**/ + +/** +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.REST], { + root: 'user' + }); + + var Users = Y.Base.create('users', Y.ModelList, [Y.ModelSync.REST], { + model: User, + root : 'user' + }); + +@class ModelSync.Local +@extensionfor Model +@extensionfor ModelList +@since 3.6.0 +**/ +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 3.6.0 +**/ +LocalSync._NON_ATTRS_CFG = ['root']; + +/** +Object of key/value pairs to fall back on when localStorage is not available. + +@property _data +@type Object +@private +**/ +LocalSync._data = {}; + +LocalSync.prototype = { + + // -- Public Methods ------------------------------------------------------- + + /** + Root used as the key inside of localStorage and/or the in-memory store. + + @property root + @type String + @default "" + @since 3.6.0 + **/ + root: '', + + /** + Shortcut for access to localStorage. + + @property storage + @type Storage + @default null + @since 3.6.0 + **/ + storage: null, + + // -- Lifecycle Methods ----------------------------------------------------- + initializer: function (config) { + var store; + + config || (config = {}); + + this.root = config.root || this.constructor.NAME; + + try { + this.storage = Y.config.win.localStorage; + store = this.storage.getItem(this.root); + } catch (e) { + } + + // Pull in existing data from localStorage, if possible + LocalSync._data[this.root] = (store && Y.JSON.parse(store)) || {}; + }, + + // -- 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 + falsy. + @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; + + 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; + } + + if (response) { + callback(null, response); + } else { + callback('Data not found'); + } + }, + + // -- 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 3.6.0 + **/ + _index: function (options) { + return Y.Object.values(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 3.6.0 + **/ + _show: function (options) { + return LocalSync._data[this.root][this.get('id')]; + }, + + /** + Sync method correlating to the "create" operation + + @method _show + @return {Object} The new object created. + @protected + @since 3.6.0 + **/ + _create: function (options) { + var hash = this.toJSON(); + hash.id = this._generateID(this.root); + LocalSync._data[this.root][hash.id] = hash; + + this._save(); + return hash; + }, + + /** + Sync method correlating to the "update" operation. Merges + + @method _update + @return {Object} The updated object. + @protected + @since 3.6.0 + **/ + _update: function (options) { + var hash = this.toJSON(); + LocalSync._data[this.root][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 3.6.0 + **/ + _destroy: function (options) { + delete LocalSync._data[this.root][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 3.6.0 + **/ + _save: function () { + this.storage && this.storage.setItem( + this.root, + Y.JSON.stringify(LocalSync._data[this.root]) + ); + }, + + /** + 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 + '_'); + } +}; + +// -- Namespace --------------------------------------------------------------- + +Y.namespace('ModelSync').Local = LocalSync; + + +}, '@VERSION@' ,{requires:['model', 'model-list', 'json-stringify']}); From 838026411c0b060006d66068850a6d1b79ce03e1 Mon Sep 17 00:00:00 2001 From: Clarence Leung Date: Mon, 16 Jul 2012 23:47:45 -0400 Subject: [PATCH 6/6] Removing model-list dependency and fixing docs. --- src/app/build-model-sync-local.properties | 2 +- src/app/js/model-extensions/model-sync-local.js | 4 ++-- src/app/meta/app.json | 1 - 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/app/build-model-sync-local.properties b/src/app/build-model-sync-local.properties index 7200007aa73..30dca59ba51 100644 --- a/src/app/build-model-sync-local.properties +++ b/src/app/build-model-sync-local.properties @@ -5,4 +5,4 @@ srcdir=../.. component=model-sync-local component.jsfiles=model-extensions/model-sync-local.js -component.requires=model, model-list, json-stringify +component.requires=model, json-stringify diff --git a/src/app/js/model-extensions/model-sync-local.js b/src/app/js/model-extensions/model-sync-local.js index ab1ffd1f6b3..fb1c09ae3dc 100644 --- a/src/app/js/model-extensions/model-sync-local.js +++ b/src/app/js/model-extensions/model-sync-local.js @@ -16,11 +16,11 @@ 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.REST], { + var User = Y.Base.create('user', Y.Model, [Y.ModelSync.Local], { root: 'user' }); - var Users = Y.Base.create('users', Y.ModelList, [Y.ModelSync.REST], { + var Users = Y.Base.create('users', Y.ModelList, [Y.ModelSync.Local], { model: User, root : 'user' }); diff --git a/src/app/meta/app.json b/src/app/meta/app.json index 81d6baee51a..21dfd40c2f6 100644 --- a/src/app/meta/app.json +++ b/src/app/meta/app.json @@ -67,7 +67,6 @@ "model-sync-local": { "requires": [ "model", - "model-list", "json-stringify" ] },