ValidationTextBox.js 17 KB

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