MOBILE-2389 qtype: Implement calculated and calculatedsimple types
This commit is contained in:
		
							parent
							
								
									a89737a5d3
								
							
						
					
					
						commit
						38184308a3
					
				
							
								
								
									
										46
									
								
								src/addon/qtype/calculated/calculated.module.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										46
									
								
								src/addon/qtype/calculated/calculated.module.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,46 @@ | ||||
| // (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 { IonicModule } from 'ionic-angular'; | ||||
| import { TranslateModule } from '@ngx-translate/core'; | ||||
| import { CoreQuestionDelegate } from '@core/question/providers/delegate'; | ||||
| import { CoreDirectivesModule } from '@directives/directives.module'; | ||||
| import { AddonQtypeCalculatedHandler } from './providers/handler'; | ||||
| import { AddonQtypeCalculatedComponent } from './component/calculated'; | ||||
| 
 | ||||
| @NgModule({ | ||||
|     declarations: [ | ||||
|         AddonQtypeCalculatedComponent | ||||
|     ], | ||||
|     imports: [ | ||||
|         IonicModule, | ||||
|         TranslateModule.forChild(), | ||||
|         CoreDirectivesModule | ||||
|     ], | ||||
|     providers: [ | ||||
|         AddonQtypeCalculatedHandler | ||||
|     ], | ||||
|     exports: [ | ||||
|         AddonQtypeCalculatedComponent | ||||
|     ], | ||||
|     entryComponents: [ | ||||
|         AddonQtypeCalculatedComponent | ||||
|     ] | ||||
| }) | ||||
| export class AddonQtypeCalculatedModule { | ||||
|     constructor(questionDelegate: CoreQuestionDelegate, handler: AddonQtypeCalculatedHandler) { | ||||
|         questionDelegate.registerHandler(handler); | ||||
|     } | ||||
| } | ||||
							
								
								
									
										55
									
								
								src/addon/qtype/calculated/component/calculated.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										55
									
								
								src/addon/qtype/calculated/component/calculated.html
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,55 @@ | ||||
| <section ion-list class="addon-qtype-calculated-container" *ngIf="question.text || question.text === ''"> | ||||
|     <ion-item text-wrap> | ||||
|         <p><core-format-text [component]="component" [componentId]="componentId" [text]="question.text"></core-format-text></p> | ||||
|     </ion-item> | ||||
| 
 | ||||
|     <!-- Display unit options before the answer input. --> | ||||
|     <ng-container *ngIf="question.options && question.options.length && question.optionsFirst"> | ||||
|         <ng-container *ngTemplateOutlet="radioUnits"></ng-container> | ||||
|     </ng-container> | ||||
| 
 | ||||
|     <ion-item text-wrap> | ||||
|         <ion-row> | ||||
|             <!-- Display unit select before the answer input. --> | ||||
|             <ng-container *ngIf="question.select && question.selectFirst"> | ||||
|                 <ng-container *ngTemplateOutlet="selectUnits"></ng-container> | ||||
|             </ng-container> | ||||
| 
 | ||||
|             <!-- Input to enter the answer. --> | ||||
|             <ion-col> | ||||
|                 <ion-input type="text" placeholder="{{ 'core.question.answer' | translate }}" [attr.name]="question.input.name" [value]="question.input.value" [disabled]="question.input.readOnly" [ngClass]='{"core-question-answer-correct": question.input.isCorrect === 1, "core-question-answer-incorrect": question.input.isCorrect === 0}' autocorrect="off"> | ||||
|                 </ion-input> | ||||
|             </ion-col> | ||||
| 
 | ||||
|             <!-- Display unit select after the answer input. --> | ||||
|             <ng-container *ngIf="question.select && !question.selectFirst"> | ||||
|                 <ng-container *ngTemplateOutlet="selectUnits"></ng-container> | ||||
|             </ng-container> | ||||
|         </ion-row> | ||||
|     </ion-item> | ||||
| 
 | ||||
|     <!-- Display unit options after the answer input. --> | ||||
|     <ng-container *ngIf="question.options && question.options.length && !question.optionsFirst"> | ||||
|         <ng-container *ngTemplateOutlet="radioUnits"></ng-container> | ||||
|     </ng-container> | ||||
| </section> | ||||
| 
 | ||||
| <!-- Template for units entered using a select. --> | ||||
| <ng-template #selectUnits> | ||||
|     <ion-col> | ||||
|         <label *ngIf="question.select.accessibilityLabel" class="accesshide" for="{{question.select.id}}">{{ question.select.accessibilityLabel }}</label> | ||||
|         <ion-select id="{{question.select.id}}" [name]="question.select.name" [ngModel]="question.select.selected"> | ||||
|             <ion-option *ngFor="let option of question.select.options" [value]="option.value">{{option.label}}</ion-option> | ||||
|         </ion-select> | ||||
|         <!-- @todo: select fix? --> | ||||
|     </ion-col> | ||||
| </ng-template> | ||||
| 
 | ||||
| <!-- Template for units entered using radio buttons. --> | ||||
| <ng-template #radioUnits> | ||||
|     <div radio-group [ngModel]="question.unit" [name]="question.optionsName"> | ||||
|         <ion-radio *ngFor="let option of question.options" [value]="option.value" [disabled]="option.disabled"> | ||||
|             <p>{{option.text}}</p> | ||||
|         </ion-radio> | ||||
|     </div> | ||||
| </ng-template> | ||||
							
								
								
									
										40
									
								
								src/addon/qtype/calculated/component/calculated.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										40
									
								
								src/addon/qtype/calculated/component/calculated.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,40 @@ | ||||
| // (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 { CoreLoggerProvider } from '@providers/logger'; | ||||
| import { CoreDomUtilsProvider } from '@providers/utils/dom'; | ||||
| import { CoreQuestionHelperProvider } from '@core/question/providers/helper'; | ||||
| import { CoreQuestionBaseComponent } from '@core/question/classes/base-question-component'; | ||||
| 
 | ||||
| /** | ||||
|  * Component to render a calculated question. | ||||
|  */ | ||||
| @Component({ | ||||
|     selector: 'addon-qtype-calculated', | ||||
|     templateUrl: 'calculated.html' | ||||
| }) | ||||
| export class AddonQtypeCalculatedComponent extends CoreQuestionBaseComponent implements OnInit { | ||||
| 
 | ||||
|     constructor(logger: CoreLoggerProvider, questionHelper: CoreQuestionHelperProvider, domUtils: CoreDomUtilsProvider) { | ||||
|         super(logger, 'AddonQtypeCalculatedComponent', questionHelper, domUtils); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Component being initialized. | ||||
|      */ | ||||
|     ngOnInit(): void { | ||||
|         this.initCalculatedComponent(); | ||||
|     } | ||||
| } | ||||
							
								
								
									
										128
									
								
								src/addon/qtype/calculated/providers/handler.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										128
									
								
								src/addon/qtype/calculated/providers/handler.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,128 @@ | ||||
| 
 | ||||
| // (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 { Injectable, Injector } from '@angular/core'; | ||||
| import { CoreUtilsProvider } from '@providers/utils/utils'; | ||||
| import { CoreQuestionHandler } from '@core/question/providers/delegate'; | ||||
| import { AddonQtypeNumericalHandler } from '@addon/qtype/numerical/providers/handler'; | ||||
| import { AddonQtypeCalculatedComponent } from '../component/calculated'; | ||||
| 
 | ||||
| /** | ||||
|  * Handler to support calculated question type. | ||||
|  */ | ||||
| @Injectable() | ||||
| export class AddonQtypeCalculatedHandler implements CoreQuestionHandler { | ||||
|     name = 'AddonQtypeCalculated'; | ||||
|     type = 'qtype_calculated'; | ||||
| 
 | ||||
|     constructor(private utils: CoreUtilsProvider, private numericalHandler: AddonQtypeNumericalHandler) { } | ||||
| 
 | ||||
|     /** | ||||
|      * Return the Component to use to display the question. | ||||
|      * It's recommended to return the class of the component, but you can also return an instance of the component. | ||||
|      * | ||||
|      * @param {Injector} injector Injector. | ||||
|      * @param {any} question The question to render. | ||||
|      * @return {any|Promise<any>} The component (or promise resolved with component) to use, undefined if not found. | ||||
|      */ | ||||
|     getComponent(injector: Injector, question: any): any | Promise<any> { | ||||
|         return AddonQtypeCalculatedComponent; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Check if a response is complete. | ||||
|      * | ||||
|      * @param {any} question The question. | ||||
|      * @param {any} answers Object with the question answers (without prefix). | ||||
|      * @return {number} 1 if complete, 0 if not complete, -1 if cannot determine. | ||||
|      */ | ||||
|     isCompleteResponse(question: any, answers: any): number { | ||||
|         // This question type depends on numerical.
 | ||||
|         if (this.isGradableResponse(question, answers) === 0 || !this.numericalHandler.validateUnits(answers['answer'])) { | ||||
|             return 0; | ||||
|         } | ||||
| 
 | ||||
|         if (this.requiresUnits(question)) { | ||||
|             return this.isValidValue(answers['unit']) ? 1 : 0; | ||||
|         } | ||||
| 
 | ||||
|         return -1; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Whether or not the handler is enabled on a site level. | ||||
|      * | ||||
|      * @return {boolean|Promise<boolean>} True or promise resolved with true if enabled. | ||||
|      */ | ||||
|     isEnabled(): boolean | Promise<boolean> { | ||||
|         return true; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Check if a student has provided enough of an answer for the question to be graded automatically, | ||||
|      * or whether it must be considered aborted. | ||||
|      * | ||||
|      * @param {any} question The question. | ||||
|      * @param {any} answers Object with the question answers (without prefix). | ||||
|      * @return {number} 1 if gradable, 0 if not gradable, -1 if cannot determine. | ||||
|      */ | ||||
|     isGradableResponse(question: any, answers: any): number { | ||||
|         // This question type depends on numerical.
 | ||||
|         let isGradable = this.isValidValue(answers['answer']); | ||||
|         if (isGradable && this.requiresUnits(question)) { | ||||
|             // The question requires a unit.
 | ||||
|             isGradable = this.isValidValue(answers['unit']); | ||||
|         } | ||||
| 
 | ||||
|         return isGradable ? 1 : 0; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Check if two responses are the same. | ||||
|      * | ||||
|      * @param {any} question Question. | ||||
|      * @param {any} prevAnswers Object with the previous question answers. | ||||
|      * @param {any} newAnswers Object with the new question answers. | ||||
|      * @return {boolean} Whether they're the same. | ||||
|      */ | ||||
|     isSameResponse(question: any, prevAnswers: any, newAnswers: any): boolean { | ||||
|         // This question type depends on numerical.
 | ||||
|         return this.utils.sameAtKeyMissingIsBlank(prevAnswers, newAnswers, 'answer') && | ||||
|             this.utils.sameAtKeyMissingIsBlank(prevAnswers, newAnswers, 'unit'); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Check if a value is valid (not empty). | ||||
|      * | ||||
|      * @param {string|number} value Value to check. | ||||
|      * @return {boolean} Whether the value is valid. | ||||
|      */ | ||||
|     isValidValue(value: string | number): boolean { | ||||
|         return !!value || value === '0' || value === 0; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Check if a question requires units in a separate input. | ||||
|      * | ||||
|      * @param {any} question The question. | ||||
|      * @return {boolean} Whether the question requires units. | ||||
|      */ | ||||
|     requiresUnits(question: any): boolean { | ||||
|         const div = document.createElement('div'); | ||||
|         div.innerHTML = question.html; | ||||
| 
 | ||||
|         return !!(div.querySelector('select[name*=unit]') || div.querySelector('input[type="radio"]')); | ||||
|     } | ||||
| } | ||||
							
								
								
									
										30
									
								
								src/addon/qtype/calculatedsimple/calculatedsimple.module.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								src/addon/qtype/calculatedsimple/calculatedsimple.module.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,30 @@ | ||||
| // (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 { CoreQuestionDelegate } from '@core/question/providers/delegate'; | ||||
| import { AddonQtypeCalculatedSimpleHandler } from './providers/handler'; | ||||
| 
 | ||||
| @NgModule({ | ||||
|     declarations: [ | ||||
|     ], | ||||
|     providers: [ | ||||
|         AddonQtypeCalculatedSimpleHandler | ||||
|     ], | ||||
| }) | ||||
| export class AddonQtypeCalculatedSimpleModule { | ||||
|     constructor(questionDelegate: CoreQuestionDelegate, handler: AddonQtypeCalculatedSimpleHandler) { | ||||
|         questionDelegate.registerHandler(handler); | ||||
|     } | ||||
| } | ||||
							
								
								
									
										90
									
								
								src/addon/qtype/calculatedsimple/providers/handler.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										90
									
								
								src/addon/qtype/calculatedsimple/providers/handler.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,90 @@ | ||||
| 
 | ||||
| // (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 { Injectable, Injector } from '@angular/core'; | ||||
| import { CoreQuestionHandler } from '@core/question/providers/delegate'; | ||||
| import { AddonQtypeCalculatedHandler } from '@addon/qtype/calculated/providers/handler'; | ||||
| import { AddonQtypeCalculatedComponent } from '@addon/qtype/calculated/component/calculated'; | ||||
| 
 | ||||
| /** | ||||
|  * Handler to support calculated simple question type. | ||||
|  */ | ||||
| @Injectable() | ||||
| export class AddonQtypeCalculatedSimpleHandler implements CoreQuestionHandler { | ||||
|     name = 'AddonQtypeCalculatedSimple'; | ||||
|     type = 'qtype_calculatedsimple'; | ||||
| 
 | ||||
|     constructor(private calculatedHandler: AddonQtypeCalculatedHandler) { } | ||||
| 
 | ||||
|     /** | ||||
|      * Return the Component to use to display the question. | ||||
|      * It's recommended to return the class of the component, but you can also return an instance of the component. | ||||
|      * | ||||
|      * @param {Injector} injector Injector. | ||||
|      * @param {any} question The question to render. | ||||
|      * @return {any|Promise<any>} The component (or promise resolved with component) to use, undefined if not found. | ||||
|      */ | ||||
|     getComponent(injector: Injector, question: any): any | Promise<any> { | ||||
|         // Calculated simple behaves like a calculated, use the same component.
 | ||||
|         return AddonQtypeCalculatedComponent; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Check if a response is complete. | ||||
|      * | ||||
|      * @param {any} question The question. | ||||
|      * @param {any} answers Object with the question answers (without prefix). | ||||
|      * @return {number} 1 if complete, 0 if not complete, -1 if cannot determine. | ||||
|      */ | ||||
|     isCompleteResponse(question: any, answers: any): number { | ||||
|         // This question type depends on calculated.
 | ||||
|         return this.calculatedHandler.isCompleteResponse(question, answers); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Whether or not the handler is enabled on a site level. | ||||
|      * | ||||
|      * @return {boolean|Promise<boolean>} True or promise resolved with true if enabled. | ||||
|      */ | ||||
|     isEnabled(): boolean | Promise<boolean> { | ||||
|         return true; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Check if a student has provided enough of an answer for the question to be graded automatically, | ||||
|      * or whether it must be considered aborted. | ||||
|      * | ||||
|      * @param {any} question The question. | ||||
|      * @param {any} answers Object with the question answers (without prefix). | ||||
|      * @return {number} 1 if gradable, 0 if not gradable, -1 if cannot determine. | ||||
|      */ | ||||
|     isGradableResponse(question: any, answers: any): number { | ||||
|         // This question type depends on calculated.
 | ||||
|         return this.calculatedHandler.isGradableResponse(question, answers); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Check if two responses are the same. | ||||
|      * | ||||
|      * @param {any} question Question. | ||||
|      * @param {any} prevAnswers Object with the previous question answers. | ||||
|      * @param {any} newAnswers Object with the new question answers. | ||||
|      * @return {boolean} Whether they're the same. | ||||
|      */ | ||||
|     isSameResponse(question: any, prevAnswers: any, newAnswers: any): boolean { | ||||
|         // This question type depends on calculated.
 | ||||
|         return this.calculatedHandler.isSameResponse(question, prevAnswers, newAnswers); | ||||
|     } | ||||
| } | ||||
| @ -13,7 +13,9 @@ | ||||
| // limitations under the License.
 | ||||
| 
 | ||||
| import { NgModule } from '@angular/core'; | ||||
| import { AddonQtypeCalculatedModule } from './calculated/calculated.module'; | ||||
| import { AddonQtypeCalculatedMultiModule } from './calculatedmulti/calculatedmulti.module'; | ||||
| import { AddonQtypeCalculatedSimpleModule } from './calculatedsimple/calculatedsimple.module'; | ||||
| import { AddonQtypeDescriptionModule } from './description/description.module'; | ||||
| import { AddonQtypeMatchModule } from './match/match.module'; | ||||
| import { AddonQtypeMultichoiceModule } from './multichoice/multichoice.module'; | ||||
| @ -25,7 +27,9 @@ import { AddonQtypeTrueFalseModule } from './truefalse/truefalse.module'; | ||||
| @NgModule({ | ||||
|     declarations: [], | ||||
|     imports: [ | ||||
|         AddonQtypeCalculatedModule, | ||||
|         AddonQtypeCalculatedMultiModule, | ||||
|         AddonQtypeCalculatedSimpleModule, | ||||
|         AddonQtypeDescriptionModule, | ||||
|         AddonQtypeMatchModule, | ||||
|         AddonQtypeMultichoiceModule, | ||||
|  | ||||
| @ -36,6 +36,124 @@ export class CoreQuestionBaseComponent { | ||||
|         this.logger = logger.getInstance(logName); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Initialize a question component of type calculated or calculated simple. | ||||
|      * | ||||
|      * @return {void|HTMLElement} Element containing the question HTML, void if the data is not valid. | ||||
|      */ | ||||
|     initCalculatedComponent(): void | HTMLElement { | ||||
|         // Treat the input text first.
 | ||||
|         const questionDiv = this.initInputTextComponent(); | ||||
|         if (questionDiv) { | ||||
| 
 | ||||
|             // Check if the question has a select for units.
 | ||||
|             const selectModel: any = {}, | ||||
|                 select = <HTMLSelectElement> questionDiv.querySelector('select[name*=unit]'), | ||||
|                 options = select && Array.from(select.querySelectorAll('option')); | ||||
| 
 | ||||
|             if (select && options && options.length) { | ||||
| 
 | ||||
|                 selectModel.id = select.id; | ||||
|                 selectModel.name = select.name; | ||||
|                 selectModel.disabled = select.disabled; | ||||
|                 selectModel.options = []; | ||||
| 
 | ||||
|                 // Treat each option.
 | ||||
|                 for (const i in options) { | ||||
|                     const optionEl = options[i]; | ||||
| 
 | ||||
|                     if (typeof optionEl.value == 'undefined') { | ||||
|                         this.logger.warn('Aborting because couldn\'t find input.', this.question.name); | ||||
| 
 | ||||
|                         return this.questionHelper.showComponentError(this.onAbort); | ||||
|                     } | ||||
| 
 | ||||
|                     const option = { | ||||
|                         value: optionEl.value, | ||||
|                         label: optionEl.innerHTML | ||||
|                     }; | ||||
| 
 | ||||
|                     if (optionEl.selected) { | ||||
|                         selectModel.selected = option.value; | ||||
|                         selectModel.selectedLabel = option.label; | ||||
|                     } | ||||
| 
 | ||||
|                     selectModel.options.push(option); | ||||
|                 } | ||||
| 
 | ||||
|                 if (!selectModel.selected) { | ||||
|                     // No selected option, select the first one.
 | ||||
|                     selectModel.selected = selectModel.options[0].value; | ||||
|                     selectModel.selectedLabel = selectModel.options[0].label; | ||||
|                 } | ||||
| 
 | ||||
|                 // Get the accessibility label.
 | ||||
|                 const accessibilityLabel = questionDiv.querySelector('label[for="' + select.id + '"]'); | ||||
|                 selectModel.accessibilityLabel = accessibilityLabel && accessibilityLabel.innerHTML; | ||||
| 
 | ||||
|                 this.question.select = selectModel; | ||||
| 
 | ||||
|                 // Check which one should be displayed first: the select or the input.
 | ||||
|                 const input = questionDiv.querySelector('input[type="text"][name*=answer]'); | ||||
|                 this.question.selectFirst = | ||||
|                         questionDiv.innerHTML.indexOf(input.outerHTML) > questionDiv.innerHTML.indexOf(select.outerHTML); | ||||
| 
 | ||||
|                 return questionDiv; | ||||
|             } | ||||
| 
 | ||||
|             // Check if the question has radio buttons for units.
 | ||||
|             const radios = <HTMLInputElement[]> Array.from(questionDiv.querySelectorAll('input[type="radio"]')); | ||||
|             if (!radios.length) { | ||||
|                 // No select and no radio buttons. The units need to be entered in the input text.
 | ||||
|                 return questionDiv; | ||||
|             } | ||||
| 
 | ||||
|             this.question.options = []; | ||||
| 
 | ||||
|             for (const i in radios) { | ||||
|                 const radioEl = radios[i], | ||||
|                     option: any = { | ||||
|                         id: radioEl.id, | ||||
|                         name: radioEl.name, | ||||
|                         value: radioEl.value, | ||||
|                         checked: radioEl.checked, | ||||
|                         disabled: radioEl.disabled | ||||
|                     }, | ||||
|                     // Get the label with the question text.
 | ||||
|                     label = <HTMLElement> questionDiv.querySelector('label[for="' + option.id + '"]'); | ||||
| 
 | ||||
|                 this.question.optionsName = option.name; | ||||
| 
 | ||||
|                 if (label) { | ||||
|                     option.text = label.innerText; | ||||
| 
 | ||||
|                     // Check that we were able to successfully extract options required data.
 | ||||
|                     if (typeof option.name != 'undefined' && typeof option.value != 'undefined' && | ||||
|                                 typeof option.text != 'undefined') { | ||||
| 
 | ||||
|                         if (radioEl.checked) { | ||||
|                             // If the option is checked we use the model to select the one.
 | ||||
|                             this.question.unit = option.value; | ||||
|                         } | ||||
| 
 | ||||
|                         this.question.options.push(option); | ||||
|                         continue; | ||||
|                     } | ||||
|                 } | ||||
| 
 | ||||
|                 // Something went wrong when extracting the questions data. Abort.
 | ||||
|                 this.logger.warn('Aborting because of an error parsing options.', this.question.name, option.name); | ||||
| 
 | ||||
|                 return this.questionHelper.showComponentError(this.onAbort); | ||||
|             } | ||||
| 
 | ||||
|             // Check which one should be displayed first: the options or the input.
 | ||||
|             const input = questionDiv.querySelector('input[type="text"][name*=answer]'); | ||||
|             this.question.optionsFirst = | ||||
|                     questionDiv.innerHTML.indexOf(input.outerHTML) > questionDiv.innerHTML.indexOf(options[0].outerHTML); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Initialize the component and the question text. | ||||
|      * | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user