MOBILE-3641 feedback: Migrate form page
parent
d3e689aa81
commit
77966d6fb4
|
@ -23,6 +23,10 @@ const routes: Routes = [
|
|||
path: ':courseId/:cmId',
|
||||
component: AddonModFeedbackIndexPage,
|
||||
},
|
||||
{
|
||||
path: ':courseId/:cmId/form',
|
||||
loadChildren: () => import('./pages/form/form.module').then(m => m.AddonModFeedbackFormPageModule),
|
||||
},
|
||||
];
|
||||
|
||||
@NgModule({
|
||||
|
|
|
@ -0,0 +1,178 @@
|
|||
<ion-header>
|
||||
<ion-toolbar>
|
||||
<ion-buttons slot="start">
|
||||
<ion-back-button [attr.aria-label]="'core.back' | translate"></ion-back-button>
|
||||
</ion-buttons>
|
||||
<ion-title>
|
||||
<core-format-text [text]="title" contextLevel="module" [contextInstanceId]="cmId" [courseId]="courseId">
|
||||
</core-format-text>
|
||||
</ion-title>
|
||||
</ion-toolbar>
|
||||
</ion-header>
|
||||
<ion-content>
|
||||
<core-loading [hideUntil]="feedbackLoaded">
|
||||
<ng-container *ngIf="items && items.length">
|
||||
<ion-list class="ion-no-margin">
|
||||
<ion-item class="ion-text-wrap">
|
||||
<ion-label>
|
||||
<h2>{{ 'addon.mod_feedback.mode' | translate }}</h2>
|
||||
<p *ngIf="access!.isanonymous">{{ 'addon.mod_feedback.anonymous' | translate }}</p>
|
||||
<p *ngIf="!access!.isanonymous">{{ 'addon.mod_feedback.non_anonymous' | translate }}</p>
|
||||
</ion-label>
|
||||
</ion-item>
|
||||
<ng-container *ngFor="let item of items">
|
||||
<ion-item-divider *ngIf="item.typ == 'pagebreak'">
|
||||
<ion-label></ion-label>
|
||||
</ion-item-divider>
|
||||
<ng-container *ngIf="item.typ != 'pagebreak'">
|
||||
<ion-item class="ion-text-wrap addon-mod_feedback-item" [color]="item.dependitem > 0 ? 'light' : ''"
|
||||
[class.core-danger-item]="item.isEmpty || item.hasError">
|
||||
<ion-label [position]="item.hasTextInput ? 'stacked' : undefined">
|
||||
<p *ngIf="item.name" [core-mark-required]="item.required">
|
||||
<span *ngIf="feedback!.autonumbering && item.itemnumber">{{item.itemnumber}}. </span>
|
||||
<core-format-text [component]="component" [componentId]="cmId" [text]="item.name"
|
||||
contextLevel="module" [contextInstanceId]="cmId" [courseId]="courseId" [wsNotFiltered]="true">
|
||||
</core-format-text>
|
||||
<span *ngIf="item.postfix" class="addon-mod_feedback-postfix">{{item.postfix}}</span>
|
||||
</p>
|
||||
<p *ngIf="item.templateName == 'label'">
|
||||
<core-format-text [component]="component" [componentId]="cmId"
|
||||
[text]="item.presentation" contextLevel="module" [contextInstanceId]="cmId"
|
||||
[wsNotFiltered]="true" [courseId]="courseId">
|
||||
</core-format-text>
|
||||
</p>
|
||||
</ion-label>
|
||||
|
||||
<ion-input *ngIf="item.templateName == 'textfield'" type="text" [(ngModel)]="item.value"
|
||||
autocorrect="off" name="{{item.typ}}_{{item.id}}" maxlength="{{item.length}}"
|
||||
[required]="item.required">
|
||||
</ion-input>
|
||||
|
||||
<ng-container *ngIf="item.templateName == 'numeric'">
|
||||
<ion-input type="number" [(ngModel)]="item.value" name="{{item.typ}}_{{item.id}}"
|
||||
[required]="item.required">
|
||||
</ion-input>
|
||||
<ion-text *ngIf="item.hasError" color="danger" class="addon-mod_feedback-item-error">
|
||||
{{ 'addon.mod_feedback.numberoutofrange' | translate }} [{{item.rangefrom}}
|
||||
<span *ngIf="item.rangefrom && item.rangeto">, </span>{{item.rangeto}}]
|
||||
</ion-text>
|
||||
</ng-container>
|
||||
|
||||
<ion-textarea *ngIf="item.templateName == 'textarea'" [required]="item.required"
|
||||
name="{{item.typ}}_{{item.id}}" [attr.aria-multiline]="true" [(ngModel)]="item.value">
|
||||
</ion-textarea>
|
||||
|
||||
<ion-select *ngIf="item.templateName == 'multichoice-d'" [required]="item.required"
|
||||
name="{{item.typ}}_{{item.id}}" [(ngModel)]="item.value" interface="action-sheet">
|
||||
<ion-select-option *ngFor="let option of item.choices" [value]="option.value">
|
||||
<core-format-text [component]="component" [componentId]="cmId" [text]="option.label"
|
||||
contextLevel="module" [contextInstanceId]="cmId" [wsNotFiltered]="true"
|
||||
[courseId]="courseId">
|
||||
</core-format-text>
|
||||
</ion-select-option>
|
||||
</ion-select>
|
||||
</ion-item>
|
||||
|
||||
<ion-radio-group *ngIf="item.templateName == 'multichoice-r'" [(ngModel)]="item.value"
|
||||
[required]="item.required" name="{{item.typ}}_{{item.id}}">
|
||||
<ion-item *ngFor="let option of item.choices">
|
||||
<ion-label>
|
||||
<core-format-text [component]="component" [componentId]="cmId"
|
||||
[text]="option.label" contextLevel="module" [contextInstanceId]="cmId"
|
||||
[wsNotFiltered]="true" [courseId]="courseId">
|
||||
</core-format-text>
|
||||
</ion-label>
|
||||
<ion-radio slot="start" [value]="option.value"></ion-radio>
|
||||
</ion-item>
|
||||
</ion-radio-group>
|
||||
|
||||
<ng-container *ngIf="item.templateName == 'multichoice-c'">
|
||||
<ion-item *ngFor="let option of item.choices">
|
||||
<ion-label>
|
||||
<core-format-text [component]="component" [componentId]="cmId" [text]="option.label"
|
||||
contextLevel="module" [contextInstanceId]="cmId" [wsNotFiltered]="true"
|
||||
[courseId]="courseId">
|
||||
</core-format-text>
|
||||
</ion-label>
|
||||
<ion-checkbox [required]="item.required" name="{{item.typ}}_{{item.id}}"
|
||||
[(ngModel)]="option.checked" value="option.value">
|
||||
</ion-checkbox>
|
||||
</ion-item>
|
||||
</ng-container>
|
||||
|
||||
<ng-container *ngIf="item.templateName == 'captcha'">
|
||||
<core-recaptcha *ngIf="!preview && !offline" [publicKey]="item.captcha.recaptchapublickey"
|
||||
[model]="item" modelValueName="value">
|
||||
</core-recaptcha>
|
||||
<div *ngIf="!preview && (!item.captcha || offline)" class="core-warning-card">
|
||||
<ion-item>
|
||||
<ion-icon name="fas-exclamation-triangle" slot="start"></ion-icon>
|
||||
<ion-label>{{ 'addon.mod_feedback.captchaofflinewarning' | translate }}</ion-label>
|
||||
</ion-item>
|
||||
</div>
|
||||
</ng-container>
|
||||
</ng-container>
|
||||
</ng-container>
|
||||
<ion-grid *ngIf="!preview">
|
||||
<ion-row class="ion-align-items-center">
|
||||
<ion-col *ngIf="hasPrevPage">
|
||||
<ion-button expand="block" fill="outline" (click)="gotoPage(true)" class="ion-text-wrap">
|
||||
<ion-icon name="fas-chevron-left" slot="start"></ion-icon>
|
||||
{{ 'addon.mod_feedback.previous_page' | translate }}
|
||||
</ion-button>
|
||||
</ion-col>
|
||||
<ion-col *ngIf="hasNextPage">
|
||||
<ion-button expand="block" (click)="gotoPage(false)" class="ion-text-wrap">
|
||||
{{ 'addon.mod_feedback.next_page' | translate }}
|
||||
<ion-icon name="fas-chevron-right" slot="end"></ion-icon>
|
||||
</ion-button>
|
||||
</ion-col>
|
||||
<ion-col *ngIf="!hasNextPage">
|
||||
<ion-button expand="block" (click)="gotoPage(false)" class="ion-text-wrap">
|
||||
{{ 'addon.mod_feedback.save_entries' | translate }}
|
||||
</ion-button>
|
||||
</ion-col>
|
||||
</ion-row>
|
||||
</ion-grid>
|
||||
</ion-list>
|
||||
</ng-container>
|
||||
|
||||
<ion-card class="core-success-card" *ngIf="completed">
|
||||
<ion-item>
|
||||
<ion-icon name="fas-check" slot="start"></ion-icon>
|
||||
<ion-label>
|
||||
<p *ngIf="!completionPageContents && !completedOffline">
|
||||
{{ 'addon.mod_feedback.this_feedback_is_already_submitted' | translate }}
|
||||
</p>
|
||||
<p *ngIf="!completionPageContents && completedOffline">
|
||||
{{ 'addon.mod_feedback.feedback_submitted_offline' | translate }}
|
||||
</p>
|
||||
<p *ngIf="completionPageContents">
|
||||
<core-format-text [component]="component" componentId="componentId" [text]="completionPageContents"
|
||||
contextLevel="module" [contextInstanceId]="cmId" [courseId]="courseId">
|
||||
</core-format-text>
|
||||
</p>
|
||||
</ion-label>
|
||||
</ion-item>
|
||||
</ion-card>
|
||||
|
||||
<ion-card>
|
||||
<ion-grid *ngIf="completed">
|
||||
<ion-row class="ion-align-items-center">
|
||||
<ion-col *ngIf="access!.canviewanalysis">
|
||||
<ion-button expand="block" fill="outline" (click)="showAnalysis()" class="ion-text-wrap">
|
||||
<ion-icon name="fas-chart-bar" slot="start"></ion-icon>
|
||||
{{ 'addon.mod_feedback.completed_feedbacks' | translate }}
|
||||
</ion-button>
|
||||
</ion-col>
|
||||
<ion-col *ngIf="hasNextPage">
|
||||
<ion-button expand="block" (click)="continue()" class="ion-text-wrap">
|
||||
{{ 'core.continue' | translate }}
|
||||
<ion-icon name="fas-chevron-right" slot="end"></ion-icon>
|
||||
</ion-button>
|
||||
</ion-col>
|
||||
</ion-row>
|
||||
</ion-grid>
|
||||
</ion-card>
|
||||
</core-loading>
|
||||
</ion-content>
|
|
@ -0,0 +1,39 @@
|
|||
// (C) Copyright 2015 Moodle Pty Ltd.
|
||||
//
|
||||
// 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 { CoreSharedModule } from '@/core/shared.module';
|
||||
import { NgModule } from '@angular/core';
|
||||
import { RouterModule, Routes } from '@angular/router';
|
||||
import { CanLeaveGuard } from '@guards/can-leave';
|
||||
import { AddonModFeedbackFormPage } from './form';
|
||||
|
||||
const routes: Routes = [
|
||||
{
|
||||
path: '',
|
||||
component: AddonModFeedbackFormPage,
|
||||
canDeactivate: [CanLeaveGuard],
|
||||
},
|
||||
];
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
AddonModFeedbackFormPage,
|
||||
],
|
||||
imports: [
|
||||
RouterModule.forChild(routes),
|
||||
CoreSharedModule,
|
||||
],
|
||||
exports: [RouterModule],
|
||||
})
|
||||
export class AddonModFeedbackFormPageModule {}
|
|
@ -0,0 +1,11 @@
|
|||
:host {
|
||||
.addon-mod_feedback-item ion-label.label-stacked {
|
||||
margin: 11px 0px 10px;
|
||||
transform: none;
|
||||
}
|
||||
|
||||
.addon-mod_feedback-item-error {
|
||||
padding-top: 5px;
|
||||
padding-bottom: 8px;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,428 @@
|
|||
// (C) Copyright 2015 Moodle Pty Ltd.
|
||||
//
|
||||
// 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, OnInit, ViewChild } from '@angular/core';
|
||||
import { CoreSite } from '@classes/site';
|
||||
import { CoreContentLinksHelper } from '@features/contentlinks/services/contentlinks-helper';
|
||||
import { CoreCourse, CoreCourseCommonModWSOptions, CoreCourseWSModule } from '@features/course/services/course';
|
||||
import { CoreCourseHelper } from '@features/course/services/course-helper';
|
||||
import { CanLeave } from '@guards/can-leave';
|
||||
import { IonContent } from '@ionic/angular';
|
||||
import { CoreApp } from '@services/app';
|
||||
import { CoreNavigator } from '@services/navigator';
|
||||
import { CoreSites, CoreSitesReadingStrategy } from '@services/sites';
|
||||
import { CoreDomUtils } from '@services/utils/dom';
|
||||
import { CoreUtils } from '@services/utils/utils';
|
||||
import { Network, NgZone, Translate } from '@singletons';
|
||||
import { CoreEvents } from '@singletons/events';
|
||||
import { Subscription } from 'rxjs';
|
||||
import {
|
||||
AddonModFeedback,
|
||||
AddonModFeedbackGetFeedbackAccessInformationWSResponse,
|
||||
AddonModFeedbackPageItems,
|
||||
AddonModFeedbackProvider,
|
||||
AddonModFeedbackResponseValue,
|
||||
AddonModFeedbackWSFeedback,
|
||||
} from '../../services/feedback';
|
||||
import { AddonModFeedbackFormItem, AddonModFeedbackHelper } from '../../services/feedback-helper';
|
||||
import { AddonModFeedbackSync } from '../../services/feedback-sync';
|
||||
import { AddonModFeedbackModuleHandlerService } from '../../services/handlers/module';
|
||||
|
||||
/**
|
||||
* Page that displays feedback form.
|
||||
*/
|
||||
@Component({
|
||||
selector: 'page-addon-mod-feedback-form',
|
||||
templateUrl: 'form.html',
|
||||
styleUrls: ['form.scss'],
|
||||
})
|
||||
export class AddonModFeedbackFormPage implements OnInit, OnDestroy, CanLeave {
|
||||
|
||||
@ViewChild(IonContent) content?: IonContent;
|
||||
|
||||
protected module?: CoreCourseWSModule;
|
||||
protected currentPage?: number;
|
||||
protected siteAfterSubmit?: string;
|
||||
protected onlineObserver: Subscription;
|
||||
protected originalData?: Record<string, AddonModFeedbackResponseValue>;
|
||||
protected currentSite: CoreSite;
|
||||
protected forceLeave = false;
|
||||
|
||||
title?: string;
|
||||
preview = false;
|
||||
cmId!: number;
|
||||
courseId!: number;
|
||||
feedback?: AddonModFeedbackWSFeedback;
|
||||
completionPageContents?: string;
|
||||
component = AddonModFeedbackProvider.COMPONENT;
|
||||
offline = false;
|
||||
feedbackLoaded = false;
|
||||
access?: AddonModFeedbackGetFeedbackAccessInformationWSResponse;
|
||||
items: AddonModFeedbackFormItem[] = [];
|
||||
hasPrevPage = false;
|
||||
hasNextPage = false;
|
||||
completed = false;
|
||||
completedOffline = false;
|
||||
|
||||
constructor() {
|
||||
this.currentSite = CoreSites.getCurrentSite()!;
|
||||
|
||||
// Refresh online status when changes.
|
||||
this.onlineObserver = Network.onChange().subscribe(() => {
|
||||
// Execute the callback in the Angular zone, so change detection doesn't stop working.
|
||||
NgZone.run(() => {
|
||||
this.offline = !CoreApp.isOnline();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
async ngOnInit(): Promise<void> {
|
||||
this.cmId = CoreNavigator.getRouteNumberParam('cmId')!;
|
||||
this.courseId = CoreNavigator.getRouteNumberParam('courseId')!;
|
||||
this.currentPage = CoreNavigator.getRouteNumberParam('page');
|
||||
this.title = CoreNavigator.getRouteParam('title');
|
||||
this.preview = !!CoreNavigator.getRouteBooleanParam('preview');
|
||||
|
||||
await this.fetchData();
|
||||
|
||||
if (!this.feedback) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await AddonModFeedback.logView(this.feedback.id, this.feedback.name, true);
|
||||
|
||||
CoreCourse.checkModuleCompletion(this.courseId, this.module!.completiondata);
|
||||
} catch {
|
||||
// Ignore errors.
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* View entered.
|
||||
*/
|
||||
ionViewDidEnter(): void {
|
||||
this.forceLeave = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
async canLeave(): Promise<boolean> {
|
||||
if (this.forceLeave) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!this.preview) {
|
||||
const responses = AddonModFeedbackHelper.getPageItemsResponses(this.items);
|
||||
|
||||
if (this.items && !this.completed && this.originalData) {
|
||||
// Form submitted. Check if there is any change.
|
||||
if (!CoreUtils.basicLeftCompare(responses, this.originalData, 3)) {
|
||||
await CoreDomUtils.showConfirm(Translate.instant('core.confirmcanceledit'));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch all the data required for the view.
|
||||
*
|
||||
* @return Promise resolved when done.
|
||||
*/
|
||||
protected async fetchData(): Promise<void> {
|
||||
try {
|
||||
this.module = await CoreCourse.getModule(this.cmId, this.courseId, undefined, true, false, this.currentSite.getId());
|
||||
|
||||
this.offline = !CoreApp.isOnline();
|
||||
const options = {
|
||||
cmId: this.cmId,
|
||||
readingStrategy: this.offline ? CoreSitesReadingStrategy.PreferCache : CoreSitesReadingStrategy.OnlyNetwork,
|
||||
siteId: this.currentSite.getId(),
|
||||
};
|
||||
|
||||
this.feedback = await AddonModFeedback.getFeedback(this.courseId, this.cmId);
|
||||
|
||||
this.title = this.feedback.name || this.title;
|
||||
|
||||
await this.fetchAccessData(options);
|
||||
|
||||
let page = 0;
|
||||
|
||||
if (!this.preview && this.access!.cansubmit && !this.access!.isempty) {
|
||||
page = this.currentPage ?? await this.fetchResumePage(options);
|
||||
} else {
|
||||
this.preview = true;
|
||||
}
|
||||
|
||||
await this.fetchFeedbackPageData(page);
|
||||
} catch (message) {
|
||||
CoreDomUtils.showErrorModalDefault(message, 'core.course.errorgetmodule', true);
|
||||
this.forceLeave = true;
|
||||
CoreNavigator.back();
|
||||
} finally {
|
||||
this.feedbackLoaded = true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch access information.
|
||||
*
|
||||
* @param options Options.
|
||||
* @return Promise resolved when done.
|
||||
*/
|
||||
protected async fetchAccessData(options: CoreCourseCommonModWSOptions): Promise<void> {
|
||||
try {
|
||||
this.access = await AddonModFeedback.getFeedbackAccessInformation(this.feedback!.id, options);
|
||||
} catch (error) {
|
||||
if (this.offline || CoreUtils.isWebServiceError(error)) {
|
||||
// Already offline or shouldn't go offline, fail.
|
||||
throw error;
|
||||
}
|
||||
|
||||
// If it fails, go offline.
|
||||
this.offline = true;
|
||||
options.readingStrategy = CoreSitesReadingStrategy.PreferCache;
|
||||
|
||||
this.access = await AddonModFeedback.getFeedbackAccessInformation(this.feedback!.id, options);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get resume page from WS.
|
||||
*
|
||||
* @param options Options.
|
||||
* @return Promise resolved with the page to resume.
|
||||
*/
|
||||
protected async fetchResumePage(options: CoreCourseCommonModWSOptions): Promise<number> {
|
||||
try {
|
||||
return await AddonModFeedback.getResumePage(this.feedback!.id, options);
|
||||
} catch (error) {
|
||||
if (this.offline || CoreUtils.isWebServiceError(error)) {
|
||||
// Already offline or shouldn't go offline, fail.
|
||||
throw error;
|
||||
}
|
||||
|
||||
// Go offline.
|
||||
this.offline = true;
|
||||
options.readingStrategy = CoreSitesReadingStrategy.PreferCache;
|
||||
|
||||
return AddonModFeedback.getResumePage(this.feedback!.id, options);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch page data.
|
||||
*
|
||||
* @param page Page to load.
|
||||
* @return Promise resolved when done.
|
||||
*/
|
||||
protected async fetchFeedbackPageData(page: number = 0): Promise<void> {
|
||||
this.items = [];
|
||||
const response = await this.fetchPageItems(page);
|
||||
|
||||
this.items = <AddonModFeedbackFormItem[]> response.items
|
||||
.map((itemData) => AddonModFeedbackHelper.getItemForm(itemData, this.preview))
|
||||
.filter((itemData) => itemData); // Filter items with errors.
|
||||
|
||||
if (!this.preview) {
|
||||
const itemsCopy = CoreUtils.clone(this.items); // Copy the array to avoid modifications.
|
||||
this.originalData = AddonModFeedbackHelper.getPageItemsResponses(itemsCopy);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch page items.
|
||||
*
|
||||
* @param page Page to get.
|
||||
* @return Promise resolved with WS response.
|
||||
*/
|
||||
protected async fetchPageItems(page: number): Promise<AddonModFeedbackPageItems> {
|
||||
const options = {
|
||||
cmId: this.cmId,
|
||||
readingStrategy: this.offline ? CoreSitesReadingStrategy.PreferCache : CoreSitesReadingStrategy.OnlyNetwork,
|
||||
siteId: this.currentSite.getId(),
|
||||
};
|
||||
|
||||
if (this.preview) {
|
||||
const response = await AddonModFeedback.getItems(this.feedback!.id, options);
|
||||
|
||||
return {
|
||||
items: response.items,
|
||||
warnings: response.warnings,
|
||||
hasnextpage: false,
|
||||
hasprevpage: false,
|
||||
};
|
||||
}
|
||||
|
||||
this.currentPage = page;
|
||||
let response: AddonModFeedbackPageItems;
|
||||
|
||||
try {
|
||||
response = await AddonModFeedback.getPageItemsWithValues(this.feedback!.id, page, options);
|
||||
} catch (error) {
|
||||
if (this.offline || CoreUtils.isWebServiceError(error)) {
|
||||
// Already offline or shouldn't go offline, fail.
|
||||
throw error;
|
||||
}
|
||||
|
||||
// Go offline.
|
||||
this.offline = true;
|
||||
options.readingStrategy = CoreSitesReadingStrategy.PreferCache;
|
||||
|
||||
response = await AddonModFeedback.getPageItemsWithValues(this.feedback!.id, page, options);
|
||||
}
|
||||
|
||||
this.hasPrevPage = !!response.hasprevpage;
|
||||
this.hasNextPage = !!response.hasnextpage;
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Function to allow page navigation through the questions form.
|
||||
*
|
||||
* @param goPrevious If true it will go back to the previous page, if false, it will go forward.
|
||||
* @return Resolved when done.
|
||||
*/
|
||||
async gotoPage(goPrevious: boolean): Promise<void> {
|
||||
this.content?.scrollToTop();
|
||||
this.feedbackLoaded = false;
|
||||
|
||||
const responses = AddonModFeedbackHelper.getPageItemsResponses(this.items);
|
||||
const formHasErrors = this.items.some((item) => item.isEmpty || item.hasError);
|
||||
|
||||
try {
|
||||
// Sync other pages first.
|
||||
await CoreUtils.ignoreErrors(AddonModFeedbackSync.syncFeedback(this.feedback!.id));
|
||||
|
||||
const response = await AddonModFeedback.processPage(this.feedback!.id, this.currentPage!, responses, {
|
||||
goPrevious,
|
||||
formHasErrors,
|
||||
courseId: this.courseId,
|
||||
cmId: this.cmId,
|
||||
});
|
||||
|
||||
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;
|
||||
|
||||
CoreEvents.trigger(CoreEvents.ACTIVITY_DATA_SENT, { module: 'feedback' });
|
||||
|
||||
// Invalidate access information so user will see home page updated (continue form or completion messages).
|
||||
await Promise.all([
|
||||
AddonModFeedback.invalidateFeedbackAccessInformationData(this.feedback!.id),
|
||||
AddonModFeedback.invalidateResumePageData(this.feedback!.id),
|
||||
]);
|
||||
|
||||
// If form has been submitted, the info has been already invalidated but we should update index view.
|
||||
CoreEvents.trigger(AddonModFeedbackProvider.FORM_SUBMITTED, {
|
||||
feedbackId: this.feedback!.id,
|
||||
tab: 'overview',
|
||||
offline: this.completedOffline,
|
||||
});
|
||||
|
||||
await this.fetchAccessData({
|
||||
cmId: this.cmId,
|
||||
readingStrategy: this.offline ? CoreSitesReadingStrategy.PreferCache : CoreSitesReadingStrategy.OnlyNetwork,
|
||||
siteId: this.currentSite.getId(),
|
||||
});
|
||||
} else if (typeof response.jumpto != 'number' || response.jumpto == this.currentPage) {
|
||||
// Errors on questions, stay in page.
|
||||
} else {
|
||||
// Invalidate access information so user will see home page updated (continue form).
|
||||
await AddonModFeedback.invalidateResumePageData(this.feedback!.id);
|
||||
|
||||
CoreEvents.trigger(AddonModFeedbackProvider.FORM_SUBMITTED, {
|
||||
feedbackId: this.feedback!.id,
|
||||
tab: 'overview',
|
||||
offline: this.completedOffline,
|
||||
});
|
||||
|
||||
// Fetch the new page.
|
||||
await this.fetchFeedbackPageData(response.jumpto);
|
||||
}
|
||||
} catch (message) {
|
||||
CoreDomUtils.showErrorModalDefault(message, 'core.course.errorgetmodule', true);
|
||||
} finally {
|
||||
this.feedbackLoaded = true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Function to link implemented features.
|
||||
*/
|
||||
showAnalysis(): void {
|
||||
const indexPath = AddonModFeedbackModuleHandlerService.PAGE_NAME + `/${this.courseId}/${this.cmId}`;
|
||||
const previousPath = CoreNavigator.getPreviousPath();
|
||||
|
||||
if (previousPath.match(new RegExp(indexPath + '$'))) {
|
||||
// Previous page is the index page, go back.
|
||||
CoreEvents.trigger(AddonModFeedbackProvider.FORM_SUBMITTED, {
|
||||
feedbackId: this.feedback!.id,
|
||||
tab: 'analysis',
|
||||
offline: this.completedOffline,
|
||||
});
|
||||
|
||||
CoreNavigator.back();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
CoreNavigator.navigateToSitePath(indexPath, {
|
||||
params: {
|
||||
module: this.module,
|
||||
tab: 'analysis',
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Function to go to the page after submit.
|
||||
*/
|
||||
async continue(): Promise<void> {
|
||||
if (!this.siteAfterSubmit) {
|
||||
return CoreCourseHelper.getAndOpenCourse(this.courseId, {}, this.currentSite.getId());
|
||||
}
|
||||
|
||||
const modal = await CoreDomUtils.showModalLoading();
|
||||
|
||||
try {
|
||||
const treated = await CoreContentLinksHelper.handleLink(this.siteAfterSubmit);
|
||||
|
||||
if (!treated) {
|
||||
await this.currentSite.openInBrowserWithAutoLoginIfSameSite(this.siteAfterSubmit);
|
||||
}
|
||||
} finally {
|
||||
modal.dismiss();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Component being destroyed.
|
||||
*/
|
||||
ngOnDestroy(): void {
|
||||
this.onlineObserver.unsubscribe();
|
||||
}
|
||||
|
||||
}
|
|
@ -1,8 +1,10 @@
|
|||
@import "~theme/globals";
|
||||
|
||||
:host {
|
||||
.core-input-required-asterisk {
|
||||
font-size: 8px;
|
||||
--padding-start: 4px;
|
||||
line-height: 100%;
|
||||
vertical-align: top;
|
||||
@include padding-horizontal(4px, null);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -487,3 +487,7 @@ ion-button.core-button-select {
|
|||
@include padding(null, null, null, 15px * $i + 16px);
|
||||
}
|
||||
}
|
||||
|
||||
textarea:not([core-auto-rows]) {
|
||||
height: 200px;
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue