import ngModule from '../../module'; import ModelProxy from '../model-proxy/model-proxy'; import {mergeWhere, mergeFilters} from 'vn-loopback/util/filter'; /** * Model that uses remote loopback model as datasource. * * @property {Array} fields the list of fields to fetch */ export default class CrudModel extends ModelProxy { constructor($q, $http, $element, $scope) { super($element, $scope); this.$http = $http; this.$q = $q; this.primaryKey = 'id'; this.autoLoad = false; } $onInit() { this.initialized = true; this.autoRefresh(); } get isLoading() { return this.canceler != null; } /** * @type {String} The remote model URL */ get url() { return this._url; } set url(url) { if (this._url === url) return; this._url = url; this.clear(); if (this.initialized) this.autoRefresh(); } autoRefresh() { if (this.autoLoad) return this.refresh(); return this.$q.resolve(); } buildFilter() { let order = this.order; if (typeof order === 'string') order = this.order.split(/\s*,\s*/); let myFilter = { fields: this.fields, where: mergeWhere(this.link, this.where), include: this.include, order: order, limit: this.limit }; let filter = this.filter; filter = mergeFilters(myFilter, filter); filter = mergeFilters(this.userFilter, filter); return filter; } refresh() { if (!this._url) return this.$q.resolve(); return this.sendRequest(this.buildFilter()); } /** * Applies a new filter to the model. * * @param {Object} filter The Loopback filter * @param {Object} params Custom parameters * @return {Promise} The request promise */ applyFilter(filter, params) { this.userFilter = filter; this.userParams = params; return this.refresh(); } /** * Adds a filter to the model. * * @param {Object} filter The Loopback filter * @param {Object} params Custom parameters * @return {Promise} The request promise */ addFilter(filter, params) { this.userFilter = mergeFilters(filter, this.userFilter); this.userParams = Object.assign({}, this.userParams, params); return this.refresh(); } /** * Applies a new filter to the model. * * @param {Object} params Custom parameters * @return {Promise} The request promise */ applyParams(params) { this.userParams = params; return this.refresh(); } removeFilter() { return this.applyFilter(null, null); } /** * Cancels the current request, if any. */ cancelRequest() { if (this.canceler) { this.canceler.resolve(); this.canceler = null; } } loadMore() { if (!this.moreRows) return this.$q.resolve(); let filter = Object.assign({}, this.currentFilter); filter.skip = this.orgData ? this.orgData.length : 0; return this.sendRequest(filter, true); } clear() { this.orgData = null; this.moreRows = null; } /** * Saves current changes on the server. * * @return {Promise} The save request promise */ save() { if (!this.isChanged) return this.$q.resolve(); let deletes = []; let updates = []; let creates = []; let orgDeletes = []; let orgUpdates = []; let orgCreates = []; let pk = this.primaryKey; for (let row of this.removed) { deletes.push(row.$orgRow[pk]); orgDeletes.push(row); } for (let row of this.data) { if (row.$isNew) { let data = {}; for (let prop in row) { if (prop.charAt(0) !== '$') data[prop] = row[prop]; } creates.push(row); orgCreates.push(row); } else if (row.$oldData) { let data = {}; for (let prop in row.$oldData) data[prop] = row[prop]; updates.push({ data, where: {[pk]: row.$orgRow[pk]} }); orgUpdates.push(row); } } let changes = {deletes, updates, creates}; for (let prop in changes) { if (changes[prop].length === 0) changes[prop] = undefined; } if (!changes) return this.$q.resolve(); let url = this.saveUrl ? this.saveUrl : `${this._url}/crud`; return this.$http.post(url, changes) .then(res => { const created = res.data; // Apply new data to created instances for (let i = 0; i < orgCreates.length; i++) { const row = orgCreates[i]; row[pk] = created[i][pk]; for (let prop in row) { if (prop.charAt(0) !== '$') row[prop] = created[i][prop]; } } this.applyChanges(); super.save(); }); } buildParams() { let params = {}; if (this.params instanceof Object) Object.assign(params, this.params); if (this.userParams instanceof Object) Object.assign(params, this.userParams); return params; } sendRequest(filter, append) { this.cancelRequest(); this.canceler = this.$q.defer(); this.isPaging = append; if (!append && this.status != 'ready') this.status = 'loading'; let params = Object.assign( {filter}, this.buildParams() ); let options = { timeout: this.canceler.promise, params: params }; return this.$http.get(this._url, options).then( json => this.onRemoteDone(json, filter, append), json => this.onRemoteError(json, append) ).finally(() => { this.isPaging = false; }); } onRemoteDone(json, filter, append) { let data = json.data; if (append) this.orgData = this.orgData.concat(data); else { this.orgData = data; this.currentFilter = filter; } this.data = this.proxiedData.slice(); this.moreRows = filter.limit && data.length == filter.limit; this.onRequestEnd(); } onRemoteError(err, append) { if (!append) { this.clear(); this.status = 'error'; } this.onRequestEnd(); throw err; } onRequestEnd() { this.canceler = null; } undoChanges() { super.undoChanges(); this.data = this.proxiedData.slice(); } } CrudModel.$inject = ['$q', '$http', '$element', '$scope']; ngModule.vnComponent('vnCrudModel', { controller: CrudModel, bindings: { orgData: '