import ngModule from '../../module'; import Component from '../../lib/component'; import getModifiedData from '../../lib/modified'; import copyObject from '../../lib/copy'; import isEqual from '../../lib/equals'; import isFullEmpty from '../../lib/full-empty'; /** * Component that checks for changes on a specific model property and * asks the user to save or discard it when the state changes. * Also it can save the data to the server when the @url and @idField * properties are provided. */ export default class Watcher extends Component { constructor($element, $scope, $state, $transitions, $http, vnApp, $translate, $attrs) { super($element); this.$scope = $scope; this.$state = $state; this.$http = $http; this.$translate = $translate; this.$attrs = $attrs; this.vnApp = vnApp; this.state = null; this.deregisterCallback = $transitions.onStart({}, transition => this.callback(transition)); this.updateOriginalData(); } $onInit() { if (this.get && this.url) { this.fetchData(); } else if (this.get && !this.url) { throw new Error('Error: Parameter url ommitted'); } } $onChanges(changes) { if (this.data) { this.updateOriginalData(); } } $onDestroy() { this.deregisterCallback(); } fetchData() { let id = this.data[this.idField]; // return new Promise((resolve, reject) => { this.$http.get(`${this.url}/${id}`).then( json => { this.data = copyObject(json.data); this.updateOriginalData(); } // json => reject(json) ); // }); } /** * Submits the data and goes back in the history. * * @return {Promise} The request promise */ submitBack() { return this.submit().then( () => this.window.history.back() ); } /** * Submits the data and goes another state. * * @param {String} state The state name * @param {Object} params The request params * @return {Promise} The request promise */ submitGo(state, params) { return this.submit().then( () => this.$state.go(state, params || {}) ); } /** * Submits the data to the server. * * @return {Promise} The http request promise */ submit() { if (this.form) { this.form.$setSubmitted(); if (!this.form.$valid) return new Promise( (resolve, reject) => this.invalidForm(reject) ); } if (!this.dataChanged()) { return new Promise( (resolve, reject) => this.noChanges(reject) ); } let isPost = (this.$attrs.save && this.$attrs.save.toLowerCase() === 'post'); let changedData = isPost ? this.copyInNewObject(this.data) : getModifiedData(this.data, this.orgData); if (this.requiredField && !changedData[this.requiredField]) { let required = this.data[this.requiredField] || this.orgData[this.requiredField]; if (required === undefined) { return new Promise( (resolve, reject) => this.invalidForm(reject) ); } changedData[this.requiredField] = required; } if (this.save && this.save.accept) { this.save.model = changedData; // this.copyInNewObject(changedData); return new Promise((resolve, reject) => { this.save.accept().then( json => this.writeData({data: json}, resolve), json => reject(json) ); }); } // XXX: Alternative when mgCrud is not used let id = this.idField ? this.orgData[this.idField] : null; if (id) { return new Promise((resolve, reject) => { this.$http.patch(`${this.url}/${id}`, changedData).then( json => this.writeData(json, resolve), json => reject(json) ); }); } return new Promise((resolve, reject) => { this.$http.post(this.url, changedData).then( json => this.writeData(json, resolve), json => reject(json) ); }); } writeData(json, resolve) { Object.assign(this.data, json.data); this.updateOriginalData(); resolve(json); } noChanges(reject) { this.vnApp.showMessage( this.$translate.instant('No changes to save') ); reject(new Error('No changes to save')); } invalidForm(reject) { this.vnApp.showMessage( this.$translate.instant('Some fields are invalid') ); reject(new Error('Some fields are invalid')); } updateOriginalData() { this.orgData = this.copyInNewObject(this.data); if (this.form && this.form.$dirty) this.form.$setPristine(); } copyInNewObject(data) { let newCopy = {}; if (data && typeof data === 'object') { Object.keys(data).forEach( val => { if (!isFullEmpty(data[val])) { if (typeof data[val] === 'object') { newCopy[val] = this.copyInNewObject(data[val]); } else { newCopy[val] = data[val]; } } } ); } return newCopy; } callback(transition) { let dataChanged = this.dataChanged(); if (!this.state && dataChanged) { this.state = transition.to().name; this.$scope.confirm.show(); return false; } return true; } dataChanged() { let newData = this.copyInNewObject(this.data); if (this.form && this.form.$dirty) return !isEqual(newData, this.orgData); return !isEqual(newData, this.orgData); } onConfirmResponse(response) { if (response === 'ACCEPT') { if (this.data) Object.assign(this.data, this.orgData); this.$state.go(this.state); } else { this.state = null; } } } Watcher.$inject = ['$element', '$scope', '$state', '$transitions', '$http', 'vnApp', '$translate', '$attrs']; ngModule.component('vnWatcher', { template: require('./watcher.html'), bindings: { url: '@?', idField: '@?', requiredField: '@?', data: '<', form: '<', save: '<', get: '=?' }, controller: Watcher });