-
Notifications
You must be signed in to change notification settings - Fork 31
/
ampersand-model.js
executable file
·135 lines (116 loc) · 4.95 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
/*$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, sync;
// 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) options.attrs = assign(model.serialize(), attrs);
sync = this.sync(method, this, options);
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);
return this.sync('read', this, options);
},
// 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);
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;