@converse/skeletor 0.0.8 → 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 +9232 -5096
- 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 +21 -11
- package/src/collection.js +540 -395
- package/src/drivers/sessionStorage.js +222 -219
- package/src/element.js +132 -82
- package/src/eventemitter.js +187 -0
- package/src/helpers.js +143 -118
- package/src/{main.js → index.js} +21 -17
- package/src/listening.js +81 -0
- package/src/model.js +340 -238
- package/src/storage.js +222 -230
- 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/sessionStorage.js~ +0 -307
- 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/view.js +0 -267
package/src/model.js
CHANGED
|
@@ -1,157 +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
|
-
|
|
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() {
|
|
83
129
|
return clone(this.attributes);
|
|
84
|
-
}
|
|
130
|
+
}
|
|
85
131
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
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
|
+
}
|
|
91
142
|
|
|
92
|
-
|
|
93
|
-
|
|
143
|
+
/**
|
|
144
|
+
* Get the value of an attribute.
|
|
145
|
+
* @param {string} attr
|
|
146
|
+
*/
|
|
147
|
+
get(attr) {
|
|
94
148
|
return this.attributes[attr];
|
|
95
|
-
}
|
|
149
|
+
}
|
|
96
150
|
|
|
97
|
-
keys
|
|
151
|
+
keys() {
|
|
98
152
|
return Object.keys(this.attributes);
|
|
99
|
-
}
|
|
153
|
+
}
|
|
100
154
|
|
|
101
|
-
values
|
|
155
|
+
values() {
|
|
102
156
|
return Object.values(this.attributes);
|
|
103
|
-
}
|
|
157
|
+
}
|
|
104
158
|
|
|
105
|
-
pairs
|
|
159
|
+
pairs() {
|
|
106
160
|
return this.entries();
|
|
107
|
-
}
|
|
161
|
+
}
|
|
108
162
|
|
|
109
|
-
entries
|
|
163
|
+
entries() {
|
|
110
164
|
return Object.entries(this.attributes);
|
|
111
|
-
}
|
|
165
|
+
}
|
|
112
166
|
|
|
113
|
-
invert
|
|
167
|
+
invert() {
|
|
114
168
|
return invert(this.attributes);
|
|
115
|
-
}
|
|
169
|
+
}
|
|
116
170
|
|
|
117
|
-
pick
|
|
171
|
+
pick(...args) {
|
|
118
172
|
if (args.length === 1 && Array.isArray(args[0])) {
|
|
119
173
|
args = args[0];
|
|
120
174
|
}
|
|
121
175
|
return pick(this.attributes, args);
|
|
122
|
-
}
|
|
176
|
+
}
|
|
123
177
|
|
|
124
|
-
omit
|
|
178
|
+
omit(...args) {
|
|
125
179
|
if (args.length === 1 && Array.isArray(args[0])) {
|
|
126
180
|
args = args[0];
|
|
127
181
|
}
|
|
128
182
|
return omit(this.attributes, args);
|
|
129
|
-
}
|
|
183
|
+
}
|
|
130
184
|
|
|
131
|
-
isEmpty
|
|
185
|
+
isEmpty() {
|
|
132
186
|
return isEmpty(this.attributes);
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
// Get the HTML-escaped value of an attribute.
|
|
136
|
-
escape: function(attr) {
|
|
137
|
-
return escape(this.get(attr));
|
|
138
|
-
},
|
|
187
|
+
}
|
|
139
188
|
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
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) {
|
|
143
195
|
return this.get(attr) != null;
|
|
144
|
-
}
|
|
196
|
+
}
|
|
145
197
|
|
|
146
|
-
|
|
147
|
-
|
|
198
|
+
/**
|
|
199
|
+
* Special-cased proxy to lodash's `matches` method.
|
|
200
|
+
* @param {Attributes} attrs
|
|
201
|
+
*/
|
|
202
|
+
matches(attrs) {
|
|
148
203
|
return !!iteratee(attrs, this)(this.attributes);
|
|
149
|
-
}
|
|
204
|
+
}
|
|
150
205
|
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
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) {
|
|
155
215
|
if (key == null) return this;
|
|
156
216
|
|
|
157
217
|
// Handle both `"key", value` and `{key: value}` -style arguments.
|
|
@@ -169,10 +229,10 @@ Object.assign(Model.prototype, Events, {
|
|
|
169
229
|
if (!this._validate(attrs, options)) return false;
|
|
170
230
|
|
|
171
231
|
// Extract attributes and options.
|
|
172
|
-
const unset
|
|
173
|
-
const silent
|
|
174
|
-
const changes
|
|
175
|
-
const changing
|
|
232
|
+
const unset = options.unset;
|
|
233
|
+
const silent = options.silent;
|
|
234
|
+
const changes = [];
|
|
235
|
+
const changing = this._changing;
|
|
176
236
|
this._changing = true;
|
|
177
237
|
|
|
178
238
|
if (!changing) {
|
|
@@ -182,7 +242,7 @@ Object.assign(Model.prototype, Events, {
|
|
|
182
242
|
|
|
183
243
|
const current = this.attributes;
|
|
184
244
|
const changed = this.changed;
|
|
185
|
-
const prev
|
|
245
|
+
const prev = this._previousAttributes;
|
|
186
246
|
|
|
187
247
|
// For each `set` attribute, update or delete the current value.
|
|
188
248
|
for (const attr in attrs) {
|
|
@@ -193,7 +253,7 @@ Object.assign(Model.prototype, Events, {
|
|
|
193
253
|
} else {
|
|
194
254
|
delete changed[attr];
|
|
195
255
|
}
|
|
196
|
-
unset ? delete current[attr] : current[attr] = val;
|
|
256
|
+
unset ? delete current[attr] : (current[attr] = val);
|
|
197
257
|
}
|
|
198
258
|
|
|
199
259
|
// Update the `id`.
|
|
@@ -212,7 +272,7 @@ Object.assign(Model.prototype, Events, {
|
|
|
212
272
|
if (changing) return this;
|
|
213
273
|
if (!silent) {
|
|
214
274
|
while (this._pending) {
|
|
215
|
-
options = this._pending;
|
|
275
|
+
options = /** @type {Options} */ (this._pending);
|
|
216
276
|
this._pending = false;
|
|
217
277
|
this.trigger('change', this, options);
|
|
218
278
|
}
|
|
@@ -220,36 +280,52 @@ Object.assign(Model.prototype, Events, {
|
|
|
220
280
|
this._pending = false;
|
|
221
281
|
this._changing = false;
|
|
222
282
|
return this;
|
|
223
|
-
}
|
|
283
|
+
}
|
|
224
284
|
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
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
|
+
}
|
|
230
294
|
|
|
231
|
-
|
|
232
|
-
|
|
295
|
+
/**
|
|
296
|
+
* Clear all attributes on the model, firing `"change"`.
|
|
297
|
+
* @param {Options} options
|
|
298
|
+
*/
|
|
299
|
+
clear(options) {
|
|
233
300
|
const attrs = {};
|
|
234
301
|
for (const key in this.attributes) attrs[key] = undefined;
|
|
235
|
-
return this.set(attrs,
|
|
236
|
-
}
|
|
302
|
+
return this.set(attrs, Object.assign({}, options, { unset: true }));
|
|
303
|
+
}
|
|
237
304
|
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
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) {
|
|
241
311
|
if (attr == null) return !isEmpty(this.changed);
|
|
242
312
|
return has(this.changed, attr);
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
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
|
+
|
|
253
329
|
const old = this._changing ? this._previousAttributes : this.attributes;
|
|
254
330
|
const changed = {};
|
|
255
331
|
let hasChanged;
|
|
@@ -260,51 +336,66 @@ Object.assign(Model.prototype, Events, {
|
|
|
260
336
|
hasChanged = true;
|
|
261
337
|
}
|
|
262
338
|
return hasChanged ? changed : false;
|
|
263
|
-
}
|
|
339
|
+
}
|
|
264
340
|
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
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) {
|
|
268
347
|
if (attr == null || !this._previousAttributes) return null;
|
|
269
348
|
return this._previousAttributes[attr];
|
|
270
|
-
}
|
|
349
|
+
}
|
|
271
350
|
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
351
|
+
/**
|
|
352
|
+
* Get all of the attributes of the model at the time of the previous
|
|
353
|
+
* `"change"` event.
|
|
354
|
+
*/
|
|
355
|
+
previousAttributes() {
|
|
275
356
|
return clone(this._previousAttributes);
|
|
276
|
-
}
|
|
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);
|
|
277
366
|
|
|
278
|
-
// Fetch the model from the server, merging the response with the model's
|
|
279
|
-
// local attributes. Any changed attributes will trigger a "change" event.
|
|
280
|
-
fetch: function(options) {
|
|
281
|
-
options = extend({parse: true}, options);
|
|
282
|
-
const model = this;
|
|
283
367
|
const success = options.success;
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
if (
|
|
288
|
-
|
|
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);
|
|
289
374
|
};
|
|
375
|
+
|
|
290
376
|
wrapError(this, options);
|
|
291
377
|
return this.sync('read', this, options);
|
|
292
|
-
}
|
|
378
|
+
}
|
|
293
379
|
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
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) {
|
|
298
389
|
// Handle both `"key", value` and `{key: value}` -style arguments.
|
|
299
390
|
let attrs;
|
|
300
391
|
if (key == null || typeof key === 'object') {
|
|
301
392
|
attrs = key;
|
|
302
|
-
options = val;
|
|
393
|
+
options = /** @type {Options} */ (val);
|
|
303
394
|
} else {
|
|
304
395
|
(attrs = {})[key] = val;
|
|
305
396
|
}
|
|
306
397
|
|
|
307
|
-
options =
|
|
398
|
+
options = Object.assign({ validate: true, parse: true }, options);
|
|
308
399
|
const wait = options.wait;
|
|
309
400
|
const return_promise = options.promise;
|
|
310
401
|
const promise = return_promise && getResolveablePromise();
|
|
@@ -314,35 +405,36 @@ Object.assign(Model.prototype, Events, {
|
|
|
314
405
|
// the model will be valid when the attributes, if any, are set.
|
|
315
406
|
if (attrs && !wait) {
|
|
316
407
|
if (!this.set(attrs, options)) return false;
|
|
317
|
-
} else if (!this._validate(attrs, options)) {
|
|
408
|
+
} else if (!this._validate(/** @type {Object} */ (attrs), options)) {
|
|
318
409
|
return false;
|
|
319
410
|
}
|
|
320
411
|
|
|
321
412
|
// After a successful server-side save, the client is (optionally)
|
|
322
413
|
// updated with the server-side state.
|
|
323
|
-
const model = this;
|
|
324
414
|
const success = options.success;
|
|
325
415
|
const error = options.error;
|
|
326
416
|
const attributes = this.attributes;
|
|
327
|
-
|
|
417
|
+
|
|
418
|
+
options.success = (resp) => {
|
|
328
419
|
// Ensure attributes are restored during synchronous saves.
|
|
329
|
-
|
|
330
|
-
let serverAttrs = options.parse ?
|
|
331
|
-
if (wait) serverAttrs =
|
|
332
|
-
if (serverAttrs && !
|
|
333
|
-
if (success) success.call(options.context,
|
|
334
|
-
|
|
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);
|
|
335
426
|
return_promise && promise.resolve();
|
|
336
427
|
};
|
|
337
|
-
|
|
428
|
+
|
|
429
|
+
options.error = (model, e, options) => {
|
|
338
430
|
error && error.call(options.context, model, e, options);
|
|
339
431
|
return_promise && promise.reject(e);
|
|
340
|
-
}
|
|
432
|
+
};
|
|
341
433
|
|
|
342
434
|
wrapError(this, options);
|
|
343
435
|
|
|
344
436
|
// Set temporary attributes if `{wait: true}` to properly find new ids.
|
|
345
|
-
if (attrs && wait) this.attributes =
|
|
437
|
+
if (attrs && wait) this.attributes = Object.assign({}, attributes, attrs);
|
|
346
438
|
|
|
347
439
|
const method = this.isNew() ? 'create' : options.patch ? 'patch' : 'update';
|
|
348
440
|
if (method === 'patch' && !options.attrs) options.attrs = attrs;
|
|
@@ -356,27 +448,28 @@ Object.assign(Model.prototype, Events, {
|
|
|
356
448
|
} else {
|
|
357
449
|
return xhr;
|
|
358
450
|
}
|
|
451
|
+
}
|
|
359
452
|
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
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) {
|
|
366
460
|
options = options ? clone(options) : {};
|
|
367
|
-
const model = this;
|
|
368
461
|
const success = options.success;
|
|
369
462
|
const wait = options.wait;
|
|
370
463
|
|
|
371
|
-
const destroy =
|
|
372
|
-
|
|
373
|
-
|
|
464
|
+
const destroy = () => {
|
|
465
|
+
this.stopListening();
|
|
466
|
+
this.trigger('destroy', this, this.collection, options);
|
|
374
467
|
};
|
|
375
468
|
|
|
376
|
-
options.success =
|
|
469
|
+
options.success = (resp) => {
|
|
377
470
|
if (wait) destroy();
|
|
378
|
-
if (success) success.call(options.context,
|
|
379
|
-
if (!
|
|
471
|
+
if (success) success.call(options.context, this, resp, options);
|
|
472
|
+
if (!this.isNew()) this.trigger('sync', this, resp, options);
|
|
380
473
|
};
|
|
381
474
|
|
|
382
475
|
let xhr = false;
|
|
@@ -388,50 +481,59 @@ Object.assign(Model.prototype, Events, {
|
|
|
388
481
|
}
|
|
389
482
|
if (!wait) destroy();
|
|
390
483
|
return xhr;
|
|
391
|
-
}
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
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();
|
|
401
493
|
if (this.isNew()) return base;
|
|
402
494
|
const id = this.get(this.idAttribute);
|
|
403
495
|
return base.replace(/[^\/]$/, '$&/') + encodeURIComponent(id);
|
|
404
|
-
}
|
|
496
|
+
}
|
|
405
497
|
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
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) {
|
|
409
505
|
return resp;
|
|
410
|
-
}
|
|
411
|
-
|
|
412
|
-
// Create a new model with identical attributes to this one.
|
|
413
|
-
clone: function() {
|
|
414
|
-
return new this.constructor(this.attributes);
|
|
415
|
-
},
|
|
506
|
+
}
|
|
416
507
|
|
|
417
|
-
|
|
418
|
-
|
|
508
|
+
/**
|
|
509
|
+
* A model is new if it has never been saved to the server, and lacks an id.
|
|
510
|
+
*/
|
|
511
|
+
isNew() {
|
|
419
512
|
return !this.has(this.idAttribute);
|
|
420
|
-
}
|
|
513
|
+
}
|
|
421
514
|
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
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
|
+
}
|
|
426
522
|
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
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) {
|
|
430
530
|
if (!options.validate || !this.validate) return true;
|
|
431
|
-
attrs =
|
|
432
|
-
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);
|
|
433
533
|
if (!error) return true;
|
|
434
|
-
this.trigger('invalid', this, error,
|
|
534
|
+
this.trigger('invalid', this, error, Object.assign(options, { validationError: error }));
|
|
435
535
|
return false;
|
|
436
536
|
}
|
|
437
|
-
}
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
export { Model };
|