|
| 1 | +/** |
| 2 | + * Backbone localStorage Adapter |
| 3 | + * Version 1.1.7 |
| 4 | + * |
| 5 | + * https://github.com/jeromegn/Backbone.localStorage |
| 6 | + */ |
| 7 | +(function (root, factory) { |
| 8 | + if (typeof exports === 'object' && typeof require === 'function') { |
| 9 | + module.exports = factory(require("underscore"), require("backbone")); |
| 10 | + } else if (typeof define === "function" && define.amd) { |
| 11 | + // AMD. Register as an anonymous module. |
| 12 | + define(["underscore","backbone"], function(_, Backbone) { |
| 13 | + // Use global variables if the locals are undefined. |
| 14 | + return factory(_ || root._, Backbone || root.Backbone); |
| 15 | + }); |
| 16 | + } else { |
| 17 | + // RequireJS isn't being used. Assume underscore and backbone are loaded in <script> tags |
| 18 | + factory(_, Backbone); |
| 19 | + } |
| 20 | +}(this, function(_, Backbone) { |
| 21 | +// A simple module to replace `Backbone.sync` with *localStorage*-based |
| 22 | +// persistence. Models are given GUIDS, and saved into a JSON object. Simple |
| 23 | +// as that. |
| 24 | + |
| 25 | +// Hold reference to Underscore.js and Backbone.js in the closure in order |
| 26 | +// to make things work even if they are removed from the global namespace |
| 27 | + |
| 28 | +// Generate four random hex digits. |
| 29 | +function S4() { |
| 30 | + return (((1+Math.random())*0x10000)|0).toString(16).substring(1); |
| 31 | +}; |
| 32 | + |
| 33 | +// Generate a pseudo-GUID by concatenating random hexadecimal. |
| 34 | +function guid() { |
| 35 | + return (S4()+S4()+"-"+S4()+"-"+S4()+"-"+S4()+"-"+S4()+S4()+S4()); |
| 36 | +}; |
| 37 | + |
| 38 | +// Our Store is represented by a single JS object in *localStorage*. Create it |
| 39 | +// with a meaningful name, like the name you'd give a table. |
| 40 | +// window.Store is deprectated, use Backbone.LocalStorage instead |
| 41 | +Backbone.LocalStorage = window.Store = function(name) { |
| 42 | + if( !this.localStorage ) { |
| 43 | + throw "Backbone.localStorage: Environment does not support localStorage." |
| 44 | + } |
| 45 | + this.name = name; |
| 46 | + var store = this.localStorage().getItem(this.name); |
| 47 | + this.records = (store && store.split(",")) || []; |
| 48 | +}; |
| 49 | + |
| 50 | +_.extend(Backbone.LocalStorage.prototype, { |
| 51 | + |
| 52 | + // Save the current state of the **Store** to *localStorage*. |
| 53 | + save: function() { |
| 54 | + this.localStorage().setItem(this.name, this.records.join(",")); |
| 55 | + }, |
| 56 | + |
| 57 | + // Add a model, giving it a (hopefully)-unique GUID, if it doesn't already |
| 58 | + // have an id of it's own. |
| 59 | + create: function(model) { |
| 60 | + if (!model.id) { |
| 61 | + model.id = guid(); |
| 62 | + model.set(model.idAttribute, model.id); |
| 63 | + } |
| 64 | + this.localStorage().setItem(this.name+"-"+model.id, JSON.stringify(model)); |
| 65 | + this.records.push(model.id.toString()); |
| 66 | + this.save(); |
| 67 | + return this.find(model); |
| 68 | + }, |
| 69 | + |
| 70 | + // Update a model by replacing its copy in `this.data`. |
| 71 | + update: function(model) { |
| 72 | + this.localStorage().setItem(this.name+"-"+model.id, JSON.stringify(model)); |
| 73 | + if (!_.include(this.records, model.id.toString())) |
| 74 | + this.records.push(model.id.toString()); this.save(); |
| 75 | + return this.find(model); |
| 76 | + }, |
| 77 | + |
| 78 | + // Retrieve a model from `this.data` by id. |
| 79 | + find: function(model) { |
| 80 | + return this.jsonData(this.localStorage().getItem(this.name+"-"+model.id)); |
| 81 | + }, |
| 82 | + |
| 83 | + // Return the array of all models currently in storage. |
| 84 | + findAll: function() { |
| 85 | + // Lodash removed _#chain in v1.0.0-rc.1 |
| 86 | + return (_.chain || _)(this.records) |
| 87 | + .map(function(id){ |
| 88 | + return this.jsonData(this.localStorage().getItem(this.name+"-"+id)); |
| 89 | + }, this) |
| 90 | + .compact() |
| 91 | + .value(); |
| 92 | + }, |
| 93 | + |
| 94 | + // Delete a model from `this.data`, returning it. |
| 95 | + destroy: function(model) { |
| 96 | + if (model.isNew()) |
| 97 | + return false |
| 98 | + this.localStorage().removeItem(this.name+"-"+model.id); |
| 99 | + this.records = _.reject(this.records, function(id){ |
| 100 | + return id === model.id.toString(); |
| 101 | + }); |
| 102 | + this.save(); |
| 103 | + return model; |
| 104 | + }, |
| 105 | + |
| 106 | + localStorage: function() { |
| 107 | + return localStorage; |
| 108 | + }, |
| 109 | + |
| 110 | + // fix for "illegal access" error on Android when JSON.parse is passed null |
| 111 | + jsonData: function (data) { |
| 112 | + return data && JSON.parse(data); |
| 113 | + }, |
| 114 | + |
| 115 | + // Clear localStorage for specific collection. |
| 116 | + _clear: function() { |
| 117 | + var local = this.localStorage(), |
| 118 | + itemRe = new RegExp("^" + this.name + "-"); |
| 119 | + |
| 120 | + // Remove id-tracking item (e.g., "foo"). |
| 121 | + local.removeItem(this.name); |
| 122 | + |
| 123 | + // Lodash removed _#chain in v1.0.0-rc.1 |
| 124 | + // Match all data items (e.g., "foo-ID") and remove. |
| 125 | + (_.chain || _)(local).keys() |
| 126 | + .filter(function (k) { return itemRe.test(k); }) |
| 127 | + .each(function (k) { local.removeItem(k); }); |
| 128 | + |
| 129 | + this.records.length = 0; |
| 130 | + }, |
| 131 | + |
| 132 | + // Size of localStorage. |
| 133 | + _storageSize: function() { |
| 134 | + return this.localStorage().length; |
| 135 | + } |
| 136 | + |
| 137 | +}); |
| 138 | + |
| 139 | +// localSync delegate to the model or collection's |
| 140 | +// *localStorage* property, which should be an instance of `Store`. |
| 141 | +// window.Store.sync and Backbone.localSync is deprecated, use Backbone.LocalStorage.sync instead |
| 142 | +Backbone.LocalStorage.sync = window.Store.sync = Backbone.localSync = function(method, model, options) { |
| 143 | + var store = model.localStorage || model.collection.localStorage; |
| 144 | + |
| 145 | + var resp, errorMessage, syncDfd = Backbone.$.Deferred && Backbone.$.Deferred(); //If $ is having Deferred - use it. |
| 146 | + |
| 147 | + try { |
| 148 | + |
| 149 | + switch (method) { |
| 150 | + case "read": |
| 151 | + resp = model.id != undefined ? store.find(model) : store.findAll(); |
| 152 | + break; |
| 153 | + case "create": |
| 154 | + resp = store.create(model); |
| 155 | + break; |
| 156 | + case "update": |
| 157 | + resp = store.update(model); |
| 158 | + break; |
| 159 | + case "delete": |
| 160 | + resp = store.destroy(model); |
| 161 | + break; |
| 162 | + } |
| 163 | + |
| 164 | + } catch(error) { |
| 165 | + if (error.code === 22 && store._storageSize() === 0) |
| 166 | + errorMessage = "Private browsing is unsupported"; |
| 167 | + else |
| 168 | + errorMessage = error.message; |
| 169 | + } |
| 170 | + |
| 171 | + if (resp) { |
| 172 | + if (options && options.success) { |
| 173 | + if (Backbone.VERSION === "0.9.10") { |
| 174 | + options.success(model, resp, options); |
| 175 | + } else { |
| 176 | + options.success(resp); |
| 177 | + } |
| 178 | + } |
| 179 | + if (syncDfd) { |
| 180 | + syncDfd.resolve(resp); |
| 181 | + } |
| 182 | + |
| 183 | + } else { |
| 184 | + errorMessage = errorMessage ? errorMessage |
| 185 | + : "Record Not Found"; |
| 186 | + |
| 187 | + if (options && options.error) |
| 188 | + if (Backbone.VERSION === "0.9.10") { |
| 189 | + options.error(model, errorMessage, options); |
| 190 | + } else { |
| 191 | + options.error(errorMessage); |
| 192 | + } |
| 193 | + |
| 194 | + if (syncDfd) |
| 195 | + syncDfd.reject(errorMessage); |
| 196 | + } |
| 197 | + |
| 198 | + // add compatibility with $.ajax |
| 199 | + // always execute callback for success and error |
| 200 | + if (options && options.complete) options.complete(resp); |
| 201 | + |
| 202 | + return syncDfd && syncDfd.promise(); |
| 203 | +}; |
| 204 | + |
| 205 | +Backbone.ajaxSync = Backbone.sync; |
| 206 | + |
| 207 | +Backbone.getSyncMethod = function(model) { |
| 208 | + if(model.localStorage || (model.collection && model.collection.localStorage)) { |
| 209 | + return Backbone.localSync; |
| 210 | + } |
| 211 | + |
| 212 | + return Backbone.ajaxSync; |
| 213 | +}; |
| 214 | + |
| 215 | +// Override 'Backbone.sync' to default to localSync, |
| 216 | +// the original 'Backbone.sync' is still available in 'Backbone.ajaxSync' |
| 217 | +Backbone.sync = function(method, model, options) { |
| 218 | + return Backbone.getSyncMethod(model).apply(this, [method, model, options]); |
| 219 | +}; |
| 220 | + |
| 221 | +return Backbone.LocalStorage; |
| 222 | +})); |
0 commit comments