ValidationTextBox.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296
  1. require({cache:{
  2. 'url:dijit/form/templates/ValidationTextBox.html':"<div class=\"dijit dijitReset dijitInline 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\" data-dojo-attach-point='textbox,focusNode' autocomplete=\"off\"\n\t\t\t${!nameAttrSetting} type='${type}'\n\t/></div\n></div>\n"}});
  3. define("dijit/form/ValidationTextBox", [
  4. "dojo/_base/declare", // declare
  5. "dojo/i18n", // i18n.getLocalization
  6. "./TextBox",
  7. "../Tooltip",
  8. "dojo/text!./templates/ValidationTextBox.html",
  9. "dojo/i18n!./nls/validate"
  10. ], function(declare, i18n, TextBox, Tooltip, template){
  11. /*=====
  12. var Tooltip = dijit.Tooltip;
  13. var TextBox = dijit.form.TextBox;
  14. =====*/
  15. // module:
  16. // dijit/form/ValidationTextBox
  17. // summary:
  18. // Base class for textbox widgets with the ability to validate content of various types and provide user feedback.
  19. /*=====
  20. dijit.form.ValidationTextBox.__Constraints = function(){
  21. // locale: String
  22. // locale used for validation, picks up value from this widget's lang attribute
  23. // _flags_: anything
  24. // various flags passed to regExpGen function
  25. this.locale = "";
  26. this._flags_ = "";
  27. }
  28. =====*/
  29. return declare("dijit.form.ValidationTextBox", TextBox, {
  30. // summary:
  31. // Base class for textbox widgets with the ability to validate content of various types and provide user feedback.
  32. // tags:
  33. // protected
  34. templateString: template,
  35. baseClass: "dijitTextBox dijitValidationTextBox",
  36. // required: Boolean
  37. // User is required to enter data into this field.
  38. required: false,
  39. // promptMessage: String
  40. // If defined, display this hint string immediately on focus to the textbox, if empty.
  41. // Also displays if the textbox value is Incomplete (not yet valid but will be with additional input).
  42. // Think of this like a tooltip that tells the user what to do, not an error message
  43. // that tells the user what they've done wrong.
  44. //
  45. // Message disappears when user starts typing.
  46. promptMessage: "",
  47. // invalidMessage: String
  48. // The message to display if value is invalid.
  49. // The translated string value is read from the message file by default.
  50. // Set to "" to use the promptMessage instead.
  51. invalidMessage: "$_unset_$",
  52. // missingMessage: String
  53. // The message to display if value is empty and the field is required.
  54. // The translated string value is read from the message file by default.
  55. // Set to "" to use the invalidMessage instead.
  56. missingMessage: "$_unset_$",
  57. // message: String
  58. // Currently error/prompt message.
  59. // When using the default tooltip implementation, this will only be
  60. // displayed when the field is focused.
  61. message: "",
  62. // constraints: dijit.form.ValidationTextBox.__Constraints
  63. // user-defined object needed to pass parameters to the validator functions
  64. constraints: {},
  65. // regExp: [extension protected] String
  66. // regular expression string used to validate the input
  67. // Do not specify both regExp and regExpGen
  68. regExp: ".*",
  69. regExpGen: function(/*dijit.form.ValidationTextBox.__Constraints*/ /*===== constraints =====*/){
  70. // summary:
  71. // Overridable function used to generate regExp when dependent on constraints.
  72. // Do not specify both regExp and regExpGen.
  73. // tags:
  74. // extension protected
  75. return this.regExp; // String
  76. },
  77. // state: [readonly] String
  78. // Shows current state (ie, validation result) of input (""=Normal, Incomplete, or Error)
  79. state: "",
  80. // tooltipPosition: String[]
  81. // See description of `dijit.Tooltip.defaultPosition` for details on this parameter.
  82. tooltipPosition: [],
  83. _setValueAttr: function(){
  84. // summary:
  85. // Hook so set('value', ...) works.
  86. this.inherited(arguments);
  87. this.validate(this.focused);
  88. },
  89. validator: function(/*anything*/ value, /*dijit.form.ValidationTextBox.__Constraints*/ constraints){
  90. // summary:
  91. // Overridable function used to validate the text input against the regular expression.
  92. // tags:
  93. // protected
  94. return (new RegExp("^(?:" + this.regExpGen(constraints) + ")"+(this.required?"":"?")+"$")).test(value) &&
  95. (!this.required || !this._isEmpty(value)) &&
  96. (this._isEmpty(value) || this.parse(value, constraints) !== undefined); // Boolean
  97. },
  98. _isValidSubset: function(){
  99. // summary:
  100. // Returns true if the value is either already valid or could be made valid by appending characters.
  101. // This is used for validation while the user [may be] still typing.
  102. return this.textbox.value.search(this._partialre) == 0;
  103. },
  104. isValid: function(/*Boolean*/ /*===== isFocused =====*/){
  105. // summary:
  106. // Tests if value is valid.
  107. // Can override with your own routine in a subclass.
  108. // tags:
  109. // protected
  110. return this.validator(this.textbox.value, this.constraints);
  111. },
  112. _isEmpty: function(value){
  113. // summary:
  114. // Checks for whitespace
  115. return (this.trim ? /^\s*$/ : /^$/).test(value); // Boolean
  116. },
  117. getErrorMessage: function(/*Boolean*/ /*===== isFocused =====*/){
  118. // summary:
  119. // Return an error message to show if appropriate
  120. // tags:
  121. // protected
  122. return (this.required && this._isEmpty(this.textbox.value)) ? this.missingMessage : this.invalidMessage; // String
  123. },
  124. getPromptMessage: function(/*Boolean*/ /*===== isFocused =====*/){
  125. // summary:
  126. // Return a hint message to show when widget is first focused
  127. // tags:
  128. // protected
  129. return this.promptMessage; // String
  130. },
  131. _maskValidSubsetError: true,
  132. validate: function(/*Boolean*/ isFocused){
  133. // summary:
  134. // Called by oninit, onblur, and onkeypress.
  135. // description:
  136. // Show missing or invalid messages if appropriate, and highlight textbox field.
  137. // tags:
  138. // protected
  139. var message = "";
  140. var isValid = this.disabled || this.isValid(isFocused);
  141. if(isValid){ this._maskValidSubsetError = true; }
  142. var isEmpty = this._isEmpty(this.textbox.value);
  143. var isValidSubset = !isValid && isFocused && this._isValidSubset();
  144. this._set("state", isValid ? "" : (((((!this._hasBeenBlurred || isFocused) && isEmpty) || isValidSubset) && this._maskValidSubsetError) ? "Incomplete" : "Error"));
  145. this.focusNode.setAttribute("aria-invalid", isValid ? "false" : "true");
  146. if(this.state == "Error"){
  147. this._maskValidSubsetError = isFocused && isValidSubset; // we want the error to show up after a blur and refocus
  148. message = this.getErrorMessage(isFocused);
  149. }else if(this.state == "Incomplete"){
  150. message = this.getPromptMessage(isFocused); // show the prompt whenever the value is not yet complete
  151. this._maskValidSubsetError = !this._hasBeenBlurred || isFocused; // no Incomplete warnings while focused
  152. }else if(isEmpty){
  153. message = this.getPromptMessage(isFocused); // show the prompt whenever there's no error and no text
  154. }
  155. this.set("message", message);
  156. return isValid;
  157. },
  158. displayMessage: function(/*String*/ message){
  159. // summary:
  160. // Overridable method to display validation errors/hints.
  161. // By default uses a tooltip.
  162. // tags:
  163. // extension
  164. if(message && this.focused){
  165. Tooltip.show(message, this.domNode, this.tooltipPosition, !this.isLeftToRight());
  166. }else{
  167. Tooltip.hide(this.domNode);
  168. }
  169. },
  170. _refreshState: function(){
  171. // Overrides TextBox._refreshState()
  172. this.validate(this.focused);
  173. this.inherited(arguments);
  174. },
  175. //////////// INITIALIZATION METHODS ///////////////////////////////////////
  176. constructor: function(){
  177. this.constraints = {};
  178. },
  179. _setConstraintsAttr: function(/*Object*/ constraints){
  180. if(!constraints.locale && this.lang){
  181. constraints.locale = this.lang;
  182. }
  183. this._set("constraints", constraints);
  184. this._computePartialRE();
  185. },
  186. _computePartialRE: function(){
  187. var p = this.regExpGen(this.constraints);
  188. this.regExp = p;
  189. var partialre = "";
  190. // parse the regexp and produce a new regexp that matches valid subsets
  191. // if the regexp is .* then there's no use in matching subsets since everything is valid
  192. if(p != ".*"){ this.regExp.replace(/\\.|\[\]|\[.*?[^\\]{1}\]|\{.*?\}|\(\?[=:!]|./g,
  193. function(re){
  194. switch(re.charAt(0)){
  195. case '{':
  196. case '+':
  197. case '?':
  198. case '*':
  199. case '^':
  200. case '$':
  201. case '|':
  202. case '(':
  203. partialre += re;
  204. break;
  205. case ")":
  206. partialre += "|$)";
  207. break;
  208. default:
  209. partialre += "(?:"+re+"|$)";
  210. break;
  211. }
  212. }
  213. );}
  214. try{ // this is needed for now since the above regexp parsing needs more test verification
  215. "".search(partialre);
  216. }catch(e){ // should never be here unless the original RE is bad or the parsing is bad
  217. partialre = this.regExp;
  218. console.warn('RegExp error in ' + this.declaredClass + ': ' + this.regExp);
  219. } // should never be here unless the original RE is bad or the parsing is bad
  220. this._partialre = "^(?:" + partialre + ")$";
  221. },
  222. postMixInProperties: function(){
  223. this.inherited(arguments);
  224. this.messages = i18n.getLocalization("dijit.form", "validate", this.lang);
  225. if(this.invalidMessage == "$_unset_$"){ this.invalidMessage = this.messages.invalidMessage; }
  226. if(!this.invalidMessage){ this.invalidMessage = this.promptMessage; }
  227. if(this.missingMessage == "$_unset_$"){ this.missingMessage = this.messages.missingMessage; }
  228. if(!this.missingMessage){ this.missingMessage = this.invalidMessage; }
  229. this._setConstraintsAttr(this.constraints); // this needs to happen now (and later) due to codependency on _set*Attr calls attachPoints
  230. },
  231. _setDisabledAttr: function(/*Boolean*/ value){
  232. this.inherited(arguments); // call FormValueWidget._setDisabledAttr()
  233. this._refreshState();
  234. },
  235. _setRequiredAttr: function(/*Boolean*/ value){
  236. this._set("required", value);
  237. this.focusNode.setAttribute("aria-required", value);
  238. this._refreshState();
  239. },
  240. _setMessageAttr: function(/*String*/ message){
  241. this._set("message", message);
  242. this.displayMessage(message);
  243. },
  244. reset:function(){
  245. // Overrides dijit.form.TextBox.reset() by also
  246. // hiding errors about partial matches
  247. this._maskValidSubsetError = true;
  248. this.inherited(arguments);
  249. },
  250. _onBlur: function(){
  251. // the message still exists but for back-compat, and to erase the tooltip
  252. // (if the message is being displayed as a tooltip), call displayMessage('')
  253. this.displayMessage('');
  254. this.inherited(arguments);
  255. }
  256. });
  257. });