forked from vincentmac/backbone-idb
-
Notifications
You must be signed in to change notification settings - Fork 2
/
backbone-idb.js
361 lines (315 loc) · 11.9 KB
/
backbone-idb.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
/**
* @license
* Backbone IndexedDB Adapter
* Version 0.2.6
* Copyright (c) 2013-2014 Vincent Mac
*
* Available under MIT license <https://raw.github.com/vincentmac/backbone-idb/master/LICENSE>
*
* http://github.com/vincentmac/backbone-idb
*/
;(function (global, factory) {
'use strict';
if (typeof exports === 'object' && typeof require === 'function') {
// CommonJS Module - Register as a CommonJS Module
module.exports = factory(require('underscore'), require('backbone'), require('idb-wrapper'), require('jquery'), global);
} else if (typeof define === 'function' && define.amd) {
// AMD - Register as an anonymous module
define(['underscore', 'backbone', 'jquery'], function(_, Backbone, $) {
return factory(_ || global._, Backbone || global.Backbone, IDBStore || global.IDBStore, $ || global.$, 'AMD');
});
} else {
factory(_, Backbone, IDBStore, $ || jQuery, global);
}
}(global || window, function(_, Backbone, IDBStore, $, global) {
'use strict';
// // Generate four random hex digits.
// function S4() {
// return (((1+Math.random())*0x10000)|0).toString(16).substring(1);
// }
// // Generate a pseudo-GUID by concatenating random hexadecimal.
// function guid() {
// return (S4()+S4()+"-"+S4()+"-"+S4()+"-"+S4()+"-"+S4()+S4()+S4());
// }
var defaultErrorHandler = function (error) {
throw error;
};
var noop = function () {
};
Backbone.IndexedDB = function IndexedDB(options, parent) {
var that = this;
this.parent = parent; // reference to the model or collection
var defaultReadyHandler = function () {
// console.log('idb:ready this:', this); // <IDBStore>
// console.log('idb:ready that:', that); // <IndexedDB>
// By default, make the Backbone.IndexedDB available through `parent.indexedDB`
// that.parent.indexedDB = that;
// Fire ready event on parent model or collection
that.parent.trigger('idb:ready', that);
};
var defaults = {
storeName: 'Store',
storePrefix: '',
dbVersion: 1,
keyPath: 'id',
autoIncrement: true,
onStoreReady: defaultReadyHandler,
onError: defaultErrorHandler,
indexes: []
};
options = _.defaults(options || {}, defaults);
this.dbName = options.storePrefix + options.storeName;
this.store = new IDBStore(options);
this.keyPath = options.keyPath;
};
// _.extend(Backbone.IndexedDB.prototype, {
Backbone.IndexedDB.prototype = {
/**
* The version of Backbone.IndexedDB
*
* @type String
*/
version: '0.2.10',
/**
* Add a new model to the store
*
* @param {Backbone.Model} model - Backbone model to add to store
* @param {Object} options - sync options created by Backbone
* @param {Function} [options.success] - overridable success callback
* @param {Function} [options.error] - overridable error callback
*/
create: function(model, options) {
var data = model.attributes;
var that = this;
this.store.put(data, function(insertedId) {
data[that.keyPath] = insertedId;
options.success(data)
}, options.error);
},
/**
* Update a model in the store
*
* @param {Backbone.Model} model - Backbone model to update and save to store
* @param {Object} options - sync options created by Backbone
* @param {Function} [options.success] - overridable success callback
* @param {Function} [options.error] - overridable error callback
*/
update: function(model, options) {
this.store.put(model.attributes, options.success, options.error);
},
/**
* Retrieve a model from the store
*
* @param {Backbone.Model} model - Backbone model to get from store
* @param {Object} options - sync options created by Backbone
* @param {Function} [options.success] - overridable success callback
* @param {Function} [options.error] - overridable error callback
*/
read: function(model, options) {
this.store.get(model.id, options.success, options.error);
},
/**
* Retrieve a collection from the store
*
* @param {Object} options - sync options created by Backbone
* @param {Function} [options.success] - overridable success callback
* @param {Function} [options.error] - overridable error callback
*/
getAll: function(options) {
this.store.getAll(options.success, options.error);
},
/**
* Delete a model from the store
*
* @param {Backbone.Model} model - Backbone model to delete from store
* @param {Object} options - sync options created by Backbone
* @param {Function} [options.success] - overridable success callback
* @param {Function} [options.error] - overridable error callback
*/
destroy: function(model, options) {
if (model.isNew()) {
return false;
}
this.store.remove(model.id, options.success, options.error);
},
/**
* Iterates over the store using the given options and calling onItem
* for each entry matching the options.
*
* @param {Function} onItem - A callback to be called for each match
* @param {Object} [options] - An object defining specific options
* @param {Object} [options.index=null] - An IDBIndex to operate on
* @param {String} [options.order=ASC] - The order in which to provide the
* results, can be 'DESC' or 'ASC'
* @param {Boolean} [options.autoContinue=true] - Whether to automatically
* iterate the cursor to the next result
* @param {Boolean} [options.filterDuplicates=false] - Whether to exclude
* duplicate matches
* @param {Object} [options.keyRange=null] - An IDBKeyRange to use
* @param {Boolean} [options.writeAccess=false] - Whether grant write access
* to the store in the onItem callback
* @param {Function} [options.onEnd=null] - A callback to be called after
* iteration has ended
* @param {Function} [options.onError=throw] - A callback to be called
* if an error occurred during the operation.
*/
iterate: function(onItem, options) {
if (options.keyRange && !(options.keyRange instanceof global.IDBKeyRange)) {
options.keyRange = this.makeKeyRange(options.keyRange);
}
this.store.iterate(onItem, options);
},
/**
* Creates a key range using specified options. This key range can be
* handed over to the count() and iterate() methods.
*
* Note: You must provide at least one or both of "lower" or "upper" value.
*
* @param {Object} options The options for the key range to create
* @param {*} [options.lower] The lower bound
* @param {Boolean} [options.excludeLower] Whether to exclude the lower
* bound passed in options.lower from the key range
* @param {*} [options.upper] The upper bound
* @param {Boolean} [options.excludeUpper] Whether to exclude the upper
* bound passed in options.upper from the key range
* @param {*} [options.only] A single key value. Use this if you need a key
* range that only includes one value for a key. Providing this
* property invalidates all other properties.
* @return {Object} The IDBKeyRange representing the specified options
*/
makeKeyRange: function(options) {
return this.store.makeKeyRange(options);
},
/**
* Perform a batch operation to save all models in the current collection to indexedDB.
*
* @param {Function} [onSuccess] - success callback
* @param {Function} [onError] - error callback
*/
saveAll: function(onSuccess, onError) {
onSuccess || (onSuccess = noop);
onError || (onError = defaultErrorHandler);
this.store.putBatch(this.parent.toJSON(), onSuccess, onError);
},
/**
* Perform a batch operation to save and/or remove models in the current collection to
* indexedDB. This is a proxy to the idbstore `batch` method
*
* @param {Array} dataArray - Array of objects containing the operation to run and
* the model (for put operations).
* @param {Function} [onSuccess] - success callback
* @param {Function} [onError] - error callback
*/
batch: function(dataArray, onSuccess, onError) {
onSuccess || (onSuccess = noop);
onError || (onError = defaultErrorHandler);
this.store.batch(dataArray, onSuccess, onError);
},
/**
* Perform a batch put operation to save models to indexedDB. This is a
* proxy to the idbstore `putBatch` method
*
* @param {Array} dataArray - Array of models (in JSON) to store
* @param {Function} [onSuccess] - success callback
* @param {Function} [onError] - error callback
*/
putBatch: function(dataArray, onSuccess, onError) {
onSuccess || (onSuccess = noop);
onError || (onError = defaultErrorHandler);
this.store.putBatch(dataArray, onSuccess, onError);
},
/**
* Perform a batch operation to remove models from indexedDB. This is a
* proxy to the idbstore `removeBtch` method
*
* @param {Array} keyArray - keyArray An array of keys to remove
* @param {Function} [onSuccess] - success callback
* @param {Function} [onError] - error callback
*/
removeBatch: function(keyArray, onSuccess, onError) {
onSuccess || (onSuccess = noop);
onError || (onError = defaultErrorHandler);
this.store.removeBatch(keyArray, onSuccess, onError);
},
/**
* Clears all content from the current indexedDB for this collection/model
*
* @param {Function} [onSuccess] - success callback
* @param {Function} [onError] - error callback
*/
clear: function(onSuccess, onError) {
onSuccess || (onSuccess = noop);
onError || (onError = defaultErrorHandler);
this.store.clear(onSuccess, onError);
},
/**
* Deletes the current indexedDB for this collection/model
*/
deleteDatabase: function() {
this.store.deleteDatabase();
}
// });
};
/**
* Backbone.sync drop-in replacement
*
* This function replaces the model or collection's sync method and remains
* compliant with Backbone's api.
*/
Backbone.IndexedDB.sync = Backbone.idbSync = function(method, model, options) {
var deferred = new $.Deferred();
var db = model.indexedDB || model.collection.indexedDB;
// console.log('Backbone.IndexedDB.sync', method, model, options);
var success = options.success || noop;
var error = options.success || noop;
options.success = function (result) {
success.apply(this, arguments);
deferred.resolve(result);
};
options.error = function (result) {
error.apply(this, arguments);
deferred.reject(result);
};
switch (method) {
// Retrieve an individual model or entire collection from indexedDB
case 'read':
model.id !== undefined ? db.read(model, options) : db.getAll(options);
break;
case 'create':
if (model.id) {
db.update(model, options);
} else {
db.create(model, options);
}
break;
case 'update':
if (model.id) {
db.update(model, options);
} else {
db.create(model, options);
}
break;
case 'delete':
if (model.id) {
db.destroy(model, options);
}
break;
}
return deferred.promise();
};
// Reference original `Backbone.sync`
Backbone.ajaxSync = Backbone.sync;
Backbone.getIDBSyncMethod = function(model) {
if(model.indexedDB || (model.collection && model.collection.indexedDB)) {
return Backbone.idbSync;
}
return Backbone.ajaxSync;
};
// Override 'Backbone.sync' to default to idbSync,
// the original 'Backbone.sync' is still available in 'Backbone.ajaxSync'
Backbone.sync = function(method, model, options) {
return Backbone.getIDBSyncMethod(model).apply(this, [method, model, options]);
};
Backbone.IndexedDB.version = Backbone.IndexedDB.prototype.version;
return Backbone.IndexedDB;
}));