MOBILE-2427 qtype: Support units in numerical questions
This commit is contained in:
		
							parent
							
								
									21899c74f7
								
							
						
					
					
						commit
						e27e294d5b
					
				| @ -17,7 +17,6 @@ import { Injectable, Injector } from '@angular/core'; | ||||
| import { CoreDomUtilsProvider } from '@providers/utils/dom'; | ||||
| 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'; | ||||
| 
 | ||||
| /** | ||||
| @ -28,8 +27,7 @@ export class AddonQtypeCalculatedHandler implements CoreQuestionHandler { | ||||
|     name = 'AddonQtypeCalculated'; | ||||
|     type = 'qtype_calculated'; | ||||
| 
 | ||||
|     constructor(private utils: CoreUtilsProvider, private numericalHandler: AddonQtypeNumericalHandler, | ||||
|             private domUtils: CoreDomUtilsProvider) { } | ||||
|     constructor(private utils: CoreUtilsProvider, private domUtils: CoreDomUtilsProvider) { } | ||||
| 
 | ||||
|     /** | ||||
|      * Return the Component to use to display the question. | ||||
| @ -51,8 +49,7 @@ export class AddonQtypeCalculatedHandler implements CoreQuestionHandler { | ||||
|      * @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'])) { | ||||
|         if (this.isGradableResponse(question, answers) === 0 || !this.validateUnits(answers['answer'])) { | ||||
|             return 0; | ||||
|         } | ||||
| 
 | ||||
| @ -81,7 +78,6 @@ export class AddonQtypeCalculatedHandler implements CoreQuestionHandler { | ||||
|      * @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.
 | ||||
| @ -100,7 +96,6 @@ export class AddonQtypeCalculatedHandler implements CoreQuestionHandler { | ||||
|      * @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'); | ||||
|     } | ||||
| @ -126,4 +121,38 @@ export class AddonQtypeCalculatedHandler implements CoreQuestionHandler { | ||||
| 
 | ||||
|         return !!(element.querySelector('select[name*=unit]') || element.querySelector('input[type="radio"]')); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Validate a number with units. We don't have the list of valid units and conversions, so we can't perform | ||||
|      * a full validation. If this function returns true it means we can't be sure it's valid. | ||||
|      * | ||||
|      * @param {string} answer Answer. | ||||
|      * @return {boolean} False if answer isn't valid, true if we aren't sure if it's valid. | ||||
|      */ | ||||
|     validateUnits(answer: string): boolean { | ||||
|         if (!answer) { | ||||
|             return false; | ||||
|         } | ||||
| 
 | ||||
|         const regexString = '[+-]?(?:\\d+(?:\\.\\d*)?|\\.\\d+)(?:e[-+]?\\d+)?'; | ||||
| 
 | ||||
|         // Strip spaces (which may be thousands separators) and change other forms of writing e to e.
 | ||||
|         answer = answer.replace(' ', ''); | ||||
|         answer = answer.replace(/(?:e|E|(?:x|\*|×)10(?:\^|\*\*))([+-]?\d+)/, 'e$1'); | ||||
| 
 | ||||
|         // If a '.' is present or there are multiple ',' (i.e. 2,456,789) assume ',' is a thousands separator and stip it.
 | ||||
|         // Else assume it is a decimal separator, and change it to '.'.
 | ||||
|         if (answer.indexOf('.') != -1 || answer.split(',').length - 1 > 1) { | ||||
|             answer = answer.replace(',', ''); | ||||
|         } else { | ||||
|             answer = answer.replace(',', '.'); | ||||
|         } | ||||
| 
 | ||||
|         // We don't know if units should be before or after so we check both.
 | ||||
|         if (answer.match(new RegExp('^' + regexString)) === null || answer.match(new RegExp(regexString + '$')) === null) { | ||||
|             return false; | ||||
|         } | ||||
| 
 | ||||
|         return true; | ||||
|     } | ||||
| } | ||||
|  | ||||
| @ -13,113 +13,15 @@ | ||||
| // 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 { AddonQtypeShortAnswerComponent } from '@addon/qtype/shortanswer/component/shortanswer'; | ||||
| import { Injectable } from '@angular/core'; | ||||
| import { AddonQtypeCalculatedHandler } from '@addon/qtype/calculated/providers/handler'; | ||||
| 
 | ||||
| /** | ||||
|  * Handler to support numerical question type. | ||||
|  * This question type depends on calculated question type. | ||||
|  */ | ||||
| @Injectable() | ||||
| export class AddonQtypeNumericalHandler implements CoreQuestionHandler { | ||||
| export class AddonQtypeNumericalHandler extends AddonQtypeCalculatedHandler { | ||||
|     name = 'AddonQtypeNumerical'; | ||||
|     type = 'qtype_numerical'; | ||||
| 
 | ||||
|     constructor(private utils: CoreUtilsProvider) { } | ||||
| 
 | ||||
|     /** | ||||
|      * 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> { | ||||
|         // Numerical behaves like a short answer, use the same component.
 | ||||
|         return AddonQtypeShortAnswerComponent; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * 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 { | ||||
|         if (this.isGradableResponse(question, answers) === 0 || !this.validateUnits(answers['answer'])) { | ||||
|             return 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 { | ||||
|         return (answers['answer'] || answers['answer'] === '0' || answers['answer'] === 0) ? 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 { | ||||
|         return this.utils.sameAtKeyMissingIsBlank(prevAnswers, newAnswers, 'answer'); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Validate a number with units. We don't have the list of valid units and conversions, so we can't perform | ||||
|      * a full validation. If this function returns true it means we can't be sure it's valid. | ||||
|      * | ||||
|      * @param {string} answer Answer. | ||||
|      * @return {boolean} False if answer isn't valid, true if we aren't sure if it's valid. | ||||
|      */ | ||||
|     validateUnits(answer: string): boolean { | ||||
|         if (!answer) { | ||||
|             return false; | ||||
|         } | ||||
| 
 | ||||
|         const regexString = '[+-]?(?:\\d+(?:\\.\\d*)?|\\.\\d+)(?:e[-+]?\\d+)?'; | ||||
| 
 | ||||
|         // Strip spaces (which may be thousands separators) and change other forms of writing e to e.
 | ||||
|         answer = answer.replace(' ', ''); | ||||
|         answer = answer.replace(/(?:e|E|(?:x|\*|×)10(?:\^|\*\*))([+-]?\d+)/, 'e$1'); | ||||
| 
 | ||||
|         // If a '.' is present or there are multiple ',' (i.e. 2,456,789) assume ',' is a thousands separator and stip it.
 | ||||
|         // Else assume it is a decimal separator, and change it to '.'.
 | ||||
|         if (answer.indexOf('.') != -1 || answer.split(',').length - 1 > 1) { | ||||
|             answer = answer.replace(',', ''); | ||||
|         } else { | ||||
|             answer = answer.replace(',', '.'); | ||||
|         } | ||||
| 
 | ||||
|         // We don't know if units should be before or after so we check both.
 | ||||
|         if (answer.match(new RegExp('^' + regexString)) === null || answer.match(new RegExp(regexString + '$')) === null) { | ||||
|             return false; | ||||
|         } | ||||
| 
 | ||||
|         return true; | ||||
|     } | ||||
| } | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user