]> git.wh0rd.org Git - tt-rss.git/blob - lib/dijit/form/ValidationTextBox.js
upgrade Dojo to 1.6.1
[tt-rss.git] / lib / dijit / form / ValidationTextBox.js
1 /*
2         Copyright (c) 2004-2011, The Dojo Foundation All Rights Reserved.
3         Available via Academic Free License >= 2.1 OR the modified BSD license.
4         see: http://dojotoolkit.org/license for details
5 */
6
7
8 if(!dojo._hasResource["dijit.form.ValidationTextBox"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
9 dojo._hasResource["dijit.form.ValidationTextBox"] = true;
10 dojo.provide("dijit.form.ValidationTextBox");
11 dojo.require("dojo.i18n");
12 dojo.require("dijit.form.TextBox");
13 dojo.require("dijit.Tooltip");
14 dojo.requireLocalization("dijit.form", "validate", null, "ROOT,ar,ca,cs,da,de,el,es,fi,fr,he,hu,it,ja,kk,ko,nb,nl,pl,pt,pt-pt,ro,ru,sk,sl,sv,th,tr,zh,zh-tw");
15
16
17 /*=====
18         dijit.form.ValidationTextBox.__Constraints = function(){
19                 // locale: String
20                 //              locale used for validation, picks up value from this widget's lang attribute
21                 // _flags_: anything
22                 //              various flags passed to regExpGen function
23                 this.locale = "";
24                 this._flags_ = "";
25         }
26 =====*/
27
28 dojo.declare(
29         "dijit.form.ValidationTextBox",
30         dijit.form.TextBox,
31         {
32                 // summary:
33                 //              Base class for textbox widgets with the ability to validate content of various types and provide user feedback.
34                 // tags:
35                 //              protected
36
37                 templateString: dojo.cache("dijit.form", "templates/ValidationTextBox.html", "<div class=\"dijit dijitReset dijitInlineTable dijitLeft\"\n\tid=\"widget_${id}\" role=\"presentation\"\n\t><div class='dijitReset dijitValidationContainer'\n\t\t><input class=\"dijitReset dijitInputField dijitValidationIcon dijitValidationInner\" value=\"&#935; \" type=\"text\" tabIndex=\"-1\" readonly=\"readonly\" role=\"presentation\"\n\t/></div\n\t><div class=\"dijitReset dijitInputField dijitInputContainer\"\n\t\t><input class=\"dijitReset dijitInputInner\" dojoAttachPoint='textbox,focusNode' autocomplete=\"off\"\n\t\t\t${!nameAttrSetting} type='${type}'\n\t/></div\n></div>\n"),
38                 baseClass: "dijitTextBox dijitValidationTextBox",
39
40                 // required: Boolean
41                 //              User is required to enter data into this field.
42                 required: false,
43
44                 // promptMessage: String
45                 //              If defined, display this hint string immediately on focus to the textbox, if empty.
46                 //              Also displays if the textbox value is Incomplete (not yet valid but will be with additional input).
47                 //              Think of this like a tooltip that tells the user what to do, not an error message
48                 //              that tells the user what they've done wrong.
49                 //
50                 //              Message disappears when user starts typing.
51                 promptMessage: "",
52
53                 // invalidMessage: String
54                 //              The message to display if value is invalid.
55                 //              The translated string value is read from the message file by default.
56                 //              Set to "" to use the promptMessage instead.
57                 invalidMessage: "$_unset_$",
58
59                 // missingMessage: String
60                 //              The message to display if value is empty and the field is required.
61                 //              The translated string value is read from the message file by default.
62                 //              Set to "" to use the invalidMessage instead.
63                 missingMessage: "$_unset_$",
64
65                 // message: String
66                 //              Currently error/prompt message.
67                 //              When using the default tooltip implementation, this will only be
68                 //              displayed when the field is focused.
69                 message: "",
70
71                 // constraints: dijit.form.ValidationTextBox.__Constraints
72                 //              user-defined object needed to pass parameters to the validator functions
73                 constraints: {},
74
75                 // regExp: [extension protected] String
76                 //              regular expression string used to validate the input
77                 //              Do not specify both regExp and regExpGen
78                 regExp: ".*",
79
80                 regExpGen: function(/*dijit.form.ValidationTextBox.__Constraints*/ constraints){
81                         // summary:
82                         //              Overridable function used to generate regExp when dependent on constraints.
83                         //              Do not specify both regExp and regExpGen.
84                         // tags:
85                         //              extension protected
86                         return this.regExp; // String
87                 },
88
89                 // state: [readonly] String
90                 //              Shows current state (ie, validation result) of input (""=Normal, Incomplete, or Error)
91                 state: "",
92
93                 // tooltipPosition: String[]
94                 //              See description of `dijit.Tooltip.defaultPosition` for details on this parameter.
95                 tooltipPosition: [],
96
97                 _setValueAttr: function(){
98                         // summary:
99                         //              Hook so set('value', ...) works.
100                         this.inherited(arguments);
101                         this.validate(this._focused);
102                 },
103
104                 validator: function(/*anything*/ value, /*dijit.form.ValidationTextBox.__Constraints*/ constraints){
105                         // summary:
106                         //              Overridable function used to validate the text input against the regular expression.
107                         // tags:
108                         //              protected
109                         return (new RegExp("^(?:" + this.regExpGen(constraints) + ")"+(this.required?"":"?")+"$")).test(value) &&
110                                 (!this.required || !this._isEmpty(value)) &&
111                                 (this._isEmpty(value) || this.parse(value, constraints) !== undefined); // Boolean
112                 },
113
114                 _isValidSubset: function(){
115                         // summary:
116                         //              Returns true if the value is either already valid or could be made valid by appending characters.
117                         //              This is used for validation while the user [may be] still typing.
118                         return this.textbox.value.search(this._partialre) == 0;
119                 },
120
121                 isValid: function(/*Boolean*/ isFocused){
122                         // summary:
123                         //              Tests if value is valid.
124                         //              Can override with your own routine in a subclass.
125                         // tags:
126                         //              protected
127                         return this.validator(this.textbox.value, this.constraints);
128                 },
129
130                 _isEmpty: function(value){
131                         // summary:
132                         //              Checks for whitespace
133                         return (this.trim ? /^\s*$/ : /^$/).test(value); // Boolean
134                 },
135
136                 getErrorMessage: function(/*Boolean*/ isFocused){
137                         // summary:
138                         //              Return an error message to show if appropriate
139                         // tags:
140                         //              protected
141                         return (this.required && this._isEmpty(this.textbox.value)) ? this.missingMessage : this.invalidMessage; // String
142                 },
143
144                 getPromptMessage: function(/*Boolean*/ isFocused){
145                         // summary:
146                         //              Return a hint message to show when widget is first focused
147                         // tags:
148                         //              protected
149                         return this.promptMessage; // String
150                 },
151
152                 _maskValidSubsetError: true,
153                 validate: function(/*Boolean*/ isFocused){
154                         // summary:
155                         //              Called by oninit, onblur, and onkeypress.
156                         // description:
157                         //              Show missing or invalid messages if appropriate, and highlight textbox field.
158                         // tags:
159                         //              protected
160                         var message = "";
161                         var isValid = this.disabled || this.isValid(isFocused);
162                         if(isValid){ this._maskValidSubsetError = true; }
163                         var isEmpty = this._isEmpty(this.textbox.value);
164                         var isValidSubset = !isValid && isFocused && this._isValidSubset();
165                         this._set("state", isValid ? "" : (((((!this._hasBeenBlurred || isFocused) && isEmpty) || isValidSubset) && this._maskValidSubsetError) ? "Incomplete" : "Error"));
166                         dijit.setWaiState(this.focusNode, "invalid", isValid ? "false" : "true");
167
168                         if(this.state == "Error"){
169                                 this._maskValidSubsetError = isFocused && isValidSubset; // we want the error to show up after a blur and refocus
170                                 message = this.getErrorMessage(isFocused);
171                         }else if(this.state == "Incomplete"){
172                                 message = this.getPromptMessage(isFocused); // show the prompt whenever the value is not yet complete
173                                 this._maskValidSubsetError = !this._hasBeenBlurred || isFocused; // no Incomplete warnings while focused
174                         }else if(isEmpty){
175                                 message = this.getPromptMessage(isFocused); // show the prompt whenever there's no error and no text
176                         }
177                         this.set("message", message);
178
179                         return isValid;
180                 },
181
182                 displayMessage: function(/*String*/ message){
183                         // summary:
184                         //              Overridable method to display validation errors/hints.
185                         //              By default uses a tooltip.
186                         // tags:
187                         //              extension
188                         dijit.hideTooltip(this.domNode);
189                         if(message && this._focused){
190                                 dijit.showTooltip(message, this.domNode, this.tooltipPosition, !this.isLeftToRight());
191                         }
192                 },
193
194                 _refreshState: function(){
195                         // Overrides TextBox._refreshState()
196                         this.validate(this._focused);
197                         this.inherited(arguments);
198                 },
199
200                 //////////// INITIALIZATION METHODS ///////////////////////////////////////
201
202                 constructor: function(){
203                         this.constraints = {};
204                 },
205
206                 _setConstraintsAttr: function(/*Object*/ constraints){
207                         if(!constraints.locale && this.lang){
208                                 constraints.locale = this.lang;
209                         }
210                         this._set("constraints", constraints);
211                         this._computePartialRE();
212                 },
213
214                 _computePartialRE: function(){
215                         var p = this.regExpGen(this.constraints);
216                         this.regExp = p;
217                         var partialre = "";
218                         // parse the regexp and produce a new regexp that matches valid subsets
219                         // if the regexp is .* then there's no use in matching subsets since everything is valid
220                         if(p != ".*"){ this.regExp.replace(/\\.|\[\]|\[.*?[^\\]{1}\]|\{.*?\}|\(\?[=:!]|./g,
221                                 function (re){
222                                         switch(re.charAt(0)){
223                                                 case '{':
224                                                 case '+':
225                                                 case '?':
226                                                 case '*':
227                                                 case '^':
228                                                 case '$':
229                                                 case '|':
230                                                 case '(':
231                                                         partialre += re;
232                                                         break;
233                                                 case ")":
234                                                         partialre += "|$)";
235                                                         break;
236                                                  default:
237                                                         partialre += "(?:"+re+"|$)";
238                                                         break;
239                                         }
240                                 }
241                         );}
242                         try{ // this is needed for now since the above regexp parsing needs more test verification
243                                 "".search(partialre);
244                         }catch(e){ // should never be here unless the original RE is bad or the parsing is bad
245                                 partialre = this.regExp;
246                                 console.warn('RegExp error in ' + this.declaredClass + ': ' + this.regExp);
247                         } // should never be here unless the original RE is bad or the parsing is bad
248                         this._partialre = "^(?:" + partialre + ")$";
249                 },
250
251                 postMixInProperties: function(){
252                         this.inherited(arguments);
253                         this.messages = dojo.i18n.getLocalization("dijit.form", "validate", this.lang);
254                         if(this.invalidMessage == "$_unset_$"){ this.invalidMessage = this.messages.invalidMessage; }
255                         if(!this.invalidMessage){ this.invalidMessage = this.promptMessage; }
256                         if(this.missingMessage == "$_unset_$"){ this.missingMessage = this.messages.missingMessage; }
257                         if(!this.missingMessage){ this.missingMessage = this.invalidMessage; }
258                         this._setConstraintsAttr(this.constraints); // this needs to happen now (and later) due to codependency on _set*Attr calls attachPoints
259                 },
260
261                 _setDisabledAttr: function(/*Boolean*/ value){
262                         this.inherited(arguments);      // call FormValueWidget._setDisabledAttr()
263                         this._refreshState();
264                 },
265
266                 _setRequiredAttr: function(/*Boolean*/ value){
267                         this._set("required", value);
268                         dijit.setWaiState(this.focusNode, "required", value);
269                         this._refreshState();
270                 },
271
272                 _setMessageAttr: function(/*String*/ message){
273                         this._set("message", message);
274                         this.displayMessage(message);
275                 },
276
277                 reset:function(){
278                         // Overrides dijit.form.TextBox.reset() by also
279                         // hiding errors about partial matches
280                         this._maskValidSubsetError = true;
281                         this.inherited(arguments);
282                 },
283
284                 _onBlur: function(){
285                         // the message still exists but for back-compat, and to erase the tooltip
286                         // (if the message is being displayed as a tooltip), call displayMessage('')
287                         this.displayMessage('');
288
289                         this.inherited(arguments);
290                 }
291         }
292 );
293
294 dojo.declare(
295         "dijit.form.MappedTextBox",
296         dijit.form.ValidationTextBox,
297         {
298                 // summary:
299                 //              A dijit.form.ValidationTextBox subclass which provides a base class for widgets that have
300                 //              a visible formatted display value, and a serializable
301                 //              value in a hidden input field which is actually sent to the server.
302                 // description:
303                 //              The visible display may
304                 //              be locale-dependent and interactive.  The value sent to the server is stored in a hidden
305                 //              input field which uses the `name` attribute declared by the original widget.  That value sent
306                 //              to the server is defined by the dijit.form.MappedTextBox.serialize method and is typically
307                 //              locale-neutral.
308                 // tags:
309                 //              protected
310
311                 postMixInProperties: function(){
312                         this.inherited(arguments);
313
314                         // we want the name attribute to go to the hidden <input>, not the displayed <input>,
315                         // so override _FormWidget.postMixInProperties() setting of nameAttrSetting
316                         this.nameAttrSetting = "";
317                 },
318
319                 serialize: function(/*anything*/ val, /*Object?*/ options){
320                         // summary:
321                         //              Overridable function used to convert the get('value') result to a canonical
322                         //              (non-localized) string.  For example, will print dates in ISO format, and
323                         //              numbers the same way as they are represented in javascript.
324                         // tags:
325                         //              protected extension
326                         return val.toString ? val.toString() : ""; // String
327                 },
328
329                 toString: function(){
330                         // summary:
331                         //              Returns widget as a printable string using the widget's value
332                         // tags:
333                         //              protected
334                         var val = this.filter(this.get('value')); // call filter in case value is nonstring and filter has been customized
335                         return val != null ? (typeof val == "string" ? val : this.serialize(val, this.constraints)) : ""; // String
336                 },
337
338                 validate: function(){
339                         // Overrides `dijit.form.TextBox.validate`
340                         this.valueNode.value = this.toString();
341                         return this.inherited(arguments);
342                 },
343
344                 buildRendering: function(){
345                         // Overrides `dijit._Templated.buildRendering`
346
347                         this.inherited(arguments);
348
349                         // Create a hidden <input> node with the serialized value used for submit
350                         // (as opposed to the displayed value).
351                         // Passing in name as markup rather than calling dojo.create() with an attrs argument
352                         // to make dojo.query(input[name=...]) work on IE. (see #8660)
353                         this.valueNode = dojo.place("<input type='hidden'" + (this.name ? " name='" + this.name.replace(/'/g, "&quot;") + "'" : "") + "/>", this.textbox, "after");
354                 },
355
356                 reset: function(){
357                         // Overrides `dijit.form.ValidationTextBox.reset` to
358                         // reset the hidden textbox value to ''
359                         this.valueNode.value = '';
360                         this.inherited(arguments);
361                 }
362         }
363 );
364
365 /*=====
366         dijit.form.RangeBoundTextBox.__Constraints = function(){
367                 // min: Number
368                 //              Minimum signed value.  Default is -Infinity
369                 // max: Number
370                 //              Maximum signed value.  Default is +Infinity
371                 this.min = min;
372                 this.max = max;
373         }
374 =====*/
375
376 dojo.declare(
377         "dijit.form.RangeBoundTextBox",
378         dijit.form.MappedTextBox,
379         {
380                 // summary:
381                 //              Base class for textbox form widgets which defines a range of valid values.
382
383                 // rangeMessage: String
384                 //              The message to display if value is out-of-range
385                 rangeMessage: "",
386
387                 /*=====
388                 // constraints: dijit.form.RangeBoundTextBox.__Constraints
389                 constraints: {},
390                 ======*/
391
392                 rangeCheck: function(/*Number*/ primitive, /*dijit.form.RangeBoundTextBox.__Constraints*/ constraints){
393                         // summary:
394                         //              Overridable function used to validate the range of the numeric input value.
395                         // tags:
396                         //              protected
397                         return  ("min" in constraints? (this.compare(primitive,constraints.min) >= 0) : true) &&
398                                 ("max" in constraints? (this.compare(primitive,constraints.max) <= 0) : true); // Boolean
399                 },
400
401                 isInRange: function(/*Boolean*/ isFocused){
402                         // summary:
403                         //              Tests if the value is in the min/max range specified in constraints
404                         // tags:
405                         //              protected
406                         return this.rangeCheck(this.get('value'), this.constraints);
407                 },
408
409                 _isDefinitelyOutOfRange: function(){
410                         // summary:
411                         //              Returns true if the value is out of range and will remain
412                         //              out of range even if the user types more characters
413                         var val = this.get('value');
414                         var isTooLittle = false;
415                         var isTooMuch = false;
416                         if("min" in this.constraints){
417                                 var min = this.constraints.min;
418                                 min = this.compare(val, ((typeof min == "number") && min >= 0 && val !=0) ? 0 : min);
419                                 isTooLittle = (typeof min == "number") && min < 0;
420                         }
421                         if("max" in this.constraints){
422                                 var max = this.constraints.max;
423                                 max = this.compare(val, ((typeof max != "number") || max > 0) ? max : 0);
424                                 isTooMuch = (typeof max == "number") && max > 0;
425                         }
426                         return isTooLittle || isTooMuch;
427                 },
428
429                 _isValidSubset: function(){
430                         // summary:
431                         //              Overrides `dijit.form.ValidationTextBox._isValidSubset`.
432                         //              Returns true if the input is syntactically valid, and either within
433                         //              range or could be made in range by more typing.
434                         return this.inherited(arguments) && !this._isDefinitelyOutOfRange();
435                 },
436
437                 isValid: function(/*Boolean*/ isFocused){
438                         // Overrides dijit.form.ValidationTextBox.isValid to check that the value is also in range.
439                         return this.inherited(arguments) &&
440                                 ((this._isEmpty(this.textbox.value) && !this.required) || this.isInRange(isFocused)); // Boolean
441                 },
442
443                 getErrorMessage: function(/*Boolean*/ isFocused){
444                         // Overrides dijit.form.ValidationTextBox.getErrorMessage to print "out of range" message if appropriate
445                         var v = this.get('value');
446                         if(v !== null && v !== '' && v !== undefined && (typeof v != "number" || !isNaN(v)) && !this.isInRange(isFocused)){ // don't check isInRange w/o a real value
447                                 return this.rangeMessage; // String
448                         }
449                         return this.inherited(arguments);
450                 },
451
452                 postMixInProperties: function(){
453                         this.inherited(arguments);
454                         if(!this.rangeMessage){
455                                 this.messages = dojo.i18n.getLocalization("dijit.form", "validate", this.lang);
456                                 this.rangeMessage = this.messages.rangeMessage;
457                         }
458                 },
459
460                 _setConstraintsAttr: function(/*Object*/ constraints){
461                         this.inherited(arguments);
462                         if(this.focusNode){ // not set when called from postMixInProperties
463                                 if(this.constraints.min !== undefined){
464                                         dijit.setWaiState(this.focusNode, "valuemin", this.constraints.min);
465                                 }else{
466                                         dijit.removeWaiState(this.focusNode, "valuemin");
467                                 }
468                                 if(this.constraints.max !== undefined){
469                                         dijit.setWaiState(this.focusNode, "valuemax", this.constraints.max);
470                                 }else{
471                                         dijit.removeWaiState(this.focusNode, "valuemax");
472                                 }
473                         }
474                 },
475
476                 _setValueAttr: function(/*Number*/ value, /*Boolean?*/ priorityChange){
477                         // summary:
478                         //              Hook so set('value', ...) works.
479
480                         dijit.setWaiState(this.focusNode, "valuenow", value);
481                         this.inherited(arguments);
482                 }
483         }
484 );
485
486 }