source: Dev/trunk/src/client/dijit/form/ValidationTextBox.js @ 532

Last change on this file since 532 was 483, checked in by hendrikvanantwerpen, 11 years ago

Added Dojo 1.9.3 release.

File size: 11.4 KB
Line 
1define([
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});
Note: See TracBrowser for help on using the repository browser.