[483] | 1 | define([ |
---|
| 2 | "dojo/_base/declare", // declare |
---|
| 3 | "dojo/_base/kernel", // kernel.deprecated |
---|
| 4 | "dojo/i18n", // i18n.getLocalization |
---|
| 5 | "./TextBox", |
---|
| 6 | "../Tooltip", |
---|
| 7 | "dojo/text!./templates/ValidationTextBox.html", |
---|
| 8 | "dojo/i18n!./nls/validate" |
---|
| 9 | ], function(declare, kernel, i18n, TextBox, Tooltip, template){ |
---|
| 10 | |
---|
| 11 | // module: |
---|
| 12 | // dijit/form/ValidationTextBox |
---|
| 13 | |
---|
| 14 | |
---|
| 15 | /*===== |
---|
| 16 | var __Constraints = { |
---|
| 17 | // locale: String |
---|
| 18 | // locale used for validation, picks up value from this widget's lang attribute |
---|
| 19 | // _flags_: anything |
---|
| 20 | // various flags passed to pattern function |
---|
| 21 | }; |
---|
| 22 | =====*/ |
---|
| 23 | |
---|
| 24 | var ValidationTextBox; |
---|
| 25 | return ValidationTextBox = declare("dijit.form.ValidationTextBox", TextBox, { |
---|
| 26 | // summary: |
---|
| 27 | // Base class for textbox widgets with the ability to validate content of various types and provide user feedback. |
---|
| 28 | |
---|
| 29 | templateString: template, |
---|
| 30 | |
---|
| 31 | // required: Boolean |
---|
| 32 | // User is required to enter data into this field. |
---|
| 33 | required: false, |
---|
| 34 | |
---|
| 35 | // promptMessage: String |
---|
| 36 | // If defined, display this hint string immediately on focus to the textbox, if empty. |
---|
| 37 | // Also displays if the textbox value is Incomplete (not yet valid but will be with additional input). |
---|
| 38 | // Think of this like a tooltip that tells the user what to do, not an error message |
---|
| 39 | // that tells the user what they've done wrong. |
---|
| 40 | // |
---|
| 41 | // Message disappears when user starts typing. |
---|
| 42 | promptMessage: "", |
---|
| 43 | |
---|
| 44 | // invalidMessage: String |
---|
| 45 | // The message to display if value is invalid. |
---|
| 46 | // The translated string value is read from the message file by default. |
---|
| 47 | // Set to "" to use the promptMessage instead. |
---|
| 48 | invalidMessage: "$_unset_$", |
---|
| 49 | |
---|
| 50 | // missingMessage: String |
---|
| 51 | // The message to display if value is empty and the field is required. |
---|
| 52 | // The translated string value is read from the message file by default. |
---|
| 53 | // Set to "" to use the invalidMessage instead. |
---|
| 54 | missingMessage: "$_unset_$", |
---|
| 55 | |
---|
| 56 | // message: String |
---|
| 57 | // Currently error/prompt message. |
---|
| 58 | // When using the default tooltip implementation, this will only be |
---|
| 59 | // displayed when the field is focused. |
---|
| 60 | message: "", |
---|
| 61 | |
---|
| 62 | // constraints: __Constraints |
---|
| 63 | // user-defined object needed to pass parameters to the validator functions |
---|
| 64 | constraints: {}, |
---|
| 65 | |
---|
| 66 | // pattern: [extension protected] String|Function(constraints) returning a string. |
---|
| 67 | // This defines the regular expression used to validate the input. |
---|
| 68 | // Do not add leading ^ or $ characters since the widget adds these. |
---|
| 69 | // A function may be used to generate a valid pattern when dependent on constraints or other runtime factors. |
---|
| 70 | // set('pattern', String|Function). |
---|
| 71 | pattern: ".*", |
---|
| 72 | |
---|
| 73 | // regExp: Deprecated [extension protected] String. Use "pattern" instead. |
---|
| 74 | regExp: "", |
---|
| 75 | |
---|
| 76 | regExpGen: function(/*__Constraints*/ /*===== constraints =====*/){ |
---|
| 77 | // summary: |
---|
| 78 | // Deprecated. Use set('pattern', Function) instead. |
---|
| 79 | }, |
---|
| 80 | |
---|
| 81 | // state: [readonly] String |
---|
| 82 | // Shows current state (ie, validation result) of input (""=Normal, Incomplete, or Error) |
---|
| 83 | state: "", |
---|
| 84 | |
---|
| 85 | // tooltipPosition: String[] |
---|
| 86 | // See description of `dijit/Tooltip.defaultPosition` for details on this parameter. |
---|
| 87 | tooltipPosition: [], |
---|
| 88 | |
---|
| 89 | _deprecateRegExp: function(attr, value){ |
---|
| 90 | if(value != ValidationTextBox.prototype[attr]){ |
---|
| 91 | kernel.deprecated("ValidationTextBox id="+this.id+", set('" + attr + "', ...) is deprecated. Use set('pattern', ...) instead.", "", "2.0"); |
---|
| 92 | this.set('pattern', value); |
---|
| 93 | } |
---|
| 94 | }, |
---|
| 95 | _setRegExpGenAttr: function(/*Function*/ newFcn){ |
---|
| 96 | this._deprecateRegExp("regExpGen", newFcn); |
---|
| 97 | this._set("regExpGen", this._computeRegexp); // backward compat with this.regExpGen(this.constraints) |
---|
| 98 | }, |
---|
| 99 | _setRegExpAttr: function(/*String*/ value){ |
---|
| 100 | this._deprecateRegExp("regExp", value); |
---|
| 101 | }, |
---|
| 102 | |
---|
| 103 | _setValueAttr: function(){ |
---|
| 104 | // summary: |
---|
| 105 | // Hook so set('value', ...) works. |
---|
| 106 | this.inherited(arguments); |
---|
| 107 | this._refreshState(); |
---|
| 108 | }, |
---|
| 109 | |
---|
| 110 | validator: function(/*anything*/ value, /*__Constraints*/ constraints){ |
---|
| 111 | // summary: |
---|
| 112 | // Overridable function used to validate the text input against the regular expression. |
---|
| 113 | // tags: |
---|
| 114 | // protected |
---|
| 115 | return (new RegExp("^(?:" + this._computeRegexp(constraints) + ")"+(this.required?"":"?")+"$")).test(value) && |
---|
| 116 | (!this.required || !this._isEmpty(value)) && |
---|
| 117 | (this._isEmpty(value) || this.parse(value, constraints) !== undefined); // Boolean |
---|
| 118 | }, |
---|
| 119 | |
---|
| 120 | _isValidSubset: function(){ |
---|
| 121 | // summary: |
---|
| 122 | // Returns true if the value is either already valid or could be made valid by appending characters. |
---|
| 123 | // This is used for validation while the user [may be] still typing. |
---|
| 124 | return this.textbox.value.search(this._partialre) == 0; |
---|
| 125 | }, |
---|
| 126 | |
---|
| 127 | isValid: function(/*Boolean*/ /*===== isFocused =====*/){ |
---|
| 128 | // summary: |
---|
| 129 | // Tests if value is valid. |
---|
| 130 | // Can override with your own routine in a subclass. |
---|
| 131 | // tags: |
---|
| 132 | // protected |
---|
| 133 | return this.validator(this.textbox.value, this.get('constraints')); |
---|
| 134 | }, |
---|
| 135 | |
---|
| 136 | _isEmpty: function(value){ |
---|
| 137 | // summary: |
---|
| 138 | // Checks for whitespace |
---|
| 139 | return (this.trim ? /^\s*$/ : /^$/).test(value); // Boolean |
---|
| 140 | }, |
---|
| 141 | |
---|
| 142 | getErrorMessage: function(/*Boolean*/ /*===== isFocused =====*/){ |
---|
| 143 | // summary: |
---|
| 144 | // Return an error message to show if appropriate |
---|
| 145 | // tags: |
---|
| 146 | // protected |
---|
| 147 | var invalid = this.invalidMessage == "$_unset_$" ? this.messages.invalidMessage : |
---|
| 148 | !this.invalidMessage ? this.promptMessage : this.invalidMessage; |
---|
| 149 | var missing = this.missingMessage == "$_unset_$" ? this.messages.missingMessage : |
---|
| 150 | !this.missingMessage ? invalid : this.missingMessage; |
---|
| 151 | return (this.required && this._isEmpty(this.textbox.value)) ? missing : invalid; // String |
---|
| 152 | }, |
---|
| 153 | |
---|
| 154 | getPromptMessage: function(/*Boolean*/ /*===== isFocused =====*/){ |
---|
| 155 | // summary: |
---|
| 156 | // Return a hint message to show when widget is first focused |
---|
| 157 | // tags: |
---|
| 158 | // protected |
---|
| 159 | return this.promptMessage; // String |
---|
| 160 | }, |
---|
| 161 | |
---|
| 162 | _maskValidSubsetError: true, |
---|
| 163 | validate: function(/*Boolean*/ isFocused){ |
---|
| 164 | // summary: |
---|
| 165 | // Called by oninit, onblur, and onkeypress. |
---|
| 166 | // description: |
---|
| 167 | // Show missing or invalid messages if appropriate, and highlight textbox field. |
---|
| 168 | // tags: |
---|
| 169 | // protected |
---|
| 170 | var message = ""; |
---|
| 171 | var isValid = this.disabled || this.isValid(isFocused); |
---|
| 172 | if(isValid){ this._maskValidSubsetError = true; } |
---|
| 173 | var isEmpty = this._isEmpty(this.textbox.value); |
---|
| 174 | var isValidSubset = !isValid && isFocused && this._isValidSubset(); |
---|
| 175 | this._set("state", isValid ? "" : (((((!this._hasBeenBlurred || isFocused) && isEmpty) || isValidSubset) && (this._maskValidSubsetError || (isValidSubset && !this._hasBeenBlurred && isFocused))) ? "Incomplete" : "Error")); |
---|
| 176 | this.focusNode.setAttribute("aria-invalid", isValid ? "false" : "true"); |
---|
| 177 | |
---|
| 178 | if(this.state == "Error"){ |
---|
| 179 | this._maskValidSubsetError = isFocused && isValidSubset; // we want the error to show up after a blur and refocus |
---|
| 180 | message = this.getErrorMessage(isFocused); |
---|
| 181 | }else if(this.state == "Incomplete"){ |
---|
| 182 | message = this.getPromptMessage(isFocused); // show the prompt whenever the value is not yet complete |
---|
| 183 | this._maskValidSubsetError = !this._hasBeenBlurred || isFocused; // no Incomplete warnings while focused |
---|
| 184 | }else if(isEmpty){ |
---|
| 185 | message = this.getPromptMessage(isFocused); // show the prompt whenever there's no error and no text |
---|
| 186 | } |
---|
| 187 | this.set("message", message); |
---|
| 188 | |
---|
| 189 | return isValid; |
---|
| 190 | }, |
---|
| 191 | |
---|
| 192 | displayMessage: function(/*String*/ message){ |
---|
| 193 | // summary: |
---|
| 194 | // Overridable method to display validation errors/hints. |
---|
| 195 | // By default uses a tooltip. |
---|
| 196 | // tags: |
---|
| 197 | // extension |
---|
| 198 | if(message && this.focused){ |
---|
| 199 | Tooltip.show(message, this.domNode, this.tooltipPosition, !this.isLeftToRight()); |
---|
| 200 | }else{ |
---|
| 201 | Tooltip.hide(this.domNode); |
---|
| 202 | } |
---|
| 203 | }, |
---|
| 204 | |
---|
| 205 | _refreshState: function(){ |
---|
| 206 | // Overrides TextBox._refreshState() |
---|
| 207 | if(this._created){ // should instead be this._started but that would require all programmatic ValidationTextBox instantiations to call startup() |
---|
| 208 | this.validate(this.focused); |
---|
| 209 | } |
---|
| 210 | this.inherited(arguments); |
---|
| 211 | }, |
---|
| 212 | |
---|
| 213 | //////////// INITIALIZATION METHODS /////////////////////////////////////// |
---|
| 214 | |
---|
| 215 | constructor: function(params /*===== , srcNodeRef =====*/){ |
---|
| 216 | // summary: |
---|
| 217 | // Create the widget. |
---|
| 218 | // params: Object|null |
---|
| 219 | // Hash of initialization parameters for widget, including scalar values (like title, duration etc.) |
---|
| 220 | // and functions, typically callbacks like onClick. |
---|
| 221 | // The hash can contain any of the widget's properties, excluding read-only properties. |
---|
| 222 | // srcNodeRef: DOMNode|String? |
---|
| 223 | // If a srcNodeRef (DOM node) is specified, replace srcNodeRef with my generated DOM tree. |
---|
| 224 | |
---|
| 225 | this.constraints = {}; |
---|
| 226 | this.baseClass += ' dijitValidationTextBox'; |
---|
| 227 | }, |
---|
| 228 | |
---|
| 229 | startup: function(){ |
---|
| 230 | this.inherited(arguments); |
---|
| 231 | this._refreshState(); // after all _set* methods have run |
---|
| 232 | }, |
---|
| 233 | |
---|
| 234 | _setConstraintsAttr: function(/*__Constraints*/ constraints){ |
---|
| 235 | if(!constraints.locale && this.lang){ |
---|
| 236 | constraints.locale = this.lang; |
---|
| 237 | } |
---|
| 238 | this._set("constraints", constraints); |
---|
| 239 | this._refreshState(); |
---|
| 240 | }, |
---|
| 241 | |
---|
| 242 | _setPatternAttr: function(/*String|Function*/ pattern){ |
---|
| 243 | this._set("pattern", pattern); // don't set on INPUT to avoid native HTML5 validation |
---|
| 244 | this._refreshState(); |
---|
| 245 | }, |
---|
| 246 | |
---|
| 247 | _computeRegexp: function(/*__Constraints*/ constraints){ |
---|
| 248 | // summary: |
---|
| 249 | // Hook to get the current regExp and to compute the partial validation RE. |
---|
| 250 | |
---|
| 251 | var p = this.pattern; |
---|
| 252 | if(typeof p == "function"){ |
---|
| 253 | p = p.call(this, constraints); |
---|
| 254 | } |
---|
| 255 | if(p != this._lastRegExp){ |
---|
| 256 | var partialre = ""; |
---|
| 257 | this._lastRegExp = p; |
---|
| 258 | // parse the regexp and produce a new regexp that matches valid subsets |
---|
| 259 | // if the regexp is .* then there's no use in matching subsets since everything is valid |
---|
| 260 | if(p != ".*"){ |
---|
| 261 | p.replace(/\\.|\[\]|\[.*?[^\\]{1}\]|\{.*?\}|\(\?[=:!]|./g, |
---|
| 262 | function(re){ |
---|
| 263 | switch(re.charAt(0)){ |
---|
| 264 | case '{': |
---|
| 265 | case '+': |
---|
| 266 | case '?': |
---|
| 267 | case '*': |
---|
| 268 | case '^': |
---|
| 269 | case '$': |
---|
| 270 | case '|': |
---|
| 271 | case '(': |
---|
| 272 | partialre += re; |
---|
| 273 | break; |
---|
| 274 | case ")": |
---|
| 275 | partialre += "|$)"; |
---|
| 276 | break; |
---|
| 277 | default: |
---|
| 278 | partialre += "(?:"+re+"|$)"; |
---|
| 279 | break; |
---|
| 280 | } |
---|
| 281 | }); |
---|
| 282 | } |
---|
| 283 | try{ // this is needed for now since the above regexp parsing needs more test verification |
---|
| 284 | "".search(partialre); |
---|
| 285 | }catch(e){ // should never be here unless the original RE is bad or the parsing is bad |
---|
| 286 | partialre = this.pattern; |
---|
| 287 | console.warn('RegExp error in ' + this.declaredClass + ': ' + this.pattern); |
---|
| 288 | } // should never be here unless the original RE is bad or the parsing is bad |
---|
| 289 | this._partialre = "^(?:" + partialre + ")$"; |
---|
| 290 | } |
---|
| 291 | return p; |
---|
| 292 | }, |
---|
| 293 | |
---|
| 294 | postMixInProperties: function(){ |
---|
| 295 | this.inherited(arguments); |
---|
| 296 | this.messages = i18n.getLocalization("dijit.form", "validate", this.lang); |
---|
| 297 | this._setConstraintsAttr(this.constraints); // this needs to happen now (and later) due to codependency on _set*Attr calls attachPoints |
---|
| 298 | }, |
---|
| 299 | |
---|
| 300 | _setDisabledAttr: function(/*Boolean*/ value){ |
---|
| 301 | this.inherited(arguments); // call FormValueWidget._setDisabledAttr() |
---|
| 302 | this._refreshState(); |
---|
| 303 | }, |
---|
| 304 | |
---|
| 305 | _setRequiredAttr: function(/*Boolean*/ value){ |
---|
| 306 | this._set("required", value); |
---|
| 307 | this.focusNode.setAttribute("aria-required", value); |
---|
| 308 | this._refreshState(); |
---|
| 309 | }, |
---|
| 310 | |
---|
| 311 | _setMessageAttr: function(/*String*/ message){ |
---|
| 312 | this._set("message", message); |
---|
| 313 | this.displayMessage(message); |
---|
| 314 | }, |
---|
| 315 | |
---|
| 316 | reset:function(){ |
---|
| 317 | // Overrides dijit/form/TextBox.reset() by also |
---|
| 318 | // hiding errors about partial matches |
---|
| 319 | this._maskValidSubsetError = true; |
---|
| 320 | this.inherited(arguments); |
---|
| 321 | }, |
---|
| 322 | |
---|
| 323 | _onBlur: function(){ |
---|
| 324 | // the message still exists but for back-compat, and to erase the tooltip |
---|
| 325 | // (if the message is being displayed as a tooltip), call displayMessage('') |
---|
| 326 | this.displayMessage(''); |
---|
| 327 | |
---|
| 328 | this.inherited(arguments); |
---|
| 329 | } |
---|
| 330 | }); |
---|
| 331 | }); |
---|