-
Notifications
You must be signed in to change notification settings - Fork 31
/
ampersand-model.js
142 lines (124 loc) · 5.44 KB
/
ampersand-model.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
/*$AMPERSAND_VERSION*/
var State = require('ampersand-state');
var sync = require('ampersand-sync');
var assign = require('lodash/assign');
var isObject = require('lodash/isObject');
var clone = require('lodash/clone');
var result = require('lodash/result');
// Throw an error when a URL is needed, and none is supplied.
var urlError = function () {
throw new Error('A "url" property or function must be specified');
};
// Wrap an optional error callback with a fallback error event.
var wrapError = function (model, options) {
var error = options.error;
options.error = function (resp) {
if (error) error(model, resp, options);
model.trigger('error', model, resp, options);
};
};
var Model = State.extend({
save: function (key, val, options) {
var attrs, method;
// Handle both `"key", value` and `{key: value}` -style arguments.
if (key == null || typeof key === 'object') {
attrs = key;
options = val;
} else {
(attrs = {})[key] = val;
}
options = assign({validate: true}, options);
// If we're not waiting and attributes exist, save acts as
// `set(attr).save(null, opts)` with validation. Otherwise, check if
// the model will be valid when the attributes, if any, are set.
if (attrs && !options.wait) {
if (!this.set(attrs, options)) return false;
} else {
if (!this._validate(attrs, options)) return false;
}
// After a successful server-side save, the client is (optionally)
// updated with the server-side state.
if (options.parse === void 0) options.parse = true;
var model = this;
var success = options.success;
options.success = function (resp) {
var serverAttrs = model.parse(resp, options);
if (options.wait) serverAttrs = assign(attrs || {}, serverAttrs);
if (isObject(serverAttrs) && !model.set(serverAttrs, options)) {
return false;
}
if (success) success(model, resp, options);
model.trigger('sync', model, resp, options);
};
wrapError(this, options);
method = this.isNew() ? 'create' : (options.patch ? 'patch' : 'update');
if (method === 'patch') options.attrs = attrs;
// if we're waiting we haven't actually set our attributes yet so
// we need to do make sure we send right data
if (options.wait && method !== 'patch') options.attrs = assign(model.serialize(), attrs);
var sync = this.sync(method, this, options);
// Make the request available on the options object so it can be accessed
// further down the line by `parse`, attached listeners, etc
// Same thing is done below for fetch and destroy
// https://github.com/AmpersandJS/ampersand-collection-rest-mixin/commit/d32d788aaff912387eb1106f2d7ad183ec39e11a#diff-84c84703169bf5017b1bc323653acaa3R32
options.xhr = sync;
return sync;
},
// Fetch the model from the server. If the server's representation of the
// model differs from its current attributes, they will be overridden,
// triggering a `"change"` event.
fetch: function (options) {
options = options ? clone(options) : {};
if (options.parse === void 0) options.parse = true;
var model = this;
var success = options.success;
options.success = function (resp) {
if (!model.set(model.parse(resp, options), options)) return false;
if (success) success(model, resp, options);
model.trigger('sync', model, resp, options);
};
wrapError(this, options);
var sync = this.sync('read', this, options);
options.xhr = sync;
return sync;
},
// Destroy this model on the server if it was already persisted.
// Optimistically removes the model from its collection, if it has one.
// If `wait: true` is passed, waits for the server to respond before removal.
destroy: function (options) {
options = options ? clone(options) : {};
var model = this;
var success = options.success;
var destroy = function () {
model.trigger('destroy', model, model.collection, options);
};
options.success = function (resp) {
if (options.wait || model.isNew()) destroy();
if (success) success(model, resp, options);
if (!model.isNew()) model.trigger('sync', model, resp, options);
};
if (this.isNew()) {
options.success();
return false;
}
wrapError(this, options);
var sync = this.sync('delete', this, options);
options.xhr = sync;
if (!options.wait) destroy();
return sync;
},
// Proxy `ampersand-sync` by default -- but override this if you need
// custom syncing semantics for *this* particular model.
sync: function () {
return sync.apply(this, arguments);
},
// Default URL for the model's representation on the server -- if you're
// using Backbone's restful methods, override this to change the endpoint
// that will be called.
url: function () {
var base = result(this, 'urlRoot') || result(this.collection, 'url') || urlError();
if (this.isNew()) return base;
return base + (base.charAt(base.length - 1) === '/' ? '' : '/') + encodeURIComponent(this.getId());
}
});
module.exports = Model;