]> git.wh0rd.org - 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 }