347 lines
13 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, OnDestroy, Optional, NgZone } from '@angular/core';
import { IonicPage, NavParams, NavController, Content } from 'ionic-angular';
import { Network } from '@ionic-native/network';
import { TranslateService } from '@ngx-translate/core';
import { AddonModFeedbackProvider } from '../../providers/feedback';
import { AddonModFeedbackHelperProvider } from '../../providers/helper';
import { AddonModFeedbackSyncProvider } from '../../providers/sync';
import { CoreDomUtilsProvider } from '@providers/utils/dom';
import { CoreUtilsProvider } from '@providers/utils/utils';
import { CoreAppProvider } from '@providers/app';
import { CoreEventsProvider } from '@providers/events';
import { CoreCourseProvider } from '@core/course/providers/course';
import { CoreLoginHelperProvider } from '@core/login/providers/helper';
import { CoreContentLinksHelperProvider } from '@core/contentlinks/providers/helper';
import { CoreSitesProvider } from '@providers/sites';
/**
* Page that displays feedback form.
*/
@IonicPage({ segment: 'addon-mod-feedback-form' })
@Component({
selector: 'page-addon-mod-feedback-form',
templateUrl: 'form.html',
})
export class AddonModFeedbackFormPage implements OnDestroy {
protected module: any;
protected currentPage: number;
protected submitted: any;
protected feedback;
protected siteAfterSubmit;
protected onlineObserver;
protected originalData;
protected currentSite;
protected forceLeave = false;
title: string;
preview = false;
courseId: number;
componentId: number;
completionPageContents: string;
component = AddonModFeedbackProvider.COMPONENT;
offline = false;
feedbackLoaded = false;
access: any;
items = [];
hasPrevPage = false;
hasNextPage = false;
completed = false;
completedOffline = false;
constructor(navParams: NavParams, protected feedbackProvider: AddonModFeedbackProvider, protected appProvider: CoreAppProvider,
protected utils: CoreUtilsProvider, protected domUtils: CoreDomUtilsProvider, protected navCtrl: NavController,
protected feedbackHelper: AddonModFeedbackHelperProvider, protected courseProvider: CoreCourseProvider,
protected eventsProvider: CoreEventsProvider, protected feedbackSync: AddonModFeedbackSyncProvider, network: Network,
protected translate: TranslateService, protected loginHelper: CoreLoginHelperProvider,
protected linkHelper: CoreContentLinksHelperProvider, sitesProvider: CoreSitesProvider,
@Optional() private content: Content, zone: NgZone) {
this.module = navParams.get('module');
this.courseId = navParams.get('courseId');
this.currentPage = navParams.get('page');
this.title = navParams.get('title');
this.preview = !!navParams.get('preview');
this.componentId = navParams.get('moduleId') || this.module.id;
this.currentSite = sitesProvider.getCurrentSite();
// Refresh online status when changes.
this.onlineObserver = network.onchange().subscribe((online) => {
// Execute the callback in the Angular zone, so change detection doesn't stop working.
zone.run(() => {
this.offline = !online;
});
});
}
/**
* View loaded.
*/
ionViewDidLoad(): void {
this.fetchData().then(() => {
this.feedbackProvider.logView(this.feedback.id, true).then(() => {
this.courseProvider.checkModuleCompletion(this.courseId, this.module.completionstatus);
}).catch(() => {
// Ignore errors.
});
});
}
/**
* View entered.
*/
ionViewDidEnter(): void {
this.forceLeave = false;
}
/**
* 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.forceLeave) {
return true;
}
if (!this.preview) {
const responses = this.feedbackHelper.getPageItemsResponses(this.items);
if (this.items && !this.completed && this.originalData) {
// Form submitted. Check if there is any change.
if (!this.utils.basicLeftCompare(responses, this.originalData, 3)) {
return this.domUtils.showConfirm(this.translate.instant('core.confirmcanceledit'));
}
}
}
return Promise.resolve();
}
/**
* Fetch all the data required for the view.
*
* @return {Promise<any>} Promise resolved when done.
*/
protected fetchData(): Promise<any> {
this.offline = !this.appProvider.isOnline();
return this.feedbackProvider.getFeedback(this.courseId, this.module.id).then((feedbackData) => {
this.feedback = feedbackData;
this.title = this.feedback.name || this.title;
return this.fetchAccessData();
}).then((accessData) => {
if (!this.preview && accessData.cansubmit && !accessData.isempty) {
return typeof this.currentPage == 'undefined' ?
this.feedbackProvider.getResumePage(this.feedback.id, this.offline, true) :
Promise.resolve(this.currentPage);
} else {
this.preview = true;
return Promise.resolve(0);
}
}).catch((error) => {
if (!this.offline && !this.utils.isWebServiceError(error)) {
// If it fails, go offline.
this.offline = true;
return this.feedbackProvider.getResumePage(this.feedback.id, true);
}
return Promise.reject(error);
}).then((page) => {
return this.fetchFeedbackPageData(page || 0);
}).catch((message) => {
this.domUtils.showErrorModalDefault(message, 'core.course.errorgetmodule', true);
this.forceLeave = true;
this.navCtrl.pop();
return Promise.reject(null);
}).finally(() => {
this.feedbackLoaded = true;
});
}
/**
* Fetch access information.
*
* @return {Promise<any>} Promise resolved when done.
*/
protected fetchAccessData(): Promise<any> {
return this.feedbackProvider.getFeedbackAccessInformation(this.feedback.id, this.offline, true).catch((error) => {
if (!this.offline && !this.utils.isWebServiceError(error)) {
// If it fails, go offline.
this.offline = true;
return this.feedbackProvider.getFeedbackAccessInformation(this.feedback.id, true);
}
return Promise.reject(error);
}).then((accessData) => {
this.access = accessData;
return accessData;
});
}
protected fetchFeedbackPageData(page: number = 0): Promise<void> {
let promise;
this.items = [];
if (this.preview) {
promise = this.feedbackProvider.getItems(this.feedback.id);
} else {
this.currentPage = page;
promise = this.feedbackProvider.getPageItemsWithValues(this.feedback.id, page, this.offline, true).catch((error) => {
if (!this.offline && !this.utils.isWebServiceError(error)) {
// If it fails, go offline.
this.offline = true;
return this.feedbackProvider.getPageItemsWithValues(this.feedback.id, page, true);
}
return Promise.reject(error);
}).then((response) => {
this.hasPrevPage = !!response.hasprevpage;
this.hasNextPage = !!response.hasnextpage;
return response;
});
}
return promise.then((response) => {
this.items = response.items.map((itemData) => {
return this.feedbackHelper.getItemForm(itemData, this.preview);
}).filter((itemData) => {
// Filter items with errors.
return itemData;
});
if (!this.preview) {
const itemsCopy = this.utils.clone(this.items); // Copy the array to avoid modifications.
this.originalData = this.feedbackHelper.getPageItemsResponses(itemsCopy);
}
});
}
/**
* Function to allow page navigation through the questions form.
*
* @param {boolean} goPrevious If true it will go back to the previous page, if false, it will go forward.
* @return {Promise<void>} Resolved when done.
*/
gotoPage(goPrevious: boolean): Promise<void> {
this.domUtils.scrollToTop(this.content);
this.feedbackLoaded = false;
const responses = this.feedbackHelper.getPageItemsResponses(this.items),
formHasErrors = this.items.some((item) => {
return item.isEmpty || item.hasError;
});
// Sync other pages first.
return this.feedbackSync.syncFeedback(this.feedback.id).catch(() => {
// Ignore errors.
}).then(() => {
return this.feedbackProvider.processPage(this.feedback.id, this.currentPage, responses, goPrevious, formHasErrors,
this.courseId).then((response) => {
const jumpTo = parseInt(response.jumpto, 10);
if (response.completed) {
// Form is completed, show completion message and buttons.
this.items = [];
this.completed = true;
this.completedOffline = !!response.offline;
this.completionPageContents = response.completionpagecontents;
this.siteAfterSubmit = response.siteaftersubmit;
this.submitted = true;
// Invalidate access information so user will see home page updated (continue form or completion messages).
const promises = [];
promises.push(this.feedbackProvider.invalidateFeedbackAccessInformationData(this.feedback.id));
promises.push(this.feedbackProvider.invalidateResumePageData(this.feedback.id));
return Promise.all(promises).then(() => {
return this.fetchAccessData();
});
} else if (isNaN(jumpTo) || jumpTo == this.currentPage) {
// Errors on questions, stay in page.
return Promise.resolve();
} else {
this.submitted = true;
// Invalidate access information so user will see home page updated (continue form).
this.feedbackProvider.invalidateResumePageData(this.feedback.id);
// Fetch the new page.
return this.fetchFeedbackPageData(jumpTo);
}
});
}).catch((message) => {
this.domUtils.showErrorModalDefault(message, 'core.course.errorgetmodule', true);
return Promise.reject(null);
}).finally(() => {
this.feedbackLoaded = true;
});
}
/**
* Function to link implemented features.
*/
showAnalysis(): void {
this.submitted = 'analysis';
this.feedbackHelper.openFeature('analysis', this.navCtrl, this.module, this.courseId);
}
/**
* Function to go to the page after submit.
*/
continue(): void {
if (this.siteAfterSubmit) {
const modal = this.domUtils.showModalLoading();
this.linkHelper.handleLink(this.siteAfterSubmit).then((treated) => {
if (!treated) {
return this.currentSite.openInBrowserWithAutoLoginIfSameSite(this.siteAfterSubmit);
}
}).finally(() => {
modal.dismiss();
});
} else {
// Use redirect to make the course the new history root (to avoid "loops" in history).
this.loginHelper.redirect('CoreCourseSectionPage', {
course: { id: this.courseId }
}, this.currentSite.getId());
}
}
/**
* Component being destroyed.
*/
ngOnDestroy(): void {
if (this.submitted) {
const tab = this.submitted == 'analysis' ? 'analysis' : 'overview';
// If form has been submitted, the info has been already invalidated but we should update index view.
this.eventsProvider.trigger(AddonModFeedbackProvider.FORM_SUBMITTED, {feedbackId: this.feedback.id, tab: tab});
}
this.onlineObserver && this.onlineObserver.unsubscribe();
}
}