PageRenderTime 51ms CodeModel.GetById 21ms RepoModel.GetById 0ms app.codeStats 0ms

/backbone.safe.js

https://github.com/Fer0x/Backbone.Safe
JavaScript | 248 lines | 133 code | 39 blank | 76 comment | 28 complexity | d1dce03c3c1e5623b371fc389bbfb801 MD5 | raw file
Possible License(s): MIT
  1. /**
  2. * Safe - support for storing Backbone.Model to localstorage/sessionstorage
  3. * using the 'set' method of Model
  4. *
  5. * @constructor - use the key 'safe' to define unique storage key for backbone safe
  6. *
  7. * examples:
  8. *
  9. * // simple defintion for safe
  10. * Backbone.Model.extend({ key: 'my-unique-key' });
  11. *
  12. * // advanced defintion for safe with options
  13. * Backbone.Model.extend({
  14. *
  15. * safe: {
  16. * key: 'my-unique-key',
  17. * options: {
  18. * reload: true
  19. * }
  20. * }
  21. *
  22. * })
  23. *
  24. * @requires Backbone.js, Underscore.js
  25. * @param {string} uniqueID - the name of the storage you'de like to use
  26. * @param {object} context - the Backbone.Model instance reference
  27. * @param {object} options - (optional) configuration for setting up various features
  28. * - {boolean} reload - true to reload (before initialize) data from local/session storage if exists
  29. *
  30. * @author Oren Farhi, http://orizens.com
  31. *
  32. * @version 0.3
  33. *
  34. */
  35. (function(){
  36. var _ = this._;
  37. var Backbone = this.Backbone;
  38. // if Underscore or Backbone have not been loaded
  39. // exit to prevent js errors
  40. if (!_ || !Backbone || !JSON) {
  41. return;
  42. }
  43. // factory for creating extend replacement for Backbone Objects
  44. function BackboneExtender(bbObject, plugins) {
  45. var thisExtender = this;
  46. this.plugins = plugins;
  47. bbObject["extend"] = _.wrap(bbObject["extend"], function(sourceExtend, config){
  48. config = config || {}
  49. // thisExtender.config = config;
  50. var _sourceFn = config.initialize || this.prototype.initialize || function(){};
  51. config.initialize = function(){
  52. var args = [].slice.call(arguments);
  53. thisExtender.config = config;
  54. thisExtender.applyPlugins(this, args);
  55. _sourceFn.apply(this, args);
  56. };
  57. return sourceExtend.call(this, config);
  58. });
  59. };
  60. BackboneExtender.prototype.applyPlugins = function(instance, args) {
  61. var config = this.config,
  62. plugins = this.plugins,
  63. args = args || [];
  64. // run the plugins on this
  65. _.each(plugins, function(plugFn){
  66. plugFn.call(instance, config, args);
  67. });
  68. };
  69. BackboneExtender.prototype.addPlug = function(plugFn) {
  70. this.plugins.push(plugFn);
  71. };
  72. var SafePlug = function (config, args) {
  73. var storageKey,
  74. storageType;
  75. // create safe if exist as key
  76. if (config && config.safe) {
  77. // handle key, value safe
  78. storageKey = config.safe.key ? config.safe.key : config.safe;
  79. // get which storage should be use
  80. storageType = config.safe.type ? config.safe.type : 'local';
  81. Backbone.Safe.create(storageKey, this, storageType, config.safe.options || { reload: true });
  82. }
  83. }
  84. // extend Model & Collection constructor to handle safe initialization
  85. // Backbone.Model.extend = _.wrap(Backbone.Model.extend, BackboneExtender)
  86. var modelSafePlugin = new BackboneExtender(Backbone.Model, [ SafePlug ]);
  87. var collectionSafePlugin = new BackboneExtender(Backbone.Collection, [ SafePlug ]);
  88. Backbone.Safe = function(uniqueID, context, type, options) {
  89. // parsing options settings
  90. this._reload = options && options.reload && options.reload === true;
  91. this.uid = uniqueID;
  92. this.type = type;
  93. this.context = context;
  94. this.isCollection = context.models && context.add;
  95. // mixins for collection and model
  96. var collection = {
  97. // events that Safe is listening in order to
  98. // trigger save to storage
  99. events: 'add reset change sort dump',
  100. // the value to be used when cleaning the safe
  101. emptyValue: '[]',
  102. reload: function(options) {
  103. context.add(this.getData(), options);
  104. },
  105. fetch: function(options) {
  106. var fetchFromSafe = options && options.from;
  107. if (fetchFromSafe && fetchFromSafe === "safe") {
  108. this.safe.reload(options);
  109. } else {
  110. Backbone.Collection.prototype.fetch.apply(this, arguments);
  111. }
  112. },
  113. toJSON: function(model) {
  114. if (model.collection) { // From add and remove, this will be a model
  115. return model.collection.toJSON();
  116. }
  117. else {
  118. return model.toJSON();
  119. }
  120. }
  121. };
  122. var model = {
  123. events: 'change',
  124. emptyValue: '{}',
  125. reload: function(options) {
  126. context.set(this.getData(), options);
  127. },
  128. // options = { from: "safe" }
  129. fetch: function (options) {
  130. var fetchFromSafe = options && options.from;
  131. if (fetchFromSafe && fetchFromSafe === "safe") {
  132. this.safe.reload(options);
  133. } else {
  134. Backbone.Model.prototype.fetch.apply(this, arguments);
  135. }
  136. },
  137. toJSON: function(model) {
  138. return model.toJSON();
  139. }
  140. };
  141. // attach relevant object to Safe prototype
  142. _.extend( this, this.isCollection ? collection : model );
  143. // if the uid doesn't exist, create it
  144. this.ensureUID();
  145. // These are the lines that are responsible for
  146. // loading the saved data from the storage to the model
  147. //
  148. // the data is loaded before the Safe binds to change events
  149. // storage exist ? -> save to model
  150. // if it's a collection - use add
  151. if (this._reload) {
  152. this.reload();
  153. }
  154. // attach Backbone custom methods
  155. _.extend(context, _.pick(this, ['fetch']));
  156. // listen to any change event and cache it
  157. this.debouncedStore = _.debounce(_.bind(this.store, this), 75);
  158. context.on(this.events, this.debouncedStore, this);
  159. // adding destroy handler
  160. context.on('destroy', this.destroy, this);
  161. };
  162. Backbone.Safe.prototype = {
  163. /**
  164. * creates a storage item with the provided
  165. * UID if not exist
  166. */
  167. ensureUID: function() {
  168. if (_.isNull(this.getData())){
  169. this.create();
  170. }
  171. },
  172. create: function() {
  173. this.storage().setItem(this.uid, this.emptyValue);
  174. },
  175. /*
  176. * @bbDataObj {collection/model}
  177. */
  178. store: function(bbDataObj) {
  179. this.storage()
  180. .setItem(this.uid, JSON.stringify( this.toJSON( bbDataObj )));
  181. },
  182. storage: function() {
  183. return this.type == 'session' ? sessionStorage : localStorage;
  184. },
  185. /**
  186. * returns json object of the local saved data
  187. * @return {json}
  188. */
  189. getData: function() {
  190. // JSON.parse can't be run with an empty string
  191. this._current = this.storage().getItem(this.uid);
  192. return this._current ? JSON.parse(this._current) : this._current;
  193. },
  194. // set the local storage key to the empty value
  195. reset: function() {
  196. this.create();
  197. },
  198. // removes the key from the localstorage
  199. destroy: function() {
  200. this.storage().removeItem( this.uid );
  201. }
  202. };
  203. // factory method
  204. Backbone.Safe.create = function( uniqueID, context, type, options) {
  205. if (uniqueID && context) {
  206. context.safe = new Backbone.Safe(uniqueID, context, type, options);
  207. }
  208. };
  209. })();