validator.js 11KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392
  1. /*
  2. Validator v1.1.0
  3. (c) Yair Even Or
  4. https://github.com/yairEO/validator
  5. MIT-style license.
  6. */
  7. var validator = (function($){
  8. var message, tests, checkField, validate, mark, unmark, field, minmax, defaults,
  9. validateWords, lengthRange, lengthLimit, pattern, alertTxt, data,
  10. email_illegalChars = /[\(\)\<\>\,\;\:\\\/\"\[\]]/,
  11. email_filter = /^.+@.+\..{2,3}$/;
  12. /* general text messages
  13. */
  14. message = {
  15. invalid : 'invalid input',
  16. empty : 'please put something here',
  17. min : 'input is too short',
  18. max : 'input is too long',
  19. number_min : 'too low',
  20. number_max : 'too high',
  21. url : 'invalid URL',
  22. number : 'not a number',
  23. email : 'email address is invalid',
  24. email_repeat : 'emails do not match',
  25. password_repeat : 'passwords do not match',
  26. repeat : 'no match',
  27. complete : 'input is not complete',
  28. select : 'Please select an option'
  29. };
  30. if(!window.console){
  31. console={};
  32. console.log=console.warn=function(){ return; }
  33. }
  34. // defaults
  35. defaults = { alerts:true };
  36. /* Tests for each type of field (including Select element)
  37. */
  38. tests = {
  39. sameAsPlaceholder : function(a){
  40. return $.fn.placeholder && a.attr('placeholder') !== undefined && data.val == a.prop('placeholder');
  41. },
  42. hasValue : function(a){
  43. if( !a ){
  44. alertTxt = message.empty;
  45. return false;
  46. }
  47. return true;
  48. },
  49. // 'linked' is a special test case for inputs which their values should be equal to each other (ex. confirm email or retype password)
  50. linked : function(a,b){
  51. if( b != a ){
  52. // choose a specific message or a general one
  53. alertTxt = message[data.type + '_repeat'] || message.no_match;
  54. return false;
  55. }
  56. return true;
  57. },
  58. email : function(a){
  59. if ( !email_filter.test( a ) || a.match( email_illegalChars ) ){
  60. alertTxt = a ? message.email : message.empty;
  61. return false;
  62. }
  63. return true;
  64. },
  65. text : function(a){
  66. // make sure there are at least X number of words, each at least 2 chars long.
  67. // for example 'john F kenedy' should be at least 2 words and will pass validation
  68. if( validateWords ){
  69. var words = a.split(' ');
  70. // iterrate on all the words
  71. var wordsLength = function(len){
  72. for( var w = words.length; w--; )
  73. if( words[w].length < len )
  74. return false;
  75. return true;
  76. };
  77. if( words.length < validateWords || !wordsLength(2) ){
  78. alertTxt = message.complete;
  79. return false;
  80. }
  81. return true;
  82. }
  83. if( lengthRange && a.length < lengthRange[0] ){
  84. alertTxt = message.min;
  85. return false;
  86. }
  87. // check if there is max length & field length is greater than the allowed
  88. if( lengthRange && lengthRange[1] && a.length > lengthRange[1] ){
  89. alertTxt = message.max;
  90. return false;
  91. }
  92. // check if the field's value should obey any length limits, and if so, make sure the length of the value is as specified
  93. if( lengthLimit && lengthLimit.length ){
  94. var obeyLimit = false;
  95. while( lengthLimit.length ){
  96. if( lengthLimit.pop() == a.length )
  97. obeyLimit = true;
  98. }
  99. if( !obeyLimit ){
  100. alertTxt = message.complete;
  101. return false;
  102. }
  103. }
  104. if( pattern ){
  105. var regex, jsRegex;
  106. switch( pattern ){
  107. case 'alphanumeric' :
  108. regex = /^[a-z0-9]+$/i;
  109. break;
  110. case 'numeric' :
  111. regex = /^[0-9]+$/i;
  112. break;
  113. case 'phone' :
  114. regex = /^\+?([0-9]|[-|' '])+$/i;
  115. break;
  116. default :
  117. regex = pattern;
  118. }
  119. try{
  120. jsRegex = new RegExp(regex).test(a);
  121. if( a && !jsRegex )
  122. return false;
  123. }
  124. catch(err){
  125. console.log(err, field, 'regex is invalid');
  126. return false;
  127. }
  128. }
  129. return true;
  130. },
  131. number : function(a){
  132. // if not not a number
  133. if( isNaN(parseFloat(a)) && !isFinite(a) ){
  134. alertTxt = message.number;
  135. return false;
  136. }
  137. // not enough numbers
  138. else if( lengthRange && a.length < lengthRange[0] ){
  139. alertTxt = message.min;
  140. return false;
  141. }
  142. // check if there is max length & field length is greater than the allowed
  143. else if( lengthRange && lengthRange[1] && a.length > lengthRange[1] ){
  144. alertTxt = message.max;
  145. return false;
  146. }
  147. else if( minmax[0] && (a|0) < minmax[0] ){
  148. alertTxt = message.number_min;
  149. return false;
  150. }
  151. else if( minmax[1] && (a|0) > minmax[1] ){
  152. alertTxt = message.number_max;
  153. return false;
  154. }
  155. return true;
  156. },
  157. // Date is validated in European format (day,month,year)
  158. date : function(a){
  159. var day, A = a.split(/[-./]/g), i;
  160. // if there is native HTML5 support:
  161. if( field[0].valueAsNumber )
  162. return true;
  163. for( i = A.length; i--; ){
  164. if( isNaN(parseFloat(a)) && !isFinite(a) )
  165. return false;
  166. }
  167. try{
  168. day = new Date(A[2], A[1]-1, A[0]);
  169. if( day.getMonth()+1 == A[1] && day.getDate() == A[0] )
  170. return day;
  171. return false;
  172. }
  173. catch(er){
  174. console.log('date test: ', err);
  175. return false;
  176. }
  177. },
  178. url : function(a){
  179. // minimalistic URL validation
  180. function testUrl(url){
  181. return /^(https?:\/\/)?([\w\d\-_]+\.+[A-Za-z]{2,})+\/?/.test( url );
  182. }
  183. if( !testUrl( a ) ){
  184. console.log(a);
  185. alertTxt = a ? message.url : message.empty;
  186. return false;
  187. }
  188. return true;
  189. },
  190. hidden : function(a){
  191. if( lengthRange && a.length < lengthRange[0] ){
  192. alertTxt = message.min;
  193. return false;
  194. }
  195. if( pattern ){
  196. var regex;
  197. if( pattern == 'alphanumeric' ){
  198. regex = /^[a-z0-9]+$/i;
  199. if( !regex.test(a) ){
  200. return false;
  201. }
  202. }
  203. }
  204. return true;
  205. },
  206. select : function(a){
  207. if( !tests.hasValue(a) ){
  208. alertTxt = message.select;
  209. return false;
  210. }
  211. return true;
  212. }
  213. };
  214. /* marks invalid fields
  215. */
  216. mark = function( field, text ){
  217. if( !text || !field || !field.length )
  218. return false;
  219. // check if not already marked as a 'bad' record and add the 'alert' object.
  220. // if already is marked as 'bad', then make sure the text is set again because i might change depending on validation
  221. var item = field.parents('.item'),
  222. warning;
  223. if( item.hasClass('bad') ){
  224. if( defaults.alerts )
  225. item.find('.alert').html(text);
  226. }
  227. else if( defaults.alerts ){
  228. warning = $('<div class="alert">').html( text );
  229. item.append( warning );
  230. }
  231. item.removeClass('bad');
  232. // a delay so the "alert" could be transitioned via CSS
  233. setTimeout(function(){
  234. item.addClass('bad');
  235. }, 0);
  236. };
  237. /* un-marks invalid fields
  238. */
  239. unmark = function( field ){
  240. if( !field || !field.length ){
  241. console.warn('no "field" argument, null or DOM object not found');
  242. return false;
  243. }
  244. field.parents('.item')
  245. .removeClass('bad')
  246. .find('.alert').remove();
  247. };
  248. function testByType(type, value){
  249. if( type == 'tel' )
  250. pattern = pattern || 'phone';
  251. if( !type || type == 'password' || type == 'tel' )
  252. type = 'text';
  253. return tests[type](value);
  254. }
  255. function prepareFieldData(el){
  256. field = $(el);
  257. field.data( 'valid', true ); // initialize validness of field by first checking if it's even filled out of now
  258. field.data( 'type', field.attr('type') ); // every field starts as 'valid=true' until proven otherwise
  259. pattern = el.pattern;
  260. }
  261. /* Validations per-character keypress
  262. */
  263. function keypress(e){
  264. prepareFieldData(this);
  265. if( e.charCode )
  266. return testByType( data.type, String.fromCharCode(e.charCode) );
  267. }
  268. /* Checks a single form field by it's type and specific (custom) attributes
  269. */
  270. function checkField(){
  271. // skip testing fields whom their type is not HIDDEN but they are HIDDEN via CSS.
  272. if( this.type !='hidden' && $(this).is(':hidden') )
  273. return true;
  274. prepareFieldData(this);
  275. field.data( 'val', field[0].value.replace(/^\s+|\s+$/g, "") ); // cache the value of the field and trim it
  276. data = field.data();
  277. // Check if there is a specific error message for that field, if not, use the default 'invalid' message
  278. alertTxt = message[field.prop('name')] || message.invalid;
  279. // SELECT / TEXTAREA nodes needs special treatment
  280. if( field[0].nodeName.toLowerCase() === "select" ){
  281. data.type = 'select';
  282. }
  283. if( field[0].nodeName.toLowerCase() === "textarea" ){
  284. data.type = 'text';
  285. }
  286. /* Gather Custom data attributes for specific validation:
  287. */
  288. validateWords = data['validateWords'] || 0;
  289. lengthRange = data['validateLengthRange'] ? (data['validateLengthRange']+'').split(',') : [1];
  290. lengthLimit = data['validateLength'] ? (data['validateLength']+'').split(',') : false;
  291. minmax = data['validateMinmax'] ? (data['validateMinmax']+'').split(',') : ''; // for type 'number', defines the minimum and/or maximum for the value as a number.
  292. data.valid = tests.hasValue(data.val);
  293. // check if field has any value
  294. if( data.valid ){
  295. /* Validate the field's value is different than the placeholder attribute (and attribute exists)
  296. * this is needed when fixing the placeholders for older browsers which does not support them.
  297. * in this case, make sure the "placeholder" jQuery plugin was even used before proceeding
  298. */
  299. if( tests.sameAsPlaceholder(field) ){
  300. alertTxt = message.empty;
  301. data.valid = false;
  302. }
  303. // if this field is linked to another field (their values should be the same)
  304. if( data.validateLinked ){
  305. var linkedTo = data['validateLinked'].indexOf('#') == 0 ? $(data['validateLinked']) : $(':input[name=' + data['validateLinked'] + ']');
  306. data.valid = tests.linked( data.val, linkedTo.val() );
  307. }
  308. /* validate by type of field. use 'attr()' is proffered to get the actual value and not what the browsers sees for unsupported types.
  309. */
  310. else if( data.valid || data.type == 'select' )
  311. data.valid = testByType(data.type, data.val);
  312. // optional fields are only validated if they are not empty
  313. if( field.hasClass('optional') && !data.val )
  314. data.valid = true;
  315. }
  316. // mark / unmark the field, and set the general 'submit' flag accordingly
  317. if( data.valid )
  318. unmark( field );
  319. else{
  320. mark( field, alertTxt );
  321. submit = false;
  322. }
  323. return data.valid;
  324. }
  325. /* vaildates all the REQUIRED fields prior to submiting the form
  326. */
  327. function checkAll( $form ){
  328. $form = $($form);
  329. if( $form.length == 0 ){
  330. console.warn('element not found');
  331. return false;
  332. }
  333. var that = this,
  334. submit = true, // save the scope
  335. fieldsToCheck = $form.find(':input').filter('[required=required], .required, .optional').not('[disabled=disabled]');
  336. fieldsToCheck.each(function(){
  337. // use an AND operation, so if any of the fields returns 'false' then the submitted result will be also FALSE
  338. submit = submit * checkField.apply(this);
  339. });
  340. return !!submit; // casting the variable to make sure it's a boolean
  341. }
  342. return {
  343. defaults : defaults,
  344. checkField : checkField,
  345. keypress : keypress,
  346. checkAll : checkAll,
  347. mark : mark,
  348. unmark : unmark,
  349. message : message,
  350. tests : tests
  351. }
  352. })(jQuery);