import ngModule from '../../module'; import Component from '../../lib/component'; /** * A data model. It allows to filter, sort and paginate data. * * @property {Boolean} autoLoad Whether to load data automatically when required attributes are setted * @event dataChange Emitted when data property changes */ export class DataModel extends Component { /** * @type {Array} A JavaScript array with the model data. */ get data() { return null; } /** * Refresh the model data. * * @return {Promise} The request promise */ refresh() { return Promise.resolve(); } /** * Clears the model, removing all it's data. */ clear() {} } ngModule.vnComponent('vnDataModel', { controller: DataModel, bindings: { data: '=?', autoLoad: '} A JavaScript array with the model data. * Rows data can be modified directly but for insertion or removing * rows use the remove() or insert() model methods, otherwise changes * are not detected by the model. */ get data() { return this._data; } set data(value) { this._data = value; if (value == null) this.status = 'clear'; else if (value.length) this.status = 'ready'; else this.status = 'empty'; this.emit('dataChange'); this.emit('dataUpdate'); } /** * Removes a row from the model and emits the 'rowRemove' event. * * @param {Number} index The row index * @return {Promise} The save request promise */ remove(index) { let [row] = this.data.splice(index, 1); let proxiedIndex = this.proxiedData.indexOf(row); this.proxiedData.splice(proxiedIndex, 1); if (!row.$isNew) this.removed.push(row); this.isChanged = true; if (!this.data.length) this.status = 'empty'; this.emit('rowRemove', index); this.emit('dataUpdate'); if (this.autoSave) return this.save(); else return this.$q.resolve(); } /** * Removes a row from the model and emits the 'rowRemove' event. * * @param {Object} row The row object * @return {Promise} The save request promise */ removeRow(row) { return this.remove(this.data.indexOf(row)); } /** * Inserts a new row into the model and emits the 'rowInsert' event. * * @param {Object} data The initial data for the new row * @return {Number} The inserted row index */ insert(data) { data = Object.assign(data || {}, this.link); let newRow = this.createRow(data, null); newRow.$isNew = true; let index = this.proxiedData.push(newRow) - 1; if (this.data) this.data.push(newRow); this.isChanged = true; this.status = 'ready'; this.emit('rowInsert', index); this.emit('dataUpdate'); return index; } createRow(obj, index, orgRow) { Object.assign(obj, { $orgIndex: index, $orgRow: orgRow, $oldData: null, $isNew: false }); return new Proxy(obj, { set: (obj, prop, value) => { let changed = prop.charAt(0) !== '$' && value !== obj[prop]; if (changed && !obj.$isNew) { if (!obj.$oldData) obj.$oldData = {}; if (!obj.$oldData[prop]) obj.$oldData[prop] = value; this.isChanged = true; } let ret = Reflect.set(obj, prop, value); if (changed) { this.emit('rowChange', {obj, prop, value}); this.emit('dataUpdate'); if (!obj.$isNew && this.autoSave) this.save(); } return ret; } }); } resetChanges() { this.removed = []; this.isChanged = false; let data = this.proxiedData; if (data) { for (let row of data) row.$oldData = null; } } /** * Applies all changes made to the model into the original data source. */ applyChanges() { let data = this.proxiedData; let orgData = this.orgData; if (!data) return; for (let row of data) { if (row.$isNew) { let orgRow = {}; for (let prop in row) { if (prop.charAt(0) !== '$') orgRow[prop] = row[prop]; } row.$orgIndex = orgData.push(orgRow) - 1; row.$orgRow = orgRow; row.$isNew = false; } else if (row.$oldData) { for (let prop in row.$oldData) row.$orgRow[prop] = row[prop]; } } let removed = this.removed; if (removed) { removed = removed.sort((a, b) => b.$orgIndex - a.$orgIndex); for (let row of this.removed) orgData.splice(row.$orgIndex, 1); } this.resetChanges(); } /** * Should be implemented by child classes. */ save() { this.emit('save'); } /** * Undoes all changes made to the model data. */ undoChanges() { let data = this.proxiedData; if (!data) return; for (let i = 0; i < data.length; i++) { let row = data[i]; if (row.$oldData) Object.assign(row, row.$oldData); if (row.$isNew) data.splice(i--, 1); } let removed = this.removed; if (removed) { removed = removed.sort((a, b) => a.$orgIndex - b.$orgIndex); for (let row of this.removed) data.splice(row.$orgIndex, 0, row); } this.resetChanges(); } } ngModule.vnComponent('vnModelProxy', { controller: ModelProxy, bindings: { orgData: '|Function} order The sort specification. */ export class Sortable {} /** * Interface used to paginate data coming from different datasources. * * @property {Number} limit The page size */ export class Paginable { /** * @type {Boolean} Whether the model is loading. */ get isLoading() { return false; } /** * @type {ready|loading|clear|empty|error} The current model status. */ get status() { return null; } /** * @type {Boolean} Whether the model is paging. */ get isPaging() { return false; } /** * @type {Boolean} Whether the data is paginated and there are more rows to load. */ get moreRows() { return false; } /** * When limit is enabled, loads the next set of rows. * * @return {Promise} The request promise */ loadMore() { return Promise.resolve(); } }