From e7f0025b69dcf572bf1303efcb1a9da8e760b3ed Mon Sep 17 00:00:00 2001 From: Dani Palou Date: Wed, 28 Mar 2018 16:27:24 +0200 Subject: [PATCH] MOBILE-2348 quiz: Implement attempt page --- src/addon/mod/quiz/pages/attempt/attempt.html | 44 +++++ .../mod/quiz/pages/attempt/attempt.module.ts | 33 ++++ src/addon/mod/quiz/pages/attempt/attempt.ts | 177 ++++++++++++++++++ 3 files changed, 254 insertions(+) create mode 100644 src/addon/mod/quiz/pages/attempt/attempt.html create mode 100644 src/addon/mod/quiz/pages/attempt/attempt.module.ts create mode 100644 src/addon/mod/quiz/pages/attempt/attempt.ts diff --git a/src/addon/mod/quiz/pages/attempt/attempt.html b/src/addon/mod/quiz/pages/attempt/attempt.html new file mode 100644 index 000000000..cc31105dd --- /dev/null +++ b/src/addon/mod/quiz/pages/attempt/attempt.html @@ -0,0 +1,44 @@ + + + + + + + + + + + + +

{{ 'addon.mod_quiz.attemptnumber' | translate }}

+

{{ 'addon.mod_quiz.preview' | translate }}

+

{{ attempt.attempt }}

+
+ +

{{ 'addon.mod_quiz.attemptstate' | translate }}

+

{{ sentence }}

+
+ +

{{ 'addon.mod_quiz.marks' | translate }} / {{ quiz.sumGradesFormatted }}

+

{{ attempt.readableMark }}

+
+ +

{{ 'addon.mod_quiz.grade' | translate }} / {{ quiz.gradeFormatted }}

+

{{ attempt.readableGrade }}

+
+ +

{{ 'addon.mod_quiz.feedback' | translate }}

+

+
+ + + + +

{{ 'addon.mod_quiz.noreviewattempt' | translate }}

+
+
+
+
diff --git a/src/addon/mod/quiz/pages/attempt/attempt.module.ts b/src/addon/mod/quiz/pages/attempt/attempt.module.ts new file mode 100644 index 000000000..66257ff1b --- /dev/null +++ b/src/addon/mod/quiz/pages/attempt/attempt.module.ts @@ -0,0 +1,33 @@ +// (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 { NgModule } from '@angular/core'; +import { IonicPageModule } from 'ionic-angular'; +import { TranslateModule } from '@ngx-translate/core'; +import { CoreComponentsModule } from '@components/components.module'; +import { CoreDirectivesModule } from '@directives/directives.module'; +import { AddonModQuizAttemptPage } from './attempt'; + +@NgModule({ + declarations: [ + AddonModQuizAttemptPage, + ], + imports: [ + CoreComponentsModule, + CoreDirectivesModule, + IonicPageModule.forChild(AddonModQuizAttemptPage), + TranslateModule.forChild() + ], +}) +export class AddonModQuizAttemptPageModule {} diff --git a/src/addon/mod/quiz/pages/attempt/attempt.ts b/src/addon/mod/quiz/pages/attempt/attempt.ts new file mode 100644 index 000000000..ed6abe247 --- /dev/null +++ b/src/addon/mod/quiz/pages/attempt/attempt.ts @@ -0,0 +1,177 @@ +// (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 } from '@angular/core'; +import { IonicPage, NavParams } from 'ionic-angular'; +import { CoreDomUtilsProvider } from '@providers/utils/dom'; +import { AddonModQuizProvider } from '../../providers/quiz'; +import { AddonModQuizHelperProvider } from '../../providers/helper'; + +/** + * Page that displays some summary data about an attempt. + */ +@IonicPage({ segment: 'addon-mod-quiz-attempt' }) +@Component({ + selector: 'page-addon-mod-quiz-attempt', + templateUrl: 'attempt.html', +}) +export class AddonModQuizAttemptPage implements OnInit { + courseId: number; // The course ID the quiz belongs to. + quiz: any; // The quiz the attempt belongs to. + attempt: any; // The attempt to view. + component = AddonModQuizProvider.COMPONENT; // Component to link the files to. + componentId: number; // Component ID to use in conjunction with the component. + loaded: boolean; // Whether data has been loaded. + + protected attemptId: number; // Attempt to view. + protected quizId: number; // ID of the quiz the attempt belongs to. + + constructor(navParams: NavParams, protected domUtils: CoreDomUtilsProvider, protected quizProvider: AddonModQuizProvider, + protected quizHelper: AddonModQuizHelperProvider) { + + this.attemptId = navParams.get('attemptId'); + this.quizId = navParams.get('quizId'); + this.courseId = navParams.get('courseId'); + } + + /** + * Component being initialized. + */ + ngOnInit(): void { + this.fetchQuizData().finally(() => { + this.loaded = true; + }); + } + + /** + * Refresh the data. + * + * @param {any} refresher Refresher. + */ + doRefresh(refresher: any): void { + this.refreshData().finally(() => { + refresher.complete(); + }); + } + + /** + * Get quiz data and attempt data. + * + * @return {Promise} Promise resolved when done. + */ + protected fetchQuizData(): Promise { + return this.quizProvider.getQuizById(this.courseId, this.quizId).then((quizData) => { + this.quiz = quizData; + this.componentId = this.quiz.coursemodule; + + return this.fetchAttempt(); + }).catch((message) => { + this.domUtils.showErrorModalDefault(message, 'addon.mod_quiz.errorgetattempt', true); + }); + } + + /** + * Get the attempt data. + * + * @return {Promise} Promise resolved when done. + */ + protected fetchAttempt(): Promise { + const promises = []; + let options, + accessInfo; + + // Get all the attempts and search the one we want. + promises.push(this.quizProvider.getUserAttempts(this.quizId).then((attempts) => { + for (let i = 0; i < attempts.length; i++) { + const attempt = attempts[i]; + if (attempt.id == this.attemptId) { + this.attempt = attempt; + break; + } + } + + if (!this.attempt) { + // Attempt not found, error. + return Promise.reject(null); + } + + // Load flag to show if attempt is finished but not synced. + return this.quizProvider.loadFinishedOfflineData([this.attempt]); + })); + + promises.push(this.quizProvider.getCombinedReviewOptions(this.quiz.id).then((opts) => { + options = opts; + })); + + // Check if the user can review the attempt. + promises.push(this.quizProvider.getQuizAccessInformation(this.quiz.id).then((quizAccessInfo) => { + accessInfo = quizAccessInfo; + + if (accessInfo.canreviewmyattempts) { + return this.quizProvider.getAttemptReview(this.attemptId, -1).catch(() => { + // Error getting the review, assume the user cannot review the attempt. + accessInfo.canreviewmyattempts = false; + }); + } + })); + + return Promise.all(promises).then(() => { + + // Determine fields to show. + this.quizHelper.setQuizCalculatedData(this.quiz, options); + this.quiz.showReviewColumn = accessInfo.canreviewmyattempts; + + // Get readable data for the attempt. + this.quizHelper.setAttemptCalculatedData(this.quiz, this.attempt, false); + + // Check if the feedback should be displayed. + const grade = Number(this.attempt.rescaledGrade); + if (this.quiz.showFeedbackColumn && this.quizProvider.isAttemptFinished(this.attempt.state) && + options.someoptions.overallfeedback && !isNaN(grade)) { + + // Feedback should be displayed, get the feedback for the grade. + return this.quizProvider.getFeedbackForGrade(this.quiz.id, grade).then((response) => { + this.attempt.feedback = response.feedbacktext; + }); + } else { + delete this.attempt.feedback; + } + }); + } + + /** + * Refresh the data. + * + * @return {Promise} Promise resolved when done. + */ + protected refreshData(): Promise { + const promises = []; + + promises.push(this.quizProvider.invalidateQuizData(this.courseId)); + promises.push(this.quizProvider.invalidateUserAttemptsForUser(this.quizId)); + promises.push(this.quizProvider.invalidateQuizAccessInformation(this.quizId)); + promises.push(this.quizProvider.invalidateCombinedReviewOptionsForUser(this.quizId)); + promises.push(this.quizProvider.invalidateAttemptReview(this.attemptId)); + + if (this.attempt && typeof this.attempt.feedback != 'undefined') { + promises.push(this.quizProvider.invalidateFeedback(this.quizId)); + } + + return Promise.all(promises).catch(() => { + // Ignore errors. + }).then(() => { + return this.fetchQuizData(); + }); + } +}