@converse/skeletor 0.0.7 → 0.0.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +38 -25
- package/dist/skeletor.js +10413 -6875
- package/dist/skeletor.js.map +1 -1
- package/dist/skeletor.min.js +74 -3
- package/dist/skeletor.min.js.map +1 -1
- package/package.json +23 -12
- package/src/collection.js +540 -395
- package/src/drivers/sessionStorage.js +226 -232
- package/src/element.js +132 -82
- package/src/eventemitter.js +187 -0
- package/src/helpers.js +148 -114
- package/src/{main.js → index.js} +21 -17
- package/src/listening.js +81 -0
- package/src/model.js +340 -240
- package/src/storage.js +223 -241
- package/src/types/collection.d.ts +366 -0
- package/src/types/collection.d.ts.map +1 -0
- package/src/types/drivers/sessionStorage.d.ts +37 -0
- package/src/types/drivers/sessionStorage.d.ts.map +1 -0
- package/src/types/element.d.ts +115 -0
- package/src/types/element.d.ts.map +1 -0
- package/src/types/eventemitter.d.ts +93 -0
- package/src/types/eventemitter.d.ts.map +1 -0
- package/src/types/helpers.d.ts +62 -0
- package/src/types/helpers.d.ts.map +1 -0
- package/src/types/index.d.ts +19 -0
- package/src/types/index.d.ts.map +1 -0
- package/src/types/listening.d.ts +40 -0
- package/src/types/listening.d.ts.map +1 -0
- package/src/types/model.d.ts +230 -0
- package/src/types/model.d.ts.map +1 -0
- package/src/types/storage.d.ts +35 -0
- package/src/types/storage.d.ts.map +1 -0
- package/src/types/utils/events.d.ts +28 -0
- package/src/types/utils/events.d.ts.map +1 -0
- package/src/utils/events.js +174 -0
- package/src/drivers/indexeddb-getitems.js~ +0 -1116
- package/src/drivers/indexeddb.js~ +0 -1115
- package/src/events.js +0 -328
- package/src/history.js +0 -301
- package/src/overview.js +0 -191
- package/src/router.js +0 -119
- package/src/storage.js~ +0 -269
- package/src/view.js +0 -267
package/src/model.js
CHANGED
|
@@ -1,159 +1,217 @@
|
|
|
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
|
-
|
|
1
|
+
import { getResolveablePromise, getSyncMethod, urlError, wrapError } from './helpers.js';
|
|
2
|
+
import clone from 'lodash-es/clone.js';
|
|
3
|
+
import defaults from 'lodash-es/defaults.js';
|
|
4
|
+
import defer from 'lodash-es/defer.js';
|
|
5
|
+
import has from 'lodash-es/has.js';
|
|
6
|
+
import invert from 'lodash-es/invert.js';
|
|
7
|
+
import isEmpty from 'lodash-es/isEmpty.js';
|
|
8
|
+
import isEqual from 'lodash-es/isEqual.js';
|
|
9
|
+
import iteratee from 'lodash-es/iteratee.js';
|
|
10
|
+
import omit from 'lodash-es/omit.js';
|
|
11
|
+
import pick from 'lodash-es/pick.js';
|
|
12
|
+
import result from 'lodash-es/result.js';
|
|
13
|
+
import uniqueId from 'lodash-es/uniqueId.js';
|
|
14
|
+
import EventEmitter from './eventemitter.js';
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* @typedef {import('./collection.js').Collection} Collection
|
|
18
|
+
* @typedef {Record.<string, any>} Attributes
|
|
19
|
+
*
|
|
20
|
+
* @typedef {Record.<string, any>} Options
|
|
21
|
+
* @property {boolean} [validate]
|
|
22
|
+
*
|
|
23
|
+
* @typedef {Record.<string, any>} ModelOptions
|
|
24
|
+
* @property {Collection} [collection]
|
|
25
|
+
* @property {boolean} [parse]
|
|
26
|
+
* @property {boolean} [unset]
|
|
27
|
+
* @property {boolean} [silent]
|
|
28
|
+
*/
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* **Models** are the basic data object in the framework --
|
|
32
|
+
* frequently representing a row in a table in a database on your server.
|
|
33
|
+
* A discrete chunk of data and a bunch of useful, related methods for
|
|
34
|
+
* performing computations and transformations on that data.
|
|
35
|
+
*/
|
|
36
|
+
class Model extends EventEmitter(Object) {
|
|
37
|
+
/**
|
|
38
|
+
* Create a new model with the specified attributes. A client id (`cid`)
|
|
39
|
+
* is automatically generated and assigned for you.
|
|
40
|
+
* @param {Attributes} [attributes]
|
|
41
|
+
* @param {ModelOptions} [options]
|
|
42
|
+
*/
|
|
43
|
+
constructor(attributes, options) {
|
|
44
|
+
super();
|
|
45
|
+
let attrs = attributes || {};
|
|
46
|
+
options || (options = {});
|
|
47
|
+
this.preinitialize.apply(this, arguments);
|
|
48
|
+
this.cid = uniqueId(this.cidPrefix);
|
|
49
|
+
this.attributes = {};
|
|
50
|
+
|
|
51
|
+
// The value returned during the last failed validation.
|
|
52
|
+
this.validationError = null;
|
|
53
|
+
|
|
54
|
+
if (options.collection) this.collection = options.collection;
|
|
55
|
+
if (options.parse) attrs = this.parse(attrs, options) || {};
|
|
56
|
+
|
|
57
|
+
const default_attrs = result(this, 'defaults');
|
|
58
|
+
attrs = defaults(Object.assign({}, default_attrs, attrs), default_attrs);
|
|
59
|
+
|
|
60
|
+
this.set(attrs, options);
|
|
61
|
+
|
|
62
|
+
this.initialize.apply(this, arguments);
|
|
63
|
+
|
|
64
|
+
// A hash of attributes whose current and previous value differ.
|
|
65
|
+
this.changed = {};
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* @param {Storage} storage
|
|
70
|
+
*/
|
|
71
|
+
set browserStorage(storage) {
|
|
72
|
+
this._browserStorage = storage;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* @returns {Storage} storage
|
|
77
|
+
*/
|
|
78
|
+
get browserStorage() {
|
|
79
|
+
return this._browserStorage;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* The default name for the JSON `id` attribute is `"id"`. MongoDB and
|
|
84
|
+
* CouchDB users may want to set this to `"_id"` (by overriding this getter
|
|
85
|
+
* in a subclass).
|
|
86
|
+
*/
|
|
87
|
+
// eslint-disable-next-line class-methods-use-this
|
|
88
|
+
get idAttribute() {
|
|
89
|
+
return 'id';
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* The prefix is used to create the client id which is used to identify models locally.
|
|
94
|
+
* You may want to override this if you're experiencing name clashes with model ids.
|
|
95
|
+
*/
|
|
96
|
+
// eslint-disable-next-line class-methods-use-this
|
|
97
|
+
get cidPrefix() {
|
|
98
|
+
return 'c';
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* preinitialize is an empty function by default. You can override it with a function
|
|
103
|
+
* or object. preinitialize will run before any instantiation logic is run in the Model.
|
|
104
|
+
*/
|
|
105
|
+
// eslint-disable-next-line class-methods-use-this
|
|
106
|
+
preinitialize() {}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Initialize is an empty function by default. Override it with your own
|
|
110
|
+
* initialization logic.
|
|
111
|
+
* @param {Attributes} [attrs]
|
|
112
|
+
* @param {ModelOptions} [options]
|
|
113
|
+
*/
|
|
114
|
+
initialize(attrs, options) {}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* @param {object} attrs
|
|
118
|
+
* @param {object} [options]
|
|
119
|
+
* @returns {string} The validation error message
|
|
120
|
+
*/
|
|
121
|
+
validate(attrs, options) {
|
|
122
|
+
return '';
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Return a copy of the model's `attributes` object.
|
|
127
|
+
*/
|
|
128
|
+
toJSON() {
|
|
85
129
|
return clone(this.attributes);
|
|
86
|
-
}
|
|
130
|
+
}
|
|
87
131
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
132
|
+
/**
|
|
133
|
+
* Override this if you need custom syncing semantics for *this* particular model.
|
|
134
|
+
* @param {'create'|'update'|'patch'|'delete'|'read'} method
|
|
135
|
+
* @param {Model} model
|
|
136
|
+
* @param {Options} options
|
|
137
|
+
*/
|
|
138
|
+
// eslint-disable-next-line class-methods-use-this
|
|
139
|
+
sync(method, model, options) {
|
|
140
|
+
return getSyncMethod(model)(method, model, options);
|
|
141
|
+
}
|
|
93
142
|
|
|
94
|
-
|
|
95
|
-
|
|
143
|
+
/**
|
|
144
|
+
* Get the value of an attribute.
|
|
145
|
+
* @param {string} attr
|
|
146
|
+
*/
|
|
147
|
+
get(attr) {
|
|
96
148
|
return this.attributes[attr];
|
|
97
|
-
}
|
|
149
|
+
}
|
|
98
150
|
|
|
99
|
-
keys
|
|
151
|
+
keys() {
|
|
100
152
|
return Object.keys(this.attributes);
|
|
101
|
-
}
|
|
153
|
+
}
|
|
102
154
|
|
|
103
|
-
values
|
|
155
|
+
values() {
|
|
104
156
|
return Object.values(this.attributes);
|
|
105
|
-
}
|
|
157
|
+
}
|
|
106
158
|
|
|
107
|
-
pairs
|
|
159
|
+
pairs() {
|
|
108
160
|
return this.entries();
|
|
109
|
-
}
|
|
161
|
+
}
|
|
110
162
|
|
|
111
|
-
entries
|
|
163
|
+
entries() {
|
|
112
164
|
return Object.entries(this.attributes);
|
|
113
|
-
}
|
|
165
|
+
}
|
|
114
166
|
|
|
115
|
-
invert
|
|
167
|
+
invert() {
|
|
116
168
|
return invert(this.attributes);
|
|
117
|
-
}
|
|
169
|
+
}
|
|
118
170
|
|
|
119
|
-
pick
|
|
171
|
+
pick(...args) {
|
|
120
172
|
if (args.length === 1 && Array.isArray(args[0])) {
|
|
121
173
|
args = args[0];
|
|
122
174
|
}
|
|
123
175
|
return pick(this.attributes, args);
|
|
124
|
-
}
|
|
176
|
+
}
|
|
125
177
|
|
|
126
|
-
omit
|
|
178
|
+
omit(...args) {
|
|
127
179
|
if (args.length === 1 && Array.isArray(args[0])) {
|
|
128
180
|
args = args[0];
|
|
129
181
|
}
|
|
130
182
|
return omit(this.attributes, args);
|
|
131
|
-
}
|
|
183
|
+
}
|
|
132
184
|
|
|
133
|
-
isEmpty
|
|
185
|
+
isEmpty() {
|
|
134
186
|
return isEmpty(this.attributes);
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
// Get the HTML-escaped value of an attribute.
|
|
138
|
-
escape: function(attr) {
|
|
139
|
-
return escape(this.get(attr));
|
|
140
|
-
},
|
|
187
|
+
}
|
|
141
188
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
189
|
+
/**
|
|
190
|
+
* Returns `true` if the attribute contains a value that is not null
|
|
191
|
+
* or undefined.
|
|
192
|
+
* @param {string} attr
|
|
193
|
+
*/
|
|
194
|
+
has(attr) {
|
|
145
195
|
return this.get(attr) != null;
|
|
146
|
-
}
|
|
196
|
+
}
|
|
147
197
|
|
|
148
|
-
|
|
149
|
-
|
|
198
|
+
/**
|
|
199
|
+
* Special-cased proxy to lodash's `matches` method.
|
|
200
|
+
* @param {Attributes} attrs
|
|
201
|
+
*/
|
|
202
|
+
matches(attrs) {
|
|
150
203
|
return !!iteratee(attrs, this)(this.attributes);
|
|
151
|
-
}
|
|
204
|
+
}
|
|
152
205
|
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
206
|
+
/**
|
|
207
|
+
* Set a hash of model attributes on the object, firing `"change"`. This is
|
|
208
|
+
* the core primitive operation of a model, updating the data and notifying
|
|
209
|
+
* anyone who needs to know about the change in state. The heart of the beast.
|
|
210
|
+
* @param {string|Object} key
|
|
211
|
+
* @param {string|Object} [val]
|
|
212
|
+
* @param {Options} [options]
|
|
213
|
+
*/
|
|
214
|
+
set(key, val, options) {
|
|
157
215
|
if (key == null) return this;
|
|
158
216
|
|
|
159
217
|
// Handle both `"key", value` and `{key: value}` -style arguments.
|
|
@@ -171,10 +229,10 @@ Object.assign(Model.prototype, Events, {
|
|
|
171
229
|
if (!this._validate(attrs, options)) return false;
|
|
172
230
|
|
|
173
231
|
// Extract attributes and options.
|
|
174
|
-
const unset
|
|
175
|
-
const silent
|
|
176
|
-
const changes
|
|
177
|
-
const changing
|
|
232
|
+
const unset = options.unset;
|
|
233
|
+
const silent = options.silent;
|
|
234
|
+
const changes = [];
|
|
235
|
+
const changing = this._changing;
|
|
178
236
|
this._changing = true;
|
|
179
237
|
|
|
180
238
|
if (!changing) {
|
|
@@ -184,7 +242,7 @@ Object.assign(Model.prototype, Events, {
|
|
|
184
242
|
|
|
185
243
|
const current = this.attributes;
|
|
186
244
|
const changed = this.changed;
|
|
187
|
-
const prev
|
|
245
|
+
const prev = this._previousAttributes;
|
|
188
246
|
|
|
189
247
|
// For each `set` attribute, update or delete the current value.
|
|
190
248
|
for (const attr in attrs) {
|
|
@@ -195,7 +253,7 @@ Object.assign(Model.prototype, Events, {
|
|
|
195
253
|
} else {
|
|
196
254
|
delete changed[attr];
|
|
197
255
|
}
|
|
198
|
-
unset ? delete current[attr] : current[attr] = val;
|
|
256
|
+
unset ? delete current[attr] : (current[attr] = val);
|
|
199
257
|
}
|
|
200
258
|
|
|
201
259
|
// Update the `id`.
|
|
@@ -214,7 +272,7 @@ Object.assign(Model.prototype, Events, {
|
|
|
214
272
|
if (changing) return this;
|
|
215
273
|
if (!silent) {
|
|
216
274
|
while (this._pending) {
|
|
217
|
-
options = this._pending;
|
|
275
|
+
options = /** @type {Options} */ (this._pending);
|
|
218
276
|
this._pending = false;
|
|
219
277
|
this.trigger('change', this, options);
|
|
220
278
|
}
|
|
@@ -222,36 +280,52 @@ Object.assign(Model.prototype, Events, {
|
|
|
222
280
|
this._pending = false;
|
|
223
281
|
this._changing = false;
|
|
224
282
|
return this;
|
|
225
|
-
}
|
|
283
|
+
}
|
|
226
284
|
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
285
|
+
/**
|
|
286
|
+
* Remove an attribute from the model, firing `"change"`. `unset` is a noop
|
|
287
|
+
* if the attribute doesn't exist.
|
|
288
|
+
* @param {string} attr
|
|
289
|
+
* @param {Options} [options]
|
|
290
|
+
*/
|
|
291
|
+
unset(attr, options) {
|
|
292
|
+
return this.set(attr, undefined, Object.assign({}, options, { unset: true }));
|
|
293
|
+
}
|
|
232
294
|
|
|
233
|
-
|
|
234
|
-
|
|
295
|
+
/**
|
|
296
|
+
* Clear all attributes on the model, firing `"change"`.
|
|
297
|
+
* @param {Options} options
|
|
298
|
+
*/
|
|
299
|
+
clear(options) {
|
|
235
300
|
const attrs = {};
|
|
236
301
|
for (const key in this.attributes) attrs[key] = undefined;
|
|
237
|
-
return this.set(attrs,
|
|
238
|
-
}
|
|
302
|
+
return this.set(attrs, Object.assign({}, options, { unset: true }));
|
|
303
|
+
}
|
|
239
304
|
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
305
|
+
/**
|
|
306
|
+
* Determine if the model has changed since the last `"change"` event.
|
|
307
|
+
* If you specify an attribute name, determine if that attribute has changed.
|
|
308
|
+
* @param {string} [attr]
|
|
309
|
+
*/
|
|
310
|
+
hasChanged(attr) {
|
|
243
311
|
if (attr == null) return !isEmpty(this.changed);
|
|
244
312
|
return has(this.changed, attr);
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
/**
|
|
316
|
+
* Return an object containing all the attributes that have changed, or
|
|
317
|
+
* false if there are no changed attributes. Useful for determining what
|
|
318
|
+
* parts of a view need to be updated and/or what attributes need to be
|
|
319
|
+
* persisted to the server. Unset attributes will be set to undefined.
|
|
320
|
+
* You can also pass an attributes object to diff against the model,
|
|
321
|
+
* determining if there *would be* a change.
|
|
322
|
+
* @param {Object} diff
|
|
323
|
+
*/
|
|
324
|
+
changedAttributes(diff) {
|
|
325
|
+
if (!diff) {
|
|
326
|
+
return this.hasChanged() ? clone(this.changed) : false;
|
|
327
|
+
}
|
|
328
|
+
|
|
255
329
|
const old = this._changing ? this._previousAttributes : this.attributes;
|
|
256
330
|
const changed = {};
|
|
257
331
|
let hasChanged;
|
|
@@ -262,51 +336,66 @@ Object.assign(Model.prototype, Events, {
|
|
|
262
336
|
hasChanged = true;
|
|
263
337
|
}
|
|
264
338
|
return hasChanged ? changed : false;
|
|
265
|
-
}
|
|
339
|
+
}
|
|
266
340
|
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
341
|
+
/**
|
|
342
|
+
* Get the previous value of an attribute, recorded at the time the last
|
|
343
|
+
* `"change"` event was fired.
|
|
344
|
+
* @param {string} [attr]
|
|
345
|
+
*/
|
|
346
|
+
previous(attr) {
|
|
270
347
|
if (attr == null || !this._previousAttributes) return null;
|
|
271
348
|
return this._previousAttributes[attr];
|
|
272
|
-
}
|
|
349
|
+
}
|
|
273
350
|
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
351
|
+
/**
|
|
352
|
+
* Get all of the attributes of the model at the time of the previous
|
|
353
|
+
* `"change"` event.
|
|
354
|
+
*/
|
|
355
|
+
previousAttributes() {
|
|
277
356
|
return clone(this._previousAttributes);
|
|
278
|
-
}
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
/**
|
|
360
|
+
* Fetch the model from the server, merging the response with the model's
|
|
361
|
+
* local attributes. Any changed attributes will trigger a "change" event.
|
|
362
|
+
* @param {Options} [options={}]
|
|
363
|
+
*/
|
|
364
|
+
fetch(options={}) {
|
|
365
|
+
options = Object.assign({ parse: true }, options);
|
|
279
366
|
|
|
280
|
-
// Fetch the model from the server, merging the response with the model's
|
|
281
|
-
// local attributes. Any changed attributes will trigger a "change" event.
|
|
282
|
-
fetch: function(options) {
|
|
283
|
-
options = extend({parse: true}, options);
|
|
284
|
-
const model = this;
|
|
285
367
|
const success = options.success;
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
if (
|
|
290
|
-
|
|
368
|
+
|
|
369
|
+
options.success = (resp) => {
|
|
370
|
+
const serverAttrs = options.parse ? this.parse(resp, options) : resp;
|
|
371
|
+
if (!this.set(serverAttrs, options)) return false;
|
|
372
|
+
if (success) success.call(options.context, this, resp, options);
|
|
373
|
+
this.trigger('sync', this, resp, options);
|
|
291
374
|
};
|
|
375
|
+
|
|
292
376
|
wrapError(this, options);
|
|
293
377
|
return this.sync('read', this, options);
|
|
294
|
-
}
|
|
378
|
+
}
|
|
295
379
|
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
380
|
+
/**
|
|
381
|
+
* Set a hash of model attributes, and sync the model to the server.
|
|
382
|
+
* If the server returns an attributes hash that differs, the model's
|
|
383
|
+
* state will be `set` again.
|
|
384
|
+
* @param {string|Attributes} [key]
|
|
385
|
+
* @param {boolean|number|string|Options} [val]
|
|
386
|
+
* @param {Options} [options]
|
|
387
|
+
*/
|
|
388
|
+
save(key, val, options) {
|
|
300
389
|
// Handle both `"key", value` and `{key: value}` -style arguments.
|
|
301
390
|
let attrs;
|
|
302
391
|
if (key == null || typeof key === 'object') {
|
|
303
392
|
attrs = key;
|
|
304
|
-
options = val;
|
|
393
|
+
options = /** @type {Options} */ (val);
|
|
305
394
|
} else {
|
|
306
395
|
(attrs = {})[key] = val;
|
|
307
396
|
}
|
|
308
397
|
|
|
309
|
-
options =
|
|
398
|
+
options = Object.assign({ validate: true, parse: true }, options);
|
|
310
399
|
const wait = options.wait;
|
|
311
400
|
const return_promise = options.promise;
|
|
312
401
|
const promise = return_promise && getResolveablePromise();
|
|
@@ -316,35 +405,36 @@ Object.assign(Model.prototype, Events, {
|
|
|
316
405
|
// the model will be valid when the attributes, if any, are set.
|
|
317
406
|
if (attrs && !wait) {
|
|
318
407
|
if (!this.set(attrs, options)) return false;
|
|
319
|
-
} else if (!this._validate(attrs, options)) {
|
|
408
|
+
} else if (!this._validate(/** @type {Object} */ (attrs), options)) {
|
|
320
409
|
return false;
|
|
321
410
|
}
|
|
322
411
|
|
|
323
412
|
// After a successful server-side save, the client is (optionally)
|
|
324
413
|
// updated with the server-side state.
|
|
325
|
-
const model = this;
|
|
326
414
|
const success = options.success;
|
|
327
415
|
const error = options.error;
|
|
328
416
|
const attributes = this.attributes;
|
|
329
|
-
|
|
417
|
+
|
|
418
|
+
options.success = (resp) => {
|
|
330
419
|
// Ensure attributes are restored during synchronous saves.
|
|
331
|
-
|
|
332
|
-
let serverAttrs = options.parse ?
|
|
333
|
-
if (wait) serverAttrs =
|
|
334
|
-
if (serverAttrs && !
|
|
335
|
-
if (success) success.call(options.context,
|
|
336
|
-
|
|
420
|
+
this.attributes = attributes;
|
|
421
|
+
let serverAttrs = options.parse ? this.parse(resp, options) : resp;
|
|
422
|
+
if (wait) serverAttrs = Object.assign({}, attrs, serverAttrs);
|
|
423
|
+
if (serverAttrs && !this.set(serverAttrs, options)) return false;
|
|
424
|
+
if (success) success.call(options.context, this, resp, options);
|
|
425
|
+
this.trigger('sync', this, resp, options);
|
|
337
426
|
return_promise && promise.resolve();
|
|
338
427
|
};
|
|
339
|
-
|
|
428
|
+
|
|
429
|
+
options.error = (model, e, options) => {
|
|
340
430
|
error && error.call(options.context, model, e, options);
|
|
341
431
|
return_promise && promise.reject(e);
|
|
342
|
-
}
|
|
432
|
+
};
|
|
343
433
|
|
|
344
434
|
wrapError(this, options);
|
|
345
435
|
|
|
346
436
|
// Set temporary attributes if `{wait: true}` to properly find new ids.
|
|
347
|
-
if (attrs && wait) this.attributes =
|
|
437
|
+
if (attrs && wait) this.attributes = Object.assign({}, attributes, attrs);
|
|
348
438
|
|
|
349
439
|
const method = this.isNew() ? 'create' : options.patch ? 'patch' : 'update';
|
|
350
440
|
if (method === 'patch' && !options.attrs) options.attrs = attrs;
|
|
@@ -358,27 +448,28 @@ Object.assign(Model.prototype, Events, {
|
|
|
358
448
|
} else {
|
|
359
449
|
return xhr;
|
|
360
450
|
}
|
|
451
|
+
}
|
|
361
452
|
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
453
|
+
/**
|
|
454
|
+
* Destroy this model on the server if it was already persisted.
|
|
455
|
+
* Optimistically removes the model from its collection, if it has one.
|
|
456
|
+
* If `wait: true` is passed, waits for the server to respond before removal.
|
|
457
|
+
* @param {Options} [options]
|
|
458
|
+
*/
|
|
459
|
+
destroy(options) {
|
|
368
460
|
options = options ? clone(options) : {};
|
|
369
|
-
const model = this;
|
|
370
461
|
const success = options.success;
|
|
371
462
|
const wait = options.wait;
|
|
372
463
|
|
|
373
|
-
const destroy =
|
|
374
|
-
|
|
375
|
-
|
|
464
|
+
const destroy = () => {
|
|
465
|
+
this.stopListening();
|
|
466
|
+
this.trigger('destroy', this, this.collection, options);
|
|
376
467
|
};
|
|
377
468
|
|
|
378
|
-
options.success =
|
|
469
|
+
options.success = (resp) => {
|
|
379
470
|
if (wait) destroy();
|
|
380
|
-
if (success) success.call(options.context,
|
|
381
|
-
if (!
|
|
471
|
+
if (success) success.call(options.context, this, resp, options);
|
|
472
|
+
if (!this.isNew()) this.trigger('sync', this, resp, options);
|
|
382
473
|
};
|
|
383
474
|
|
|
384
475
|
let xhr = false;
|
|
@@ -390,50 +481,59 @@ Object.assign(Model.prototype, Events, {
|
|
|
390
481
|
}
|
|
391
482
|
if (!wait) destroy();
|
|
392
483
|
return xhr;
|
|
393
|
-
}
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
urlError();
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
/**
|
|
487
|
+
* Default URL for the model's representation on the server -- if you're
|
|
488
|
+
* using Backbone's restful methods, override this to change the endpoint
|
|
489
|
+
* that will be called.
|
|
490
|
+
*/
|
|
491
|
+
url() {
|
|
492
|
+
const base = result(this, 'urlRoot') || result(this.collection, 'url') || urlError();
|
|
403
493
|
if (this.isNew()) return base;
|
|
404
494
|
const id = this.get(this.idAttribute);
|
|
405
495
|
return base.replace(/[^\/]$/, '$&/') + encodeURIComponent(id);
|
|
406
|
-
}
|
|
496
|
+
}
|
|
407
497
|
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
498
|
+
/**
|
|
499
|
+
* **parse** converts a response into the hash of attributes to be `set` on
|
|
500
|
+
* the model. The default implementation is just to pass the response along.
|
|
501
|
+
* @param {Options} resp
|
|
502
|
+
* @param {Options} [options]
|
|
503
|
+
*/
|
|
504
|
+
parse(resp, options) {
|
|
411
505
|
return resp;
|
|
412
|
-
}
|
|
413
|
-
|
|
414
|
-
// Create a new model with identical attributes to this one.
|
|
415
|
-
clone: function() {
|
|
416
|
-
return new this.constructor(this.attributes);
|
|
417
|
-
},
|
|
506
|
+
}
|
|
418
507
|
|
|
419
|
-
|
|
420
|
-
|
|
508
|
+
/**
|
|
509
|
+
* A model is new if it has never been saved to the server, and lacks an id.
|
|
510
|
+
*/
|
|
511
|
+
isNew() {
|
|
421
512
|
return !this.has(this.idAttribute);
|
|
422
|
-
}
|
|
513
|
+
}
|
|
423
514
|
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
515
|
+
/**
|
|
516
|
+
* Check if the model is currently in a valid state.
|
|
517
|
+
* @param {Options} [options]
|
|
518
|
+
*/
|
|
519
|
+
isValid(options) {
|
|
520
|
+
return this._validate({}, Object.assign({}, options, { validate: true }));
|
|
521
|
+
}
|
|
428
522
|
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
523
|
+
/**
|
|
524
|
+
* Run validation against the next complete set of model attributes,
|
|
525
|
+
* returning `true` if all is well. Otherwise, fire an `"invalid"` event.
|
|
526
|
+
* @param {Attributes} attrs
|
|
527
|
+
* @param {Options} [options]
|
|
528
|
+
*/
|
|
529
|
+
_validate(attrs, options) {
|
|
432
530
|
if (!options.validate || !this.validate) return true;
|
|
433
|
-
attrs =
|
|
434
|
-
const error = this.validationError = this.validate(attrs, options) || null;
|
|
531
|
+
attrs = Object.assign({}, this.attributes, attrs);
|
|
532
|
+
const error = (this.validationError = this.validate(attrs, options) || null);
|
|
435
533
|
if (!error) return true;
|
|
436
|
-
this.trigger('invalid', this, error,
|
|
534
|
+
this.trigger('invalid', this, error, Object.assign(options, { validationError: error }));
|
|
437
535
|
return false;
|
|
438
536
|
}
|
|
439
|
-
}
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
export { Model };
|