UNPKG

can

Version:

MIT-licensed, client-side, JavaScript framework that makes building rich web applications easy.

142 lines (141 loc) 4.7 kB
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; });