430 lines
9.8 KiB
JavaScript
430 lines
9.8 KiB
JavaScript
import ngModule from '../../module';
|
|
import Component from '../../lib/component';
|
|
import getModifiedData from '../../lib/modified';
|
|
import isEqual from '../../lib/equals';
|
|
import isFullEmpty from '../../lib/full-empty';
|
|
import UserError from '../../lib/user-error';
|
|
import {mergeFilters} from 'vn-loopback/util/filter';
|
|
|
|
/**
|
|
* 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.
|
|
*
|
|
* @property {String} idField The id field name, 'id' if not specified
|
|
* @property {*} idValue The id field value
|
|
* @property {Boolean} isNew Whether is a new instance
|
|
* @property {Boolean} insertMode Whether to enable insert mode
|
|
* @property {String} url The base HTTP request path
|
|
* @property {Boolean} get Whether to fetch initial data
|
|
*/
|
|
export default class Watcher extends Component {
|
|
constructor(...args) {
|
|
super(...args);
|
|
this.idField = 'id';
|
|
this.get = true;
|
|
this.insertMode = false;
|
|
this.state = null;
|
|
this.deregisterCallback = this.$transitions.onStart({},
|
|
transition => this.callback(transition));
|
|
this.snapshot();
|
|
}
|
|
|
|
$onInit() {
|
|
let fetch = !this.insertMode
|
|
&& this.get
|
|
&& this.url
|
|
&& this.idValue;
|
|
|
|
if (fetch)
|
|
this.fetch();
|
|
else {
|
|
this.isNew = !!this.insertMode;
|
|
this.snapshot();
|
|
}
|
|
}
|
|
|
|
$onDestroy() {
|
|
this.deregisterCallback();
|
|
}
|
|
|
|
/**
|
|
* @type {Booelan} The computed instance HTTP path
|
|
*/
|
|
get instanceUrl() {
|
|
return `${this.url}/${this.idValue}`;
|
|
}
|
|
|
|
/**
|
|
* @type {Object} The instance data
|
|
*/
|
|
get data() {
|
|
return this._data;
|
|
}
|
|
|
|
set data(value) {
|
|
this._data = value;
|
|
this.isNew = !!this.insertMode;
|
|
this.snapshot();
|
|
}
|
|
|
|
/**
|
|
* @type {Booelan} Whether it's popullated with data
|
|
*/
|
|
get hasData() {
|
|
return !!this.data;
|
|
}
|
|
|
|
set hasData(value) {
|
|
if (value)
|
|
this.fill();
|
|
else
|
|
this.delete();
|
|
}
|
|
|
|
/**
|
|
* @type {Booelan} Whether instance data have been modified
|
|
*/
|
|
get dirty() {
|
|
return this.form && this.form.$dirty || this.dataChanged();
|
|
}
|
|
|
|
fetch() {
|
|
let filter = mergeFilters({
|
|
fields: this.fields,
|
|
where: this.where,
|
|
include: this.include
|
|
}, this.filter);
|
|
|
|
let params = filter ? {filter} : null;
|
|
|
|
return this.$http.get(this.instanceUrl, params)
|
|
.then(json => {
|
|
this.overwrite(json.data);
|
|
this.isNew = false;
|
|
this.snapshot();
|
|
})
|
|
.catch(err => {
|
|
if (!(err.name == 'HttpError' && err.status == 404))
|
|
throw err;
|
|
|
|
if (this.autoFill) {
|
|
this.insert();
|
|
this.snapshot();
|
|
}
|
|
});
|
|
}
|
|
|
|
insert(data) {
|
|
this.assign({[this.idField]: this.idValue}, data);
|
|
this.isNew = true;
|
|
this.deleted = null;
|
|
}
|
|
|
|
delete() {
|
|
if (!this.hasData) return;
|
|
this.deleted = this.makeSnapshot();
|
|
this.clear();
|
|
this.isNew = false;
|
|
}
|
|
|
|
recover() {
|
|
if (!this.deleted) return;
|
|
this.restoreSnapshot(this.deleted);
|
|
}
|
|
|
|
fill() {
|
|
if (this.hasData)
|
|
return;
|
|
|
|
if (this.deleted)
|
|
this.recover();
|
|
else if (this.original && this.original.data)
|
|
this.reset();
|
|
else
|
|
this.insert();
|
|
}
|
|
|
|
reset() {
|
|
this.restoreSnapshot(this.original);
|
|
this.setPristine();
|
|
}
|
|
|
|
snapshot() {
|
|
const snapshot = this.makeSnapshot();
|
|
|
|
if (snapshot.data) {
|
|
const idValue = snapshot.data[this.idField];
|
|
if (idValue) this.idValue = idValue;
|
|
}
|
|
|
|
this.original = snapshot;
|
|
this.orgData = snapshot.data;
|
|
this.deleted = null;
|
|
this.setPristine();
|
|
}
|
|
|
|
makeSnapshot() {
|
|
return {
|
|
data: this.copyData(),
|
|
isNew: this.isNew,
|
|
ref: this.data
|
|
};
|
|
}
|
|
|
|
restoreSnapshot(snapshot) {
|
|
if (!snapshot) return;
|
|
this._data = snapshot.ref;
|
|
this.overwrite(snapshot.data);
|
|
this.isNew = snapshot.isNew;
|
|
this.deleted = null;
|
|
}
|
|
|
|
writeData(res) {
|
|
if (this.hasData)
|
|
this.assign(res.data);
|
|
this.isNew = false;
|
|
this.snapshot();
|
|
return res;
|
|
}
|
|
|
|
clear() {
|
|
this._data = null;
|
|
}
|
|
|
|
overwrite(data) {
|
|
if (data) {
|
|
if (!this.data) this._data = {};
|
|
overwrite(this.data, data);
|
|
} else
|
|
this._data = null;
|
|
}
|
|
|
|
assign(...args) {
|
|
this._data = Object.assign(this.data || {}, ...args);
|
|
}
|
|
|
|
copyData() {
|
|
return copyObject(this.data);
|
|
}
|
|
|
|
refresh() {
|
|
return this.fetch();
|
|
}
|
|
|
|
dataChanged() {
|
|
return !isEqual(this.orgData, this.copyData());
|
|
}
|
|
|
|
/**
|
|
* Submits the data and goes back in the history.
|
|
*
|
|
* @return {Promise} The request promise
|
|
*/
|
|
submitBack() {
|
|
return this.submit().then(res => {
|
|
this.window.history.back();
|
|
return res;
|
|
});
|
|
}
|
|
|
|
/**
|
|
* 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(res => {
|
|
this.$state.go(state, params || {});
|
|
return res;
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Submits the data to the server.
|
|
*
|
|
* @return {Promise} The http request promise
|
|
*/
|
|
submit() {
|
|
try {
|
|
if (this.isNew)
|
|
this.isInvalid();
|
|
else
|
|
this.check();
|
|
} catch (err) {
|
|
return this.$q.reject(err);
|
|
}
|
|
|
|
return this.realSubmit().then(res => {
|
|
this.notifySaved();
|
|
return res;
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Submits the data without checking data validity or changes.
|
|
*
|
|
* @return {Promise} The http request promise
|
|
*/
|
|
realSubmit() {
|
|
if (this.form)
|
|
this.form.$setSubmitted();
|
|
|
|
if (!this.dataChanged() && !this.isNew) {
|
|
this.snapshot();
|
|
return this.$q.resolve();
|
|
}
|
|
|
|
let changedData = this.isNew
|
|
? this.data
|
|
: getModifiedData(this.data, this.orgData);
|
|
|
|
// If watcher is associated to mgCrud
|
|
|
|
if (this.save && this.save.accept) {
|
|
this.save.model = changedData;
|
|
return this.save.accept()
|
|
.then(json => this.writeData({data: json}));
|
|
}
|
|
|
|
// When mgCrud is not used
|
|
|
|
let req;
|
|
|
|
if (this.deleted)
|
|
req = this.$http.delete(this.instanceUrl);
|
|
else if (this.isNew)
|
|
req = this.$http.post(this.url, changedData);
|
|
else
|
|
req = this.$http.patch(this.instanceUrl, changedData);
|
|
|
|
return req.then(res => this.writeData(res));
|
|
}
|
|
|
|
/**
|
|
* Checks if data is ready to send.
|
|
*/
|
|
check() {
|
|
this.isInvalid();
|
|
if (!this.dirty)
|
|
throw new UserError('No changes to save');
|
|
}
|
|
|
|
/**
|
|
* Checks if the form is valid.
|
|
*/
|
|
isInvalid() {
|
|
if (this.form && this.form.$invalid)
|
|
throw new UserError('Some fields are invalid');
|
|
}
|
|
|
|
/**
|
|
* Notifies the user that the data has been saved.
|
|
*/
|
|
notifySaved() {
|
|
this.vnApp.showSuccess(this.$t('Data saved!'));
|
|
}
|
|
|
|
setPristine() {
|
|
if (this.form) this.form.$setPristine();
|
|
}
|
|
|
|
setDirty() {
|
|
if (this.form) this.form.$setDirty();
|
|
}
|
|
|
|
callback(transition) {
|
|
if (!this.state && this.dirty) {
|
|
this.state = transition.to().name;
|
|
this.$.confirm.show();
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
onConfirmResponse(response) {
|
|
if (response === 'accept') {
|
|
this.reset();
|
|
this.$state.go(this.state);
|
|
} else
|
|
this.state = null;
|
|
}
|
|
|
|
/**
|
|
* @deprecated Use reset()
|
|
*/
|
|
loadOriginalData() {
|
|
this.reset();
|
|
}
|
|
|
|
/**
|
|
* @deprecated Use snapshot()
|
|
*/
|
|
updateOriginalData() {
|
|
this.snapshot();
|
|
}
|
|
}
|
|
Watcher.$inject = ['$element', '$scope'];
|
|
|
|
function copyObject(data) {
|
|
let newCopy;
|
|
|
|
if (data && typeof data === 'object') {
|
|
newCopy = {};
|
|
Object.keys(data).forEach(
|
|
key => {
|
|
let value = data[key];
|
|
if (value instanceof Date)
|
|
newCopy[key] = new Date(value.getTime());
|
|
else if (!isFullEmpty(value)) {
|
|
if (typeof value === 'object')
|
|
newCopy[key] = copyObject(value);
|
|
else
|
|
newCopy[key] = value;
|
|
}
|
|
}
|
|
);
|
|
} else
|
|
newCopy = data;
|
|
|
|
return newCopy;
|
|
}
|
|
|
|
function clearObject(obj) {
|
|
if (!obj) return;
|
|
for (let key in obj) {
|
|
if (obj.hasOwnProperty(key))
|
|
delete obj[key];
|
|
}
|
|
}
|
|
|
|
function overwrite(obj, data) {
|
|
if (!obj) return;
|
|
clearObject(obj);
|
|
Object.assign(obj, data);
|
|
}
|
|
|
|
ngModule.vnComponent('vnWatcher', {
|
|
template: require('./watcher.html'),
|
|
bindings: {
|
|
url: '@?',
|
|
idField: '@?',
|
|
idValue: '<?',
|
|
data: '=',
|
|
form: '<',
|
|
save: '<?',
|
|
get: '<?',
|
|
insertMode: '<?',
|
|
autoFill: '<?',
|
|
filter: '<?',
|
|
fields: '<?',
|
|
where: '<?',
|
|
include: '<?'
|
|
},
|
|
controller: Watcher
|
|
});
|