@@ -24,6 +24,14 @@ import { AddonQtypeCalculatedComponent } from '../component/calculated';
2424 */
2525@Injectable ( )
2626export class AddonQtypeCalculatedHandler implements CoreQuestionHandler {
27+ static UNITINPUT = '0' ;
28+ static UNITRADIO = '1' ;
29+ static UNITSELECT = '2' ;
30+ static UNITNONE = '3' ;
31+
32+ static UNITGRADED = '1' ;
33+ static UNITOPTIONAL = '0' ;
34+
2735 name = 'AddonQtypeCalculated' ;
2836 type = 'qtype_calculated' ;
2937
@@ -41,6 +49,23 @@ export class AddonQtypeCalculatedHandler implements CoreQuestionHandler {
4149 return AddonQtypeCalculatedComponent ;
4250 }
4351
52+ /**
53+ * Check if the units are in a separate field for the question.
54+ *
55+ * @param question Question.
56+ * @return Whether units are in a separate field.
57+ */
58+ hasSeparateUnitField ( question : any ) : boolean {
59+ if ( ! question . displayoptions ) {
60+ const element = this . domUtils . convertToElement ( question . html ) ;
61+
62+ return ! ! ( element . querySelector ( 'select[name*=unit]' ) || element . querySelector ( 'input[type="radio"]' ) ) ;
63+ }
64+
65+ return question . displayoptions . showunits === AddonQtypeCalculatedHandler . UNITRADIO ||
66+ question . displayoptions . showunits === AddonQtypeCalculatedHandler . UNITSELECT ;
67+ }
68+
4469 /**
4570 * Check if a response is complete.
4671 *
@@ -51,15 +76,42 @@ export class AddonQtypeCalculatedHandler implements CoreQuestionHandler {
5176 * @return 1 if complete, 0 if not complete, -1 if cannot determine.
5277 */
5378 isCompleteResponse ( question : any , answers : any , component : string , componentId : string | number ) : number {
54- if ( this . isGradableResponse ( question , answers ) === 0 || ! this . validateUnits ( answers [ 'answer' ] ) ) {
79+ if ( ! this . isGradableResponse ( question , answers ) ) {
5580 return 0 ;
5681 }
5782
58- if ( this . requiresUnits ( question ) ) {
59- return this . isValidValue ( answers [ 'unit' ] ) ? 1 : 0 ;
83+ const parsedAnswer = this . parseAnswer ( question , answers [ 'answer' ] ) ;
84+ if ( parsedAnswer . answer === null ) {
85+ return 0 ;
86+ }
87+
88+ if ( ! question . displayoptions ) {
89+ if ( this . hasSeparateUnitField ( question ) ) {
90+ return this . isValidValue ( answers [ 'unit' ] ) ? 1 : 0 ;
91+ }
92+
93+ // We cannot know if the answer should contain units or not.
94+ return - 1 ;
95+ }
96+
97+ if ( question . displayoptions . showunits != AddonQtypeCalculatedHandler . UNITINPUT && parsedAnswer . unit ) {
98+ // There should be no units or be outside of the input, not valid.
99+ return 0 ;
100+ }
101+
102+ if ( this . hasSeparateUnitField ( question ) && ! this . isValidValue ( answers [ 'unit' ] ) ) {
103+ // Unit not supplied as a separate field and it's required.
104+ return 0 ;
60105 }
61106
62- return - 1 ;
107+ if ( question . displayoptions . showunits == AddonQtypeCalculatedHandler . UNITINPUT &&
108+ question . displayoptions . unitgradingtype == AddonQtypeCalculatedHandler . UNITGRADED &&
109+ ! this . isValidValue ( parsedAnswer . unit ) ) {
110+ // Unit not supplied inside the input and it's required.
111+ return 0 ;
112+ }
113+
114+ return 1 ;
63115 }
64116
65117 /**
@@ -80,13 +132,7 @@ export class AddonQtypeCalculatedHandler implements CoreQuestionHandler {
80132 * @return 1 if gradable, 0 if not gradable, -1 if cannot determine.
81133 */
82134 isGradableResponse ( question : any , answers : any ) : number {
83- let isGradable = this . isValidValue ( answers [ 'answer' ] ) ;
84- if ( isGradable && this . requiresUnits ( question ) ) {
85- // The question requires a unit.
86- isGradable = this . isValidValue ( answers [ 'unit' ] ) ;
87- }
88-
89- return isGradable ? 1 : 0 ;
135+ return this . isValidValue ( answers [ 'answer' ] ) ? 1 : 0 ;
90136 }
91137
92138 /**
@@ -115,48 +161,56 @@ export class AddonQtypeCalculatedHandler implements CoreQuestionHandler {
115161 }
116162
117163 /**
118- * Check if a question requires units in a separate input.
119- *
120- * @param question The question.
121- * @return Whether the question requires units.
122- */
123- requiresUnits ( question : any ) : boolean {
124- const element = this . domUtils . convertToElement ( question . html ) ;
125-
126- return ! ! ( element . querySelector ( 'select[name*=unit]' ) || element . querySelector ( 'input[type="radio"]' ) ) ;
127- }
128-
129- /**
130- * Validate a number with units. We don't have the list of valid units and conversions, so we can't perform
131- * a full validation. If this function returns true it means we can't be sure it's valid.
164+ * Parse an answer string.
132165 *
166+ * @param question Question.
133167 * @param answer Answer.
134- * @return False if answer isn't valid, true if we aren't sure if it's valid.
168+ * @return 0 if answer isn't valid, 1 if answer is valid, -1 if we aren't sure if it's valid.
135169 */
136- validateUnits ( answer : string ) : boolean {
170+ parseAnswer ( question : any , answer : string ) : { answer : number , unit : string } {
137171 if ( ! answer ) {
138- return false ;
172+ return { answer : null , unit : null } ;
139173 }
140174
141- const regexString = '[+-]?(?:\\d+(?:\\.\\d*)?|\\.\\d+)(?:e[-+]?\\d+)?' ;
175+ let regexString = '[+-]?(?:\\d+(?:\\.\\d*)?|\\.\\d+)(?:e[-+]?\\d+)?' ;
142176
143177 // Strip spaces (which may be thousands separators) and change other forms of writing e to e.
144- answer = answer . replace ( ' ' , '' ) ;
178+ answer = answer . replace ( / / g , '' ) ;
145179 answer = answer . replace ( / (?: e | E | (?: x | \* | × ) 1 0 (?: \^ | \* \* ) ) ( [ + - ] ? \d + ) / , 'e$1' ) ;
146180
147- // If a '.' is present or there are multiple ',' (i.e. 2,456,789) assume ',' is a thousands separator and stip it.
181+ // If a '.' is present or there are multiple ',' (i.e. 2,456,789) assume ',' is a thousands separator and strip it.
148182 // Else assume it is a decimal separator, and change it to '.'.
149183 if ( answer . indexOf ( '.' ) != - 1 || answer . split ( ',' ) . length - 1 > 1 ) {
150184 answer = answer . replace ( ',' , '' ) ;
151185 } else {
152186 answer = answer . replace ( ',' , '.' ) ;
153187 }
154188
155- // We don't know if units should be before or after so we check both.
156- if ( answer . match ( new RegExp ( '^' + regexString ) ) === null || answer . match ( new RegExp ( regexString + '$' ) ) === null ) {
157- return false ;
189+ let unitsLeft = false ;
190+ let match = null ;
191+
192+ if ( ! question . displayoptions ) {
193+ // We don't know if units should be before or after so we check both.
194+ match = answer . match ( new RegExp ( '^' + regexString ) ) ;
195+ if ( ! match ) {
196+ unitsLeft = true ;
197+ match = answer . match ( new RegExp ( regexString + '$' ) ) ;
198+ }
199+ } else {
200+ unitsLeft = question . displayoptions . unitsleft == '1' ;
201+ regexString = unitsLeft ? regexString + '$' : '^' + regexString ;
202+
203+ match = answer . match ( new RegExp ( regexString ) ) ;
204+ }
205+
206+ if ( ! match ) {
207+ return { answer : null , unit : null } ;
158208 }
159209
160- return true ;
210+ const numberString = match [ 0 ] ;
211+ const unit = unitsLeft ? answer . substr ( 0 , answer . length - match [ 0 ] . length ) : answer . substr ( match [ 0 ] . length ) ;
212+
213+ // No need to calculate the multiplier.
214+ return { answer : Number ( numberString ) , unit : unit } ;
161215 }
162216}
0 commit comments