Vmeda.Online/src/addon/calendar/pages/edit-event/edit-event.ts

590 lines
23 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, OnInit, OnDestroy, Optional, ViewChild } from '@angular/core';
import { FormControl, FormGroup, FormBuilder, Validators } from '@angular/forms';
import { IonicPage, NavController, NavParams } from 'ionic-angular';
import { TranslateService } from '@ngx-translate/core';
import { CoreEventsProvider } from '@providers/events';
import { CoreGroupsProvider } from '@providers/groups';
import { CoreSitesProvider } from '@providers/sites';
import { CoreSyncProvider } from '@providers/sync';
import { CoreDomUtilsProvider } from '@providers/utils/dom';
import { CoreTextUtilsProvider } from '@providers/utils/text';
import { CoreTimeUtilsProvider } from '@providers/utils/time';
import { CoreUtilsProvider } from '@providers/utils/utils';
import { CoreCoursesProvider } from '@core/courses/providers/courses';
import { CoreSplitViewComponent } from '@components/split-view/split-view';
import { CoreRichTextEditorComponent } from '@components/rich-text-editor/rich-text-editor.ts';
import { AddonCalendarProvider } from '../../providers/calendar';
import { AddonCalendarOfflineProvider } from '../../providers/calendar-offline';
import { AddonCalendarHelperProvider } from '../../providers/helper';
import { AddonCalendarSyncProvider } from '../../providers/calendar-sync';
import { CoreSite } from '@classes/site';
/**
* Page that displays a form to create/edit an event.
*/
@IonicPage({ segment: 'addon-calendar-edit-event' })
@Component({
selector: 'page-addon-calendar-edit-event',
templateUrl: 'edit-event.html',
})
export class AddonCalendarEditEventPage implements OnInit, OnDestroy {
@ViewChild(CoreRichTextEditorComponent) descriptionEditor: CoreRichTextEditorComponent;
title: string;
dateFormat: string;
component = AddonCalendarProvider.COMPONENT;
loaded = false;
hasOffline = false;
eventTypes = [];
categories = [];
courses = [];
groups = [];
loadingGroups = false;
courseGroupSet = false;
advanced = false;
errors: any;
event: any; // The event object (when editing an event).
// Form variables.
eventForm: FormGroup;
eventTypeControl: FormControl;
groupControl: FormControl;
descriptionControl: FormControl;
protected eventId: number;
protected courseId: number;
protected originalData: any;
protected currentSite: CoreSite;
protected types: any; // Object with the supported types.
protected showAll: boolean;
protected isDestroyed = false;
protected error = false;
protected gotEventData = false;
constructor(navParams: NavParams,
private navCtrl: NavController,
private translate: TranslateService,
private domUtils: CoreDomUtilsProvider,
private textUtils: CoreTextUtilsProvider,
private timeUtils: CoreTimeUtilsProvider,
private eventsProvider: CoreEventsProvider,
private groupsProvider: CoreGroupsProvider,
sitesProvider: CoreSitesProvider,
private coursesProvider: CoreCoursesProvider,
private utils: CoreUtilsProvider,
private calendarProvider: AddonCalendarProvider,
private calendarOffline: AddonCalendarOfflineProvider,
private calendarHelper: AddonCalendarHelperProvider,
private calendarSync: AddonCalendarSyncProvider,
private fb: FormBuilder,
private syncProvider: CoreSyncProvider,
@Optional() private svComponent: CoreSplitViewComponent) {
this.eventId = navParams.get('eventId');
this.courseId = navParams.get('courseId');
this.title = this.eventId ? 'addon.calendar.editevent' : 'addon.calendar.newevent';
const timestamp = navParams.get('timestamp');
this.currentSite = sitesProvider.getCurrentSite();
this.errors = {
required: this.translate.instant('core.required')
};
// Calculate format to use. ion-datetime doesn't support escaping characters ([]), so we remove them.
this.dateFormat = this.timeUtils.convertPHPToMoment(this.translate.instant('core.strftimedatetimeshort'))
.replace(/[\[\]]/g, '');
// Initialize form variables.
this.eventForm = new FormGroup({});
this.eventTypeControl = this.fb.control('', Validators.required);
this.groupControl = this.fb.control('');
this.descriptionControl = this.fb.control('');
const currentDate = this.timeUtils.toDatetimeFormat(timestamp);
this.eventForm.addControl('name', this.fb.control('', Validators.required));
this.eventForm.addControl('timestart', this.fb.control(currentDate, Validators.required));
this.eventForm.addControl('eventtype', this.eventTypeControl);
this.eventForm.addControl('categoryid', this.fb.control(''));
this.eventForm.addControl('courseid', this.fb.control(this.courseId));
this.eventForm.addControl('groupcourseid', this.fb.control(''));
this.eventForm.addControl('groupid', this.groupControl);
this.eventForm.addControl('description', this.descriptionControl);
this.eventForm.addControl('location', this.fb.control(''));
this.eventForm.addControl('duration', this.fb.control(0));
this.eventForm.addControl('timedurationuntil', this.fb.control(currentDate));
this.eventForm.addControl('timedurationminutes', this.fb.control(''));
this.eventForm.addControl('repeat', this.fb.control(false));
this.eventForm.addControl('repeats', this.fb.control('1'));
this.eventForm.addControl('repeateditall', this.fb.control(1));
}
/**
* Component being initialized.
*/
ngOnInit(): void {
this.fetchData().finally(() => {
this.originalData = this.utils.clone(this.eventForm.value);
this.loaded = true;
});
}
/**
* Fetch the data needed to render the form.
*
* @param {boolean} [refresh] Whether it's refreshing data.
* @return {Promise<any>} Promise resolved when done.
*/
protected fetchData(refresh?: boolean): Promise<any> {
let accessInfo;
this.error = false;
// Get access info.
return this.calendarProvider.getAccessInformation(this.courseId).then((info) => {
accessInfo = info;
return this.calendarProvider.getAllowedEventTypes(this.courseId);
}).then((types) => {
this.types = types;
const promises = [],
eventTypes = this.calendarHelper.getEventTypeOptions(types);
if (!eventTypes.length) {
return Promise.reject(this.translate.instant('addon.calendar.nopermissiontoupdatecalendar'));
}
if (this.eventId && !this.gotEventData) {
// Editing an event, get the event data. Wait for sync first.
promises.push(this.calendarSync.waitForSync(AddonCalendarSyncProvider.SYNC_ID).then(() => {
// Do not block if the scope is already destroyed.
if (!this.isDestroyed) {
this.syncProvider.blockOperation(AddonCalendarProvider.COMPONENT, this.eventId);
}
const promises = [];
// Get the event offline data if there's any.
promises.push(this.calendarOffline.getEvent(this.eventId).then((event) => {
this.hasOffline = true;
return event;
}).catch(() => {
// No offline data.
this.hasOffline = false;
}));
if (this.eventId > 0) {
// It's an online event. get its data from server.
promises.push(this.calendarProvider.getEventById(this.eventId).then((event) => {
this.event = event;
if (event && event.repeatid) {
event.othereventscount = event.eventcount ? event.eventcount - 1 : '';
}
return event;
}));
}
return Promise.all(promises).then((result) => {
this.gotEventData = true;
const event = result[0] || result[1]; // Use offline data first.
if (event) {
// Load the data in the form.
return this.loadEventData(event, !!result[0]);
}
});
}));
}
if (types.category) {
// Get the categories.
promises.push(this.coursesProvider.getCategories(0, true).then((cats) => {
this.categories = cats;
}));
}
this.showAll = this.utils.isTrueOrOne(this.currentSite.getStoredConfig('calendar_adminseesall')) &&
accessInfo.canmanageentries;
if (types.course || types.groups) {
// Get the courses.
const promise = this.showAll ? this.coursesProvider.getCoursesByField() : this.coursesProvider.getUserCourses();
promises.push(promise.then((courses) => {
if (this.showAll) {
// Remove site home from the list of courses.
const siteHomeId = this.currentSite.getSiteHomeId();
courses = courses.filter((course) => {
return course.id != siteHomeId;
});
}
// Format the name of the courses.
const subPromises = [];
courses.forEach((course) => {
subPromises.push(this.textUtils.formatText(course.fullname).then((text) => {
course.fullname = text;
}).catch(() => {
// Ignore errors.
}));
});
return Promise.all(subPromises).then(() => {
// Sort courses by name.
this.courses = courses.sort((a, b) => {
const compareA = a.fullname.toLowerCase(),
compareB = b.fullname.toLowerCase();
return compareA.localeCompare(compareB);
});
});
}));
}
return Promise.all(promises).then(() => {
if (!this.eventTypeControl.value) {
// Initialize event type value. If course is allowed, select it first.
if (types.course) {
this.eventTypeControl.setValue(AddonCalendarProvider.TYPE_COURSE);
} else {
this.eventTypeControl.setValue(eventTypes[0].value);
}
}
this.eventTypes = eventTypes;
});
}).catch((error) => {
this.domUtils.showErrorModalDefault(error, 'Error getting data.');
this.error = true;
if (!this.svComponent || !this.svComponent.isOn()) {
this.originalData = null; // Avoid asking for confirmation.
this.navCtrl.pop();
}
});
}
/**
* Load an event data into the form.
*
* @param {any} event Event data.
* @param {boolean} isOffline Whether the data is from offline or not.
* @return {Promise<any>} Promise resolved when done.
*/
protected loadEventData(event: any, isOffline: boolean): Promise<any> {
const courseId = event.course ? event.course.id : event.courseid;
this.eventForm.controls.name.setValue(event.name);
this.eventForm.controls.timestart.setValue(this.timeUtils.toDatetimeFormat(event.timestart * 1000));
this.eventForm.controls.eventtype.setValue(event.eventtype);
this.eventForm.controls.categoryid.setValue(event.categoryid || '');
this.eventForm.controls.courseid.setValue(courseId || '');
this.eventForm.controls.groupcourseid.setValue(event.groupcourseid || courseId || '');
this.eventForm.controls.groupid.setValue(event.groupid || '');
this.eventForm.controls.description.setValue(event.description);
this.eventForm.controls.location.setValue(event.location);
if (isOffline) {
// It's an offline event, use the data as it is.
this.eventForm.controls.duration.setValue(event.duration);
this.eventForm.controls.timedurationuntil.setValue(
this.timeUtils.toDatetimeFormat((event.timedurationuntil * 1000) || Date.now()));
this.eventForm.controls.timedurationminutes.setValue(event.timedurationminutes || '');
this.eventForm.controls.repeat.setValue(!!event.repeat);
this.eventForm.controls.repeats.setValue(event.repeats || '1');
this.eventForm.controls.repeateditall.setValue(event.repeateditall || 1);
} else {
// Online event, we'll have to calculate the data.
if (event.timeduration > 0) {
this.eventForm.controls.duration.setValue(1);
this.eventForm.controls.timedurationuntil.setValue(this.timeUtils.toDatetimeFormat(
(event.timestart + event.timeduration) * 1000));
} else {
// No duration.
this.eventForm.controls.duration.setValue(0);
this.eventForm.controls.timedurationuntil.setValue(this.timeUtils.toDatetimeFormat());
}
this.eventForm.controls.timedurationminutes.setValue('');
this.eventForm.controls.repeat.setValue(!!event.repeatid);
this.eventForm.controls.repeats.setValue(event.eventcount || '1');
this.eventForm.controls.repeateditall.setValue(1);
}
if (event.eventtype == 'group' && courseId) {
return this.loadGroups(courseId);
}
return Promise.resolve();
}
/**
* Pull to refresh.
*
* @param {any} refresher Refresher.
*/
refreshData(refresher: any): void {
const promises = [
this.calendarProvider.invalidateAccessInformation(this.courseId),
this.calendarProvider.invalidateAllowedEventTypes(this.courseId)
];
if (this.types) {
if (this.types.category) {
promises.push(this.coursesProvider.invalidateCategories(0, true));
}
if (this.types.course || this.types.groups) {
if (this.showAll) {
promises.push(this.coursesProvider.invalidateCoursesByField());
} else {
promises.push(this.coursesProvider.invalidateUserCourses());
}
}
}
Promise.all(promises).finally(() => {
this.fetchData(true).finally(() => {
refresher.complete();
});
});
}
/**
* A course was selected, get its groups.
*
* @param {number} courseId Course ID.
*/
groupCourseSelected(courseId: number): void {
if (!courseId) {
return;
}
const modal = this.domUtils.showModalLoading();
this.loadGroups(courseId).then(() => {
this.groupControl.setValue('');
}).catch((error) => {
this.domUtils.showErrorModalDefault(error, 'Error getting data.');
}).finally(() => {
modal.dismiss();
});
}
/**
* Load groups of a certain course.
*
* @param {number} courseId Course ID.
* @return {Promise<any>} Promise resolved when done.
*/
protected loadGroups(courseId: number): Promise<any> {
this.loadingGroups = true;
return this.groupsProvider.getUserGroupsInCourse(courseId).then((groups) => {
this.groups = groups;
this.courseGroupSet = true;
}).finally(() => {
this.loadingGroups = false;
});
}
/**
* Show or hide advanced form fields.
*/
toggleAdvanced(): void {
this.advanced = !this.advanced;
}
/**
* Create the event.
*/
submit(): void {
// Validate data.
const formData = this.eventForm.value,
timeStartDate = new Date(formData.timestart),
timeUntilDate = this.timeUtils.datetimeToDate(formData.timedurationuntil),
timeDurationMinutes = parseInt(formData.timedurationminutes || '', 10);
let error;
if (formData.eventtype == AddonCalendarProvider.TYPE_COURSE && !formData.courseid) {
error = 'core.selectacourse';
} else if (formData.eventtype == AddonCalendarProvider.TYPE_GROUP && !formData.groupcourseid) {
error = 'core.selectacourse';
} else if (formData.eventtype == AddonCalendarProvider.TYPE_GROUP && !formData.groupid) {
error = 'core.selectagroup';
} else if (formData.eventtype == AddonCalendarProvider.TYPE_CATEGORY && !formData.categoryid) {
error = 'core.selectacategory';
} else if (formData.duration == 1 && timeStartDate.getTime() > timeUntilDate.getTime()) {
error = 'addon.calendar.invalidtimedurationuntil';
} else if (formData.duration == 2 && (isNaN(timeDurationMinutes) || timeDurationMinutes < 1)) {
error = 'addon.calendar.invalidtimedurationminutes';
}
if (error) {
// Show error and stop.
this.domUtils.showErrorModal(this.translate.instant(error));
return;
}
// Format the data to send.
const data: any = {
name: formData.name,
eventtype: formData.eventtype,
timestart: Math.floor(timeStartDate.getTime() / 1000),
description: {
text: formData.description,
format: 1
},
location: formData.location,
duration: formData.duration,
repeat: formData.repeat
};
if (formData.eventtype == AddonCalendarProvider.TYPE_COURSE) {
data.courseid = formData.courseid;
} else if (formData.eventtype == AddonCalendarProvider.TYPE_GROUP) {
data.groupcourseid = formData.groupcourseid;
data.groupid = formData.groupid;
} else if (formData.eventtype == AddonCalendarProvider.TYPE_CATEGORY) {
data.categoryid = formData.categoryid;
}
if (formData.duration == 1) {
data.timedurationuntil = Math.floor(timeUntilDate.getTime() / 1000);
} else if (formData.duration == 2) {
data.timedurationminutes = formData.timedurationminutes;
}
if (formData.repeat) {
data.repeats = formData.repeats;
}
if (this.event && this.event.repeatid) {
data.repeatid = this.event.repeatid;
data.repeateditall = formData.repeateditall;
}
// Send the data.
const modal = this.domUtils.showModalLoading('core.sending', true);
this.calendarProvider.submitEvent(this.eventId, data).then((result) => {
const numberOfRepetitions = formData.repeat ? formData.repeats :
(data.repeateditall && this.event.othereventscount ? this.event.othereventscount + 1 : 1);
this.calendarHelper.invalidateRepeatedEventsOnCalendar(result.event, numberOfRepetitions).catch(() => {
// Ignore errors.
}).then(() => {
this.returnToList(result.event);
});
}).catch((error) => {
this.domUtils.showErrorModalDefault(error, 'Error sending data.');
}).finally(() => {
modal.dismiss();
});
}
/**
* Convenience function to update or return to event list depending on device.
*
* @param {number} [event] Event.
*/
protected returnToList(event?: any): void {
// Unblock the sync because the view will be destroyed and the sync process could be triggered before ngOnDestroy.
this.unblockSync();
if (this.eventId > 0) {
// Editing an event.
const data: any = {
event: event
};
this.eventsProvider.trigger(AddonCalendarProvider.EDIT_EVENT_EVENT, data, this.currentSite.getId());
} else {
if (event) {
const data: any = {
event: event
};
this.eventsProvider.trigger(AddonCalendarProvider.NEW_EVENT_EVENT, data, this.currentSite.getId());
} else {
this.eventsProvider.trigger(AddonCalendarProvider.NEW_EVENT_DISCARDED_EVENT, {}, this.currentSite.getId());
}
}
if (this.svComponent && this.svComponent.isOn()) {
// Empty form.
this.hasOffline = false;
this.eventForm.reset(this.originalData);
this.originalData = this.utils.clone(this.eventForm.value);
} else {
this.originalData = null; // Avoid asking for confirmation.
this.navCtrl.pop();
}
}
/**
* Discard an offline saved discussion.
*/
discard(): void {
this.domUtils.showConfirm(this.translate.instant('core.areyousure')).then(() => {
this.calendarOffline.deleteEvent(this.eventId).then(() => {
this.returnToList();
}).catch(() => {
// Shouldn't happen.
this.domUtils.showErrorModal('Error discarding event.');
});
}).catch(() => {
// Cancelled.
});
}
/**
* Check if we can leave the page or not.
*
* @return {boolean|Promise<void>} Resolved if we can leave it, rejected if not.
*/
ionViewCanLeave(): boolean | Promise<void> {
if (this.calendarHelper.hasEventDataChanged(this.eventForm.value, this.originalData)) {
// Show confirmation if some data has been modified.
return this.domUtils.showConfirm(this.translate.instant('core.confirmcanceledit'));
} else {
return Promise.resolve();
}
}
protected unblockSync(): void {
if (this.eventId) {
this.syncProvider.unblockOperation(AddonCalendarProvider.COMPONENT, this.eventId);
}
}
/**
* Page destroyed.
*/
ngOnDestroy(): void {
this.unblockSync();
this.isDestroyed = true;
}
}