Skip to content
Merged
167 changes: 95 additions & 72 deletions src/app/exams/exams-view.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -32,86 +32,109 @@
<button mat-icon-button [disabled]="isLoading || questionNum === 1" (click)="moveQuestion(-1)"><mat-icon>navigate_before</mat-icon></button>
<button mat-icon-button [disabled]="isLoading || questionNum === maxQuestions" (click)="nextQuestion({ nextClicked: true })"><mat-icon>navigate_next</mat-icon></button>
</mat-toolbar>
<div class="progress-bar-track" *ngIf="!isLoading && maxQuestions > 0">
<div class="progress-bar-fill" [style.width.%]="progressPercent"></div>
</div>
<div class="view-container" [ngClass]="{ 'view-full-height': !isDialog }">
<ng-container *ngIf="!isLoading; else LoadingContent">
<td-markdown [content]="question?.body"></td-markdown>
<div [ngSwitch]="mode">
<ng-container *ngSwitchCase="'take'">
<ng-container [ngSwitch]="question?.type">
<mat-form-field class="full-width" *ngSwitchCase="'input'">
<mat-label i18n>Enter answer here</mat-label>
<input matInput [formControl]="answer">
</mat-form-field>
<mat-form-field *ngSwitchCase="'textarea'" class="full-width mat-form-field-type-no-underline">
<planet-markdown-textbox [formControl]="answer"></planet-markdown-textbox>
</mat-form-field>
<mat-radio-group *ngSwitchCase="'select'" class="question-list" [formControl]="answer">
<mat-radio-button [value]="option" *ngFor="let option of question?.choices">
<span class="multiple-choice-text">{{option.text}}</span>
</mat-radio-button>
<ng-container *ngIf="question?.hasOtherOption">
<div class="other-option-flex">
<mat-radio-button [value]="currentOtherOption">
<span class="multiple-choice-text" i18n>Other: </span>
<div [ngClass]="slideDirection === 'right' ? 'slide-in-right' : 'slide-in-left'">
<td-markdown [content]="question?.body"></td-markdown>
Comment thread
Mutugiii marked this conversation as resolved.
Outdated
<div [ngSwitch]="mode">
<ng-container *ngSwitchCase="'take'">
<ng-container [ngSwitch]="question?.type">
<mat-form-field class="full-width" *ngSwitchCase="'input'">
<mat-label i18n>Enter answer here</mat-label>
<input matInput [formControl]="answer">
</mat-form-field>
<mat-form-field *ngSwitchCase="'textarea'" class="full-width mat-form-field-type-no-underline">
<planet-markdown-textbox [formControl]="answer"></planet-markdown-textbox>
</mat-form-field>
<mat-radio-group *ngSwitchCase="'select'" class="question-list" [formControl]="answer">
<div class="option-card" *ngFor="let option of question?.choices" [class.selected]="isSelectOptionSelected(option)" (click)="selectOption(option)">
<mat-radio-button [value]="option">
<span class="multiple-choice-text">{{option.text}}</span>
</mat-radio-button>
<input matInput
class="other-option-input"
[disabled]="!isOtherSelected()"
[(ngModel)]="currentOtherOption.text"
(ngModelChange)="updateOtherText()">
<mat-icon class="option-check" *ngIf="isSelectOptionSelected(option)">check</mat-icon>
</div>
</ng-container>
</mat-radio-group>
<div *ngSwitchCase="'selectMultiple'" class="question-list">
<span class="mat-caption" i18n>
{examType, select, survey {You can choose one or more answers.} exam {There are one or more correct answers. Please choose all correct answers.}}
</span>
<mat-checkbox *ngFor="let option of question?.choices" [value]="option" (change)="setAnswer($event, option)" [checked]="checkboxState[option.id]">
<span class="multiple-choice-text">{{option.text}}</span>
</mat-checkbox>
<ng-container *ngIf="question?.hasOtherOption">
<div class="other-option-flex">
<mat-checkbox [checked]="checkboxState['other']" (change)="toggleOtherMultiple($event)">
<span class="multiple-choice-text" i18n>Other: </span>
<ng-container *ngIf="question?.hasOtherOption">
<div class="option-card" [class.selected]="isOtherSelected()" (click)="selectOtherRadio()">
<mat-radio-button [value]="currentOtherOption">
<span class="multiple-choice-text" i18n>Other: </span>
</mat-radio-button>
<div class="other-option-flex">
<input matInput
#singleOtherInput
class="other-option-input"
[(ngModel)]="currentOtherOption.text"
(ngModelChange)="updateOtherText()"
(focus)="selectOtherRadio()"
(click)="$event.stopPropagation()">
</div>
<mat-icon class="option-check" *ngIf="isOtherSelected()">check</mat-icon>
</div>
</ng-container>
</mat-radio-group>
<div *ngSwitchCase="'selectMultiple'" class="question-list">
<span i18n>
{examType, select, survey {You can choose one or more answers.} exam {There are one or more correct answers. Please choose all correct answers.}}
</span>
<div class="option-card" *ngFor="let option of question?.choices" [class.selected]="checkboxState[option.id]" (click)="toggleMultipleOption(option)">
<mat-checkbox [value]="option" (change)="setAnswer($event, option)" [checked]="checkboxState[option.id]" (click)="$event.stopPropagation()">
<span class="multiple-choice-text">{{option.text}}</span>
</mat-checkbox>
<input matInput class="other-option-input" [disabled]="!checkboxState['other']" [(ngModel)]="currentOtherOption.text" (ngModelChange)="updateOtherText()">
<mat-icon class="option-check" *ngIf="checkboxState[option.id]">check</mat-icon>
</div>
</ng-container>
</div>
<div *ngSwitchCase="'ratingScale'" class="rating-scale-keypad">
<div class="rating-scale-grid">
<button type="button" mat-raised-button class="rating-scale-button" *ngFor="let num of [1,2,3,4,5,6,7,8,9]" [class.selected]="answer.value === num.toString()" [attr.data-rating]="num" (click)="setRatingScaleAnswer(num)">
{{num}}
</button>
<ng-container *ngIf="question?.hasOtherOption">
<div class="option-card" [class.selected]="checkboxState['other']" (click)="toggleOtherCheckbox()">
<mat-checkbox [checked]="checkboxState['other']" (change)="toggleOtherMultiple($event)" (click)="$event.stopPropagation()">
<span class="multiple-choice-text" i18n>Other: </span>
</mat-checkbox>
<div class="other-option-flex">
<input matInput #multipleOtherInput class="other-option-input" [(ngModel)]="currentOtherOption.text" (ngModelChange)="updateOtherText()" (focus)="ensureOtherCheckboxSelected()" (click)="$event.stopPropagation()">
</div>
<mat-icon class="option-check" *ngIf="checkboxState['other']">check</mat-icon>
</div>
</ng-container>
</div>
</div>
<div *ngSwitchCase="'ratingScale'" class="rating-scale-keypad">
<div class="rating-scale-row">
<button type="button" class="rating-scale-button" *ngFor="let num of [1,2,3,4,5,6,7,8,9]" [class.selected]="answer.value === num.toString()" [attr.data-rating]="num" (click)="setRatingScaleAnswer(num)">
{{num}}
</button>
</div>
<div class="rating-scale-labels">
<span class="rating-scale-label" i18n>Very bad</span>
<span class="rating-scale-label" i18n>Very good</span>
</div>
</div>
</ng-container>
</ng-container>
</ng-container>
<ng-container *ngSwitchCase="'grade'">
<p><b i18n>Submitted answer:</b></p>
<td-markdown [content]="answer?.value?.text || answer?.value"></td-markdown>
<mat-radio-group [(ngModel)]="grade" [disabled]="question?.type === 'select' || question?.type === 'selectMultiple'">
<mat-radio-button [value]="1" class="planet-radio-button" i18n>Correct</mat-radio-button>
<mat-radio-button [value]="0" class="planet-radio-button" i18n>Incorrect</mat-radio-button>
</mat-radio-group>
<mat-form-field class="full-width mat-form-field-type-no-underline">
<mat-label i18n>Comment</mat-label>
<planet-markdown-textbox class="full-width" [(ngModel)]="comment"></planet-markdown-textbox>
</mat-form-field>
</ng-container>
<ng-container *ngSwitchCase="'view'">
<p><b i18n>Response:</b></p>
<td-markdown [content]="answer?.value?.text || answer?.value"></td-markdown>
<ng-container *ngIf="grade>=0">
<p><b i18n>Grade:</b></p>
<p *ngIf="grade===1" i18n>Correct</p>
<p *ngIf="grade===0" i18n>Incorrect</p>
<ng-container *ngSwitchCase="'grade'">
<p><b i18n>Submitted answer:</b></p>
<td-markdown [content]="answer?.value?.text || answer?.value"></td-markdown>
<mat-radio-group [(ngModel)]="grade" [disabled]="question?.type === 'select' || question?.type === 'selectMultiple'">
<mat-radio-button [value]="1" class="planet-radio-button" i18n>Correct</mat-radio-button>
<mat-radio-button [value]="0" class="planet-radio-button" i18n>Incorrect</mat-radio-button>
</mat-radio-group>
<mat-form-field class="full-width mat-form-field-type-no-underline">
<mat-label i18n>Comment</mat-label>
<planet-markdown-textbox class="full-width" [(ngModel)]="comment"></planet-markdown-textbox>
</mat-form-field>
</ng-container>
<ng-container *ngIf="comment">
<p><b i18n>Feedback:</b></p>
<td-markdown [content]="comment"></td-markdown>
<ng-container *ngSwitchCase="'view'">
<p><b i18n>Response:</b></p>
<td-markdown [content]="answer?.value?.text || answer?.value"></td-markdown>
<ng-container *ngIf="grade>=0">
<p><b i18n>Grade:</b></p>
<p *ngIf="grade===1" i18n>Correct</p>
<p *ngIf="grade===0" i18n>Incorrect</p>
</ng-container>
<ng-container *ngIf="comment">
<p><b i18n>Feedback:</b></p>
<td-markdown [content]="comment"></td-markdown>
</ng-container>
</ng-container>
</ng-container>
</div>
</div>
<div class="v-align-center action-buttons">
<button
Expand All @@ -124,8 +147,8 @@
</button>
<button
*ngIf="mode === 'take'"
mat-raised-button
color="accent"
mat-stroked-button
color="primary"
(click)="nextQuestion({ isFinish: true })"
[disabled]="!isComplete || !answer.valid || grade === undefined || grade === null"
i18n>
Expand Down
52 changes: 51 additions & 1 deletion src/app/exams/exams-view.component.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Component, OnInit, OnDestroy, Input } from '@angular/core';
import { Component, OnInit, OnDestroy, Input, ViewChild, ElementRef } from '@angular/core';
import {
AbstractControl, FormBuilder, FormControl, FormGroup, ValidationErrors, ValidatorFn, FormsModule, ReactiveFormsModule
} from '@angular/forms';
Expand Down Expand Up @@ -86,6 +86,10 @@ export class ExamsViewComponent implements OnInit, OnDestroy {
courseId: string;
teamId = this.route.snapshot.params.teamId || null;
currentOtherOption: ExamOtherAnswerOption = { id: 'other', text: '', isOther: true };
slideDirection: 'right' | 'left' = 'right';
@ViewChild('singleOtherInput') singleOtherInput?: ElementRef<HTMLInputElement>;
@ViewChild('multipleOtherInput') multipleOtherInput?: ElementRef<HTMLInputElement>;
progressPercent = 0;
private readonly answerValidator: ValidatorFn = (ac: AbstractControl<ExamAnswerValue>): ValidationErrors | null => {
const value = ac.value;
if (typeof value === 'string') {
Expand Down Expand Up @@ -243,6 +247,9 @@ export class ExamsViewComponent implements OnInit, OnDestroy {
}

moveQuestion(direction: number) {
if (direction !== 0) {
this.slideDirection = direction > 0 ? 'right' : 'left';
}
if (this.isDialog) {
this.questionNum = this.questionNum + direction;
this.setExamPreview();
Expand Down Expand Up @@ -291,6 +298,7 @@ export class ExamsViewComponent implements OnInit, OnDestroy {
setQuestion(questions: any[]) {
this.question = questions[this.questionNum - 1];
this.maxQuestions = questions.length;
this.progressPercent = this.maxQuestions ? Math.round(((this.questionNum - 1) / this.maxQuestions) * 100) : 0;
Comment thread
Mutugiii marked this conversation as resolved.
Outdated
this.answer.markAsUntouched();
this.currentOtherOption = { id: 'other', text: '', isOther: true };
}
Expand Down Expand Up @@ -463,12 +471,47 @@ export class ExamsViewComponent implements OnInit, OnDestroy {
return this.isOtherOption(this.answer.value);
}

isSelectOptionSelected(option: ExamAnswerOption): boolean {
const value = this.answer.value;
return this.isAnswerOption(value) && !this.isOtherOption(value) && value.id === option.id;
}

selectOption(option: ExamAnswerOption): void {
this.answer.setValue(option);
this.answer.updateValueAndValidity();
}

selectOtherRadio(): void {
if (this.isOtherSelected()) {
this.focusOtherInput('single');
return;
}
this.answer.setValue(this.currentOtherOption);
this.answer.updateValueAndValidity();
this.focusOtherInput('single');
}

toggleMultipleOption(option: ExamAnswerOption): void {
this.setAnswer({ checked: !this.checkboxState[option.id] }, option);
}

toggleOtherCheckbox(): void {
this.toggleOtherMultiple({ checked: !this.checkboxState['other'] });
}

ensureOtherCheckboxSelected(): void {
if (!this.checkboxState['other']) {
this.toggleOtherMultiple({ checked: true });
}
}

toggleOtherMultiple({ checked }: Pick<MatCheckboxChange, 'checked'>): void {
this.checkboxState['other'] = checked;
if (checked) {
if (this.currentOtherOption) {
this.setAnswer({ checked: true }, this.currentOtherOption);
}
this.focusOtherInput('multiple');
} else {
const remaining = Array.isArray(this.answer.value) ? this.answer.value.filter(o => o.id !== 'other') : [];
this.answer.setValue(remaining.length ? remaining : null);
Expand All @@ -488,4 +531,11 @@ export class ExamsViewComponent implements OnInit, OnDestroy {
return this.isAnswerOption(value) && value.isOther === true;
}

private focusOtherInput(type: 'single' | 'multiple'): void {
setTimeout(() => {
const inputRef = type === 'single' ? this.singleOtherInput : this.multipleOtherInput;
inputRef?.nativeElement.focus();
});
}

}
Loading
Loading