forked from hobbyfarm/ui
-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Markdown Syntax to create a quiz within a scenario (hobbyfarm#195)
* Add quiz-checkbox component It is now possible to define questions in markdown within scenario steps * Refactor helper text and validation * Add question type "radio" * Resolve linting warnings and errors (code quality) * Run prettier on new html and scss files * Fix linting errors * Fix linting errors * Make validation optional * Improve checkbox validation * Fix linting errors * Refactor quiz * Remove empty constructor * Remove unused variable * Change update method * Disable input for quiz questions after submission * remove ngSubmit * Remove now unused variable * submit forms * Fix validation FormGroup.disable() not only disables the form but also validation. Therefore, we can not rely on FormGroup.valid anymore. * Only submit enabled forms * Validate radio buttons after(!) hitting submit * Fix linting error/warning * Remove commented out code * Rm console.log * Add form typings * Run prettier * Remove unneccessary code * Add error/success message customization * Fix radio quiz validation * Fix validation config * Fix linting errors/warnings * Add optional reset button for quiz component * Run prettier * Fix linting error * Add detailed validation for checkbox component * Make helper/error/success message optional * Add detailed validation for radio quiz type * Fix linting error * Run prettier * Disable submit button if a quiz was submitted and not reset. * Add default success/error msg for validation standard mode * Refactoring * Remove unused imports * Fix radio validation * Remove empty constructor --------- Co-authored-by: Jan-Gerrit Goebel <[email protected]>
- Loading branch information
Showing
23 changed files
with
705 additions
and
22 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
import { QuestionType } from './QuestionType'; | ||
import { Validation } from './Validation'; | ||
|
||
export interface QuestionParams { | ||
questionTitle: string; | ||
helperText: string; | ||
questionType: QuestionType; | ||
validation: Validation; | ||
successMsg: string; | ||
errorMsg: string; | ||
} | ||
|
||
export type QuestionParam = | ||
| 'title' | ||
| 'info' | ||
| 'type' | ||
| 'validation' | ||
| 'successMsg' | ||
| 'errorMsg'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
export type QuestionType = 'radio' | 'checkbox'; | ||
|
||
export function isQuestionType(value: string): value is QuestionType { | ||
const validValues: string[] = ['radio', 'checkbox']; | ||
return validValues.includes(value); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
import { FormArray, FormControl, FormGroup } from '@angular/forms'; | ||
|
||
export type QuizCheckboxFormGroup = FormGroup<{ | ||
quiz: FormArray<FormControl<boolean>>; | ||
}>; | ||
|
||
export type QuizRadioFormGroup = FormGroup<{ | ||
quiz: FormControl<number | null>; | ||
}>; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
export type Validation = 'none' | 'standard' | 'detailed'; | ||
|
||
export function isValidation(value: string): value is Validation { | ||
const validValues: string[] = ['none', 'standard', 'detailed']; | ||
return validValues.includes(value); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,86 @@ | ||
import { FormGroup } from '@angular/forms'; | ||
import { ClrForm } from '@clr/angular'; | ||
import { Validation } from './Validation'; | ||
import { Component, Input, OnInit, ViewChild } from '@angular/core'; | ||
|
||
@Component({ | ||
template: '', | ||
}) | ||
export abstract class QuizBaseComponent implements OnInit { | ||
@Input() | ||
public options: string; | ||
@Input() | ||
public helperText: string; | ||
@Input() | ||
public title: string; | ||
@Input() | ||
public validation: Validation; | ||
@Input() | ||
public errMsg: string; | ||
@Input() | ||
public successMsg: string; | ||
|
||
@ViewChild(ClrForm, { static: true }) | ||
clrForm: ClrForm; | ||
abstract quizForm: FormGroup; | ||
|
||
public optionTitles: string[] = []; | ||
public isSubmitted = false; | ||
public validSubmission = false; | ||
public validationEnabled: boolean; | ||
|
||
ngOnInit(): void { | ||
this.validationEnabled = this.validation != 'none'; | ||
this.extractQuizOptions(); | ||
this.createQuizForm(); | ||
} | ||
|
||
// This function extracts the different possible answers to a quiz question and identifies correct answers | ||
protected abstract extractQuizOptions(): void; | ||
|
||
// Create the quiz form group | ||
protected abstract createQuizForm(): void; | ||
|
||
public submit() { | ||
this.isSubmitted = true; | ||
if (this.quizForm.invalid) { | ||
this.clrForm.markAsTouched(); | ||
} else { | ||
this.validSubmission = true; | ||
} | ||
this.quizForm.disable(); | ||
} | ||
|
||
public reset() { | ||
this.isSubmitted = false; | ||
this.validSubmission = false; | ||
this.quizForm.reset(); | ||
this.quizForm.enable(); | ||
} | ||
|
||
// returns if the option at the specified index is selected | ||
protected abstract isSelectedOption(index: number): boolean; | ||
|
||
// returns if the option at the specified index is correct | ||
protected abstract isCorrectOption(index: number): boolean; | ||
|
||
// funtion for a label to determine if it should be styled as correctly selected option | ||
public hasCorrectOptionClass(index: number): boolean { | ||
return ( | ||
this.validation == 'detailed' && | ||
this.isSubmitted && | ||
this.isCorrectOption(index) | ||
); | ||
} | ||
|
||
// funtion for a label to determine if it should be styled as incorrectly selected option | ||
public hasIncorrectOptionClass(index: number): boolean { | ||
return ( | ||
this.validation == 'detailed' && | ||
this.isSubmitted && | ||
!this.validSubmission && | ||
this.isSelectedOption(index) && | ||
!this.isCorrectOption(index) | ||
); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
<ng-container> | ||
<span class="clr-subtext" *ngIf="!isSubmitted && helperText !== ''">{{ | ||
helperText | ||
}}</span> | ||
<clr-alert | ||
[clrAlertType]="'danger'" | ||
[clrAlertClosable]="false" | ||
[clrAlertSizeSmall]="true" | ||
*ngIf="validationEnabled && isSubmitted && !isValid && errMsg !== ''" | ||
> | ||
<clr-alert-item> | ||
<span class="alert-text">{{ errMsg }}</span> | ||
</clr-alert-item> | ||
</clr-alert> | ||
<clr-alert | ||
[clrAlertType]="'success'" | ||
[clrAlertClosable]="false" | ||
[clrAlertSizeSmall]="true" | ||
*ngIf="validationEnabled && isSubmitted && isValid && successMsg !== ''" | ||
> | ||
<clr-alert-item> | ||
<span class="alert-text">{{ successMsg }}</span> | ||
</clr-alert-item> | ||
</clr-alert> | ||
</ng-container> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
.clr-subtext { | ||
margin-bottom: 0.3rem; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
import { Component, Input } from '@angular/core'; | ||
|
||
@Component({ | ||
// eslint-disable-next-line @angular-eslint/component-selector | ||
selector: 'quiz-body', | ||
templateUrl: 'quiz-body.component.html', | ||
styleUrls: ['quiz-body.component.scss'], | ||
}) | ||
export class QuizBodyComponent { | ||
@Input() | ||
public helperText = ''; | ||
@Input() | ||
public isValid: boolean; | ||
@Input() | ||
public isSubmitted: boolean; | ||
@Input() | ||
public validationEnabled: boolean; | ||
@Input() | ||
public errMsg = ''; | ||
@Input() | ||
public successMsg = ''; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
<form class="quiz-form" clrForm [formGroup]="quizForm"> | ||
<clr-checkbox-container [class.clr-error]="quizForm.invalid && isSubmitted"> | ||
<label>{{ title }}</label> | ||
<clr-checkbox-wrapper | ||
formArrayName="quiz" | ||
*ngFor="let optionTitle of optionTitles; let i = index" | ||
> | ||
<input type="checkbox" clrCheckbox [formControlName]="i" /> | ||
<label> | ||
<quiz-label | ||
[optionTitle]="optionTitle" | ||
[hasCorrectOptionClass]="hasCorrectOptionClass(i)" | ||
[hasIncorrectOptionClass]="hasIncorrectOptionClass(i)" | ||
></quiz-label> | ||
</label> | ||
</clr-checkbox-wrapper> | ||
</clr-checkbox-container> | ||
<quiz-body | ||
[helperText]="helperText" | ||
[isValid]="validSubmission" | ||
[isSubmitted]="isSubmitted" | ||
[validationEnabled]="validationEnabled" | ||
[errMsg]="errMsg" | ||
[successMsg]="successMsg" | ||
></quiz-body> | ||
</form> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
pre { | ||
display: flex; | ||
flex-direction: column; | ||
} | ||
form { | ||
display: inherit; | ||
flex-direction: column; | ||
} | ||
clr-checkbox-container { | ||
flex-direction: column !important; | ||
margin-top: 0; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,92 @@ | ||
import { Component } from '@angular/core'; | ||
import { | ||
AbstractControl, | ||
FormArray, | ||
NonNullableFormBuilder, | ||
FormControl, | ||
ValidatorFn, | ||
} from '@angular/forms'; | ||
import { QuizCheckboxFormGroup } from './QuizFormGroup'; | ||
import { QuizBaseComponent } from './quiz-base.component'; | ||
|
||
@Component({ | ||
// eslint-disable-next-line @angular-eslint/component-selector | ||
selector: 'quiz-checkbox', | ||
templateUrl: 'quiz-checkbox.component.html', | ||
styleUrls: ['quiz-checkbox.component.scss'], | ||
}) | ||
export class QuizCheckboxComponent extends QuizBaseComponent { | ||
public override quizForm: QuizCheckboxFormGroup; | ||
public requiredValues: boolean[] = []; | ||
|
||
constructor(private fb: NonNullableFormBuilder) { | ||
super(); | ||
} | ||
|
||
protected override extractQuizOptions() { | ||
this.options.split('\n- ').forEach((option: string) => { | ||
this.optionTitles.push(option.split(':(')[0]); | ||
const requiredValue = option.split(':(')[1].toLowerCase() === 'x)'; | ||
this.requiredValues.push(requiredValue); | ||
}); | ||
} | ||
|
||
protected override createQuizForm() { | ||
if (this.validationEnabled) { | ||
this.quizForm = this.fb.group( | ||
{ | ||
quiz: new FormArray<FormControl<boolean>>( | ||
[], | ||
this.validateCheckboxes(), | ||
), | ||
}, | ||
{ updateOn: 'change' }, | ||
); | ||
} else { | ||
this.quizForm = this.fb.group( | ||
{ | ||
quiz: new FormArray<FormControl<boolean>>([]), | ||
}, | ||
{ updateOn: 'change' }, | ||
); | ||
} | ||
this.addCheckboxes(); | ||
} | ||
|
||
private addCheckboxes() { | ||
this.optionTitles.forEach(() => | ||
this.optionsFormArray.push(this.fb.control(false)), | ||
); | ||
} | ||
|
||
private get optionsFormArray(): FormArray<FormControl<boolean>> { | ||
return this.quizForm.controls.quiz; | ||
} | ||
|
||
private validateCheckboxes(): ValidatorFn { | ||
return (control: AbstractControl) => { | ||
const formArray = control as FormArray<FormControl<boolean>>; | ||
let validatedCheckboxes = true; | ||
formArray.controls.forEach( | ||
(control: FormControl<boolean>, index: number) => { | ||
validatedCheckboxes = | ||
validatedCheckboxes && control.value === this.requiredValues[index]; | ||
}, | ||
); | ||
if (!validatedCheckboxes) { | ||
return { | ||
checkboxesValidated: true, | ||
}; | ||
} | ||
return null; | ||
}; | ||
} | ||
|
||
protected override isSelectedOption(index: number): boolean { | ||
return this.optionsFormArray.at(index).value; | ||
} | ||
|
||
protected override isCorrectOption(index: number): boolean { | ||
return this.requiredValues[index]; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
<span | ||
[ngClass]="{ | ||
'correct-option': hasCorrectOptionClass, | ||
'incorrect-option': hasIncorrectOptionClass, | ||
}" | ||
>{{ optionTitle }}</span | ||
> | ||
<cds-icon | ||
*ngIf="hasCorrectOptionClass" | ||
shape="success-standard" | ||
status="success" | ||
solid="true" | ||
></cds-icon | ||
><cds-icon | ||
*ngIf="hasIncorrectOptionClass" | ||
shape="exclamation-circle" | ||
status="danger" | ||
solid="true" | ||
></cds-icon> |
Oops, something went wrong.