forked from EVOgeek/Vmeda.Online
		
	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 { CoreDomUtilsProvider } from '@providers/utils/dom';
 | 
				
			||||||
import { CoreUtilsProvider } from '@providers/utils/utils';
 | 
					import { CoreUtilsProvider } from '@providers/utils/utils';
 | 
				
			||||||
import { CoreQuestionHandler } from '@core/question/providers/delegate';
 | 
					import { CoreQuestionHandler } from '@core/question/providers/delegate';
 | 
				
			||||||
import { AddonQtypeNumericalHandler } from '@addon/qtype/numerical/providers/handler';
 | 
					 | 
				
			||||||
import { AddonQtypeCalculatedComponent } from '../component/calculated';
 | 
					import { AddonQtypeCalculatedComponent } from '../component/calculated';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
@ -28,8 +27,7 @@ export class AddonQtypeCalculatedHandler implements CoreQuestionHandler {
 | 
				
			|||||||
    name = 'AddonQtypeCalculated';
 | 
					    name = 'AddonQtypeCalculated';
 | 
				
			||||||
    type = 'qtype_calculated';
 | 
					    type = 'qtype_calculated';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    constructor(private utils: CoreUtilsProvider, private numericalHandler: AddonQtypeNumericalHandler,
 | 
					    constructor(private utils: CoreUtilsProvider, private domUtils: CoreDomUtilsProvider) { }
 | 
				
			||||||
            private domUtils: CoreDomUtilsProvider) { }
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * Return the Component to use to display the question.
 | 
					     * 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.
 | 
					     * @return {number} 1 if complete, 0 if not complete, -1 if cannot determine.
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    isCompleteResponse(question: any, answers: any): number {
 | 
					    isCompleteResponse(question: any, answers: any): number {
 | 
				
			||||||
        // This question type depends on numerical.
 | 
					        if (this.isGradableResponse(question, answers) === 0 || !this.validateUnits(answers['answer'])) {
 | 
				
			||||||
        if (this.isGradableResponse(question, answers) === 0 || !this.numericalHandler.validateUnits(answers['answer'])) {
 | 
					 | 
				
			||||||
            return 0;
 | 
					            return 0;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -81,7 +78,6 @@ export class AddonQtypeCalculatedHandler implements CoreQuestionHandler {
 | 
				
			|||||||
     * @return {number} 1 if gradable, 0 if not gradable, -1 if cannot determine.
 | 
					     * @return {number} 1 if gradable, 0 if not gradable, -1 if cannot determine.
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    isGradableResponse(question: any, answers: any): number {
 | 
					    isGradableResponse(question: any, answers: any): number {
 | 
				
			||||||
        // This question type depends on numerical.
 | 
					 | 
				
			||||||
        let isGradable = this.isValidValue(answers['answer']);
 | 
					        let isGradable = this.isValidValue(answers['answer']);
 | 
				
			||||||
        if (isGradable && this.requiresUnits(question)) {
 | 
					        if (isGradable && this.requiresUnits(question)) {
 | 
				
			||||||
            // The question requires a unit.
 | 
					            // The question requires a unit.
 | 
				
			||||||
@ -100,7 +96,6 @@ export class AddonQtypeCalculatedHandler implements CoreQuestionHandler {
 | 
				
			|||||||
     * @return {boolean} Whether they're the same.
 | 
					     * @return {boolean} Whether they're the same.
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    isSameResponse(question: any, prevAnswers: any, newAnswers: any): boolean {
 | 
					    isSameResponse(question: any, prevAnswers: any, newAnswers: any): boolean {
 | 
				
			||||||
        // This question type depends on numerical.
 | 
					 | 
				
			||||||
        return this.utils.sameAtKeyMissingIsBlank(prevAnswers, newAnswers, 'answer') &&
 | 
					        return this.utils.sameAtKeyMissingIsBlank(prevAnswers, newAnswers, 'answer') &&
 | 
				
			||||||
            this.utils.sameAtKeyMissingIsBlank(prevAnswers, newAnswers, 'unit');
 | 
					            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"]'));
 | 
					        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
 | 
					// See the License for the specific language governing permissions and
 | 
				
			||||||
// limitations under the License.
 | 
					// limitations under the License.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import { Injectable, Injector } from '@angular/core';
 | 
					import { Injectable } from '@angular/core';
 | 
				
			||||||
import { CoreUtilsProvider } from '@providers/utils/utils';
 | 
					import { AddonQtypeCalculatedHandler } from '@addon/qtype/calculated/providers/handler';
 | 
				
			||||||
import { CoreQuestionHandler } from '@core/question/providers/delegate';
 | 
					 | 
				
			||||||
import { AddonQtypeShortAnswerComponent } from '@addon/qtype/shortanswer/component/shortanswer';
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * Handler to support numerical question type.
 | 
					 * Handler to support numerical question type.
 | 
				
			||||||
 | 
					 * This question type depends on calculated question type.
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
@Injectable()
 | 
					@Injectable()
 | 
				
			||||||
export class AddonQtypeNumericalHandler implements CoreQuestionHandler {
 | 
					export class AddonQtypeNumericalHandler extends AddonQtypeCalculatedHandler {
 | 
				
			||||||
    name = 'AddonQtypeNumerical';
 | 
					    name = 'AddonQtypeNumerical';
 | 
				
			||||||
    type = 'qtype_numerical';
 | 
					    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