can
Version:
MIT-licensed, client-side, JavaScript framework that makes building rich web applications easy.
142 lines (141 loc) • 4.7 kB
JavaScript
steal('can/util', 'can/model', 'can/map/backup', function (can) {
var cleanAttrs = function (changedAttrs, attrs) {
var newAttrs = can.extend(true, {}, attrs),
current, path;
if (changedAttrs) {
// go through the attributes returned from the server
// and remove those that were changed during the current
// request batch
for (var i = 0; i < changedAttrs.length; i++) {
current = newAttrs;
path = changedAttrs[i].split('.');
while (path.length > 1) {
current = current && current[path.shift()];
}
if (current) {
delete current[path.shift()];
}
}
}
return newAttrs;
}, queueRequests = function (success, error, method, callback) {
this._changedAttrs = this._changedAttrs || [];
var def = new can.Deferred(),
self = this,
attrs = this.serialize(),
queue = this._requestQueue,
changedAttrs = this._changedAttrs,
reqFn, index;
reqFn = function (self, type, success, error) {
// Function that performs actual request
return function () {
// pass already serialized attributes because we want to
// save model in state it was when request was queued, not
// when request is ran
return self.constructor._makeRequest([
self,
attrs
], type || (self.isNew() ? 'create' : 'update'), success, error, callback);
};
}(this, method, function () {
// resolve deferred with results from the request
def.resolveWith(self, arguments);
// remove current deferred from the queue
queue.splice(0, 1);
if (queue.length > 0) {
// replace queued wrapper function with deferred
// returned from the makeRequest function so we
// can access it's `abort` function
queue[0] = queue[0]();
} else {
// clean up changed attrs since there is no more requests in the queue
changedAttrs.splice(0);
}
}, function () {
// reject deferred with results from the request
def.rejectWith(self, arguments);
// since we failed remove all pending requests from the queue
queue.splice(0);
// clean up changed attrs since there is no more requests in the queue
changedAttrs.splice(0);
});
// Add our fn to the queue
index = queue.push(reqFn) - 1;
// If there is only one request in the queue, run
// it immediately.
if (queue.length === 1) {
// replace queued wrapper function with deferred
// returned from the makeRequest function so we
// can access it's `abort` function
queue[0] = queue[0]();
}
def.abort = function () {
var abort;
// check if this request is running, if it's not
// just remove it from the queue
//
// also all subsequent requests should be removed too
abort = queue[index].abort && queue[index].abort();
// remove aborted request and any requests after it
queue.splice(index);
// if there is no more requests in the queue clean up
// the changed attributes array
if (queue.length === 0) {
changedAttrs.splice(0);
}
return abort;
};
// deferred will be resolved with original success and
// error functions
def.then(success, error);
return def;
}, _triggerChange = can.Model.prototype._triggerChange,
destroyFn = can.Model.prototype.destroy,
setupFn = can.Model.prototype.setup;
can.each([
'created',
'updated',
'destroyed'
], function (fn) {
var prototypeFn = can.Model.prototype[fn];
can.Model.prototype[fn] = function (attrs) {
if (attrs && typeof attrs === 'object') {
attrs = attrs.attr ? attrs.attr() : attrs;
// Create backup of last good known state returned
// from the server. This allows users to restore it
// if API returns error
this._backupStore(attrs);
attrs = cleanAttrs(this._changedAttrs || [], attrs);
}
// call the original function with the cleaned up attributes
prototypeFn.call(this, attrs);
};
});
can.extend(can.Model.prototype, {
setup: function () {
setupFn.apply(this, arguments);
this._requestQueue = new can.List();
},
_triggerChange: function (attr, how, newVal, oldVal) {
// record changes if there is a request running
if (this._changedAttrs) {
this._changedAttrs.push(attr);
}
_triggerChange.apply(this, arguments);
},
hasQueuedRequests: function () {
return this._requestQueue.attr('length') > 1;
},
save: function () {
return queueRequests.apply(this, arguments);
},
destroy: function (success, error) {
if (this.isNew()) {
// if it's a new instance, call default destroy method
return destroyFn.call(this, success, error);
}
return queueRequests.call(this, success, error, 'destroy', 'destroyed');
}
});
return can;
});