367 lines
14 KiB
TypeScript

// (C) Copyright 2015 Martin Dougiamas
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import { Component, ViewChild } from '@angular/core';
import { Content, IonicPage, NavParams, NavController } from 'ionic-angular';
import { TranslateService } from '@ngx-translate/core';
import { FormGroup } from '@angular/forms';
import { CoreUtilsProvider } from '@providers/utils/utils';
import { CoreDomUtilsProvider } from '@providers/utils/dom';
import { CoreSitesProvider } from '@providers/sites';
import { CoreGroupsProvider } from '@providers/groups';
import { CoreEventsProvider } from '@providers/events';
import { CoreFileUploaderProvider } from '@core/fileuploader/providers/fileuploader';
import { CoreCourseProvider } from '@core/course/providers/course';
import { AddonModDataProvider } from '../../providers/data';
import { AddonModDataHelperProvider } from '../../providers/helper';
import { AddonModDataOfflineProvider } from '../../providers/offline';
import { AddonModDataFieldsDelegate } from '../../providers/fields-delegate';
import { AddonModDataComponentsModule } from '../../components/components.module';
/**
* Page that displays the view edit page.
*/
@IonicPage({ segment: 'addon-mod-data-edit' })
@Component({
selector: 'page-addon-mod-data-edit',
templateUrl: 'edit.html',
})
export class AddonModDataEditPage {
@ViewChild(Content) content: Content;
protected module: any;
protected courseId: number;
protected data: any;
protected entryId: number;
protected entry: any;
protected offlineActions = [];
protected fields = {};
protected fieldsArray = [];
protected siteId: string;
protected offline: boolean;
protected forceLeave = false; // To allow leaving the page without checking for changes.
title = '';
component = AddonModDataProvider.COMPONENT;
loaded = false;
selectedGroup = 0;
cssClass = '';
cssTemplate = '';
groupInfo: any;
editFormRender = '';
editForm: FormGroup;
extraImports = [AddonModDataComponentsModule];
jsData: any;
errors = {};
constructor(params: NavParams, protected utils: CoreUtilsProvider, protected groupsProvider: CoreGroupsProvider,
protected domUtils: CoreDomUtilsProvider, protected fieldsDelegate: AddonModDataFieldsDelegate,
protected courseProvider: CoreCourseProvider, protected dataProvider: AddonModDataProvider,
protected dataOffline: AddonModDataOfflineProvider, protected dataHelper: AddonModDataHelperProvider,
sitesProvider: CoreSitesProvider, protected navCtrl: NavController, protected translate: TranslateService,
protected eventsProvider: CoreEventsProvider, protected fileUploaderProvider: CoreFileUploaderProvider) {
this.module = params.get('module') || {};
this.entryId = params.get('entryId') || null;
this.courseId = params.get('courseId');
this.selectedGroup = params.get('group') || 0;
this.siteId = sitesProvider.getCurrentSiteId();
this.title = this.module.name;
this.editForm = new FormGroup({});
}
/**
* View loaded.
*/
ionViewDidLoad(): void {
this.fetchEntryData();
}
/**
* Check if we can leave the page or not and ask to confirm the lost of data.
*
* @return {boolean | Promise<void>} Resolved if we can leave it, rejected if not.
*/
ionViewCanLeave(): boolean | Promise<void> {
if (this.forceLeave) {
return true;
}
const inputData = this.editForm.value;
return this.dataHelper.hasEditDataChanged(inputData, this.fieldsArray, this.data.id,
this.entry.contents).then((changed) => {
if (!changed) {
return Promise.resolve();
}
// Show confirmation if some data has been modified.
return this.domUtils.showConfirm(this.translate.instant('core.confirmcanceledit'));
}).then(() => {
// Delete the local files from the tmp folder.
return this.dataHelper.getEditTmpFiles(inputData, this.fieldsArray, this.data.id,
this.entry.contents).then((files) => {
this.fileUploaderProvider.clearTmpFiles(files);
});
});
}
/**
* Fetch the entry data.
*
* @return {Promise<any>} Resolved when done.
*/
protected fetchEntryData(): Promise<any> {
return this.dataProvider.getDatabase(this.courseId, this.module.id).then((data) => {
this.title = data.name || this.title;
this.data = data;
this.cssClass = 'addon-data-entries-' + data.id;
return this.dataProvider.getDatabaseAccessInformation(data.id);
}).then((accessData) => {
this.cssTemplate = this.dataHelper.prefixCSS(this.data.csstemplate, '.' + this.cssClass);
if (this.entryId) {
return this.groupsProvider.getActivityGroupInfo(this.data.coursemodule, accessData.canmanageentries)
.then((groupInfo) => {
this.groupInfo = groupInfo;
// Check selected group is accessible.
if (groupInfo && groupInfo.groups && groupInfo.groups.length > 0) {
if (!groupInfo.groups.some((group) => this.selectedGroup == group.id)) {
this.selectedGroup = groupInfo.groups[0].id;
}
}
});
}
}).then(() => {
return this.dataOffline.getEntryActions(this.data.id, this.entryId);
}).then((actions) => {
this.offlineActions = actions;
return this.dataProvider.getFields(this.data.id);
}).then((fieldsData) => {
this.fieldsArray = fieldsData;
this.fields = this.utils.arrayToObject(fieldsData, 'id');
return this.dataHelper.getEntry(this.data, this.entryId, this.offlineActions);
}).then((entry) => {
if (entry) {
entry = entry.entry;
// Index contents by fieldid.
entry.contents = this.utils.arrayToObject(entry.contents, 'fieldid');
} else {
entry = {
contents: {}
};
}
return this.dataHelper.applyOfflineActions(entry, this.offlineActions, this.fieldsArray);
}).then((entryData) => {
this.entry = entryData;
this.editFormRender = this.displayEditFields();
}).catch((message) => {
this.domUtils.showErrorModalDefault(message, 'core.course.errorgetmodule', true);
}).finally(() => {
this.loaded = true;
});
}
/**
* Saves data.
*
* @param {Event} e Event.
* @return {Promise<any>} Resolved when done.
*/
save(e: Event): Promise<any> {
e.preventDefault();
e.stopPropagation();
const inputData = this.editForm.value;
return this.dataHelper.hasEditDataChanged(inputData, this.fieldsArray, this.data.id,
this.entry.contents).then((changed) => {
if (!changed) {
if (this.entryId) {
return this.returnToEntryList();
}
// New entry, no changes means no field filled, warn the user.
return Promise.reject('addon.mod_data.emptyaddform');
}
const modal = this.domUtils.showModalLoading('core.sending', true);
// Create an ID to assign files.
const entryTemp = this.entryId ? this.entryId : - (new Date().getTime());
return this.dataHelper.getEditDataFromForm(inputData, this.fieldsArray, this.data.id, entryTemp, this.entry.contents,
this.offline).catch((e) => {
if (!this.offline) {
// Cannot submit in online, prepare for offline usage.
this.offline = true;
return this.dataHelper.getEditDataFromForm(inputData, this.fieldsArray, this.data.id, entryTemp,
this.entry.contents, this.offline);
}
return Promise.reject(e);
}).then((editData) => {
if (editData.length > 0) {
if (this.entryId) {
return this.dataProvider.editEntry(this.data.id, this.entryId, this.courseId, editData, this.fields,
undefined, this.offline);
}
return this.dataProvider.addEntry(this.data.id, entryTemp, this.courseId, editData, this.selectedGroup,
this.fields, undefined, this.offline);
}
return false;
}).then((result: any) => {
if (!result) {
// No field filled, warn the user.
return Promise.reject('addon.mod_data.emptyaddform');
}
// This is done if entry is updated when editing or creating if not.
if ((this.entryId && result.updated) || (!this.entryId && result.newentryid)) {
const promises = [];
this.entryId = this.entryId || result.newentryid;
promises.push(this.dataProvider.invalidateEntryData(this.data.id, this.entryId, this.siteId));
promises.push(this.dataProvider.invalidateEntriesData(this.data.id, this.siteId));
return Promise.all(promises).then(() => {
this.eventsProvider.trigger(AddonModDataProvider.ENTRY_CHANGED,
{ dataId: this.data.id, entryId: this.entryId } , this.siteId);
}).finally(() => {
return this.returnToEntryList();
});
} else {
this.errors = {};
if (result.fieldnotifications) {
result.fieldnotifications.forEach((fieldNotif) => {
const field = this.fieldsArray.find((field) => field.name == fieldNotif.fieldname);
if (field) {
this.errors[field.id] = fieldNotif.notification;
}
});
}
this.jsData['errors'] = this.errors;
setTimeout(() => {
this.scrollToFirstError();
});
}
}).finally(() => {
modal.dismiss();
});
}).catch((error) => {
this.domUtils.showErrorModalDefault(error, 'Cannot edit entry', true);
});
}
/**
* Set group to see the database.
*
* @param {number} groupId Group identifier to set.
* @return {Promise<any>} Resolved when done.
*/
setGroup(groupId: number): Promise<any> {
this.selectedGroup = groupId;
this.loaded = false;
return this.fetchEntryData();
}
/**
* Displays Edit Search Fields.
*
* @return {string} Generated HTML.
*/
protected displayEditFields(): string {
if (!this.data.addtemplate) {
return '';
}
this.jsData = {
fields: this.fields,
contents: this.utils.clone(this.entry.contents),
form: this.editForm,
data: this.data,
errors: this.errors
};
let replace,
render,
template = this.data.addtemplate;
// Replace the fields found on template.
this.fieldsArray.forEach((field) => {
replace = '[[' + field.name + ']]';
replace = replace.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, '\\$&');
replace = new RegExp(replace, 'gi');
// Replace field by a generic directive.
render = '<addon-mod-data-field-plugin mode="edit" [field]="fields[' + field.id + ']"\
[value]="contents[' + field.id + ']" [form]="form" [database]="data" [error]="errors[' + field.id + ']">\
</addon-mod-data-field-plugin>';
template = template.replace(replace, render);
// Replace the field id tag.
replace = '[[' + field.name + '#id]]';
replace = replace.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, '\\$&');
replace = new RegExp(replace, 'gi');
template = template.replace(replace, 'field_' + field.id);
});
return template;
}
/**
* Return to the entry list (previous page) discarding temp data.
*
* @return {Promise<any>} Resolved when done.
*/
protected returnToEntryList(): Promise<any> {
const inputData = this.editForm.value;
return this.dataHelper.getEditTmpFiles(inputData, this.fieldsArray, this.data.id,
this.entry.contents).then((files) => {
this.fileUploaderProvider.clearTmpFiles(files);
}).finally(() => {
// Go back to entry list.
this.forceLeave = true;
this.navCtrl.pop();
});
}
/**
* Scroll to first error or to the top if not found.
*/
protected scrollToFirstError(): void {
if (!this.domUtils.scrollToElementBySelector(this.content, '.addon-data-error')) {
this.domUtils.scrollToTop(this.content);
}
}
}