UNPKG

can

Version:

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

397 lines (396 loc) 16.2 kB
/*! * CanJS - 2.3.34 * http://canjs.com/ * Copyright (c) 2018 Bitovi * Mon, 30 Apr 2018 20:56:51 GMT * Licensed MIT */ /*can@2.3.34#model/model*/ define([ 'can/util/library', 'can/map', 'can/list' ], function (can) { var pipe = function (def, thisArg, func) { var d = new can.Deferred(); def.then(function () { var args = can.makeArray(arguments), success = true; try { args[0] = func.apply(thisArg, args); } catch (e) { success = false; d.rejectWith(d, [e].concat(args)); } if (success) { d.resolveWith(d, args); } }, function () { d.rejectWith(this, arguments); }); if (typeof def.abort === 'function') { d.abort = function () { return def.abort(); }; } return d; }, modelNum = 0, getId = function (inst) { can.__observe(inst, inst.constructor.id); return inst.___get(inst.constructor.id); }, ajax = function (ajaxOb, data, type, dataType, success, error) { var params = {}; if (typeof ajaxOb === 'string') { var parts = ajaxOb.split(/\s+/); params.url = parts.pop(); if (parts.length) { params.type = parts.pop(); } } else { can.extend(params, ajaxOb); } params.data = typeof data === 'object' && !can.isArray(data) ? can.extend(params.data || {}, data) : data; params.url = can.sub(params.url, params.data, true); return can.ajax(can.extend({ type: type || 'post', dataType: dataType || 'json', success: success, error: error }, params)); }, makeRequest = function (modelObj, type, success, error, method) { var args; if (can.isArray(modelObj)) { args = modelObj[1]; modelObj = modelObj[0]; } else { args = modelObj.serialize(); } args = [args]; var deferred, model = modelObj.constructor, jqXHR; if (type === 'update' || type === 'destroy') { args.unshift(getId(modelObj)); } jqXHR = model[type].apply(model, args); deferred = pipe(jqXHR, modelObj, function (data) { modelObj[method || type + 'd'](data, jqXHR); return modelObj; }); if (jqXHR.abort) { deferred.abort = function () { jqXHR.abort(); }; } deferred.then(success, error); return deferred; }, converters = { models: function (instancesRawData, oldList, xhr) { can.Model._reqs++; if (!instancesRawData) { return; } if (instancesRawData instanceof this.List) { return instancesRawData; } var self = this, tmp = [], ListClass = self.List || ML, modelList = oldList instanceof can.List ? oldList : new ListClass(), rawDataIsList = instancesRawData instanceof ML, raw = rawDataIsList ? instancesRawData.serialize() : instancesRawData; raw = self.parseModels(raw, xhr); if (raw.data) { instancesRawData = raw; raw = raw.data; } if (typeof raw === 'undefined' || !can.isArray(raw)) { throw new Error('Could not get any raw data while converting using .models'); } if (!raw.length) { can.dev.warn('model.js models has no data.'); } if (modelList.length) { modelList.splice(0); } can.each(raw, function (rawPart) { tmp.push(self.model(rawPart, xhr)); }); modelList.push.apply(modelList, tmp); if (!can.isArray(instancesRawData)) { can.each(instancesRawData, function (val, prop) { if (prop !== 'data') { modelList.attr(prop, val); } }); } setTimeout(can.proxy(this._clean, this), 1); return modelList; }, model: function (attributes, oldModel, xhr) { if (!attributes) { return; } if (typeof attributes.serialize === 'function') { attributes = attributes.serialize(); } else { attributes = this.parseModel(attributes, xhr); } var id = attributes[this.id]; if ((id || id === 0) && this.store[id]) { oldModel = this.store[id]; } var model = oldModel && can.isFunction(oldModel.attr) ? oldModel.attr(attributes, this.removeAttr || false) : new this(attributes); return model; } }, makeParser = { parseModel: function (prop) { return function (attributes) { return prop ? can.getObject(prop, attributes) : attributes; }; }, parseModels: function (prop) { return function (attributes) { if (can.isArray(attributes)) { return attributes; } prop = prop || 'data'; var result = can.getObject(prop, attributes); if (!can.isArray(result)) { throw new Error('Could not get any raw data while converting using .models'); } return result; }; } }, ajaxMethods = { create: { url: '_shortName', type: 'post' }, update: { data: function (id, attrs) { attrs = attrs || {}; var identity = this.id; if (attrs[identity] && attrs[identity] !== id) { attrs['new' + can.capitalize(id)] = attrs[identity]; delete attrs[identity]; } attrs[identity] = id; return attrs; }, type: 'put' }, destroy: { type: 'delete', data: function (id, attrs) { attrs = attrs || {}; attrs.id = attrs[this.id] = id; return attrs; } }, findAll: { url: '_shortName' }, findOne: {} }, ajaxMaker = function (ajaxMethod, str) { return function (data) { data = ajaxMethod.data ? ajaxMethod.data.apply(this, arguments) : data; return ajax(str || this[ajaxMethod.url || '_url'], data, ajaxMethod.type || 'get'); }; }, createURLFromResource = function (model, name) { if (!model.resource) { return; } var resource = model.resource.replace(/\/+$/, ''); if (name === 'findAll' || name === 'create') { return resource; } else { return resource + '/{' + model.id + '}'; } }; can.Model = can.Map.extend({ fullName: 'can.Model', _reqs: 0, setup: function (base, fullName, staticProps, protoProps) { if (typeof fullName !== 'string') { protoProps = staticProps; staticProps = fullName; } if (!protoProps) { can.dev.warn('can/model/model.js: can.Model extended without static properties.'); protoProps = staticProps; } this.store = {}; can.Map.setup.apply(this, arguments); if (!can.Model) { return; } if (staticProps && staticProps.List) { this.List = staticProps.List; this.List.Map = this; } else { this.List = base.List.extend({ Map: this }, {}); } var self = this, clean = can.proxy(this._clean, self); can.each(ajaxMethods, function (method, name) { if (staticProps && staticProps[name] && (typeof staticProps[name] === 'string' || typeof staticProps[name] === 'object')) { self[name] = ajaxMaker(method, staticProps[name]); } else if (staticProps && staticProps.resource && !can.isFunction(staticProps[name])) { self[name] = ajaxMaker(method, createURLFromResource(self, name)); } if (self['make' + can.capitalize(name)]) { var newMethod = self['make' + can.capitalize(name)](self[name]); can.Construct._overwrite(self, base, name, function () { can.Model._reqs++; var def = newMethod.apply(this, arguments); var then = def.then(clean, clean); then.abort = def.abort; return then; }); } }); var hasCustomConverter = {}; can.each(converters, function (converter, name) { var parseName = 'parse' + can.capitalize(name), dataProperty = staticProps && staticProps[name] || self[name]; if (typeof dataProperty === 'string') { self[parseName] = dataProperty; can.Construct._overwrite(self, base, name, converter); } else if (staticProps && staticProps[name]) { hasCustomConverter[parseName] = true; } }); can.each(makeParser, function (maker, parseName) { var prop = staticProps && staticProps[parseName] || self[parseName]; if (typeof prop === 'string') { can.Construct._overwrite(self, base, parseName, maker(prop)); } else if ((!staticProps || !can.isFunction(staticProps[parseName])) && !self[parseName]) { var madeParser = maker(); madeParser.useModelConverter = hasCustomConverter[parseName]; can.Construct._overwrite(self, base, parseName, madeParser); } }); if (self.fullName === 'can.Model' || !self.fullName) { self.fullName = 'Model' + ++modelNum; } can.Model._reqs = 0; this._url = this._shortName + '/{' + this.id + '}'; }, _ajax: ajaxMaker, _makeRequest: makeRequest, _clean: function () { can.Model._reqs--; if (!can.Model._reqs) { for (var id in this.store) { if (!this.store[id]._bindings) { delete this.store[id]; } } } return arguments[0]; }, models: converters.models, model: converters.model }, { setup: function (attrs) { var id = attrs && attrs[this.constructor.id]; if (can.Model._reqs && id != null) { this.constructor.store[id] = this; } can.Map.prototype.setup.apply(this, arguments); }, isNew: function () { var id = getId(this); return !(id || id === 0); }, save: function (success, error) { return makeRequest(this, this.isNew() ? 'create' : 'update', success, error); }, destroy: function (success, error) { if (this.isNew()) { var self = this; var def = can.Deferred(); def.then(success, error); return def.done(function (data) { self.destroyed(data); }).resolve(self); } return makeRequest(this, 'destroy', success, error, 'destroyed'); }, _bindsetup: function () { var modelInstance = this.___get(this.constructor.id); if (modelInstance != null) { this.constructor.store[modelInstance] = this; } return can.Map.prototype._bindsetup.apply(this, arguments); }, _bindteardown: function () { delete this.constructor.store[getId(this)]; return can.Map.prototype._bindteardown.apply(this, arguments); }, ___set: function (prop, val) { can.Map.prototype.___set.call(this, prop, val); if (prop === this.constructor.id && this._bindings) { this.constructor.store[getId(this)] = this; } } }); var makeGetterHandler = function (name) { return function (data, readyState, xhr) { return this[name](data, null, xhr); }; }, createUpdateDestroyHandler = function (data) { if (this.parseModel.useModelConverter) { return this.model(data); } return this.parseModel(data); }; var responseHandlers = { makeFindAll: makeGetterHandler('models'), makeFindOne: makeGetterHandler('model'), makeCreate: createUpdateDestroyHandler, makeUpdate: createUpdateDestroyHandler, makeDestroy: createUpdateDestroyHandler }; can.each(responseHandlers, function (method, name) { can.Model[name] = function (oldMethod) { return function () { var args = can.makeArray(arguments), oldArgs = can.isFunction(args[1]) ? args.splice(0, 1) : args.splice(0, 2), def = pipe(oldMethod.apply(this, oldArgs), this, method); def.then(args[0], args[1]); return def; }; }; }); can.each([ 'created', 'updated', 'destroyed' ], function (funcName) { can.Model.prototype[funcName] = function (attrs) { var self = this, constructor = self.constructor; if (attrs && typeof attrs === 'object') { this.attr(can.isFunction(attrs.attr) ? attrs.attr() : attrs); } can.dispatch.call(this, { type: funcName, target: this }, []); can.dev.log('Model.js - ' + constructor.shortName + ' ' + funcName); can.dispatch.call(constructor, funcName, [this]); }; }); var ML = can.Model.List = can.List.extend({ _bubbleRule: function (eventName, list) { var bubbleRules = can.List._bubbleRule(eventName, list); bubbleRules.push('destroyed'); return bubbleRules; } }, { setup: function (params) { if (can.isPlainObject(params) && !can.isArray(params)) { can.List.prototype.setup.apply(this); this.replace(can.isPromise(params) ? params : this.constructor.Map.findAll(params)); } else { can.List.prototype.setup.apply(this, arguments); } this.bind('destroyed', can.proxy(this._destroyed, this)); }, _destroyed: function (ev, attr) { if (/\w+/.test(attr)) { var index; while ((index = this.indexOf(ev.target)) > -1) { this.splice(index, 1); } } } }); return can.Model; });