validator.js 14 KB

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