MOBILE-2345 lesson: Implement user retake page
parent
ff06a812d2
commit
b1469987e2
|
@ -0,0 +1,159 @@
|
|||
<ion-header>
|
||||
<ion-navbar>
|
||||
<ion-title>{{ 'addon.mod_lesson.detailedstats' | translate }}</ion-title>
|
||||
</ion-navbar>
|
||||
</ion-header>
|
||||
<ion-content>
|
||||
<ion-refresher [enabled]="loaded" (ionRefresh)="doRefresh($event)">
|
||||
<ion-refresher-content pullingText="{{ 'core.pulltorefresh' | translate }}"></ion-refresher-content>
|
||||
</ion-refresher>
|
||||
|
||||
<core-loading [hideUntil]="loaded">
|
||||
<ion-list *ngIf="student">
|
||||
<!-- Student data. -->
|
||||
<a ion-item text-wrap core-user-link [userId]="student.id" [courseId]="courseId" [title]="student.fullname">
|
||||
<ion-avatar *ngIf="student.profileimageurl" item-start>
|
||||
<img [src]="student.profileimageurl" [alt]="'core.pictureof' | translate:{$a: student.fullname}" core-external-content role="presentation">
|
||||
</ion-avatar>
|
||||
<h2>{{student.fullname}}</h2>
|
||||
<core-progress-bar [progress]="student.bestgrade"></core-progress-bar>
|
||||
</a>
|
||||
|
||||
<!-- Retake selector if there is more than one retake. -->
|
||||
<ion-item text-wrap *ngIf="student.attempts && student.attempts.length > 1">
|
||||
<ion-label id="addon-mod_lesson-retakeslabel">{{ 'addon.mod_lesson.attemptheader' | translate }}</ion-label>
|
||||
<ion-select [(ngModel)]="selectedRetake" (ionChange)="changeRetake(selectedRetake)" aria-labelledby="addon-mod_lesson-retakeslabel" interface="popover">
|
||||
<ion-option *ngFor="let retake of student.attempts" [value]="retake.try">{{retake.label}}</ion-option>
|
||||
</ion-select>
|
||||
</ion-item>
|
||||
|
||||
<!-- Retake stats. -->
|
||||
<div *ngIf="retake && retake.userstats && retake.userstats.gradeinfo" class="addon-mod_lesson-userstats">
|
||||
<ion-item text-wrap>
|
||||
<ion-row>
|
||||
<ion-col>
|
||||
<p class="item-heading">{{ 'addon.mod_lesson.grade' | translate }}</p>
|
||||
<p>{{ 'core.percentagenumber' | translate:{$a: retake.userstats.grade} }}</p>
|
||||
</ion-col>
|
||||
|
||||
<ion-col>
|
||||
<p class="item-heading">{{ 'addon.mod_lesson.rawgrade' | translate }}</p>
|
||||
<p>{{ retake.userstats.gradeinfo.earned }} / {{ retake.userstats.gradeinfo.total }}</p>
|
||||
</ion-col>
|
||||
</ion-row>
|
||||
</ion-item>
|
||||
<ion-item text-wrap>
|
||||
<p class="item-heading">{{ 'addon.mod_lesson.timetaken' | translate }}</p>
|
||||
<p>{{ retake.userstats.timetakenReadable }}</p>
|
||||
</ion-item>
|
||||
<ion-item text-wrap>
|
||||
<p class="item-heading">{{ 'addon.mod_lesson.completed' | translate }}</p>
|
||||
<p>{{ retake.userstats.completed * 1000 | coreFormatDate:"dfmediumdate" }}</p>
|
||||
</ion-item>
|
||||
</div>
|
||||
|
||||
<!-- Not completed, no stats. -->
|
||||
<ion-item text-wrap *ngIf="retake && (!retake.userstats || !retake.userstats.gradeinfo)">
|
||||
{{ 'addon.mod_lesson.notcompleted' | translate }}
|
||||
</ion-item>
|
||||
|
||||
<!-- Pages. -->
|
||||
<ng-container *ngIf="retake">
|
||||
<!-- The "text-dimmed" class does nothing, but the same goes for the "dimmed" class in Moodle. -->
|
||||
<ion-card *ngFor="let page of retake.answerpages" class="addon-mod_lesson-answerpage" [ngClass]="{'text-dimmed': page.grayout}">
|
||||
<ion-card-header text-wrap>
|
||||
<h2>{{page.qtype}}: {{page.title}}</h2>
|
||||
</ion-card-header>
|
||||
<ion-item text-wrap>
|
||||
<p class="item-heading">{{ 'addon.mod_lesson.question' | translate }}</p>
|
||||
<p><core-format-text [component]="component" [componentId]="lesson.coursemodule" [maxHeight]="50" [text]="page.contents"></core-format-text></p>
|
||||
</ion-item>
|
||||
<ion-item text-wrap>
|
||||
<p class="item-heading">{{ 'addon.mod_lesson.answer' | translate }}</p>
|
||||
</ion-item>
|
||||
<ion-item text-wrap *ngIf="!page.answerdata || !page.answerdata.answers || !page.answerdata.answers.length">
|
||||
<p>{{ 'addon.mod_lesson.didnotanswerquestion' | translate }}</p>
|
||||
</ion-item>
|
||||
<div *ngIf="page.answerdata && page.answerdata.answers && page.answerdata.answers.length" class="addon-mod_lesson-answer">
|
||||
<div *ngFor="let answer of page.answerdata.answers">
|
||||
<ion-item text-wrap *ngIf="page.isContent">
|
||||
<!-- Content page, display a button and the content. -->
|
||||
<ion-row>
|
||||
<ion-col>
|
||||
<button ion-button block color="light" [disabled]="true">{{ answer[0].buttonText }}</button>
|
||||
</ion-col>
|
||||
<ion-col>
|
||||
<p [innerHTML]="answer[0].content"></p>
|
||||
</ion-col>
|
||||
</ion-row>
|
||||
</ion-item>
|
||||
|
||||
<div *ngIf="page.isQuestion">
|
||||
<!-- Question page, show the right input for the answer. -->
|
||||
|
||||
<!-- Truefalse or matching. -->
|
||||
<ion-item text-wrap *ngIf="answer[0].isCheckbox" [ngClass]="{'addon-mod_lesson-highlight': answer[0].highlight}">
|
||||
<ion-label>
|
||||
<p><core-format-text [component]="component" [componentId]="lesson.coursemodule" [text]="answer[0].content"></core-format-text></p>
|
||||
<ion-badge *ngIf="answer[1]" color="dark">
|
||||
<core-format-text [component]="component" [componentId]="lesson.coursemodule" [text]="answer[1]"></core-format-text>
|
||||
</ion-badge>
|
||||
</ion-label>
|
||||
<ion-checkbox [attr.name]="answer[0].name" [ngModel]="answer[0].checked" [disabled]="true" item-end>
|
||||
</ion-checkbox>
|
||||
</ion-item>
|
||||
|
||||
<!-- Short answer or numeric. -->
|
||||
<ion-item text-wrap *ngIf="answer[0].isText">
|
||||
<p>{{ answer[0].value }}</p>
|
||||
<ion-badge *ngIf="answer[1]" color="dark">
|
||||
<core-format-text [component]="component" [componentId]="lesson.coursemodule" [text]="answer[1]"></core-format-text>
|
||||
</ion-badge>
|
||||
</ion-item>
|
||||
|
||||
<!-- Matching. -->
|
||||
<ion-item text-wrap *ngIf="answer[0].isSelect">
|
||||
<ion-row>
|
||||
<ion-col>
|
||||
<p><core-format-text [component]="component" [componentId]="lesson.coursemodule" [text]=" answer[0].content"></core-format-text></p>
|
||||
</ion-col>
|
||||
<ion-col>
|
||||
<p>{{answer[0].value}}</p>
|
||||
<ion-badge *ngIf="answer[1]" color="dark">
|
||||
<core-format-text [component]="component" [componentId]="lesson.coursemodule" [text]="answer[1]"></core-format-text>
|
||||
</ion-badge>
|
||||
</ion-col>
|
||||
</ion-row>
|
||||
</ion-item>
|
||||
|
||||
<!-- Essay or couldn't determine. -->
|
||||
<ion-item text-wrap *ngIf="!answer[0].isCheckbox && !answer[0].isText && !answer[0].isSelect">
|
||||
<p><core-format-text [component]="component" [componentId]="lesson.coursemodule" [text]="answer[0]"></core-format-text></p>
|
||||
<ion-badge *ngIf="answer[1]" color="dark">
|
||||
<core-format-text [component]="component" [componentId]="lesson.coursemodule" [text]="answer[1]"></core-format-text>
|
||||
</ion-badge>
|
||||
</ion-item>
|
||||
</div>
|
||||
|
||||
<ion-item text-wrap *ngIf="!page.isContent && !page.isQuestion">
|
||||
<!-- Another page (end of branch, ...). -->
|
||||
<p><core-format-text [component]="component" [componentId]="lesson.coursemodule" [text]="answer[0]"></core-format-text></p>
|
||||
<ion-badge *ngIf="answer[1]" color="dark">
|
||||
<core-format-text [component]="component" [componentId]="lesson.coursemodule" [text]="answer[1]"></core-format-text>
|
||||
</ion-badge>
|
||||
</ion-item>
|
||||
</div>
|
||||
|
||||
<ion-item text-wrap *ngIf="page.answerdata.response">
|
||||
<p class="item-heading">{{ 'addon.mod_lesson.response' | translate }}</p>
|
||||
<p><core-format-text [component]="component" [componentId]="lesson.coursemodule" [text]="page.answerdata.response"></core-format-text></p>
|
||||
</ion-item>
|
||||
<ion-item text-wrap *ngIf="page.answerdata.score">
|
||||
<p>{{page.answerdata.score}}</p>
|
||||
</ion-item>
|
||||
</div>
|
||||
</ion-card>
|
||||
</ng-container>
|
||||
</ion-list>
|
||||
</core-loading>
|
||||
</ion-content>
|
|
@ -0,0 +1,35 @@
|
|||
// (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 { CorePipesModule } from '@pipes/pipes.module';
|
||||
import { AddonModLessonUserRetakePage } from './user-retake';
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
AddonModLessonUserRetakePage,
|
||||
],
|
||||
imports: [
|
||||
CoreComponentsModule,
|
||||
CoreDirectivesModule,
|
||||
CorePipesModule,
|
||||
IonicPageModule.forChild(AddonModLessonUserRetakePage),
|
||||
TranslateModule.forChild()
|
||||
],
|
||||
})
|
||||
export class AddonModLessonUserRetakePageModule {}
|
|
@ -0,0 +1,8 @@
|
|||
page-addon-mod-lesson-user-retake {
|
||||
.addon-mod_lesson-highlight {
|
||||
background: $blue-light;
|
||||
.label, .label p {
|
||||
color: $blue-dark;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,228 @@
|
|||
// (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 { TranslateService } from '@ngx-translate/core';
|
||||
import { CoreSitesProvider } from '@providers/sites';
|
||||
import { CoreDomUtilsProvider } from '@providers/utils/dom';
|
||||
import { CoreTextUtilsProvider } from '@providers/utils/text';
|
||||
import { CoreTimeUtilsProvider } from '@providers/utils/time';
|
||||
import { CoreUserProvider } from '@core/user/providers/user';
|
||||
import { AddonModLessonProvider } from '../../providers/lesson';
|
||||
import { AddonModLessonHelperProvider } from '../../providers/helper';
|
||||
|
||||
/**
|
||||
* Page that displays a retake made by a certain user.
|
||||
*/
|
||||
@IonicPage({ segment: 'addon-mod-lesson-user-retake' })
|
||||
@Component({
|
||||
selector: 'page-addon-mod-lesson-user-retake',
|
||||
templateUrl: 'user-retake.html',
|
||||
})
|
||||
export class AddonModLessonUserRetakePage implements OnInit {
|
||||
|
||||
component = AddonModLessonProvider.COMPONENT;
|
||||
lesson: any; // The lesson the retake belongs to.
|
||||
courseId: number; // Course ID the lesson belongs to.
|
||||
selectedRetake: number; // The retake to see.
|
||||
student: any; // Data about the student and his retakes.
|
||||
retake: any; // Data about the retake.
|
||||
loaded: boolean; // Whether the data has been loaded.
|
||||
|
||||
protected lessonId: number; // The lesson ID the retake belongs to.
|
||||
protected userId: number; // User ID to see the retakes.
|
||||
protected retakeNumber: number; // Number of the initial retake to see.
|
||||
|
||||
constructor(navParams: NavParams, sitesProvider: CoreSitesProvider, protected textUtils: CoreTextUtilsProvider,
|
||||
protected translate: TranslateService, protected domUtils: CoreDomUtilsProvider,
|
||||
protected userProvider: CoreUserProvider, protected timeUtils: CoreTimeUtilsProvider,
|
||||
protected lessonProvider: AddonModLessonProvider, protected lessonHelper: AddonModLessonHelperProvider) {
|
||||
|
||||
this.lessonId = navParams.get('lessonId');
|
||||
this.courseId = navParams.get('courseId');
|
||||
this.userId = navParams.get('userId') || sitesProvider.getCurrentSiteUserId();
|
||||
this.retakeNumber = navParams.get('retake');
|
||||
}
|
||||
|
||||
/**
|
||||
* Component being initialized.
|
||||
*/
|
||||
ngOnInit(): void {
|
||||
// Fetch the data.
|
||||
this.fetchData().finally(() => {
|
||||
this.loaded = true;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Change the retake displayed.
|
||||
*
|
||||
* @param {number} retakeNumber The new retake number.
|
||||
*/
|
||||
changeRetake(retakeNumber: number): void {
|
||||
this.loaded = false;
|
||||
|
||||
this.setRetake(retakeNumber).catch((error) => {
|
||||
this.domUtils.showErrorModalDefault(error, 'Error getting attempt.');
|
||||
}).finally(() => {
|
||||
this.loaded = true;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Pull to refresh.
|
||||
*
|
||||
* @param {any} refresher Refresher.
|
||||
*/
|
||||
doRefresh(refresher: any): void {
|
||||
this.refreshData().finally(() => {
|
||||
refresher.complete();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get lesson and retake data.
|
||||
*
|
||||
* @return {Promise<any>} Promise resolved when done.
|
||||
*/
|
||||
protected fetchData(): Promise<any> {
|
||||
return this.lessonProvider.getLessonById(this.courseId, this.lessonId).then((lessonData) => {
|
||||
this.lesson = lessonData;
|
||||
|
||||
// Get the retakes overview for all participants.
|
||||
return this.lessonProvider.getRetakesOverview(this.lesson.id);
|
||||
}).then((data) => {
|
||||
// Search the student.
|
||||
let student;
|
||||
|
||||
if (data && data.students) {
|
||||
for (let i = 0; i < data.students.length; i++) {
|
||||
if (data.students[i].id == this.userId) {
|
||||
student = data.students[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!student) {
|
||||
// Student not found.
|
||||
return Promise.reject(this.translate.instant('addon.mod_lesson.cannotfinduser'));
|
||||
}
|
||||
|
||||
if (!student.attempts || !student.attempts.length) {
|
||||
// No retakes.
|
||||
return Promise.reject(this.translate.instant('addon.mod_lesson.cannotfindattempt'));
|
||||
}
|
||||
|
||||
student.bestgrade = this.textUtils.roundToDecimals(student.bestgrade, 2);
|
||||
student.attempts.forEach((retake) => {
|
||||
if (this.retakeNumber == retake.try) {
|
||||
// The retake specified as parameter exists. Use it.
|
||||
this.selectedRetake = this.retakeNumber;
|
||||
}
|
||||
|
||||
retake.label = this.lessonHelper.getRetakeLabel(retake);
|
||||
});
|
||||
|
||||
if (!this.selectedRetake) {
|
||||
// Retake number not specified or not valid, use the last retake.
|
||||
this.selectedRetake = student.attempts[student.attempts.length - 1].try;
|
||||
}
|
||||
|
||||
// Get the profile image of the user.
|
||||
return this.userProvider.getProfile(student.id, this.courseId, true).then((user) => {
|
||||
student.profileimageurl = user.profileimageurl;
|
||||
|
||||
return student;
|
||||
}).catch(() => {
|
||||
// Error getting profile, resolve promise without adding any extra data.
|
||||
return student;
|
||||
});
|
||||
}).then((student) => {
|
||||
this.student = student;
|
||||
|
||||
return this.setRetake(this.selectedRetake);
|
||||
}).catch((error) => {
|
||||
this.domUtils.showErrorModalDefault(error, 'Error getting data.', true);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Refreshes data.
|
||||
*
|
||||
* @return {Promise<any>} Promise resolved when done.
|
||||
*/
|
||||
protected refreshData(): Promise<any> {
|
||||
const promises = [];
|
||||
|
||||
promises.push(this.lessonProvider.invalidateLessonData(this.courseId));
|
||||
if (this.lesson) {
|
||||
promises.push(this.lessonProvider.invalidateRetakesOverview(this.lesson.id));
|
||||
promises.push(this.lessonProvider.invalidateUserRetakesForUser(this.lesson.id, this.userId));
|
||||
}
|
||||
|
||||
return Promise.all(promises).catch(() => {
|
||||
// Ignore errors.
|
||||
}).then(() => {
|
||||
return this.fetchData();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the retake to view and load its data.
|
||||
*
|
||||
* @param {number}retakeNumber Retake number to set.
|
||||
* @return {Promise<any>} Promise resolved when done.
|
||||
*/
|
||||
protected setRetake(retakeNumber: number): Promise<any> {
|
||||
this.selectedRetake = retakeNumber;
|
||||
|
||||
return this.lessonProvider.getUserRetake(this.lessonId, retakeNumber, this.userId).then((data) => {
|
||||
|
||||
if (data && data.completed != -1) {
|
||||
// Completed.
|
||||
data.userstats.grade = this.textUtils.roundToDecimals(data.userstats.grade, 2);
|
||||
data.userstats.timetakenReadable = this.timeUtils.formatTime(data.userstats.timetotake);
|
||||
}
|
||||
|
||||
if (data && data.answerpages) {
|
||||
// Format pages data.
|
||||
data.answerpages.forEach((page) => {
|
||||
if (this.lessonProvider.answerPageIsContent(page)) {
|
||||
page.isContent = true;
|
||||
|
||||
if (page.answerdata && page.answerdata.answers) {
|
||||
page.answerdata.answers.forEach((answer) => {
|
||||
// Content pages only have 1 valid field in the answer array.
|
||||
answer[0] = this.lessonHelper.getContentPageAnswerDataFromHtml(answer[0]);
|
||||
});
|
||||
}
|
||||
} else if (this.lessonProvider.answerPageIsQuestion(page)) {
|
||||
page.isQuestion = true;
|
||||
|
||||
if (page.answerdata && page.answerdata.answers) {
|
||||
page.answerdata.answers.forEach((answer) => {
|
||||
// Only the first field of the answer array requires to be parsed.
|
||||
answer[0] = this.lessonHelper.getQuestionPageAnswerDataFromHtml(answer[0]);
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
this.retake = data;
|
||||
});
|
||||
}
|
||||
}
|
|
@ -17,7 +17,9 @@ import { FormBuilder, FormGroup } from '@angular/forms';
|
|||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { CoreDomUtilsProvider } from '@providers/utils/dom';
|
||||
import { CoreTextUtilsProvider } from '@providers/utils/text';
|
||||
import { CoreTimeUtilsProvider } from '@providers/utils/time';
|
||||
import { AddonModLessonProvider } from './lesson';
|
||||
import * as moment from 'moment';
|
||||
|
||||
/**
|
||||
* Helper service that provides some features for quiz.
|
||||
|
@ -28,7 +30,7 @@ export class AddonModLessonHelperProvider {
|
|||
protected div = document.createElement('div'); // A div element to search in HTML code.
|
||||
|
||||
constructor(private domUtils: CoreDomUtilsProvider, private fb: FormBuilder, private translate: TranslateService,
|
||||
private textUtils: CoreTextUtilsProvider) { }
|
||||
private textUtils: CoreTextUtilsProvider, private timeUtils: CoreTimeUtilsProvider) { }
|
||||
|
||||
/**
|
||||
* Given the HTML of next activity link, format it to extract the href and the text.
|
||||
|
@ -55,6 +57,33 @@ export class AddonModLessonHelperProvider {
|
|||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Given the HTML of an answer from a content page, extract the data to render the answer.
|
||||
*
|
||||
* @param {String} html Answer's HTML.
|
||||
* @return {{buttonText: string, content: string}} Data to render the answer.
|
||||
*/
|
||||
getContentPageAnswerDataFromHtml(html: string): {buttonText: string, content: string} {
|
||||
const data = {
|
||||
buttonText: '',
|
||||
content: ''
|
||||
};
|
||||
|
||||
// Search the input button.
|
||||
this.div.innerHTML = html;
|
||||
const button = <HTMLInputElement> this.div.querySelector('input[type="button"]');
|
||||
|
||||
if (button) {
|
||||
// Extract the button content and remove it from the HTML.
|
||||
data.buttonText = button.value;
|
||||
button.remove();
|
||||
}
|
||||
|
||||
data.content = this.div.innerHTML.trim();
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the buttons to change pages.
|
||||
*
|
||||
|
@ -321,6 +350,103 @@ export class AddonModLessonHelperProvider {
|
|||
return question;
|
||||
}
|
||||
|
||||
/**
|
||||
* Given the HTML of an answer from a question page, extract the data to render the answer.
|
||||
*
|
||||
* @param {string} html Answer's HTML.
|
||||
* @return {any} Object with the data to render the answer. If the answer doesn't require any parsing, return a string with
|
||||
* the HTML.
|
||||
*/
|
||||
getQuestionPageAnswerDataFromHtml(html: string): any {
|
||||
const data: any = {};
|
||||
|
||||
this.div.innerHTML = html;
|
||||
|
||||
// Check if it has a checkbox.
|
||||
let input = <HTMLInputElement> this.div.querySelector('input[type="checkbox"][name*="answer"]');
|
||||
|
||||
if (input) {
|
||||
// Truefalse or multichoice.
|
||||
data.isCheckbox = true;
|
||||
data.checked = !!input.checked;
|
||||
data.name = input.name;
|
||||
data.highlight = !!this.div.querySelector('.highlight');
|
||||
|
||||
input.remove();
|
||||
data.content = this.div.innerHTML.trim();
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
// Check if it has an input text or number.
|
||||
input = <HTMLInputElement> this.div.querySelector('input[type="number"],input[type="text"]');
|
||||
if (input) {
|
||||
// Short answer or numeric.
|
||||
data.isText = true;
|
||||
data.value = input.value;
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
// Check if it has a select.
|
||||
const select = <HTMLSelectElement> this.div.querySelector('select');
|
||||
if (select && select.options) {
|
||||
// Matching.
|
||||
const selectedOption = select.options[select.selectedIndex];
|
||||
data.isSelect = true;
|
||||
data.id = select.id;
|
||||
if (selectedOption) {
|
||||
data.value = selectedOption.value;
|
||||
} else {
|
||||
data.value = '';
|
||||
}
|
||||
|
||||
select.remove();
|
||||
data.content = this.div.innerHTML.trim();
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
// The answer doesn't need any parsing, return the HTML as it is.
|
||||
return html;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a label to identify a retake (lesson attempt).
|
||||
*
|
||||
* @param {any} retake Retake object.
|
||||
* @param {boolean} [includeDuration] Whether to include the duration of the retake.
|
||||
* @return {string} Retake label.
|
||||
*/
|
||||
getRetakeLabel(retake: any, includeDuration?: boolean): string {
|
||||
const data = {
|
||||
retake: retake.try + 1,
|
||||
grade: '',
|
||||
timestart: '',
|
||||
duration: ''
|
||||
},
|
||||
hasGrade = retake.grade != null;
|
||||
|
||||
if (hasGrade || retake.end) {
|
||||
// Retake finished with or without grade (if the lesson only has content pages, it has no grade).
|
||||
if (hasGrade) {
|
||||
data.grade = this.translate.instant('core.percentagenumber', {$a: retake.grade});
|
||||
}
|
||||
data.timestart = moment(retake.timestart * 1000).format('LLL');
|
||||
if (includeDuration) {
|
||||
data.duration = this.timeUtils.formatTime(retake.timeend - retake.timestart);
|
||||
}
|
||||
} else {
|
||||
// The user has not completed the retake.
|
||||
data.grade = this.translate.instant('addon.mod_lesson.notcompleted');
|
||||
if (retake.timestart) {
|
||||
data.timestart = moment(retake.timestart * 1000).format('LLL');
|
||||
}
|
||||
}
|
||||
|
||||
return this.translate.instant('addon.mod_lesson.retakelabel' + (includeDuration ? 'full' : 'short'), data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare the question data to be sent to server.
|
||||
*
|
||||
|
|
|
@ -12,7 +12,7 @@
|
|||
<core-format-text [component]="component" [componentId]="componentId" [text]="option.text"></core-format-text>
|
||||
<p *ngIf="option.feedback" class="core-question-feedback-container"><core-format-text [component]="component" [componentId]="componentId" [text]="option.feedback"></core-format-text></p>
|
||||
</ion-label>
|
||||
<ion-checkbox [name]="option.name" [(ngModel)]="option.checked" [disabled]="option.disabled" item-end></ion-checkbox>
|
||||
<ion-checkbox [attr.name]="option.name" [(ngModel)]="option.checked" [disabled]="option.disabled" item-end></ion-checkbox>
|
||||
|
||||
<!-- ion-checkbox doesn't use an input. Create a hidden input to hold the value. -->
|
||||
<input item-content type="hidden" [ngModel]="option.checked" [attr.name]="option.name">
|
||||
|
|
Loading…
Reference in New Issue