├── .gitignore ├── example ├── loader-medium.gif ├── demo.html └── jquery.validation.css ├── bower.json ├── README.md ├── package.json ├── html5-form-validation.jquery.json ├── LICENSE ├── Gruntfile.js ├── dist ├── jquery.validation.min.js └── jquery.validation.js └── src └── jquery.validation.js /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | node_modules 3 | npm-debug.log 4 | -------------------------------------------------------------------------------- /example/loader-medium.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/running-coder/jquery-form-validation/HEAD/example/loader-medium.gif -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "html5-form-validation", 3 | "version": "1.5.3", 4 | "authors": [ 5 | "Tom Bertrand" 6 | ], 7 | "description": "jQuery plugin that provides a client site form validation with builtin options and deep customization.", 8 | "main": "jquery.validation.min.js", 9 | "keywords": [ 10 | "form", 11 | "html5", 12 | "validate", 13 | "validation", 14 | "input" 15 | ], 16 | "ignore": [], 17 | "licenses": "MIT", 18 | "homepage": "http://www.runningcoder.org/jqueryvalidation/", 19 | "repository": { 20 | "type": "git", 21 | "url": "git://github.com/running-coder/jquery-form-validation.git" 22 | }, 23 | "dependencies": { 24 | "jquery": ">=1.7.2" 25 | } 26 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | jQuery Form Validation 2 | ====================== 3 | 4 | The jQuery form validation plugin unifies the way to validate HTML forms using JavaScript. 5 | It is a simple clientside library that will save you a lot of time when it comes to adding validation on your HTML form inputs or selects! 6 | 7 | The jQuery form Validation plugin is released under the MIT License. 8 | 9 | The complete documentation, demo and further instructions can be found at www.runningcoder.org 10 | 11 | Documentation 12 | ====================== 13 | 14 | www.runningcoder.org/jqueryvalidation/documentation/ 15 | 16 | Demos 17 | ====================== 18 | 19 | www.runningcoder.org/jqueryvalidation/demo/ 20 | 21 | Patch Notes 22 | ====================== 23 | 24 | www.runningcoder.org/jqueryvalidation/version/ 25 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "html5-form-validation", 3 | "description": "jQuery plugin that provides a client site form validation with builtin options and deep customization.", 4 | "keywords": [ 5 | "form", 6 | "html5", 7 | "validate", 8 | "validation", 9 | "input" 10 | ], 11 | "homepage": "http://www.runningcoder.org/jqueryvalidation/", 12 | "bugs": "https://github.com/running-coder/jquery-form-validation/issues", 13 | "repository": { 14 | "type": "git", 15 | "url": "git://github.com/running-coder/jquery-form-validation.git" 16 | }, 17 | "author": { 18 | "name": "Tom Bertrand" 19 | }, 20 | "devDependencies": { 21 | "grunt": "~0.4", 22 | "grunt-contrib-clean": "~0.6.0", 23 | "grunt-contrib-uglify": "~0.9.1", 24 | "grunt-contrib-copy": "~0.8.0", 25 | "grunt-replace": "~0.8.0", 26 | "grunt-jsbeautifier": "~0.2.10", 27 | "grunt-stripcomments": "~0.3.1" 28 | }, 29 | "version": "1.5.3", 30 | "main": "dist/jquery.validation.min.js" 31 | } -------------------------------------------------------------------------------- /html5-form-validation.jquery.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "html5-form-validation", 3 | "title": "jQuery Form Validation", 4 | "description": "jQuery plugin that provides a client site form validation with builtin options and deep customization.", 5 | "keywords": [ 6 | "form", 7 | "html5", 8 | "validate", 9 | "validation", 10 | "input" 11 | ], 12 | "version": "1.5.3", 13 | "author": { 14 | "name": "Tom Bertrand", 15 | "url": "http://www.runningcoder.org/jqueryvalidation/" 16 | }, 17 | "licenses": [ 18 | { 19 | "type": "MIT", 20 | "url": "https://github.com/running-coder/jquery-form-validation/blob/master/LICENSE" 21 | } 22 | ], 23 | "bugs": "https://github.com/running-coder/jquery-form-validation/issues", 24 | "homepage": "http://www.runningcoder.org/jqueryvalidation/", 25 | "docs": "http://www.runningcoder.org/jqueryvalidation/documentation/", 26 | "demo": "http://www.runningcoder.org/jqueryvalidation/demo/", 27 | "dependencies": { 28 | "jquery": ">=1.7.2" 29 | } 30 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 tombertrand 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /Gruntfile.js: -------------------------------------------------------------------------------- 1 | module.exports = function (grunt) { 2 | 3 | grunt.initConfig({ 4 | 5 | pkg: grunt.file.readJSON('package.json'), 6 | 7 | banner: '/*!\n' + 8 | ' * jQuery Form Validation\n' + 9 | ' * Copyright (C) 2015 RunningCoder.org\n' + 10 | ' * Licensed under the MIT license\n' + 11 | ' *\n' + 12 | ' * @author <%= pkg.author.name %>\n' + 13 | ' * @version <%= pkg.version %> (<%= grunt.template.today("yyyy-mm-dd") %>)\n' + 14 | ' * @link http://www.runningcoder.org/jqueryvalidation/\n' + 15 | '*/\n', 16 | 17 | clean: { 18 | dist: ["dist"] 19 | }, 20 | 21 | copy: { 22 | dist: { 23 | files: [ 24 | { 25 | src: ['src/jquery.validation.js'], 26 | dest: 'dist/jquery.validation.js' 27 | }, 28 | { 29 | src: ['src/jquery.validation.js'], 30 | dest: 'dist/jquery.validation.min.js' 31 | } 32 | ] 33 | } 34 | }, 35 | 36 | comments: { 37 | dist: { 38 | options: { 39 | singleline: true, 40 | multiline: true 41 | }, 42 | src: [ 'dist/jquery.validation.js'] 43 | } 44 | }, 45 | 46 | replace: { 47 | banner: { 48 | options: { 49 | patterns: [ 50 | { 51 | match: /\/\*![\S\s]+?\*\/[\r\n]*/, 52 | replacement: '<%= banner %>' 53 | } 54 | ] 55 | }, 56 | files: [ 57 | { 58 | src: ['src/jquery.validation.js'], 59 | dest: 'src/jquery.validation.js' 60 | } 61 | ] 62 | }, 63 | removeDebug: { 64 | options: { 65 | patterns: [ 66 | { 67 | match: /\/\/\s?\{debug}[\s\S]*?\{\/debug}/g, 68 | replacement: '' 69 | } 70 | ] 71 | }, 72 | files: [ 73 | { 74 | src: ['dist/jquery.validation.min.js'], 75 | dest: 'dist/jquery.validation.min.js' 76 | } 77 | ] 78 | }, 79 | removeComments: { 80 | options: { 81 | patterns: [ 82 | { 83 | match: /\/\*[^!][\S\s]+?\*\//gm, 84 | replacement: '' 85 | } 86 | ] 87 | }, 88 | files: [ 89 | { 90 | src: ['dist/jquery.validation.js'], 91 | dest: 'dist/jquery.validation.js' 92 | } 93 | ] 94 | } 95 | }, 96 | 97 | jsbeautifier : { 98 | files : ['dist/jquery.validation.js'], 99 | options : { 100 | } 101 | }, 102 | 103 | uglify: { 104 | dist: { 105 | options: { 106 | mangle: true, 107 | compress: true, 108 | banner: '<%= banner %>' 109 | }, 110 | files: { 111 | 'dist/jquery.validation.min.js': ['dist/jquery.validation.min.js'] 112 | } 113 | } 114 | 115 | } 116 | 117 | }); 118 | 119 | grunt.loadNpmTasks('grunt-contrib-clean'); 120 | grunt.loadNpmTasks('grunt-contrib-copy'); 121 | grunt.loadNpmTasks('grunt-stripcomments'); 122 | grunt.loadNpmTasks('grunt-replace'); 123 | grunt.loadNpmTasks("grunt-jsbeautifier"); 124 | grunt.loadNpmTasks('grunt-contrib-uglify'); 125 | 126 | grunt.registerTask('default', [ 127 | 'clean:dist', 128 | 'replace:banner', 129 | 'copy:dist', 130 | 'comments', 131 | 'replace:removeComments', 132 | 'jsbeautifier', 133 | 'replace:removeDebug', 134 | 'uglify' 135 | ]); 136 | 137 | }; 138 | -------------------------------------------------------------------------------- /example/demo.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 |

Signup_v1 Demo

20 | 21 | 29 | 30 |
34 | 35 |
36 | 37 |
38 | 39 | 46 | 47 |
48 | * 49 |
50 |
51 |
52 |
53 | 54 |
55 | 56 | 60 | 61 |
62 | * 63 |
64 |
65 |
66 |
67 | 68 |
69 | 70 | 75 | 76 |
77 | * 78 |
79 |
80 |
81 |
82 | 83 |
84 | 85 | 89 | 90 |
91 | * 92 |
93 |
94 |
95 |
96 | 97 |
98 | 99 | 104 | 105 |
106 | * 107 |
108 |
109 |
110 | 111 | 112 | 113 | 116 | 117 |
118 | 119 | 167 | 168 | 169 | -------------------------------------------------------------------------------- /example/jquery.validation.css: -------------------------------------------------------------------------------- 1 | /*------------------------------------*\ 2 | CONTENTS 3 | \*------------------------------------*/ 4 | /* 5 | NORMALIZE BUTTON & INPUT - https://github.com/necolas/normalize.css 6 | LAYOUT 7 | INPUT, BUTTON & LABEL 8 | ERROR 9 | */ 10 | 11 | /*------------------------------------*\ 12 | NORMALIZE BUTTON & INPUT 13 | \*------------------------------------*/ 14 | 15 | /** 16 | * Known limitation: by default, Chrome and Safari on OS X allow very limited 17 | * styling of `select`, unless a `border` property is set. 18 | */ 19 | 20 | /** 21 | * 1. Correct color not being inherited. 22 | * Known issue: affects color of disabled elements. 23 | * 2. Correct font properties not being inherited. 24 | * 3. Address margins set differently in Firefox 4+, Safari, and Chrome. 25 | */ 26 | 27 | button, 28 | input, 29 | optgroup, 30 | select, 31 | textarea { 32 | color: inherit; /* 1 */ 33 | font: inherit; /* 2 */ 34 | margin: 0; /* 3 */ 35 | } 36 | 37 | /** 38 | * Address `overflow` set to `hidden` in IE 8/9/10/11. 39 | */ 40 | 41 | button { 42 | overflow: visible; 43 | } 44 | 45 | /** 46 | * Address inconsistent `text-transform` inheritance for `button` and `select`. 47 | * All other form control elements do not inherit `text-transform` values. 48 | * Correct `button` style inheritance in Firefox, IE 8/9/10/11, and Opera. 49 | * Correct `select` style inheritance in Firefox. 50 | */ 51 | 52 | button, 53 | select { 54 | text-transform: none; 55 | } 56 | 57 | /** 58 | * 1. Avoid the WebKit bug in Android 4.0.* where (2) destroys native `audio` 59 | * and `video` controls. 60 | * 2. Correct inability to style clickable `input` types in iOS. 61 | * 3. Improve usability and consistency of cursor style between image-type 62 | * `input` and others. 63 | */ 64 | 65 | button, 66 | html input[type="button"], /* 1 */ 67 | input[type="reset"], 68 | input[type="submit"] { 69 | -webkit-appearance: button; /* 2 */ 70 | cursor: pointer; /* 3 */ 71 | } 72 | 73 | /** 74 | * Re-set default cursor for disabled elements. 75 | */ 76 | 77 | button[disabled], 78 | html input[disabled] { 79 | cursor: default; 80 | } 81 | 82 | /** 83 | * Remove inner padding and border in Firefox 4+. 84 | */ 85 | 86 | button::-moz-focus-inner, 87 | input::-moz-focus-inner { 88 | border: 0; 89 | padding: 0; 90 | } 91 | 92 | /** 93 | * Address Firefox 4+ setting `line-height` on `input` using `!important` in 94 | * the UA stylesheet. 95 | */ 96 | 97 | input { 98 | line-height: normal; 99 | } 100 | 101 | 102 | /*------------------------------------*\ 103 | LAYOUT 104 | \*------------------------------------*/ 105 | 106 | .validation-form-container { 107 | position: relative; 108 | background-color: #fff; 109 | font-family: "Helvetica Neue", "Helvetica", Arial; 110 | color: rgba(0, 0, 0, 0.7); 111 | font-size: 16px; 112 | padding: 16px; 113 | width: 100%; 114 | max-width: 500px; 115 | border-radius: 4px; 116 | -webkit-box-shadow: 0px 0px 0px 1px rgba(0, 0, 0, 0.1) inset; 117 | box-shadow: 0px 0px 0px 1px rgba(0, 0, 0, 0.1) inset; 118 | line-height: 16px; 119 | } 120 | 121 | .validation-form-container * { 122 | -webkit-box-sizing: border-box; 123 | -moz-box-sizing: border-box; 124 | box-sizing: border-box; 125 | } 126 | 127 | .validation-form-container :last-child { 128 | margin-bottom: 0em; 129 | } 130 | 131 | .validation-form-container .field { 132 | clear: both; 133 | margin: 0em 0em 1em; 134 | } 135 | 136 | .validation-form-container ul { 137 | list-style: none; 138 | margin: 0.2em 0; 139 | padding: 0; 140 | } 141 | 142 | .validation-form-container .ui.loader.active { 143 | display: block; 144 | } 145 | .validation-form-container .ui.loader { 146 | width: 32px; 147 | height: 32px; 148 | background: url(loader-medium.gif) no-repeat; 149 | background-position: 48% 0px; 150 | display: none; 151 | position: absolute; 152 | top: 50%; 153 | left: 50%; 154 | margin: 0px; 155 | z-index: 1000; 156 | -webkit-transform: translateX(-50%) translateY(-50%); 157 | -ms-transform: translateX(-50%) translateY(-50%); 158 | transform: translateX(-50%) translateY(-50%); 159 | } 160 | 161 | 162 | /*------------------------------------*\ 163 | INPUT, BUTTON & LABEL 164 | \*------------------------------------*/ 165 | 166 | .validation-form-container .ui.button { 167 | cursor: pointer; 168 | display: inline-block; 169 | vertical-align: middle; 170 | min-height: 1em; 171 | outline: none; 172 | border: none; 173 | background-color: #FAFAFA; 174 | color: #808080; 175 | margin: 0em; 176 | padding: 0.8em 1.5em; 177 | font-size: 1rem; 178 | text-transform: uppercase; 179 | line-height: 1; 180 | font-weight: bold; 181 | font-style: normal; 182 | text-align: center; 183 | text-decoration: none; 184 | background-image: -webkit-gradient(linear, left top, left bottom, from(rgba(0, 0, 0, 0)), to(rgba(0, 0, 0, 0.05))); 185 | background-image: -webkit-linear-gradient(rgba(0, 0, 0, 0), rgba(0, 0, 0, 0.05)); 186 | background-image: linear-gradient(rgba(0, 0, 0, 0), rgba(0, 0, 0, 0.05)); 187 | border-radius: 0.25em; 188 | -webkit-box-shadow: 0px 0px 0px 1px rgba(0, 0, 0, 0.08) inset; 189 | box-shadow: 0px 0px 0px 1px rgba(0, 0, 0, 0.08) inset; 190 | -webkit-user-select: none; 191 | -moz-user-select: none; 192 | -ms-user-select: none; 193 | user-select: none; 194 | -webkit-box-sizing: border-box; 195 | -moz-box-sizing: border-box; 196 | -ms-box-sizing: border-box; 197 | box-sizing: border-box; 198 | -webkit-tap-highlight-color: rgba(0, 0, 0, 0); 199 | -webkit-transition: opacity 0.25s ease, background-color 0.25s ease, color 0.25s ease, background 0.25s ease, -webkit-box-shadow 0.25s ease; 200 | transition: opacity 0.25s ease, background-color 0.25s ease, color 0.25s ease, background 0.25s ease, box-shadow 0.25s ease; 201 | } 202 | 203 | .validation-form-container .ui.blue.button { 204 | background-color: #6ECFF5; 205 | color: #FFFFFF; 206 | } 207 | 208 | .validation-form-container .ui.blue.button:hover, 209 | .validation-form-container .ui.blue.button.active { 210 | background-color: #1AB8F3; 211 | color: #FFFFFF; 212 | } 213 | 214 | .validation-form-container .ui.blue.button:active { 215 | background-color: #0AA5DF; 216 | color: #FFFFFF; 217 | } 218 | 219 | .validation-form-container .ui.mini.button { 220 | font-size: 0.8rem; 221 | padding: 0.6em 0.8em; 222 | } 223 | 224 | .validation-form-container .ui.basic.button { 225 | background-color: transparent !important; 226 | background-image: none; 227 | color: #808080 !important; 228 | font-weight: normal; 229 | text-transform: none; 230 | -webkit-box-shadow: 0px 0px 0px 1px rgba(0, 0, 0, 0.1) inset; 231 | box-shadow: 0px 0px 0px 1px rgba(0, 0, 0, 0.1) inset; 232 | } 233 | 234 | .validation-form-container .ui.input { 235 | width: 100%; 236 | font-size: 1em; 237 | display: inline-block; 238 | position: relative; 239 | color: rgba(0, 0, 0, 0.7); 240 | } 241 | 242 | .validation-form-container .ui.labeled.input input { 243 | padding-right: 2.5em !important; 244 | } 245 | 246 | .validation-form-container textarea, 247 | .validation-form-container input[type="text"], 248 | .validation-form-container input[type="password"] { 249 | width: 100%; 250 | margin: 0em; 251 | padding: 0.65em 1em; 252 | font-size: 1em; 253 | background-color: #FFFFFF; 254 | border: 1px solid rgba(0, 0, 0, 0.15); 255 | outline: none; 256 | color: rgba(0, 0, 0, 0.7); 257 | border-radius: 0.3125em; 258 | -webkit-transition: background-color 0.3s ease-out, -webkit-box-shadow 0.2s ease, border-color 0.2s ease; 259 | transition: background-color 0.3s ease-out, box-shadow 0.2s ease, border-color 0.2s ease; 260 | -webkit-box-shadow: 0em 0em 0em 0em rgba(0, 0, 0, 0.3) inset; 261 | box-shadow: 0em 0em 0em 0em rgba(0, 0, 0, 0.3) inset; 262 | -webkit-appearance: none; 263 | -webkit-tap-highlight-color: rgba(255, 255, 255, 0); 264 | } 265 | 266 | .validation-form-container textarea:focus, 267 | .validation-form-container input[type="text"]:focus, 268 | .validation-form-container input[type="password"]:focus { 269 | color: rgba(0, 0, 0, 0.85); 270 | border-color: rgba(0, 0, 0, 0.2); 271 | border-bottom-left-radius: 0; 272 | border-top-left-radius: 0; 273 | -webkit-appearance: none; 274 | -webkit-box-shadow: 0.3em 0em 0em 0em rgba(0, 0, 0, 0.2) inset; 275 | box-shadow: 0.3em 0em 0em 0em rgba(0, 0, 0, 0.2) inset; 276 | } 277 | 278 | .validation-form-container textarea[readonly], 279 | .validation-form-container textarea[disabled], 280 | .validation-form-container input[readonly], 281 | .validation-form-container input[disabled] { 282 | cursor: not-allowed; 283 | background-color: #f7f7f7; 284 | color: #999; 285 | } 286 | 287 | .validation-form-container .field > label { 288 | margin: 0em 0em 0.3em; 289 | display: block; 290 | color: #555555; 291 | font-size: 0.875em; 292 | position: relative; 293 | } 294 | 295 | .validation-form-container .ui.label { 296 | display: inline-block; 297 | vertical-align: middle; 298 | margin: -0.25em 0.25em 0em; 299 | background-color: #E8E8E8; 300 | border-color: #E8E8E8; 301 | padding: 0.5em 0.8em; 302 | color: rgba(0, 0, 0, 0.65); 303 | text-transform: uppercase; 304 | font-weight: normal; 305 | border-radius: 0.325em; 306 | -webkit-box-sizing: border-box; 307 | -moz-box-sizing: border-box; 308 | -ms-box-sizing: border-box; 309 | box-sizing: border-box; 310 | -webkit-transition: background 0.1s linear; 311 | transition: background 0.1s linear; 312 | } 313 | 314 | .validation-form-container .ui.corner.label { 315 | top: 1px; 316 | right: 1px; 317 | overflow: hidden; 318 | font-size: 0.7em; 319 | border-radius: 0 0.3125em; 320 | background-color: transparent; 321 | position: absolute; 322 | z-index: 10; 323 | margin: 0em; 324 | width: 3em; 325 | height: 3em; 326 | padding: 0em; 327 | text-align: center; 328 | -webkit-transition: color 0.2s ease; 329 | transition: color 0.2s ease; 330 | } 331 | 332 | .validation-form-container .ui.corner.label:after { 333 | position: absolute; 334 | content: ""; 335 | right: 0em; 336 | top: 0em; 337 | z-index: -1; 338 | width: 0em; 339 | height: 0em; 340 | border-top: 0em solid transparent; 341 | border-right: 3em solid transparent; 342 | border-bottom: 3em solid transparent; 343 | border-left: 0em solid transparent; 344 | border-right-color: inherit; 345 | -webkit-transition: border-color 0.2s ease; 346 | transition: border-color 0.2s ease; 347 | } 348 | 349 | .validation-form-container .ui.corner.label .icon { 350 | font-size: 2em; 351 | margin: 0.25em 0 0 0.5em; 352 | width: auto; 353 | display: inline-block; 354 | height: 1em; 355 | font-style: normal; 356 | line-height: 1; 357 | font-weight: normal; 358 | text-decoration: inherit; 359 | text-align: center; 360 | speak: none; 361 | -webkit-font-smoothing: antialiased; 362 | -moz-font-smoothing: antialiased; 363 | font-smoothing: antialiased; 364 | } 365 | 366 | 367 | /*------------------------------------*\ 368 | ERROR 369 | \*------------------------------------*/ 370 | 371 | div.error, 372 | div.error-list, 373 | label.error, 374 | input.error, 375 | select.error, 376 | textarea.error { 377 | color: #D95C5C !important; 378 | border-color: #D95C5C !important; 379 | } 380 | 381 | 382 | .validation-form-container .error .corner.label { 383 | border-color: #D95C5C; 384 | color: #FFFFFF; 385 | } -------------------------------------------------------------------------------- /dist/jquery.validation.min.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * jQuery Form Validation 3 | * Copyright (C) 2015 RunningCoder.org 4 | * Licensed under the MIT license 5 | * 6 | * @author Tom Bertrand 7 | * @version 1.5.3 (2015-12-02) 8 | * @link http://www.runningcoder.org/jqueryvalidation/ 9 | */ 10 | !function(window,document,$,undefined){function _buildRegexFromString(a){function b(){}if(!a||"string"!=typeof a&&!(a instanceof RegExp))return b(),!1;"string"!=typeof a&&(a=a.toString());for(var c,d,e,f=a.charAt(0),g=a.length-1;g>0&&/[gimsxeU]/.test(a.charAt(g));)g--;a.charAt(g)!==f&&(f=null),f&&g!==a.length-1&&(d=a.substr(g+1,a.length-1)),c=f?a.substr(1,g-1):a;try{e=new RegExp(c,d)}catch(h){return b(),!1}return e}function isEmpty(a){for(var b in a)if(a.hasOwnProperty(b))return!1;return!0}window.Validation={form:[],labels:{},hasScrolled:!1},"function"!=typeof Object.preventExtensions&&(Object.preventExtensions=function(a){return a});var _rules={NOTEMPTY:/\S/,INTEGER:/^\d+$/,NUMERIC:/^\d+(?:[,\s]\d{3})*(?:\.\d+)?$/,MIXED:/^[\w\s-]+$/,NAME:/^['a-zãàáäâẽèéëêìíïîõòóöôùúüûñç\s-]+$/i,NOSPACE:/^(?!\s)\S*$/,TRIM:/^[^\s].*[^\s]$/,DATE:/^\d{4}-\d{2}-\d{2}(\s\d{2}:\d{2}(:\d{2})?)?$/,EMAIL:/^([^@]+?)@(([a-z0-9]-*)*[a-z0-9]+\.)+([a-z0-9]+)$/i,URL:/^(https?:\/\/)?((([a-z0-9]-*)*[a-z0-9]+\.?)*([a-z0-9]+))(\/[\w?=\.-]*)*$/,PHONE:/^(\()?\d{3}(\))?(-|\s)?\d{3}(-|\s)\d{4}$/,OPTIONAL:/\S/,COMPARISON:/^\s*([LV])\s*([<>]=?|==|!=)\s*([^<>=!]+?)\s*$/},_messages={"default":"$ contain error(s).",NOTEMPTY:"$ must not be empty.",INTEGER:"$ must be an integer.",NUMERIC:"$ must be numeric.",MIXED:"$ must be letters or numbers (no special characters).",NAME:"$ must not contain special characters.",NOSPACE:"$ must not contain spaces.",TRIM:"$ must not start or end with space character.",DATE:"$ is not a valid with format YYYY-MM-DD.",EMAIL:"$ is not valid.",URL:"$ is not valid.",PHONE:"$ is not a valid phone number.","<":"$ must be less than % characters.","<=":"$ must be less or equal to % characters.",">":"$ must be greater than % characters.",">=":"$ must be greater or equal to % characters.","==":"$ must be equal to %","!=":"$ must be different than %"},_data={validation:"data-validation",validationMessage:"data-validation-message",regex:"data-validation-regex",regexReverse:"data-validation-regex-reverse",regexMessage:"data-validation-regex-message",group:"data-validation-group",label:"data-validation-label",errorList:"data-error-list"},_options={submit:{settings:{form:null,display:"inline",insertion:"append",allErrors:!1,trigger:"click",button:"[type='submit']",errorClass:"error",errorListClass:"error-list",errorListContainer:null,errorTemplate:null,inputContainer:null,clear:"focusin",scrollToError:!1},callback:{onInit:null,onValidate:null,onError:null,onBeforeSubmit:null,onSubmit:null,onAfterSubmit:null}},dynamic:{settings:{trigger:null,delay:300},callback:{onSuccess:null,onError:null,onComplete:null}},rules:{},messages:{},labels:{},debug:!1},_supported={submit:{settings:{display:["inline","block"],insertion:["append","prepend"],allErrors:[!0,!1],clear:["focusin","keypress",!1],trigger:["click","dblclick","focusout","hover","mousedown","mouseenter","mouseleave","mousemove","mouseout","mouseover","mouseup","toggle"]}},dynamic:{settings:{trigger:["focusout","keydown","keypress","keyup"]}},debug:[!0,!1]},Validation=function(node,options){function extendRules(){options.rules=$.extend(!0,{},_rules,options.rules)}function extendMessages(){options.messages=$.extend(!0,{},_messages,options.messages)}function extendOptions(){options instanceof Object||(options={});var a=Object.preventExtensions($.extend(!0,{},_options));for(var b in options)if(options.hasOwnProperty(b)&&"debug"!==b)if(~["labels","messages","rules"].indexOf(b)&&options[b]instanceof Object)a[b]=options[b];else if(_options[b]&&options[b]instanceof Object)for(var c in options[b])if(options[b].hasOwnProperty(c)&&_options[b][c]&&options[b][c]instanceof Object){for(var d in options[b][c])options[b][c].hasOwnProperty(d)&&_supported[b]&&_supported[b][c]&&_supported[b][c][d]&&-1===$.inArray(options[b][c][d],_supported[b][c][d])&&delete options[b][c][d];a[b]&&a[b][c]&&(a[b][c]=$.extend(Object.preventExtensions(a[b][c]),options[b][c]))}a.dynamic.settings.trigger&&"keypress"===a.dynamic.settings.trigger&&"keypress"===a.submit.settings.clear&&(a.dynamic.settings.trigger="keydown"),options=a}function delegateDynamicValidation(){if(!options.dynamic.settings.trigger)return!1;if(!node.find("["+_data.validation+"],["+_data.regex+"]")[0])return!1;var a=options.dynamic.settings.trigger+delegateSuffix;"focusout"!==options.dynamic.settings.trigger&&(a+=" change"+delegateSuffix+" paste"+delegateSuffix),$.each(node.find("["+_data.validation+"],["+_data.regex+"]"),function(b,c){$(c).unbind(a).on(a,function(a){if($(this).is(":disabled"))return!1;var b=this,c=a.keyCode||null;_typeWatch(function(){validateInput(b)?_executeCallback(options.dynamic.callback.onSuccess,[node,b,c]):(displayOneError(b.name),_executeCallback(options.dynamic.callback.onError,[node,b,c,errors[b.name]])),_executeCallback(options.dynamic.callback.onComplete,[node,b,c])},options.dynamic.settings.delay)})})}function delegateValidation(){_executeCallback(options.submit.callback.onInit,[node]);var a=options.submit.settings.trigger+".vd";return node.find(options.submit.settings.button)[0]?(node.on("submit",!1),void node.find(options.submit.settings.button).off(".vd").on(a,function(a){return a.preventDefault(),resetErrors(),_executeCallback(options.submit.callback.onValidate,[node]),validateForm()?(_executeCallback(options.submit.callback.onBeforeSubmit,[node]),"function"==typeof options.submit.callback.onSubmit?_executeCallback(options.submit.callback.onSubmit,[node,formData])===!0&&submitForm():submitForm(),_executeCallback(options.submit.callback.onAfterSubmit,[node])):(displayErrors(),_executeCallback(options.submit.callback.onError,[node,errors,formData])),!1})):!1}function validateForm(){var a=isEmpty(errors);return formData={},$.each(node.find('input:not([type="submit"]), select, textarea').not(":disabled"),function(b,c){c=$(c);var d=_getInputValue(c[0]),e=c.attr("name");e&&(/\[]$/.test(e)?(e=e.replace(/\[]$/,""),formData[e]instanceof Array||(formData[e]=[]),formData[e].push(d)):formData[e]=d),(c.attr(_data.validation)||c.attr(_data.regex))&&(validateInput(c[0],d)||(a=!1))}),prepareFormData(),a}function prepareFormData(){var a,b,c={};for(var d in formData)if(formData.hasOwnProperty(d)){b=0,a=d.split(/\[(.+?)]/g);for(var e={},f=[],g=a.length-1;g>=0;g--)""!==a[g]?(f.length<1?e[a[g]]=Number(formData[d])||formData[d]:(e={},e[a[g]]=f[f.length-1]),f.push(e)):a.splice(g,1);c=$.extend(!0,c,e)}formData=c}function validateInput(a,b){var c=$(a).attr("name"),b=b||_getInputValue(a);if(!c)return!1;var d=c.replace(/]$/,"").split(/]\[|[[\]]/g),e=window.Validation.labels[c]||options.labels[c]||$(a).attr(_data.label)||d[d.length-1],f=$(a).attr(_data.validation),g=$(a).attr(_data.validationMessage),h=$(a).attr(_data.regex),i=!($(a).attr(_data.regexReverse)===undefined),j=$(a).attr(_data.regexMessage),k=!1;if(f&&(f=_api._splitValidation(f)),f instanceof Array&&f.length>0){if(""===$.trim(b)&&~f.indexOf("OPTIONAL"))return!0;$.each(f,function(a,d){if(k===!0)return!0;try{validateRule(b,d)}catch(f){(g||!options.submit.settings.allErrors)&&(k=!0),f[0]=g||f[0],registerError(c,f[0].replace("$",e).replace("%",f[1]))}})}if(h){var l=_buildRegexFromString(h);if(!(l instanceof RegExp))return!0;try{validateRule(b,l,i)}catch(m){m[0]=j||m[0],registerError(c,m[0].replace("$",e))}}return!errors[c]||errors[c]instanceof Array&&0===errors[c].length}function validateRule(value,rule,reversed){if(rule instanceof RegExp){var isValid=rule.test(value);if(reversed&&(isValid=!isValid),!isValid)throw[options.messages["default"],""]}else if(options.rules[rule]){if(!options.rules[rule].test(value))throw[options.messages[rule],""]}else{var comparison=rule.match(options.rules.COMPARISON);if(comparison&&4===comparison.length){var type=comparison[1],operator=comparison[2],compared=comparison[3],comparedValue;switch(type){case"L":if(isNaN(compared))return!1;if(!value||eval(value.length+operator+parseFloat(compared))===!1)throw[options.messages[operator],compared];break;case"V":default:if(isNaN(compared)){if(comparedValue=node.find('[name="'+compared+'"]').val(),!comparedValue)return!1;if(!value||!eval('"'+encodeURIComponent(value)+'"'+operator+'"'+encodeURIComponent(comparedValue)+'"'))throw[options.messages[operator].replace(" characters",""),compared]}else if(!value||isNaN(value)||!eval(value+operator+parseFloat(compared)))throw[options.messages[operator].replace(" characters",""),compared]}}}}function registerError(a,b){errors[a]||(errors[a]=[]),b=b.capitalize();for(var c=!1,d=0;d";if(!errors.hasOwnProperty(a))return!1;if(b=node.find('[name="'+a+'"]'),e=null,!b[0])return!1;if(f=b.attr(_data.group),f?(g=node.find('[name="'+a+'"]'),e=node.find('[id="'+f+'"]'),e[0]&&(e.addClass(options.submit.settings.errorClass),d=e)):(b.addClass(options.submit.settings.errorClass),options.submit.settings.inputContainer&&b.parentsUntil(node,options.submit.settings.inputContainer).addClass(options.submit.settings.errorClass),c=b.attr("id"),c&&(e=node.find('label[for="'+c+'"]')[0]),e||(e=b.parentsUntil(node,"label")[0]),e&&(e=$(e),e.addClass(options.submit.settings.errorClass))),"inline"===options.submit.settings.display?d=options.submit.settings.errorListContainer?b.parentsUntil(node,options.submit.settings.errorListContainer):d||b.parent():"block"===options.submit.settings.display&&(d=node),"inline"===options.submit.settings.display&&d.find("["+_data.errorList+"]")[0])return!1;("inline"===options.submit.settings.display||"block"===options.submit.settings.display&&!d.find("["+_data.errorList+"]")[0])&&("append"===options.submit.settings.insertion?d.append(h):"prepend"===options.submit.settings.insertion&&d.prepend(h));for(var i=0;i"+options.submit.settings.errorTemplate.replace("{{validation-message}}",errors[a][i])+"":"
  • "+errors[a][i]+"
  • ");if(options.submit.settings.clear||options.dynamic.settings.trigger){f&&g&&(b=g);var j="coucou"+resetSuffix;options.submit.settings.clear&&(j+=" "+options.submit.settings.clear+resetSuffix,~["radio","checkbox"].indexOf(b[0].type)&&(j+=" change"+resetSuffix)),options.dynamic.settings.trigger&&(j+=" "+options.dynamic.settings.trigger+resetSuffix,"focusout"===options.dynamic.settings.trigger||~["radio","checkbox"].indexOf(b[0].type)||(j+=" change"+resetSuffix+" paste"+resetSuffix)),b.unbind(j).on(j,function(a,b,c,d,e){return function(){e?$(c).hasClass(options.submit.settings.errorClass)&&resetOneError(a,b,c,d,e):$(b).hasClass(options.submit.settings.errorClass)&&resetOneError(a,b,c,d)}}(a,b,e,d,f))}if(options.submit.settings.scrollToError&&!window.Validation.hasScrolled){window.Validation.hasScrolled=!0;var k=parseFloat(options.submit.settings.scrollToError.offset)||0,l=parseFloat(options.submit.settings.scrollToError.duration)||500,m="block"===options.submit.settings.display?d:b;$("html, body").animate({scrollTop:m.offset().top+k},l)}}function displayErrors(){for(var a in errors)errors.hasOwnProperty(a)&&displayOneError(a)}function resetOneError(a,b,c,d,e){if(delete errors[a],d)options.submit.settings.inputContainer&&(e?c:b).parentsUntil(node,options.submit.settings.inputContainer).removeClass(options.submit.settings.errorClass),c&&c.removeClass(options.submit.settings.errorClass),b.removeClass(options.submit.settings.errorClass),"inline"===options.submit.settings.display&&d.find("["+_data.errorList+"]").remove();else{if(!b&&(b=node.find('[name="'+a+'"]'),!b[0]))return!1;b.trigger("coucou"+resetSuffix)}}function resetErrors(){errors=[],window.Validation.hasScrolled=!1,node.find("["+_data.errorList+"]").remove(),node.find("."+options.submit.settings.errorClass).removeClass(options.submit.settings.errorClass)}function submitForm(){node[0].submit()}function destroy(){return resetErrors(),node.find("["+_data.validation+"],["+_data.regex+"]").off(delegateSuffix+" "+resetSuffix),node.find(options.submit.settings.button).off(delegateSuffix).on("click"+delegateSuffix,function(){$(this).closest("form")[0].submit()}),!0}var errors=[],messages={},formData={},delegateSuffix=".vd",resetSuffix=".vr";window.Validation.hasScrolled=!1;var _getInputValue=function(a){var b;switch($(a).attr("type")){case"checkbox":b=$(a).is(":checked")?1:"";break;case"radio":b=node.find('input[name="'+$(a).attr("name")+'"]:checked').val()||"";break;default:b=$(a).val()}return b},_typeWatch=function(){var a=0;return function(b,c){clearTimeout(a),a=setTimeout(b,c)}}(),_executeCallback=function(a,b){if(!a)return!1;var c;if("function"==typeof a)c=a;else if("string"==typeof a||a instanceof Array){c=window,"string"==typeof a&&(a=[a,[]]);for(var d=a[0].split("."),e=a[1],f=!0,g=0;g>>0,c=Number(arguments[1])||0;for(c=0>c?Math.ceil(c):Math.floor(c),0>c&&(c+=b);b>c;c++)if(c in this&&this[c]===a)return c;return-1})}(window,document,window.jQuery); -------------------------------------------------------------------------------- /dist/jquery.validation.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * jQuery Form Validation 3 | * Copyright (C) 2015 RunningCoder.org 4 | * Licensed under the MIT license 5 | * 6 | * @author Tom Bertrand 7 | * @version 1.5.3 (2015-12-02) 8 | * @link http://www.runningcoder.org/jqueryvalidation/ 9 | */ 10 | ; 11 | (function(window, document, $, undefined) { 12 | 13 | window.Validation = { 14 | form: [], 15 | labels: {}, 16 | hasScrolled: false 17 | }; 18 | 19 | if (typeof Object.preventExtensions !== "function") { 20 | Object.preventExtensions = function(obj) { 21 | return obj; 22 | }; 23 | } 24 | var _rules = { 25 | NOTEMPTY: /\S/, 26 | INTEGER: /^\d+$/, 27 | NUMERIC: /^\d+(?:[,\s]\d{3})*(?:\.\d+)?$/, 28 | MIXED: /^[\w\s-]+$/, 29 | NAME: /^['a-zãàáäâẽèéëêìíïîõòóöôùúüûñç\s-]+$/i, 30 | NOSPACE: /^(?!\s)\S*$/, 31 | TRIM: /^[^\s].*[^\s]$/, 32 | DATE: /^\d{4}-\d{2}-\d{2}(\s\d{2}:\d{2}(:\d{2})?)?$/, 33 | EMAIL: /^([^@]+?)@(([a-z0-9]-*)*[a-z0-9]+\.)+([a-z0-9]+)$/i, 34 | URL: /^(https?:\/\/)?((([a-z0-9]-*)*[a-z0-9]+\.?)*([a-z0-9]+))(\/[\w?=\.-]*)*$/, 35 | PHONE: /^(\()?\d{3}(\))?(-|\s)?\d{3}(-|\s)\d{4}$/, 36 | OPTIONAL: /\S/, 37 | COMPARISON: /^\s*([LV])\s*([<>]=?|==|!=)\s*([^<>=!]+?)\s*$/ 38 | }; 39 | 40 | var _messages = { 41 | 'default': '$ contain error(s).', 42 | 'NOTEMPTY': '$ must not be empty.', 43 | 'INTEGER': '$ must be an integer.', 44 | 'NUMERIC': '$ must be numeric.', 45 | 'MIXED': '$ must be letters or numbers (no special characters).', 46 | 'NAME': '$ must not contain special characters.', 47 | 'NOSPACE': '$ must not contain spaces.', 48 | 'TRIM': '$ must not start or end with space character.', 49 | 'DATE': '$ is not a valid with format YYYY-MM-DD.', 50 | 'EMAIL': '$ is not valid.', 51 | 'URL': '$ is not valid.', 52 | 'PHONE': '$ is not a valid phone number.', 53 | '<': '$ must be less than % characters.', 54 | '<=': '$ must be less or equal to % characters.', 55 | '>': '$ must be greater than % characters.', 56 | '>=': '$ must be greater or equal to % characters.', 57 | '==': '$ must be equal to %', 58 | '!=': '$ must be different than %' 59 | }; 60 | 61 | var _data = { 62 | validation: 'data-validation', 63 | validationMessage: 'data-validation-message', 64 | regex: 'data-validation-regex', 65 | regexReverse: 'data-validation-regex-reverse', 66 | regexMessage: 'data-validation-regex-message', 67 | group: 'data-validation-group', 68 | label: 'data-validation-label', 69 | errorList: 'data-error-list' 70 | } 71 | 72 | var _options = { 73 | submit: { 74 | settings: { 75 | form: null, 76 | display: "inline", 77 | insertion: "append", 78 | allErrors: false, 79 | trigger: "click", 80 | button: "[type='submit']", 81 | errorClass: "error", 82 | errorListClass: "error-list", 83 | errorListContainer: null, 84 | errorTemplate: null, 85 | inputContainer: null, 86 | clear: "focusin", 87 | scrollToError: false 88 | }, 89 | callback: { 90 | onInit: null, 91 | onValidate: null, 92 | onError: null, 93 | onBeforeSubmit: null, 94 | onSubmit: null, 95 | onAfterSubmit: null 96 | } 97 | }, 98 | dynamic: { 99 | settings: { 100 | trigger: null, 101 | delay: 300 102 | }, 103 | callback: { 104 | onSuccess: null, 105 | onError: null, 106 | onComplete: null 107 | } 108 | }, 109 | rules: {}, 110 | messages: {}, 111 | labels: {}, 112 | debug: false 113 | }; 114 | 115 | var _supported = { 116 | submit: { 117 | settings: { 118 | display: ["inline", "block"], 119 | insertion: ["append", "prepend"], //"before", "insertBefore", "after", "insertAfter" 120 | allErrors: [true, false], 121 | clear: ["focusin", "keypress", false], 122 | trigger: [ 123 | "click", "dblclick", "focusout", 124 | "hover", "mousedown", "mouseenter", 125 | "mouseleave", "mousemove", "mouseout", 126 | "mouseover", "mouseup", "toggle" 127 | ] 128 | } 129 | }, 130 | dynamic: { 131 | settings: { 132 | trigger: ["focusout", "keydown", "keypress", "keyup"] 133 | } 134 | }, 135 | debug: [true, false] 136 | }; 137 | 138 | var Validation = function(node, options) { 139 | 140 | var errors = [], 141 | messages = {}, 142 | formData = {}, 143 | delegateSuffix = ".vd", // validation.delegate 144 | resetSuffix = ".vr"; // validation.resetError 145 | 146 | window.Validation.hasScrolled = false; 147 | 148 | function extendRules() { 149 | options.rules = $.extend( 150 | true, {}, 151 | _rules, 152 | options.rules 153 | ); 154 | } 155 | 156 | function extendMessages() { 157 | options.messages = $.extend( 158 | true, {}, 159 | _messages, 160 | options.messages 161 | ); 162 | } 163 | 164 | function extendOptions() { 165 | 166 | if (!(options instanceof Object)) { 167 | options = {}; 168 | } 169 | 170 | var tpmOptions = Object.preventExtensions($.extend(true, {}, _options)); 171 | 172 | for (var method in options) { 173 | 174 | if (!options.hasOwnProperty(method) || method === "debug") { 175 | continue; 176 | } 177 | 178 | if (~["labels", "messages", "rules"].indexOf(method) && options[method] instanceof Object) { 179 | tpmOptions[method] = options[method]; 180 | continue; 181 | } 182 | 183 | if (!_options[method] || !(options[method] instanceof Object)) { 184 | options.debug && window.Debug.log({ 185 | 'node': node, 186 | 'function': 'extendOptions()', 187 | 'arguments': '{' + method + ': ' + JSON.stringify(options[method]) + '}', 188 | 'message': 'WARNING - ' + method + ' - invalid option' 189 | }); 190 | 191 | continue; 192 | } 193 | 194 | for (var type in options[method]) { 195 | if (!options[method].hasOwnProperty(type)) { 196 | continue; 197 | } 198 | 199 | if (!_options[method][type] || !(options[method][type] instanceof Object)) { 200 | options.debug && window.Debug.log({ 201 | 'node': node, 202 | 'function': 'extendOptions()', 203 | 'arguments': '{' + type + ': ' + JSON.stringify(options[method][type]) + '}', 204 | 'message': 'WARNING - ' + type + ' - invalid option' 205 | }); 206 | 207 | continue; 208 | } 209 | 210 | for (var option in options[method][type]) { 211 | if (!options[method][type].hasOwnProperty(option)) { 212 | continue; 213 | } 214 | 215 | if (_supported[method] && 216 | _supported[method][type] && 217 | _supported[method][type][option] && 218 | $.inArray(options[method][type][option], _supported[method][type][option]) === -1) { 219 | options.debug && window.Debug.log({ 220 | 'node': node, 221 | 'function': 'extendOptions()', 222 | 'arguments': '{' + option + ': ' + JSON.stringify(options[method][type][option]) + '}', 223 | 'message': 'WARNING - ' + option.toString() + ': ' + JSON.stringify(options[method][type][option]) + ' - unsupported option' 224 | }); 225 | 226 | delete options[method][type][option]; 227 | } 228 | 229 | } 230 | if (tpmOptions[method] && tpmOptions[method][type]) { 231 | tpmOptions[method][type] = $.extend(Object.preventExtensions(tpmOptions[method][type]), options[method][type]); 232 | } 233 | } 234 | } 235 | if (options.debug && $.inArray(options.debug, _supported.debug !== -1)) { 236 | tpmOptions.debug = options.debug; 237 | } 238 | if (tpmOptions.dynamic.settings.trigger) { 239 | if (tpmOptions.dynamic.settings.trigger === "keypress" && tpmOptions.submit.settings.clear === "keypress") { 240 | tpmOptions.dynamic.settings.trigger = "keydown"; 241 | } 242 | } 243 | 244 | options = tpmOptions; 245 | 246 | } 247 | 248 | function delegateDynamicValidation() { 249 | 250 | if (!options.dynamic.settings.trigger) { 251 | return false; 252 | } 253 | options.debug && window.Debug.log({ 254 | 'node': node, 255 | 'function': 'delegateDynamicValidation()', 256 | 'message': 'OK - Dynamic Validation activated on ' + node.length + ' form(s)' 257 | }); 258 | 259 | if (!node.find('[' + _data.validation + '],[' + _data.regex + ']')[0]) { 260 | options.debug && window.Debug.log({ 261 | 'node': node, 262 | 'function': 'delegateDynamicValidation()', 263 | 'arguments': 'node.find([' + _data.validation + '],[' + _data.regex + '])', 264 | 'message': 'ERROR - [' + _data.validation + '] not found' 265 | }); 266 | 267 | return false; 268 | } 269 | 270 | var event = options.dynamic.settings.trigger + delegateSuffix; 271 | if (options.dynamic.settings.trigger !== "focusout") { 272 | event += " change" + delegateSuffix + " paste" + delegateSuffix; 273 | } 274 | 275 | $.each( 276 | node.find('[' + _data.validation + '],[' + _data.regex + ']'), 277 | function(index, input) { 278 | 279 | $(input).unbind(event).on(event, function(e) { 280 | 281 | if ($(this).is(':disabled')) { 282 | return false; 283 | } 284 | 285 | var input = this, 286 | keyCode = e.keyCode || null; 287 | 288 | _typeWatch(function() { 289 | 290 | if (!validateInput(input)) { 291 | 292 | displayOneError(input.name); 293 | _executeCallback(options.dynamic.callback.onError, [node, input, keyCode, errors[input.name]]); 294 | 295 | } else { 296 | 297 | _executeCallback(options.dynamic.callback.onSuccess, [node, input, keyCode]); 298 | 299 | } 300 | 301 | _executeCallback(options.dynamic.callback.onComplete, [node, input, keyCode]); 302 | 303 | }, options.dynamic.settings.delay); 304 | 305 | }); 306 | } 307 | ); 308 | } 309 | 310 | function delegateValidation() { 311 | 312 | _executeCallback(options.submit.callback.onInit, [node]); 313 | 314 | var event = options.submit.settings.trigger + '.vd'; 315 | options.debug && window.Debug.log({ 316 | 'node': node, 317 | 'function': 'delegateValidation()', 318 | 'message': 'OK - Validation activated on ' + node.length + ' form(s)' 319 | }); 320 | 321 | if (!node.find(options.submit.settings.button)[0]) { 322 | options.debug && window.Debug.log({ 323 | 'node': node, 324 | 'function': 'delegateValidation()', 325 | 'arguments': '{button: ' + options.submit.settings.button + '}', 326 | 'message': 'ERROR - node.find("' + options.submit.settings.button + '") not found' 327 | }); 328 | 329 | return false; 330 | 331 | } 332 | 333 | node.on("submit", false); 334 | node.find(options.submit.settings.button).off('.vd').on(event, function(e) { 335 | 336 | e.preventDefault(); 337 | 338 | resetErrors(); 339 | 340 | _executeCallback(options.submit.callback.onValidate, [node]); 341 | 342 | if (!validateForm()) { 343 | 344 | displayErrors(); 345 | _executeCallback(options.submit.callback.onError, [node, errors, formData]); 346 | 347 | } else { 348 | 349 | _executeCallback(options.submit.callback.onBeforeSubmit, [node]); 350 | 351 | if (typeof options.submit.callback.onSubmit === "function") { 352 | if (_executeCallback(options.submit.callback.onSubmit, [node, formData]) === true) { 353 | submitForm(); 354 | } 355 | } else { 356 | submitForm(); 357 | } 358 | 359 | _executeCallback(options.submit.callback.onAfterSubmit, [node]); 360 | 361 | } 362 | options.debug && window.Debug.print(); 363 | 364 | return false; 365 | 366 | }); 367 | 368 | } 369 | 370 | function validateForm() { 371 | 372 | var isValid = isEmpty(errors); 373 | 374 | formData = {}; 375 | 376 | $.each( 377 | node.find('input:not([type="submit"]), select, textarea').not(':disabled'), 378 | function(index, input) { 379 | 380 | input = $(input); 381 | 382 | var value = _getInputValue(input[0]), 383 | inputName = input.attr('name'); 384 | 385 | if (inputName) { 386 | if (/\[]$/.test(inputName)) { 387 | inputName = inputName.replace(/\[]$/, ''); 388 | if (!(formData[inputName] instanceof Array)) { 389 | formData[inputName] = []; 390 | } 391 | formData[inputName].push(value) 392 | } else { 393 | formData[inputName] = value; 394 | } 395 | } 396 | 397 | if (!!input.attr(_data.validation) || !!input.attr(_data.regex)) { 398 | if (!validateInput(input[0], value)) { 399 | isValid = false; 400 | } 401 | } 402 | } 403 | ); 404 | 405 | prepareFormData(); 406 | 407 | return isValid; 408 | 409 | } 410 | 411 | function prepareFormData() { 412 | 413 | var data = {}, 414 | matches, 415 | index; 416 | 417 | for (var i in formData) { 418 | if (!formData.hasOwnProperty(i)) continue; 419 | 420 | index = 0; 421 | matches = i.split(/\[(.+?)]/g); 422 | 423 | var tmpObject = {}, 424 | tmpArray = []; 425 | 426 | for (var k = matches.length - 1; k >= 0; k--) { 427 | if (matches[k] === "") { 428 | matches.splice(k, 1); 429 | continue; 430 | } 431 | 432 | if (tmpArray.length < 1) { 433 | tmpObject[matches[k]] = Number(formData[i]) || formData[i]; 434 | } else { 435 | tmpObject = {}; 436 | tmpObject[matches[k]] = tmpArray[tmpArray.length - 1]; 437 | } 438 | tmpArray.push(tmpObject) 439 | 440 | } 441 | 442 | data = $.extend(true, data, tmpObject); 443 | 444 | } 445 | 446 | formData = data; 447 | 448 | } 449 | 450 | function validateInput(input, value) { 451 | 452 | var inputName = $(input).attr('name'), 453 | value = value || _getInputValue(input); 454 | 455 | if (!inputName) { 456 | options.debug && window.Debug.log({ 457 | 'node': node, 458 | 'function': 'validateInput()', 459 | 'arguments': '$(input).attr("name")', 460 | 'message': 'ERROR - Missing input [name]' 461 | }); 462 | 463 | return false; 464 | } 465 | 466 | var matches = inputName.replace(/]$/, '').split(/]\[|[[\]]/g), 467 | inputShortName = window.Validation.labels[inputName] || 468 | options.labels[inputName] || 469 | $(input).attr(_data.label) || 470 | matches[matches.length - 1], 471 | 472 | validationArray = $(input).attr(_data.validation), 473 | validationMessage = $(input).attr(_data.validationMessage), 474 | validationRegex = $(input).attr(_data.regex), 475 | validationRegexReverse = !($(input).attr(_data.regexReverse) === undefined), 476 | validationRegexMessage = $(input).attr(_data.regexMessage), 477 | 478 | validateOnce = false; 479 | 480 | if (validationArray) { 481 | validationArray = _api._splitValidation(validationArray); 482 | } 483 | if (validationArray instanceof Array && validationArray.length > 0) { 484 | if ($.trim(value) === '' && ~validationArray.indexOf('OPTIONAL')) { 485 | return true; 486 | } 487 | 488 | $.each(validationArray, function(i, rule) { 489 | 490 | if (validateOnce === true) { 491 | return true; 492 | } 493 | 494 | try { 495 | 496 | validateRule(value, rule); 497 | 498 | } catch (error) { 499 | 500 | if (validationMessage || !options.submit.settings.allErrors) { 501 | validateOnce = true; 502 | } 503 | 504 | error[0] = validationMessage || error[0]; 505 | 506 | registerError(inputName, error[0].replace('$', inputShortName).replace('%', error[1])); 507 | 508 | } 509 | 510 | }); 511 | 512 | } 513 | if (validationRegex) { 514 | 515 | var rule = _buildRegexFromString(validationRegex); 516 | if (!(rule instanceof RegExp)) { 517 | return true; 518 | } 519 | 520 | try { 521 | 522 | validateRule(value, rule, validationRegexReverse); 523 | 524 | } catch (error) { 525 | 526 | error[0] = validationRegexMessage || error[0]; 527 | 528 | registerError(inputName, error[0].replace('$', inputShortName)); 529 | 530 | } 531 | 532 | } 533 | 534 | return !errors[inputName] || errors[inputName] instanceof Array && errors[inputName].length === 0; 535 | 536 | } 537 | 538 | function validateRule(value, rule, reversed) { 539 | if (rule instanceof RegExp) { 540 | var isValid = rule.test(value); 541 | 542 | if (reversed) { 543 | isValid = !isValid; 544 | } 545 | 546 | if (!isValid) { 547 | throw [options.messages['default'], '']; 548 | } 549 | return; 550 | } 551 | 552 | if (options.rules[rule]) { 553 | if (!options.rules[rule].test(value)) { 554 | throw [options.messages[rule], '']; 555 | } 556 | return; 557 | } 558 | var comparison = rule.match(options.rules.COMPARISON); 559 | 560 | if (!comparison || comparison.length !== 4) { 561 | options.debug && window.Debug.log({ 562 | 'node': node, 563 | 'function': 'validateRule()', 564 | 'arguments': 'value: ' + value + ' rule: ' + rule, 565 | 'message': 'WARNING - Invalid comparison' 566 | }); 567 | 568 | return; 569 | } 570 | 571 | var type = comparison[1], 572 | operator = comparison[2], 573 | compared = comparison[3], 574 | comparedValue; 575 | 576 | switch (type) { 577 | case "L": 578 | if (isNaN(compared)) { 579 | options.debug && window.Debug.log({ 580 | 'node': node, 581 | 'function': 'validateRule()', 582 | 'arguments': 'compare: ' + compared + ' rule: ' + rule, 583 | 'message': 'WARNING - Invalid rule, "L" compare must be numeric' 584 | }); 585 | 586 | return false; 587 | 588 | } else { 589 | 590 | if (!value || eval(value.length + operator + parseFloat(compared)) === false) { 591 | throw [options.messages[operator], compared]; 592 | } 593 | 594 | } 595 | 596 | break; 597 | case "V": 598 | default: 599 | if (isNaN(compared)) { 600 | 601 | comparedValue = node.find('[name="' + compared + '"]').val(); 602 | if (!comparedValue) { 603 | options.debug && window.Debug.log({ 604 | 'node': node, 605 | 'function': 'validateRule()', 606 | 'arguments': 'compare: ' + compared + ' rule: ' + rule, 607 | 'message': 'WARNING - Unable to find compared field [name="' + compared + '"]' 608 | }); 609 | 610 | return false; 611 | } 612 | 613 | if (!value || !eval('"' + encodeURIComponent(value) + '"' + operator + '"' + encodeURIComponent(comparedValue) + '"')) { 614 | throw [options.messages[operator].replace(' characters', ''), compared]; 615 | } 616 | 617 | } else { 618 | if (!value || isNaN(value) || !eval(value + operator + parseFloat(compared))) { 619 | throw [options.messages[operator].replace(' characters', ''), compared]; 620 | } 621 | 622 | } 623 | break; 624 | 625 | } 626 | 627 | } 628 | 629 | function registerError(inputName, error) { 630 | 631 | if (!errors[inputName]) { 632 | errors[inputName] = []; 633 | } 634 | 635 | error = error.capitalize(); 636 | 637 | var hasError = false; 638 | for (var i = 0; i < errors[inputName].length; i++) { 639 | if (errors[inputName][i] === error) { 640 | hasError = true; 641 | break; 642 | } 643 | } 644 | 645 | if (!hasError) { 646 | errors[inputName].push(error); 647 | } 648 | 649 | } 650 | 651 | function displayOneError(inputName) { 652 | 653 | var input, 654 | inputId, 655 | errorContainer, 656 | label, 657 | html = '
      ', 658 | group, 659 | groupInput; 660 | 661 | if (!errors.hasOwnProperty(inputName)) { 662 | return false; 663 | } 664 | 665 | input = node.find('[name="' + inputName + '"]'); 666 | 667 | label = null; 668 | 669 | if (!input[0]) { 670 | options.debug && window.Debug.log({ 671 | 'node': node, 672 | 'function': 'displayOneError()', 673 | 'arguments': '[name="' + inputName + '"]', 674 | 'message': 'ERROR - Unable to find input by name "' + inputName + '"' 675 | }); 676 | 677 | return false; 678 | } 679 | 680 | group = input.attr(_data.group); 681 | 682 | if (group) { 683 | 684 | groupInput = node.find('[name="' + inputName + '"]'); 685 | label = node.find('[id="' + group + '"]'); 686 | 687 | if (label[0]) { 688 | label.addClass(options.submit.settings.errorClass); 689 | errorContainer = label; 690 | } 691 | 692 | } else { 693 | 694 | input.addClass(options.submit.settings.errorClass); 695 | 696 | if (options.submit.settings.inputContainer) { 697 | input.parentsUntil(node, options.submit.settings.inputContainer).addClass(options.submit.settings.errorClass); 698 | } 699 | 700 | inputId = input.attr('id'); 701 | 702 | if (inputId) { 703 | label = node.find('label[for="' + inputId + '"]')[0]; 704 | } 705 | 706 | if (!label) { 707 | label = input.parentsUntil(node, 'label')[0]; 708 | } 709 | 710 | if (label) { 711 | label = $(label); 712 | label.addClass(options.submit.settings.errorClass); 713 | } 714 | } 715 | 716 | if (options.submit.settings.display === 'inline') { 717 | if (options.submit.settings.errorListContainer) { 718 | errorContainer = input.parentsUntil(node, options.submit.settings.errorListContainer); 719 | } else { 720 | errorContainer = errorContainer || input.parent(); 721 | } 722 | } else if (options.submit.settings.display === 'block') { 723 | errorContainer = node; 724 | } 725 | if (options.submit.settings.display === 'inline' && errorContainer.find('[' + _data.errorList + ']')[0]) { 726 | return false; 727 | } 728 | 729 | if (options.submit.settings.display === "inline" || 730 | (options.submit.settings.display === "block" && !errorContainer.find('[' + _data.errorList + ']')[0]) 731 | ) { 732 | if (options.submit.settings.insertion === 'append') { 733 | errorContainer.append(html); 734 | } else if (options.submit.settings.insertion === 'prepend') { 735 | errorContainer.prepend(html); 736 | } 737 | } 738 | 739 | for (var i = 0; i < errors[inputName].length; i++) { 740 | if (options.submit.settings.errorTemplate) { 741 | errorContainer.find('[' + _data.errorList + '] ul') 742 | .append('
    • ' + options.submit.settings.errorTemplate.replace('{{validation-message}}', errors[inputName][i]) + '
    • '); 743 | } else { 744 | errorContainer.find('[' + _data.errorList + '] ul').append('
    • ' + errors[inputName][i] + '
    • '); 745 | } 746 | } 747 | 748 | if (options.submit.settings.clear || options.dynamic.settings.trigger) { 749 | 750 | if (group && groupInput) { 751 | input = groupInput; 752 | } 753 | 754 | var event = "coucou" + resetSuffix; 755 | if (options.submit.settings.clear) { 756 | event += " " + options.submit.settings.clear + resetSuffix; 757 | if (~['radio', 'checkbox'].indexOf(input[0].type)) { 758 | event += " change" + resetSuffix; 759 | } 760 | } 761 | if (options.dynamic.settings.trigger) { 762 | event += " " + options.dynamic.settings.trigger + resetSuffix; 763 | if (options.dynamic.settings.trigger !== "focusout" && !~['radio', 'checkbox'].indexOf(input[0].type)) { 764 | event += " change" + resetSuffix + " paste" + resetSuffix; 765 | } 766 | } 767 | 768 | input.unbind(event).on(event, function(a, b, c, d, e) { 769 | 770 | return function() { 771 | if (e) { 772 | if ($(c).hasClass(options.submit.settings.errorClass)) { 773 | resetOneError(a, b, c, d, e); 774 | } 775 | } else if ($(b).hasClass(options.submit.settings.errorClass)) { 776 | resetOneError(a, b, c, d); 777 | } 778 | }; 779 | 780 | }(inputName, input, label, errorContainer, group)); 781 | } 782 | 783 | if (options.submit.settings.scrollToError && !window.Validation.hasScrolled) { 784 | 785 | window.Validation.hasScrolled = true; 786 | 787 | var offset = parseFloat(options.submit.settings.scrollToError.offset) || 0, 788 | duration = parseFloat(options.submit.settings.scrollToError.duration) || 500, 789 | handle = (options.submit.settings.display === 'block') ? errorContainer : input; 790 | 791 | $('html, body').animate({ 792 | scrollTop: handle.offset().top + offset 793 | }, duration); 794 | 795 | } 796 | 797 | } 798 | 799 | function displayErrors() { 800 | 801 | for (var inputName in errors) { 802 | if (!errors.hasOwnProperty(inputName)) continue; 803 | displayOneError(inputName); 804 | } 805 | 806 | } 807 | 808 | function resetOneError(inputName, input, label, container, group) { 809 | 810 | delete errors[inputName]; 811 | 812 | if (container) { 813 | 814 | if (options.submit.settings.inputContainer) { 815 | (group ? label : input).parentsUntil(node, options.submit.settings.inputContainer).removeClass(options.submit.settings.errorClass); 816 | } 817 | 818 | label && label.removeClass(options.submit.settings.errorClass); 819 | 820 | input.removeClass(options.submit.settings.errorClass); 821 | 822 | if (options.submit.settings.display === 'inline') { 823 | container.find('[' + _data.errorList + ']').remove(); 824 | } 825 | 826 | } else { 827 | 828 | if (!input) { 829 | input = node.find('[name="' + inputName + '"]'); 830 | 831 | if (!input[0]) { 832 | options.debug && window.Debug.log({ 833 | 'node': node, 834 | 'function': 'resetOneError()', 835 | 'arguments': '[name="' + inputName + '"]', 836 | 'message': 'ERROR - Unable to find input by name "' + inputName + '"' 837 | }); 838 | 839 | return false; 840 | } 841 | } 842 | 843 | input.trigger('coucou' + resetSuffix); 844 | } 845 | 846 | } 847 | 848 | function resetErrors() { 849 | 850 | errors = []; 851 | window.Validation.hasScrolled = false; 852 | 853 | node.find('[' + _data.errorList + ']').remove(); 854 | node.find('.' + options.submit.settings.errorClass).removeClass(options.submit.settings.errorClass); 855 | 856 | } 857 | 858 | function submitForm() { 859 | 860 | node[0].submit() 861 | 862 | } 863 | 864 | function destroy() { 865 | 866 | resetErrors(); 867 | node.find('[' + _data.validation + '],[' + _data.regex + ']').off(delegateSuffix + ' ' + resetSuffix); 868 | 869 | node.find(options.submit.settings.button).off(delegateSuffix).on('click' + delegateSuffix, function() { 870 | $(this).closest('form')[0].submit(); 871 | }); 872 | 873 | return true; 874 | 875 | } 876 | 877 | var _getInputValue = function(input) { 878 | 879 | var value; 880 | switch ($(input).attr('type')) { 881 | case 'checkbox': 882 | value = ($(input).is(':checked')) ? 1 : ''; 883 | break; 884 | case 'radio': 885 | value = node.find('input[name="' + $(input).attr('name') + '"]:checked').val() || ''; 886 | break; 887 | default: 888 | value = $(input).val(); 889 | break; 890 | } 891 | 892 | return value; 893 | 894 | }; 895 | 896 | var _typeWatch = (function() { 897 | var timer = 0; 898 | return function(callback, ms) { 899 | clearTimeout(timer); 900 | timer = setTimeout(callback, ms); 901 | }; 902 | })(); 903 | 904 | var _executeCallback = function(callback, extraParams) { 905 | 906 | if (!callback) { 907 | return false; 908 | } 909 | 910 | var _callback; 911 | 912 | if (typeof callback === "function") { 913 | 914 | _callback = callback; 915 | 916 | } else if (typeof callback === "string" || callback instanceof Array) { 917 | 918 | _callback = window; 919 | 920 | if (typeof callback === "string") { 921 | callback = [callback, []]; 922 | } 923 | 924 | var _exploded = callback[0].split('.'), 925 | _params = callback[1], 926 | _isValid = true, 927 | _splitIndex = 0; 928 | 929 | while (_splitIndex < _exploded.length) { 930 | 931 | if (typeof _callback !== 'undefined') { 932 | _callback = _callback[_exploded[_splitIndex++]]; 933 | } else { 934 | _isValid = false; 935 | break; 936 | } 937 | } 938 | 939 | if (!_isValid || typeof _callback !== "function") { 940 | options.debug && window.Debug.log({ 941 | 'node': node, 942 | 'function': '_executeCallback()', 943 | 'arguments': JSON.stringify(callback), 944 | 'message': 'WARNING - Invalid callback function"' 945 | }); 946 | 947 | return false; 948 | } 949 | 950 | } 951 | 952 | return _callback.apply(this, $.merge(_params || [], (extraParams) ? extraParams : [])); 953 | 954 | }; 955 | 956 | this.__construct = function() { 957 | 958 | extendOptions(); 959 | extendRules(); 960 | extendMessages(); 961 | 962 | delegateDynamicValidation(); 963 | delegateValidation(); 964 | options.debug && window.Debug.print(); 965 | 966 | }(); 967 | 968 | return { 969 | 970 | registerError: registerError, 971 | 972 | displayOneError: displayOneError, 973 | 974 | displayErrors: displayErrors, 975 | 976 | resetOneError: resetOneError, 977 | 978 | resetErrors: resetErrors, 979 | 980 | destroy: destroy 981 | 982 | }; 983 | 984 | }; 985 | 986 | $.fn.validate = $.validate = function(options) { 987 | 988 | return _api.validate(this, options); 989 | 990 | }; 991 | 992 | $.fn.addValidation = function(validation) { 993 | 994 | return _api.addValidation(this, validation); 995 | 996 | }; 997 | 998 | $.fn.removeValidation = function(validation) { 999 | 1000 | return _api.removeValidation(this, validation); 1001 | 1002 | }; 1003 | 1004 | $.fn.addError = function(error) { 1005 | 1006 | return _api.addError(this, error); 1007 | 1008 | }; 1009 | 1010 | $.fn.removeError = function(error) { 1011 | 1012 | return _api.removeError(this, error); 1013 | 1014 | }; 1015 | 1016 | $.fn.alterValidationRules = $.alterValidationRules = function(rules) { 1017 | 1018 | if (!(rules instanceof Array)) { 1019 | rules = [rules]; 1020 | } 1021 | 1022 | for (var i = 0; i < rules.length; i++) { 1023 | _api.alterValidationRules(rules[i]); 1024 | } 1025 | 1026 | }; 1027 | 1028 | var _api = { 1029 | 1030 | _formatValidation: function(validation) { 1031 | 1032 | validation = validation.toString().replace(/\s/g, ''); 1033 | 1034 | if (validation.charAt(0) === "[" && validation.charAt(validation.length - 1) === "]") { 1035 | validation = validation.replace(/^\[|]$/g, ''); 1036 | } 1037 | 1038 | return validation; 1039 | 1040 | }, 1041 | 1042 | _splitValidation: function(validation) { 1043 | 1044 | var validationArray = this._formatValidation(validation).split(','), 1045 | oneValidation; 1046 | 1047 | for (var i = 0; i < validationArray.length; i++) { 1048 | oneValidation = validationArray[i]; 1049 | if (/^[a-z]+$/i.test(oneValidation)) { 1050 | validationArray[i] = oneValidation.toUpperCase(); 1051 | } 1052 | } 1053 | 1054 | return validationArray; 1055 | }, 1056 | 1057 | _joinValidation: function(validation) { 1058 | 1059 | return '[' + validation.join(', ') + ']'; 1060 | 1061 | }, 1062 | 1063 | validate: function(node, options) { 1064 | 1065 | if (typeof node === "function") { 1066 | 1067 | if (!options.submit.settings.form) { 1068 | window.Debug.log({ 1069 | 'node': node, 1070 | 'function': '$.validate()', 1071 | 'arguments': '', 1072 | 'message': 'Undefined property "options.submit.settings.form - Validation dropped' 1073 | }); 1074 | 1075 | window.Debug.print(); 1076 | 1077 | return; 1078 | } 1079 | 1080 | node = $(options.submit.settings.form); 1081 | 1082 | if (!node[0] || node[0].nodeName.toLowerCase() !== "form") { 1083 | window.Debug.log({ 1084 | 'function': '$.validate()', 1085 | 'arguments': options.submit.settings.form, 1086 | 'message': 'Unable to find jQuery form element - Validation dropped' 1087 | }); 1088 | 1089 | window.Debug.print(); 1090 | 1091 | return; 1092 | } 1093 | 1094 | } else if (typeof node[0] === 'undefined') { 1095 | window.Debug.log({ 1096 | 'node': node, 1097 | 'function': '$.validate()', 1098 | 'arguments': '$("' + node.selector + '").validate()', 1099 | 'message': 'Unable to find jQuery form element - Validation dropped' 1100 | }); 1101 | 1102 | window.Debug.print(); 1103 | 1104 | return; 1105 | } 1106 | 1107 | if (options === "destroy") { 1108 | 1109 | if (!window.Validation.form[node.selector]) { 1110 | window.Debug.log({ 1111 | 'node': node, 1112 | 'function': '$.validate("destroy")', 1113 | 'arguments': '', 1114 | 'message': 'Unable to destroy "' + node.selector + '", perhaps it\'s already destroyed?' 1115 | }); 1116 | 1117 | window.Debug.print(); 1118 | 1119 | return; 1120 | } 1121 | 1122 | window.Validation.form[node.selector].destroy(); 1123 | 1124 | return; 1125 | 1126 | } 1127 | 1128 | return node.each(function() { 1129 | window.Validation.form[node.selector] = new Validation($(this), options); 1130 | }); 1131 | 1132 | }, 1133 | 1134 | addValidation: function(node, validation) { 1135 | 1136 | var self = this; 1137 | 1138 | validation = self._splitValidation(validation); 1139 | 1140 | if (!validation) { 1141 | return false; 1142 | } 1143 | 1144 | return node.each(function() { 1145 | 1146 | var $this = $(this), 1147 | validationData = $this.attr(_data.validation), 1148 | validationArray = (validationData && validationData.length) ? self._splitValidation(validationData) : [], 1149 | oneValidation; 1150 | 1151 | for (var i = 0; i < validation.length; i++) { 1152 | 1153 | oneValidation = self._formatValidation(validation[i]); 1154 | 1155 | if ($.inArray(oneValidation, validationArray) === -1) { 1156 | validationArray.push(oneValidation); 1157 | } 1158 | } 1159 | 1160 | if (validationArray.length) { 1161 | $this.attr(_data.validation, self._joinValidation(validationArray)); 1162 | } 1163 | 1164 | }); 1165 | 1166 | }, 1167 | 1168 | removeValidation: function(node, validation) { 1169 | 1170 | var self = this; 1171 | 1172 | validation = self._splitValidation(validation); 1173 | if (!validation) { 1174 | return false; 1175 | } 1176 | 1177 | return node.each(function() { 1178 | 1179 | var $this = $(this), 1180 | validationData = $this.attr(_data.validation), 1181 | validationArray = (validationData && validationData.length) ? self._splitValidation(validationData) : [], 1182 | oneValidation, 1183 | validationIndex; 1184 | 1185 | if (!validationArray.length) { 1186 | $this.removeAttr(_data.validation); 1187 | return true; 1188 | } 1189 | 1190 | for (var i = 0; i < validation.length; i++) { 1191 | oneValidation = self._formatValidation(validation[i]); 1192 | validationIndex = $.inArray(oneValidation, validationArray); 1193 | if (validationIndex !== -1) { 1194 | validationArray.splice(validationIndex, 1); 1195 | } 1196 | 1197 | } 1198 | 1199 | if (!validationArray.length) { 1200 | $this.removeAttr(_data.validation); 1201 | return true; 1202 | } 1203 | 1204 | $this.attr(_data.validation, self._joinValidation(validationArray)); 1205 | 1206 | }); 1207 | 1208 | }, 1209 | 1210 | addError: function(node, error) { 1211 | 1212 | if (!window.Validation.form[node.selector]) { 1213 | window.Debug.log({ 1214 | 'node': node, 1215 | 'function': '$.addError()', 1216 | 'arguments': 'window.Validation.form[' + node.selector + ']', 1217 | 'message': 'ERROR - Invalid node selector' 1218 | }); 1219 | 1220 | window.Debug.print(); 1221 | 1222 | return false; 1223 | } 1224 | 1225 | if (typeof error !== "object" || Object.prototype.toString.call(error) !== "[object Object]") { 1226 | window.Debug.log({ 1227 | 'node': node, 1228 | 'function': '$.addError()', 1229 | 'arguments': 'window.Validation.form[' + node.selector + ']', 1230 | 'message': 'ERROR - Invalid argument, must be type object' 1231 | }); 1232 | 1233 | window.Debug.print(); 1234 | 1235 | return false; 1236 | } 1237 | 1238 | var input, 1239 | onlyOnce = true; 1240 | for (var inputName in error) { 1241 | 1242 | if (!error.hasOwnProperty(inputName)) { 1243 | continue; 1244 | } 1245 | 1246 | if (!(error[inputName] instanceof Array)) { 1247 | error[inputName] = [error[inputName]]; 1248 | } 1249 | 1250 | input = $(node.selector).find('[name="' + inputName + '"]'); 1251 | if (!input[0]) { 1252 | window.Debug.log({ 1253 | 'node': node, 1254 | 'function': '$.addError()', 1255 | 'arguments': inputName, 1256 | 'message': 'ERROR - Unable to find ' + '$(' + node.selector + ').find("[name="' + inputName + '"]")' 1257 | }); 1258 | 1259 | window.Debug.print(); 1260 | 1261 | continue; 1262 | } 1263 | 1264 | if (onlyOnce) { 1265 | window.Validation.hasScrolled = false; 1266 | onlyOnce = false; 1267 | } 1268 | 1269 | window.Validation.form[node.selector].resetOneError(inputName, input); 1270 | 1271 | for (var i = 0; i < error[inputName].length; i++) { 1272 | 1273 | if (typeof error[inputName][i] !== "string") { 1274 | window.Debug.log({ 1275 | 'node': node, 1276 | 'function': '$.addError()', 1277 | 'arguments': error[inputName][i], 1278 | 'message': 'ERROR - Invalid error object property - Accepted format: {"inputName": "errorString"} or {"inputName": ["errorString", "errorString"]}' 1279 | }); 1280 | 1281 | window.Debug.print(); 1282 | 1283 | continue; 1284 | } 1285 | 1286 | window.Validation.form[node.selector].registerError(inputName, error[inputName][i]); 1287 | 1288 | } 1289 | 1290 | window.Validation.form[node.selector].displayOneError(inputName); 1291 | 1292 | } 1293 | 1294 | }, 1295 | 1296 | removeError: function(node, inputName) { 1297 | 1298 | if (!window.Validation.form[node.selector]) { 1299 | window.Debug.log({ 1300 | 'node': node, 1301 | 'function': '$.removeError()', 1302 | 'arguments': 'window.Validation.form[' + node.selector + ']', 1303 | 'message': 'ERROR - Invalid node selector' 1304 | }); 1305 | 1306 | window.Debug.print(); 1307 | 1308 | return false; 1309 | } 1310 | 1311 | if (!inputName) { 1312 | window.Validation.form[node.selector].resetErrors(); 1313 | return false; 1314 | } 1315 | 1316 | if (typeof inputName === "object" && Object.prototype.toString.call(inputName) !== "[object Array]") { 1317 | window.Debug.log({ 1318 | 'node': node, 1319 | 'function': '$.removeError()', 1320 | 'arguments': inputName, 1321 | 'message': 'ERROR - Invalid inputName, must be type String or Array' 1322 | }); 1323 | 1324 | window.Debug.print(); 1325 | 1326 | return false; 1327 | } 1328 | 1329 | if (!(inputName instanceof Array)) { 1330 | inputName = [inputName]; 1331 | } 1332 | 1333 | var input; 1334 | for (var i = 0; i < inputName.length; i++) { 1335 | 1336 | input = $(node.selector).find('[name="' + inputName[i] + '"]'); 1337 | if (!input[0]) { 1338 | window.Debug.log({ 1339 | 'node': node, 1340 | 'function': '$.removeError()', 1341 | 'arguments': inputName[i], 1342 | 'message': 'ERROR - Unable to find ' + '$(' + node.selector + ').find("[name="' + inputName[i] + '"]")' 1343 | }); 1344 | 1345 | window.Debug.print(); 1346 | 1347 | continue; 1348 | } 1349 | 1350 | window.Validation.form[node.selector].resetOneError(inputName[i], input); 1351 | 1352 | } 1353 | 1354 | }, 1355 | 1356 | alterValidationRules: function(ruleObj) { 1357 | 1358 | if (!ruleObj.rule || (!ruleObj.regex && !ruleObj.message)) { 1359 | window.Debug.log({ 1360 | 'function': '$.alterValidationRules()', 1361 | 'message': 'ERROR - Missing one or multiple parameter(s) {rule, regex, message}' 1362 | }); 1363 | window.Debug.print(); 1364 | return false; 1365 | } 1366 | 1367 | ruleObj.rule = ruleObj.rule.toUpperCase(); 1368 | 1369 | if (ruleObj.regex) { 1370 | 1371 | var regex = _buildRegexFromString(ruleObj.regex); 1372 | 1373 | if (!(regex instanceof RegExp)) { 1374 | window.Debug.log({ 1375 | 'function': '$.alterValidationRules(rule)', 1376 | 'arguments': regex.toString(), 1377 | 'message': 'ERROR - Invalid rule' 1378 | }); 1379 | window.Debug.print(); 1380 | return false; 1381 | } 1382 | 1383 | _rules[ruleObj.rule] = regex; 1384 | } 1385 | 1386 | if (ruleObj.message) { 1387 | _messages[ruleObj.rule] = ruleObj.message; 1388 | } 1389 | 1390 | return true; 1391 | } 1392 | 1393 | }; 1394 | 1395 | function _buildRegexFromString(regex) { 1396 | 1397 | if (!regex || (typeof regex !== "string" && !(regex instanceof RegExp))) { 1398 | _regexDebug(); 1399 | return false; 1400 | } 1401 | 1402 | if (typeof regex !== 'string') { 1403 | regex = regex.toString(); 1404 | } 1405 | 1406 | var separator = regex.charAt(0), 1407 | index = regex.length - 1, 1408 | pattern, 1409 | modifier, 1410 | rule; 1411 | 1412 | while (index > 0) { 1413 | if (/[gimsxeU]/.test(regex.charAt(index))) { 1414 | index--; 1415 | } else { 1416 | break; 1417 | } 1418 | } 1419 | 1420 | if (regex.charAt(index) !== separator) { 1421 | separator = null; 1422 | } 1423 | 1424 | if (separator && index !== regex.length - 1) { 1425 | modifier = regex.substr(index + 1, regex.length - 1); 1426 | } 1427 | 1428 | if (separator) { 1429 | pattern = regex.substr(1, index - 1); 1430 | } else { 1431 | pattern = regex; 1432 | } 1433 | 1434 | try { 1435 | rule = new RegExp(pattern, modifier); 1436 | } catch (error) { 1437 | _regexDebug(); 1438 | return false; 1439 | } 1440 | 1441 | return rule; 1442 | 1443 | function _regexDebug() { 1444 | window.Debug.log({ 1445 | 'function': '_buildRegexFromString()', 1446 | 'arguments': '{pattern: {' + (pattern || '') + '}, modifier: {' + (modifier || '') + '}', 1447 | 'message': 'WARNING - Invalid regex given: ' + regex 1448 | }); 1449 | window.Debug.print(); 1450 | } 1451 | 1452 | } 1453 | window.Debug = { 1454 | 1455 | table: {}, 1456 | log: function(debugObject) { 1457 | 1458 | if (!debugObject.message || typeof debugObject.message !== "string") { 1459 | return false; 1460 | } 1461 | 1462 | this.table[debugObject.message] = $.extend( 1463 | Object.preventExtensions({ 1464 | 'node': '', 1465 | 'function': '', 1466 | 'arguments': '' 1467 | }), debugObject 1468 | ); 1469 | 1470 | }, 1471 | print: function() { 1472 | 1473 | if (isEmpty(this.table)) { 1474 | return false; 1475 | } 1476 | 1477 | if (console.group !== undefined || console.table !== undefined) { 1478 | 1479 | console.groupCollapsed('--- jQuery Form Validation Debug ---'); 1480 | 1481 | if (console.table) { 1482 | console.table(this.table); 1483 | } else { 1484 | $.each(this.table, function(index, data) { 1485 | console.log(data['Name'] + ': ' + data['Execution Time'] + 'ms'); 1486 | }); 1487 | } 1488 | 1489 | console.groupEnd(); 1490 | 1491 | } else { 1492 | console.log('Debug is not available on your current browser, try the most recent version of Chrome or Firefox.'); 1493 | } 1494 | 1495 | this.table = {}; 1496 | 1497 | } 1498 | 1499 | }; 1500 | 1501 | String.prototype.capitalize = function() { 1502 | return this.charAt(0).toUpperCase() + this.slice(1); 1503 | }; 1504 | 1505 | function isEmpty(obj) { 1506 | for (var prop in obj) { 1507 | if (obj.hasOwnProperty(prop)) 1508 | return false; 1509 | } 1510 | 1511 | return true; 1512 | } 1513 | 1514 | if (!Array.prototype.indexOf) { 1515 | Array.prototype.indexOf = function(elt) { 1516 | var len = this.length >>> 0; 1517 | 1518 | var from = Number(arguments[1]) || 0; 1519 | from = (from < 0) ? Math.ceil(from) : Math.floor(from); 1520 | if (from < 0) 1521 | from += len; 1522 | 1523 | for (; from < len; from++) { 1524 | if (from in this && 1525 | this[from] === elt) 1526 | return from; 1527 | } 1528 | return -1; 1529 | }; 1530 | } 1531 | if (!JSON && !JSON.stringify) { 1532 | JSON.stringify = function(obj) { 1533 | var t = typeof(obj); 1534 | if (t !== "object" || obj === null) { 1535 | if (t === "string") { 1536 | obj = '"' + obj + '"'; 1537 | } 1538 | return String(obj); 1539 | } else { 1540 | var n, v, json = [], 1541 | arr = (obj && obj.constructor === Array); 1542 | for (n in obj) { 1543 | if (true) { 1544 | v = obj[n]; 1545 | t = typeof(v); 1546 | if (t === "string") { 1547 | v = '"' + v + '"'; 1548 | } else if (t === "object" && v !== null) { 1549 | v = JSON.stringify(v); 1550 | } 1551 | json.push((arr ? "" : '"' + n + '": ') + String(v)); 1552 | } 1553 | } 1554 | return (arr ? "[" : "{") + String(json) + (arr ? "]" : "}"); 1555 | } 1556 | }; 1557 | } 1558 | 1559 | }(window, document, window.jQuery)); 1560 | -------------------------------------------------------------------------------- /src/jquery.validation.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * jQuery Form Validation 3 | * Copyright (C) 2015 RunningCoder.org 4 | * Licensed under the MIT license 5 | * 6 | * @author Tom Bertrand 7 | * @version 1.5.3 (2015-12-02) 8 | * @link http://www.runningcoder.org/jqueryvalidation/ 9 | */ 10 | ; 11 | (function (window, document, $, undefined) { 12 | 13 | window.Validation = { 14 | form: [], 15 | labels: {}, 16 | hasScrolled: false 17 | }; 18 | 19 | /** 20 | * Fail-safe preventExtensions function for older browsers 21 | */ 22 | if (typeof Object.preventExtensions !== "function") { 23 | Object.preventExtensions = function (obj) { 24 | return obj; 25 | }; 26 | } 27 | 28 | // Not using strict to avoid throwing a window error on bad config extend. 29 | // console.debug is used instead to debug Validation 30 | //"use strict"; 31 | 32 | // ================================================================================================================= 33 | /** 34 | * @private 35 | * RegExp rules 36 | */ 37 | var _rules = { 38 | NOTEMPTY: /\S/, 39 | INTEGER: /^\d+$/, 40 | NUMERIC: /^\d+(?:[,\s]\d{3})*(?:\.\d+)?$/, 41 | MIXED: /^[\w\s-]+$/, 42 | NAME: /^['a-zãàáäâẽèéëêìíïîõòóöôùúüûñç\s-]+$/i, 43 | NOSPACE: /^(?!\s)\S*$/, 44 | TRIM: /^[^\s].*[^\s]$/, 45 | DATE: /^\d{4}-\d{2}-\d{2}(\s\d{2}:\d{2}(:\d{2})?)?$/, 46 | EMAIL: /^([^@]+?)@(([a-z0-9]-*)*[a-z0-9]+\.)+([a-z0-9]+)$/i, 47 | URL: /^(https?:\/\/)?((([a-z0-9]-*)*[a-z0-9]+\.?)*([a-z0-9]+))(\/[\w?=\.-]*)*$/, 48 | PHONE: /^(\()?\d{3}(\))?(-|\s)?\d{3}(-|\s)\d{4}$/, 49 | OPTIONAL: /\S/, 50 | COMPARISON: /^\s*([LV])\s*([<>]=?|==|!=)\s*([^<>=!]+?)\s*$/ 51 | }; 52 | 53 | /** 54 | * @private 55 | * Error messages 56 | */ 57 | var _messages = { 58 | 'default': '$ contain error(s).', 59 | 'NOTEMPTY': '$ must not be empty.', 60 | 'INTEGER': '$ must be an integer.', 61 | 'NUMERIC': '$ must be numeric.', 62 | 'MIXED': '$ must be letters or numbers (no special characters).', 63 | 'NAME': '$ must not contain special characters.', 64 | 'NOSPACE': '$ must not contain spaces.', 65 | 'TRIM': '$ must not start or end with space character.', 66 | 'DATE': '$ is not a valid with format YYYY-MM-DD.', 67 | 'EMAIL': '$ is not valid.', 68 | 'URL': '$ is not valid.', 69 | 'PHONE': '$ is not a valid phone number.', 70 | '<': '$ must be less than % characters.', 71 | '<=': '$ must be less or equal to % characters.', 72 | '>': '$ must be greater than % characters.', 73 | '>=': '$ must be greater or equal to % characters.', 74 | '==': '$ must be equal to %', 75 | '!=': '$ must be different than %' 76 | }; 77 | 78 | /** 79 | * @private 80 | * HTML5 data attributes 81 | */ 82 | var _data = { 83 | validation: 'data-validation', 84 | validationMessage: 'data-validation-message', 85 | regex: 'data-validation-regex', 86 | regexReverse: 'data-validation-regex-reverse', 87 | regexMessage: 'data-validation-regex-message', 88 | group: 'data-validation-group', 89 | label: 'data-validation-label', 90 | errorList: 'data-error-list' 91 | } 92 | 93 | /** 94 | * @private 95 | * Default options 96 | * 97 | * @link http://www.runningcoder.org/jqueryvalidation/documentation/ 98 | */ 99 | var _options = { 100 | submit: { 101 | settings: { 102 | form: null, 103 | display: "inline", 104 | insertion: "append", 105 | allErrors: false, 106 | trigger: "click", 107 | button: "[type='submit']", 108 | errorClass: "error", 109 | errorListClass: "error-list", 110 | errorListContainer: null, 111 | errorTemplate: null, 112 | inputContainer: null, 113 | clear: "focusin", 114 | scrollToError: false 115 | }, 116 | callback: { 117 | onInit: null, 118 | onValidate: null, 119 | onError: null, 120 | onBeforeSubmit: null, 121 | onSubmit: null, 122 | onAfterSubmit: null 123 | } 124 | }, 125 | dynamic: { 126 | settings: { 127 | trigger: null, 128 | delay: 300 129 | }, 130 | callback: { 131 | onSuccess: null, 132 | onError: null, 133 | onComplete: null 134 | } 135 | }, 136 | rules: {}, 137 | messages: {}, 138 | labels: {}, 139 | debug: false 140 | }; 141 | 142 | /** 143 | * @private 144 | * Limit the supported options on matching keys 145 | */ 146 | var _supported = { 147 | submit: { 148 | settings: { 149 | display: ["inline", "block"], 150 | insertion: ["append", "prepend"], //"before", "insertBefore", "after", "insertAfter" 151 | allErrors: [true, false], 152 | clear: ["focusin", "keypress", false], 153 | trigger: [ 154 | "click", "dblclick", "focusout", 155 | "hover", "mousedown", "mouseenter", 156 | "mouseleave", "mousemove", "mouseout", 157 | "mouseover", "mouseup", "toggle" 158 | ] 159 | } 160 | }, 161 | dynamic: { 162 | settings: { 163 | trigger: ["focusout", "keydown", "keypress", "keyup"] 164 | } 165 | }, 166 | debug: [true, false] 167 | }; 168 | 169 | // ================================================================================================================= 170 | 171 | /** 172 | * @constructor 173 | * Validation Class 174 | * 175 | * @param {Object} node jQuery form object 176 | * @param {Object} options User defined options 177 | */ 178 | var Validation = function (node, options) { 179 | 180 | var errors = [], 181 | messages = {}, 182 | formData = {}, 183 | delegateSuffix = ".vd", // validation.delegate 184 | resetSuffix = ".vr"; // validation.resetError 185 | 186 | window.Validation.hasScrolled = false; 187 | 188 | /** 189 | * Extends user-defined "options.message" into the default Validation "_message". 190 | */ 191 | function extendRules() { 192 | options.rules = $.extend( 193 | true, 194 | {}, 195 | _rules, 196 | options.rules 197 | ); 198 | } 199 | 200 | /** 201 | * Extends user-defined "options.message" into the default Validation "_message". 202 | */ 203 | function extendMessages() { 204 | options.messages = $.extend( 205 | true, 206 | {}, 207 | _messages, 208 | options.messages 209 | ); 210 | } 211 | 212 | /** 213 | * Extends user-defined "options" into the default Validation "_options". 214 | * Notes: 215 | * - preventExtensions prevents from modifying the Validation "_options" object structure 216 | * - filter through the "_supported" to delete unsupported "options" 217 | */ 218 | function extendOptions() { 219 | 220 | if (!(options instanceof Object)) { 221 | options = {}; 222 | } 223 | 224 | var tpmOptions = Object.preventExtensions($.extend(true, {}, _options)); 225 | 226 | for (var method in options) { 227 | 228 | if (!options.hasOwnProperty(method) || method === "debug") { 229 | continue; 230 | } 231 | 232 | if (~["labels", "messages", "rules"].indexOf(method) && options[method] instanceof Object) { 233 | tpmOptions[method] = options[method]; 234 | continue; 235 | } 236 | 237 | if (!_options[method] || !(options[method] instanceof Object)) { 238 | 239 | // {debug} 240 | options.debug && window.Debug.log({ 241 | 'node': node, 242 | 'function': 'extendOptions()', 243 | 'arguments': '{' + method + ': ' + JSON.stringify(options[method]) + '}', 244 | 'message': 'WARNING - ' + method + ' - invalid option' 245 | }); 246 | // {/debug} 247 | 248 | continue; 249 | } 250 | 251 | for (var type in options[method]) { 252 | if (!options[method].hasOwnProperty(type)) { 253 | continue; 254 | } 255 | 256 | if (!_options[method][type] || !(options[method][type] instanceof Object)) { 257 | 258 | // {debug} 259 | options.debug && window.Debug.log({ 260 | 'node': node, 261 | 'function': 'extendOptions()', 262 | 'arguments': '{' + type + ': ' + JSON.stringify(options[method][type]) + '}', 263 | 'message': 'WARNING - ' + type + ' - invalid option' 264 | }); 265 | // {/debug} 266 | 267 | continue; 268 | } 269 | 270 | for (var option in options[method][type]) { 271 | if (!options[method][type].hasOwnProperty(option)) { 272 | continue; 273 | } 274 | 275 | if (_supported[method] && 276 | _supported[method][type] && 277 | _supported[method][type][option] && 278 | $.inArray(options[method][type][option], _supported[method][type][option]) === -1) { 279 | 280 | // {debug} 281 | options.debug && window.Debug.log({ 282 | 'node': node, 283 | 'function': 'extendOptions()', 284 | 'arguments': '{' + option + ': ' + JSON.stringify(options[method][type][option]) + '}', 285 | 'message': 'WARNING - ' + option.toString() + ': ' + JSON.stringify(options[method][type][option]) + ' - unsupported option' 286 | }); 287 | // {/debug} 288 | 289 | delete options[method][type][option]; 290 | } 291 | 292 | } 293 | if (tpmOptions[method] && tpmOptions[method][type]) { 294 | tpmOptions[method][type] = $.extend(Object.preventExtensions(tpmOptions[method][type]), options[method][type]); 295 | } 296 | } 297 | } 298 | 299 | // {debug} 300 | if (options.debug && $.inArray(options.debug, _supported.debug !== -1)) { 301 | tpmOptions.debug = options.debug; 302 | } 303 | // {/debug} 304 | 305 | // @TODO Would there be a better fix to solve event conflict? 306 | if (tpmOptions.dynamic.settings.trigger) { 307 | if (tpmOptions.dynamic.settings.trigger === "keypress" && tpmOptions.submit.settings.clear === "keypress") { 308 | tpmOptions.dynamic.settings.trigger = "keydown"; 309 | } 310 | } 311 | 312 | options = tpmOptions; 313 | 314 | } 315 | 316 | /** 317 | * Delegates the dynamic validation on data-validation and data-validation-regex attributes based on trigger. 318 | * 319 | * @returns {Boolean} false if the option is not set 320 | */ 321 | function delegateDynamicValidation() { 322 | 323 | if (!options.dynamic.settings.trigger) { 324 | return false; 325 | } 326 | 327 | // {debug} 328 | options.debug && window.Debug.log({ 329 | 'node': node, 330 | 'function': 'delegateDynamicValidation()', 331 | 'message': 'OK - Dynamic Validation activated on ' + node.length + ' form(s)' 332 | }); 333 | // {/debug} 334 | 335 | if (!node.find('[' + _data.validation + '],[' + _data.regex + ']')[0]) { 336 | 337 | // {debug} 338 | options.debug && window.Debug.log({ 339 | 'node': node, 340 | 'function': 'delegateDynamicValidation()', 341 | 'arguments': 'node.find([' + _data.validation + '],[' + _data.regex + '])', 342 | 'message': 'ERROR - [' + _data.validation + '] not found' 343 | }); 344 | // {/debug} 345 | 346 | return false; 347 | } 348 | 349 | var event = options.dynamic.settings.trigger + delegateSuffix; 350 | if (options.dynamic.settings.trigger !== "focusout") { 351 | event += " change" + delegateSuffix + " paste" + delegateSuffix; 352 | } 353 | 354 | $.each( 355 | node.find('[' + _data.validation + '],[' + _data.regex + ']'), 356 | function (index, input) { 357 | 358 | $(input).unbind(event).on(event, function (e) { 359 | 360 | if ($(this).is(':disabled')) { 361 | return false; 362 | } 363 | 364 | //e.preventDefault(); 365 | 366 | var input = this, 367 | keyCode = e.keyCode || null; 368 | 369 | _typeWatch(function () { 370 | 371 | if (!validateInput(input)) { 372 | 373 | displayOneError(input.name); 374 | _executeCallback(options.dynamic.callback.onError, [node, input, keyCode, errors[input.name]]); 375 | 376 | } else { 377 | 378 | _executeCallback(options.dynamic.callback.onSuccess, [node, input, keyCode]); 379 | 380 | } 381 | 382 | _executeCallback(options.dynamic.callback.onComplete, [node, input, keyCode]); 383 | 384 | }, options.dynamic.settings.delay); 385 | 386 | }); 387 | } 388 | ); 389 | } 390 | 391 | /** 392 | * Delegates the submit validation on data-validation and data-validation-regex attributes based on trigger. 393 | * Note: Disable the form submit function so the callbacks are not by-passed 394 | */ 395 | function delegateValidation() { 396 | 397 | _executeCallback(options.submit.callback.onInit, [node]); 398 | 399 | var event = options.submit.settings.trigger + '.vd'; 400 | 401 | // {debug} 402 | options.debug && window.Debug.log({ 403 | 'node': node, 404 | 'function': 'delegateValidation()', 405 | 'message': 'OK - Validation activated on ' + node.length + ' form(s)' 406 | }); 407 | // {/debug} 408 | 409 | if (!node.find(options.submit.settings.button)[0]) { 410 | 411 | // {debug} 412 | options.debug && window.Debug.log({ 413 | 'node': node, 414 | 'function': 'delegateValidation()', 415 | 'arguments': '{button: ' + options.submit.settings.button + '}', 416 | 'message': 'ERROR - node.find("' + options.submit.settings.button + '") not found' 417 | }); 418 | // {/debug} 419 | 420 | return false; 421 | 422 | } 423 | 424 | node.on("submit", false); 425 | node.find(options.submit.settings.button).off('.vd').on(event, function (e) { 426 | 427 | e.preventDefault(); 428 | 429 | resetErrors(); 430 | 431 | _executeCallback(options.submit.callback.onValidate, [node]); 432 | 433 | if (!validateForm()) { 434 | 435 | displayErrors(); 436 | _executeCallback(options.submit.callback.onError, [node, errors, formData]); 437 | 438 | } else { 439 | 440 | _executeCallback(options.submit.callback.onBeforeSubmit, [node]); 441 | 442 | if (typeof options.submit.callback.onSubmit === "function") { 443 | if (_executeCallback(options.submit.callback.onSubmit, [node, formData]) === true) { 444 | submitForm(); 445 | } 446 | } else { 447 | submitForm(); 448 | } 449 | 450 | _executeCallback(options.submit.callback.onAfterSubmit, [node]); 451 | 452 | } 453 | 454 | // {debug} 455 | options.debug && window.Debug.print(); 456 | // {/debug} 457 | 458 | return false; 459 | 460 | }); 461 | 462 | } 463 | 464 | /** 465 | * For every "data-validation" & "data-pattern" attributes that are not disabled inside the jQuery "node" object 466 | * the "validateInput" function will be called. 467 | * 468 | * @returns {Boolean} true if no error(s) were found (valid form) 469 | */ 470 | function validateForm() { 471 | 472 | var isValid = isEmpty(errors); 473 | 474 | formData = {}; 475 | 476 | $.each( 477 | node.find('input:not([type="submit"]), select, textarea').not(':disabled'), 478 | function(index, input) { 479 | 480 | input = $(input); 481 | 482 | var value = _getInputValue(input[0]), 483 | inputName = input.attr('name'); 484 | 485 | if (inputName) { 486 | if (/\[]$/.test(inputName)) { 487 | inputName = inputName.replace(/\[]$/, ''); 488 | if (!(formData[inputName] instanceof Array)) { 489 | formData[inputName] = []; 490 | } 491 | formData[inputName].push(value) 492 | } else { 493 | formData[inputName] = value; 494 | } 495 | } 496 | 497 | if (!!input.attr(_data.validation) || !!input.attr(_data.regex)) { 498 | if (!validateInput(input[0], value)) { 499 | isValid = false; 500 | } 501 | } 502 | } 503 | ); 504 | 505 | prepareFormData(); 506 | 507 | return isValid; 508 | 509 | } 510 | 511 | /** 512 | * Loop through formData and build an object 513 | * 514 | * @returns {Object} data 515 | */ 516 | function prepareFormData() { 517 | 518 | var data = {}, 519 | matches, 520 | index; 521 | 522 | for (var i in formData) { 523 | if (!formData.hasOwnProperty(i)) continue; 524 | 525 | index = 0; 526 | matches = i.split(/\[(.+?)]/g); 527 | 528 | var tmpObject = {}, 529 | tmpArray = []; 530 | 531 | for (var k = matches.length - 1; k >= 0; k--) { 532 | if (matches[k] === "") { 533 | matches.splice(k, 1); 534 | continue; 535 | } 536 | 537 | if (tmpArray.length < 1) { 538 | tmpObject[matches[k]] = Number(formData[i]) || formData[i]; 539 | } else { 540 | tmpObject = {}; 541 | tmpObject[matches[k]] = tmpArray[tmpArray.length - 1]; 542 | } 543 | tmpArray.push(tmpObject) 544 | 545 | } 546 | 547 | data = $.extend(true, data, tmpObject); 548 | 549 | } 550 | 551 | formData = data; 552 | 553 | } 554 | 555 | /** 556 | * Prepare the information from the data attributes 557 | * and call the "validateRule" function. 558 | * 559 | * @param {Object} input Reference of the input element 560 | * 561 | * @returns {Boolean} true if no error(s) were found (valid input) 562 | */ 563 | function validateInput(input, value) { 564 | 565 | var inputName = $(input).attr('name'), 566 | value = value || _getInputValue(input); 567 | 568 | if (!inputName) { 569 | 570 | // {debug} 571 | options.debug && window.Debug.log({ 572 | 'node': node, 573 | 'function': 'validateInput()', 574 | 'arguments': '$(input).attr("name")', 575 | 'message': 'ERROR - Missing input [name]' 576 | }); 577 | // {/debug} 578 | 579 | return false; 580 | } 581 | 582 | var matches = inputName.replace(/]$/, '').split(/]\[|[[\]]/g), 583 | inputShortName = window.Validation.labels[inputName] || 584 | options.labels[inputName] || 585 | $(input).attr(_data.label) || 586 | matches[matches.length - 1], 587 | 588 | validationArray = $(input).attr(_data.validation), 589 | validationMessage = $(input).attr(_data.validationMessage), 590 | validationRegex = $(input).attr(_data.regex), 591 | validationRegexReverse = !($(input).attr(_data.regexReverse) === undefined), 592 | validationRegexMessage = $(input).attr(_data.regexMessage), 593 | 594 | validateOnce = false; 595 | 596 | if (validationArray) { 597 | validationArray = _api._splitValidation(validationArray); 598 | } 599 | 600 | // Validates the "data-validation" 601 | if (validationArray instanceof Array && validationArray.length > 0) { 602 | 603 | // "OPTIONAL" input will not be validated if it's empty 604 | if ($.trim(value) === '' && ~validationArray.indexOf('OPTIONAL')) { 605 | return true; 606 | } 607 | 608 | $.each(validationArray, function (i, rule) { 609 | 610 | if (validateOnce === true) { 611 | return true; 612 | } 613 | 614 | try { 615 | 616 | validateRule(value, rule); 617 | 618 | } catch (error) { 619 | 620 | if (validationMessage || !options.submit.settings.allErrors) { 621 | validateOnce = true; 622 | } 623 | 624 | error[0] = validationMessage || error[0]; 625 | 626 | registerError(inputName, error[0].replace('$', inputShortName).replace('%', error[1])); 627 | 628 | } 629 | 630 | }); 631 | 632 | } 633 | 634 | // Validates the "data-validation-regex" 635 | if (validationRegex) { 636 | 637 | var rule = _buildRegexFromString(validationRegex); 638 | 639 | // Do not block validation if a regexp is bad, only skip it 640 | if (!(rule instanceof RegExp)) { 641 | return true; 642 | } 643 | 644 | try { 645 | 646 | validateRule(value, rule, validationRegexReverse); 647 | 648 | } catch (error) { 649 | 650 | error[0] = validationRegexMessage || error[0]; 651 | 652 | registerError(inputName, error[0].replace('$', inputShortName)); 653 | 654 | } 655 | 656 | } 657 | 658 | return !errors[inputName] || errors[inputName] instanceof Array && errors[inputName].length === 0; 659 | 660 | } 661 | 662 | /** 663 | * Validate an input value against one rule. 664 | * If a "value-rule" mismatch occurs, an error is thrown to the caller function. 665 | * 666 | * @param {String} value 667 | * @param {*} rule 668 | * @param {Boolean} [reversed] 669 | * 670 | * @returns {*} Error if a mismatch occurred. 671 | */ 672 | function validateRule(value, rule, reversed) { 673 | 674 | // Validate for "data-validation-regex" and "data-validation-regex-reverse" 675 | if (rule instanceof RegExp) { 676 | var isValid = rule.test(value); 677 | 678 | if (reversed) { 679 | isValid = !isValid; 680 | } 681 | 682 | if (!isValid) { 683 | throw [options.messages['default'], '']; 684 | } 685 | return; 686 | } 687 | 688 | if (options.rules[rule]) { 689 | if (!options.rules[rule].test(value)) { 690 | throw [options.messages[rule], '']; 691 | } 692 | return; 693 | } 694 | 695 | // Validate for comparison "data-validation" 696 | var comparison = rule.match(options.rules.COMPARISON); 697 | 698 | if (!comparison || comparison.length !== 4) { 699 | 700 | // {debug} 701 | options.debug && window.Debug.log({ 702 | 'node': node, 703 | 'function': 'validateRule()', 704 | 'arguments': 'value: ' + value + ' rule: ' + rule, 705 | 'message': 'WARNING - Invalid comparison' 706 | }); 707 | // {/debug} 708 | 709 | return; 710 | } 711 | 712 | var type = comparison[1], 713 | operator = comparison[2], 714 | compared = comparison[3], 715 | comparedValue; 716 | 717 | switch (type) { 718 | 719 | // Compare input "Length" 720 | case "L": 721 | 722 | // Only numeric value for "L" are allowed 723 | if (isNaN(compared)) { 724 | 725 | // {debug} 726 | options.debug && window.Debug.log({ 727 | 'node': node, 728 | 'function': 'validateRule()', 729 | 'arguments': 'compare: ' + compared + ' rule: ' + rule, 730 | 'message': 'WARNING - Invalid rule, "L" compare must be numeric' 731 | }); 732 | // {/debug} 733 | 734 | return false; 735 | 736 | } else { 737 | 738 | if (!value || eval(value.length + operator + parseFloat(compared)) === false) { 739 | throw [options.messages[operator], compared]; 740 | } 741 | 742 | } 743 | 744 | break; 745 | 746 | // Compare input "Value" 747 | case "V": 748 | default: 749 | 750 | // Compare Field values 751 | if (isNaN(compared)) { 752 | 753 | comparedValue = node.find('[name="' + compared + '"]').val(); 754 | if (!comparedValue) { 755 | 756 | // {debug} 757 | options.debug && window.Debug.log({ 758 | 'node': node, 759 | 'function': 'validateRule()', 760 | 'arguments': 'compare: ' + compared + ' rule: ' + rule, 761 | 'message': 'WARNING - Unable to find compared field [name="' + compared + '"]' 762 | }); 763 | // {/debug} 764 | 765 | return false; 766 | } 767 | 768 | if (!value || !eval('"' + encodeURIComponent(value) + '"' + operator + '"' + encodeURIComponent(comparedValue) + '"')) { 769 | throw [options.messages[operator].replace(' characters', ''), compared]; 770 | } 771 | 772 | } else { 773 | // Compare numeric value 774 | if (!value || isNaN(value) || !eval(value + operator + parseFloat(compared))) { 775 | throw [options.messages[operator].replace(' characters', ''), compared]; 776 | } 777 | 778 | } 779 | break; 780 | 781 | } 782 | 783 | } 784 | 785 | /** 786 | * Register an error into the global "error" variable. 787 | * 788 | * @param {String} inputName Input where the error occurred 789 | * @param {String} error Description of the error to be displayed 790 | */ 791 | function registerError(inputName, error) { 792 | 793 | if (!errors[inputName]) { 794 | errors[inputName] = []; 795 | } 796 | 797 | error = error.capitalize(); 798 | 799 | var hasError = false; 800 | for (var i = 0; i < errors[inputName].length; i++) { 801 | if (errors[inputName][i] === error) { 802 | hasError = true; 803 | break; 804 | } 805 | } 806 | 807 | if (!hasError) { 808 | errors[inputName].push(error); 809 | } 810 | 811 | } 812 | 813 | /** 814 | * Display a single error based on "inputName" key inside the "errors" global array. 815 | * The input, the label and the "inputContainer" will be given the "errorClass" and other 816 | * settings will be considered. 817 | * 818 | * @param {String} inputName Key used for search into "errors" 819 | * 820 | * @returns {Boolean} false if an unwanted behavior occurs 821 | */ 822 | function displayOneError(inputName) { 823 | 824 | var input, 825 | inputId, 826 | errorContainer, 827 | label, 828 | html = '
        ', 829 | group, 830 | groupInput; 831 | 832 | if (!errors.hasOwnProperty(inputName)) { 833 | return false; 834 | } 835 | 836 | input = node.find('[name="' + inputName + '"]'); 837 | 838 | label = null; 839 | 840 | if (!input[0]) { 841 | 842 | // {debug} 843 | options.debug && window.Debug.log({ 844 | 'node': node, 845 | 'function': 'displayOneError()', 846 | 'arguments': '[name="' + inputName + '"]', 847 | 'message': 'ERROR - Unable to find input by name "' + inputName + '"' 848 | }); 849 | // {/debug} 850 | 851 | return false; 852 | } 853 | 854 | group = input.attr(_data.group); 855 | 856 | if (group) { 857 | 858 | groupInput = node.find('[name="' + inputName + '"]'); 859 | label = node.find('[id="' + group + '"]'); 860 | 861 | if (label[0]) { 862 | label.addClass(options.submit.settings.errorClass); 863 | errorContainer = label; 864 | } 865 | 866 | //node.find('[' + _data.group + '="' + group + '"]').addClass(options.submit.settings.errorClass) 867 | 868 | } else { 869 | 870 | input.addClass(options.submit.settings.errorClass); 871 | 872 | if (options.submit.settings.inputContainer) { 873 | input.parentsUntil(node, options.submit.settings.inputContainer).addClass(options.submit.settings.errorClass); 874 | } 875 | 876 | inputId = input.attr('id'); 877 | 878 | if (inputId) { 879 | label = node.find('label[for="' + inputId + '"]')[0]; 880 | } 881 | 882 | if (!label) { 883 | label = input.parentsUntil(node, 'label')[0]; 884 | } 885 | 886 | if (label) { 887 | label = $(label); 888 | label.addClass(options.submit.settings.errorClass); 889 | } 890 | } 891 | 892 | if (options.submit.settings.display === 'inline') { 893 | if (options.submit.settings.errorListContainer) { 894 | errorContainer = input.parentsUntil(node, options.submit.settings.errorListContainer); 895 | } else { 896 | errorContainer = errorContainer || input.parent(); 897 | } 898 | } else if (options.submit.settings.display === 'block') { 899 | errorContainer = node; 900 | } 901 | 902 | // Prevent double error list if the previous one has not been cleared. 903 | if (options.submit.settings.display === 'inline' && errorContainer.find('[' + _data.errorList + ']')[0]) { 904 | return false; 905 | } 906 | 907 | if (options.submit.settings.display === "inline" || 908 | (options.submit.settings.display === "block" && !errorContainer.find('[' + _data.errorList + ']')[0]) 909 | ) { 910 | if (options.submit.settings.insertion === 'append') { 911 | errorContainer.append(html); 912 | } else if (options.submit.settings.insertion === 'prepend') { 913 | errorContainer.prepend(html); 914 | } 915 | } 916 | 917 | for (var i = 0; i < errors[inputName].length; i++) { 918 | if (options.submit.settings.errorTemplate) { 919 | errorContainer.find('[' + _data.errorList + '] ul') 920 | .append('
      • ' + options.submit.settings.errorTemplate.replace('{{validation-message}}', errors[inputName][i]) + '
      • '); 921 | } else { 922 | errorContainer.find('[' + _data.errorList + '] ul').append('
      • ' + errors[inputName][i] + '
      • '); 923 | } 924 | } 925 | 926 | if (options.submit.settings.clear || options.dynamic.settings.trigger) { 927 | 928 | if (group && groupInput) { 929 | input = groupInput; 930 | } 931 | 932 | var event = "coucou" + resetSuffix; 933 | if (options.submit.settings.clear) { 934 | event += " " + options.submit.settings.clear + resetSuffix; 935 | if (~['radio', 'checkbox'].indexOf(input[0].type)) { 936 | event += " change" + resetSuffix; 937 | } 938 | } 939 | if (options.dynamic.settings.trigger) { 940 | event += " " + options.dynamic.settings.trigger + resetSuffix; 941 | if (options.dynamic.settings.trigger !== "focusout" && !~['radio', 'checkbox'].indexOf(input[0].type)) { 942 | event += " change" + resetSuffix + " paste" + resetSuffix; 943 | } 944 | } 945 | 946 | input.unbind(event).on(event, function (a, b, c, d, e) { 947 | 948 | return function () { 949 | if (e) { 950 | if ($(c).hasClass(options.submit.settings.errorClass)) { 951 | resetOneError(a, b, c, d, e); 952 | } 953 | } else if ($(b).hasClass(options.submit.settings.errorClass)) { 954 | resetOneError(a, b, c, d); 955 | } 956 | }; 957 | 958 | }(inputName, input, label, errorContainer, group)); 959 | } 960 | 961 | if (options.submit.settings.scrollToError && !window.Validation.hasScrolled) { 962 | 963 | window.Validation.hasScrolled = true; 964 | 965 | var offset = parseFloat(options.submit.settings.scrollToError.offset) || 0, 966 | duration = parseFloat(options.submit.settings.scrollToError.duration) || 500, 967 | handle = (options.submit.settings.display === 'block') ? errorContainer : input; 968 | 969 | $('html, body').animate({ 970 | scrollTop: handle.offset().top + offset 971 | }, duration); 972 | 973 | } 974 | 975 | } 976 | 977 | /** 978 | * Display all of the errors 979 | */ 980 | function displayErrors() { 981 | 982 | for (var inputName in errors) { 983 | if (!errors.hasOwnProperty(inputName)) continue; 984 | displayOneError(inputName); 985 | } 986 | 987 | } 988 | 989 | /** 990 | * Remove an input error. 991 | * 992 | * @param {String} inputName Key reference to delete the error from "errors" global variable 993 | * @param {Object} input jQuery object of the input 994 | * @param {Object} label jQuery object of the input's label 995 | * @param {Object} container jQuery object of the "errorList" 996 | * @param {String} [group] Name of the group if any (ex: used on input radio) 997 | */ 998 | function resetOneError(inputName, input, label, container, group) { 999 | 1000 | delete errors[inputName]; 1001 | 1002 | if (container) { 1003 | 1004 | //window.Validation.hasScrolled = false; 1005 | 1006 | if (options.submit.settings.inputContainer) { 1007 | (group ? label : input).parentsUntil(node, options.submit.settings.inputContainer).removeClass(options.submit.settings.errorClass); 1008 | } 1009 | 1010 | label && label.removeClass(options.submit.settings.errorClass); 1011 | 1012 | input.removeClass(options.submit.settings.errorClass); 1013 | 1014 | if (options.submit.settings.display === 'inline') { 1015 | container.find('[' + _data.errorList + ']').remove(); 1016 | } 1017 | 1018 | } else { 1019 | 1020 | if (!input) { 1021 | input = node.find('[name="' + inputName + '"]'); 1022 | 1023 | if (!input[0]) { 1024 | 1025 | // {debug} 1026 | options.debug && window.Debug.log({ 1027 | 'node': node, 1028 | 'function': 'resetOneError()', 1029 | 'arguments': '[name="' + inputName + '"]', 1030 | 'message': 'ERROR - Unable to find input by name "' + inputName + '"' 1031 | }); 1032 | // {/debug} 1033 | 1034 | return false; 1035 | } 1036 | } 1037 | 1038 | input.trigger('coucou' + resetSuffix); 1039 | } 1040 | 1041 | } 1042 | 1043 | /** 1044 | * Remove all of the input error(s) display. 1045 | */ 1046 | function resetErrors() { 1047 | 1048 | errors = []; 1049 | window.Validation.hasScrolled = false; 1050 | 1051 | node.find('[' + _data.errorList + ']').remove(); 1052 | node.find('.' + options.submit.settings.errorClass).removeClass(options.submit.settings.errorClass); 1053 | 1054 | } 1055 | 1056 | /** 1057 | * Submits the form once it succeeded the validation process. 1058 | * Note: 1059 | * - This function will be overridden if "options.submit.settings.onSubmit" is defined 1060 | * - The node can't be submitted by jQuery since it has been disabled, use the form native submit function instead 1061 | */ 1062 | function submitForm() { 1063 | 1064 | node[0].submit() 1065 | 1066 | } 1067 | 1068 | /** 1069 | * Destroy the Validation instance 1070 | * 1071 | * @returns {Boolean} 1072 | */ 1073 | function destroy() { 1074 | 1075 | resetErrors(); 1076 | node.find('[' + _data.validation + '],[' + _data.regex + ']').off(delegateSuffix + ' ' + resetSuffix); 1077 | 1078 | node.find(options.submit.settings.button).off(delegateSuffix).on('click' + delegateSuffix, function () { 1079 | $(this).closest('form')[0].submit(); 1080 | }); 1081 | 1082 | //delete window.Validation.form[node.selector]; 1083 | 1084 | return true; 1085 | 1086 | } 1087 | 1088 | /** 1089 | * @private 1090 | * Helper to get the value of an regular, radio or chackbox input 1091 | * 1092 | * @param input 1093 | * 1094 | * @returns {String} value 1095 | */ 1096 | var _getInputValue = function (input) { 1097 | 1098 | var value; 1099 | 1100 | // Get the value or state of the input based on its type 1101 | switch ($(input).attr('type')) { 1102 | case 'checkbox': 1103 | value = ($(input).is(':checked')) ? 1 : ''; 1104 | break; 1105 | case 'radio': 1106 | value = node.find('input[name="' + $(input).attr('name') + '"]:checked').val() || ''; 1107 | break; 1108 | default: 1109 | value = $(input).val(); 1110 | break; 1111 | } 1112 | 1113 | return value; 1114 | 1115 | }; 1116 | 1117 | /** 1118 | * @private 1119 | * Execute function once the timer is reached. 1120 | * If the function is recalled before the timer ends, the first call will be canceled. 1121 | */ 1122 | var _typeWatch = (function () { 1123 | var timer = 0; 1124 | return function (callback, ms) { 1125 | clearTimeout(timer); 1126 | timer = setTimeout(callback, ms); 1127 | }; 1128 | })(); 1129 | 1130 | /** 1131 | * @private 1132 | * Executes an anonymous function or a string reached from the window scope. 1133 | * 1134 | * @example 1135 | * Note: These examples works with every callbacks (onInit, onError, onSubmit, onBeforeSubmit & onAfterSubmit) 1136 | * 1137 | * // An anonymous function inside the "onInit" option 1138 | * onInit: function() { console.log(':D'); }; 1139 | * 1140 | * * // myFunction() located on window.coucou scope 1141 | * onInit: 'window.coucou.myFunction' 1142 | * 1143 | * // myFunction(a,b) located on window.coucou scope passing 2 parameters 1144 | * onInit: ['window.coucou.myFunction', [':D', ':)']]; 1145 | * 1146 | * // Anonymous function to execute a local function 1147 | * onInit: function () { myFunction(':D'); } 1148 | * 1149 | * @param {String|Array} callback The function to be called 1150 | * @param {Array} [extraParams] In some cases the function can be called with Extra parameters (onError) 1151 | * 1152 | * @returns {Boolean} 1153 | */ 1154 | var _executeCallback = function (callback, extraParams) { 1155 | 1156 | if (!callback) { 1157 | return false; 1158 | } 1159 | 1160 | var _callback; 1161 | 1162 | if (typeof callback === "function") { 1163 | 1164 | _callback = callback; 1165 | 1166 | } else if (typeof callback === "string" || callback instanceof Array) { 1167 | 1168 | _callback = window; 1169 | 1170 | if (typeof callback === "string") { 1171 | callback = [callback, []]; 1172 | } 1173 | 1174 | var _exploded = callback[0].split('.'), 1175 | _params = callback[1], 1176 | _isValid = true, 1177 | _splitIndex = 0; 1178 | 1179 | while (_splitIndex < _exploded.length) { 1180 | 1181 | if (typeof _callback !== 'undefined') { 1182 | _callback = _callback[_exploded[_splitIndex++]]; 1183 | } else { 1184 | _isValid = false; 1185 | break; 1186 | } 1187 | } 1188 | 1189 | if (!_isValid || typeof _callback !== "function") { 1190 | 1191 | // {debug} 1192 | options.debug && window.Debug.log({ 1193 | 'node': node, 1194 | 'function': '_executeCallback()', 1195 | 'arguments': JSON.stringify(callback), 1196 | 'message': 'WARNING - Invalid callback function"' 1197 | }); 1198 | // {/debug} 1199 | 1200 | return false; 1201 | } 1202 | 1203 | } 1204 | 1205 | return _callback.apply(this, $.merge(_params || [], (extraParams) ? extraParams : [])); 1206 | 1207 | }; 1208 | 1209 | /** 1210 | * @private 1211 | * Constructs Validation 1212 | */ 1213 | this.__construct = function () { 1214 | 1215 | extendOptions(); 1216 | extendRules(); 1217 | extendMessages(); 1218 | 1219 | delegateDynamicValidation(); 1220 | delegateValidation(); 1221 | 1222 | // {debug} 1223 | options.debug && window.Debug.print(); 1224 | // {/debug} 1225 | 1226 | }(); 1227 | 1228 | return { 1229 | 1230 | /** 1231 | * @public 1232 | * Register error 1233 | * 1234 | * @param inputName 1235 | * @param error 1236 | */ 1237 | registerError: registerError, 1238 | 1239 | /** 1240 | * @public 1241 | * Display one error 1242 | * 1243 | * @param inputName 1244 | */ 1245 | displayOneError: displayOneError, 1246 | 1247 | /** 1248 | * @public 1249 | * Display all errors 1250 | */ 1251 | displayErrors: displayErrors, 1252 | 1253 | /** 1254 | * @public 1255 | * Remove one error 1256 | */ 1257 | resetOneError: resetOneError, 1258 | 1259 | /** 1260 | * @public 1261 | * Remove all errors 1262 | */ 1263 | resetErrors: resetErrors, 1264 | 1265 | /** 1266 | * @public 1267 | * Destroy the Validation instance 1268 | */ 1269 | destroy: destroy 1270 | 1271 | }; 1272 | 1273 | }; 1274 | 1275 | // ================================================================================================================= 1276 | 1277 | /** 1278 | * @public 1279 | * jQuery public function to implement the Validation on the selected node(s). 1280 | * 1281 | * @param {object} options To configure the Validation class. 1282 | * 1283 | * @return {object} Modified DOM element 1284 | */ 1285 | $.fn.validate = $.validate = function (options) { 1286 | 1287 | return _api.validate(this, options); 1288 | 1289 | }; 1290 | 1291 | /** 1292 | * @public 1293 | * jQuery public function to add one or multiple "data-validation" argument. 1294 | * 1295 | * @param {string|array} validation Arguments to add in the node's data-validation 1296 | * 1297 | * @return {object} Modified DOM element 1298 | */ 1299 | $.fn.addValidation = function (validation) { 1300 | 1301 | return _api.addValidation(this, validation); 1302 | 1303 | }; 1304 | 1305 | /** 1306 | * @public 1307 | * jQuery public function to remove one or multiple "data-validation" argument. 1308 | * 1309 | * @param {string|array} validation Arguments to remove in the node's data-validation 1310 | * 1311 | * @return {object} Modified DOM element 1312 | */ 1313 | $.fn.removeValidation = function (validation) { 1314 | 1315 | return _api.removeValidation(this, validation); 1316 | 1317 | }; 1318 | 1319 | /** 1320 | * @public 1321 | * jQuery public function to add one or multiple errors. 1322 | * 1323 | * @param {object} error Object of errors where the keys are the input names 1324 | * @example 1325 | * $('form#myForm').addError({ 1326 | * 'username': 'Invalid username, please choose another one.' 1327 | * }); 1328 | * 1329 | * @return {object} Modified DOM element 1330 | */ 1331 | $.fn.addError = function (error) { 1332 | 1333 | return _api.addError(this, error); 1334 | 1335 | }; 1336 | 1337 | /** 1338 | * @public 1339 | * jQuery public function to remove one or multiple errors. 1340 | * 1341 | * @param {array} error Array of errors where the keys are the input names 1342 | * @example 1343 | * $('form#myForm').removeError([ 1344 | * 'username' 1345 | * ]); 1346 | * 1347 | * @return {object} Modified DOM element 1348 | */ 1349 | $.fn.removeError = function (error) { 1350 | 1351 | return _api.removeError(this, error); 1352 | 1353 | }; 1354 | 1355 | /** 1356 | * @public 1357 | * jQuery public function to add a validation rule. 1358 | * 1359 | * @example 1360 | * $.alterValidationRules({ 1361 | * rule: 'FILENAME', 1362 | * regex: /^[^\\/:\*\?<>\|\"\']*$/, 1363 | * message: '$ has an invalid filename.' 1364 | * }) 1365 | * 1366 | * @param {Object|Array} name 1367 | */ 1368 | $.fn.alterValidationRules = $.alterValidationRules = function (rules) { 1369 | 1370 | if (!(rules instanceof Array)) { 1371 | rules = [rules]; 1372 | } 1373 | 1374 | for (var i = 0; i < rules.length; i++) { 1375 | _api.alterValidationRules(rules[i]); 1376 | } 1377 | 1378 | }; 1379 | 1380 | // ================================================================================================================= 1381 | 1382 | /** 1383 | * @private 1384 | * API to handles "addValidation" and "removeValidation" on attribute "data-validation". 1385 | * Note: Contains fail-safe operations to unify the validation parameter. 1386 | * 1387 | * @example 1388 | * $.addValidation('NOTEMPTY, L>=6') 1389 | * $.addValidation('[notempty, v>=6]') 1390 | * $.removeValidation(['OPTIONAL', 'V>=6']) 1391 | * 1392 | * @returns {object} Updated DOM object 1393 | */ 1394 | var _api = { 1395 | 1396 | /** 1397 | * @private 1398 | * This function unifies the data through the validation process. 1399 | * String, Uppercase and spaceless. 1400 | * 1401 | * @param {string|array} validation 1402 | * 1403 | * @returns {string} 1404 | */ 1405 | _formatValidation: function (validation) { 1406 | 1407 | validation = validation.toString().replace(/\s/g, ''); 1408 | 1409 | if (validation.charAt(0) === "[" && validation.charAt(validation.length - 1) === "]") { 1410 | validation = validation.replace(/^\[|]$/g, ''); 1411 | } 1412 | 1413 | return validation; 1414 | 1415 | }, 1416 | 1417 | /** 1418 | * @private 1419 | * Splits the validation into an array, Uppercase the rules if they are not comparisons 1420 | * 1421 | * @param {String|Array} validation 1422 | * 1423 | * @returns {Array} Formatted validation keys 1424 | */ 1425 | _splitValidation: function (validation) { 1426 | 1427 | var validationArray = this._formatValidation(validation).split(','), 1428 | oneValidation; 1429 | 1430 | for (var i = 0; i < validationArray.length; i++) { 1431 | oneValidation = validationArray[i]; 1432 | if (/^[a-z]+$/i.test(oneValidation)) { 1433 | validationArray[i] = oneValidation.toUpperCase(); 1434 | } 1435 | } 1436 | 1437 | return validationArray; 1438 | }, 1439 | 1440 | /** 1441 | * @private 1442 | * Joins the validation array to create the "data-validation" value 1443 | * 1444 | * @param {Array} validation 1445 | * 1446 | * @returns {String} 1447 | */ 1448 | _joinValidation: function (validation) { 1449 | 1450 | return '[' + validation.join(', ') + ']'; 1451 | 1452 | }, 1453 | 1454 | /** 1455 | * API method to attach the submit event type on the specified node. 1456 | * Note: Clears the previous event regardless to avoid double submits or unwanted behaviors. 1457 | * 1458 | * @param {Object} node jQuery object(s) 1459 | * @param {Object} options To configure the Validation class. 1460 | * 1461 | * @returns {*} 1462 | */ 1463 | validate: function (node, options) { 1464 | 1465 | if (typeof node === "function") { 1466 | 1467 | if (!options.submit.settings.form) { 1468 | 1469 | // {debug} 1470 | window.Debug.log({ 1471 | 'node': node, 1472 | 'function': '$.validate()', 1473 | 'arguments': '', 1474 | 'message': 'Undefined property "options.submit.settings.form - Validation dropped' 1475 | }); 1476 | 1477 | window.Debug.print(); 1478 | // {/debug} 1479 | 1480 | return; 1481 | } 1482 | 1483 | node = $(options.submit.settings.form); 1484 | 1485 | if (!node[0] || node[0].nodeName.toLowerCase() !== "form") { 1486 | 1487 | // {debug} 1488 | window.Debug.log({ 1489 | 'function': '$.validate()', 1490 | 'arguments': options.submit.settings.form, 1491 | 'message': 'Unable to find jQuery form element - Validation dropped' 1492 | }); 1493 | 1494 | window.Debug.print(); 1495 | // {/debug} 1496 | 1497 | return; 1498 | } 1499 | 1500 | } else if (typeof node[0] === 'undefined') { 1501 | 1502 | // {debug} 1503 | window.Debug.log({ 1504 | 'node': node, 1505 | 'function': '$.validate()', 1506 | 'arguments': '$("' + node.selector + '").validate()', 1507 | 'message': 'Unable to find jQuery form element - Validation dropped' 1508 | }); 1509 | 1510 | window.Debug.print(); 1511 | // {/debug} 1512 | 1513 | return; 1514 | } 1515 | 1516 | if (options === "destroy") { 1517 | 1518 | if (!window.Validation.form[node.selector]) { 1519 | 1520 | // {debug} 1521 | window.Debug.log({ 1522 | 'node': node, 1523 | 'function': '$.validate("destroy")', 1524 | 'arguments': '', 1525 | 'message': 'Unable to destroy "' + node.selector + '", perhaps it\'s already destroyed?' 1526 | }); 1527 | 1528 | window.Debug.print(); 1529 | // {/debug} 1530 | 1531 | return; 1532 | } 1533 | 1534 | window.Validation.form[node.selector].destroy(); 1535 | 1536 | return; 1537 | 1538 | } 1539 | 1540 | return node.each(function () { 1541 | window.Validation.form[node.selector] = new Validation($(this), options); 1542 | }); 1543 | 1544 | }, 1545 | 1546 | /** 1547 | * API method to handle the addition of "data-validation" arguments. 1548 | * Note: ONLY the predefined validation arguments are allowed to be added 1549 | * inside the "data-validation" attribute (see configuration). 1550 | * 1551 | * @param {Object} node jQuery objects 1552 | * @param {String|Array} validation arguments to add in the node(s) "data-validation" 1553 | * 1554 | * @returns {*} 1555 | */ 1556 | addValidation: function (node, validation) { 1557 | 1558 | var self = this; 1559 | 1560 | validation = self._splitValidation(validation); 1561 | 1562 | if (!validation) { 1563 | return false; 1564 | } 1565 | 1566 | return node.each(function () { 1567 | 1568 | var $this = $(this), 1569 | validationData = $this.attr(_data.validation), 1570 | validationArray = (validationData && validationData.length) ? self._splitValidation(validationData) : [], 1571 | oneValidation; 1572 | 1573 | for (var i = 0; i < validation.length; i++) { 1574 | 1575 | oneValidation = self._formatValidation(validation[i]); 1576 | 1577 | if ($.inArray(oneValidation, validationArray) === -1) { 1578 | validationArray.push(oneValidation); 1579 | } 1580 | } 1581 | 1582 | if (validationArray.length) { 1583 | $this.attr(_data.validation, self._joinValidation(validationArray)); 1584 | } 1585 | 1586 | }); 1587 | 1588 | }, 1589 | 1590 | /** 1591 | * API method to handle the removal of "data-validation" arguments. 1592 | * 1593 | * @param {Object} node jQuery objects 1594 | * @param {String|Array} validation arguments to remove in the node(s) "data-validation" 1595 | * 1596 | * @returns {*} 1597 | */ 1598 | removeValidation: function (node, validation) { 1599 | 1600 | var self = this; 1601 | 1602 | validation = self._splitValidation(validation); 1603 | if (!validation) { 1604 | return false; 1605 | } 1606 | 1607 | return node.each(function () { 1608 | 1609 | var $this = $(this), 1610 | validationData = $this.attr(_data.validation), 1611 | validationArray = (validationData && validationData.length) ? self._splitValidation(validationData) : [], 1612 | oneValidation, 1613 | validationIndex; 1614 | 1615 | if (!validationArray.length) { 1616 | $this.removeAttr(_data.validation); 1617 | return true; 1618 | } 1619 | 1620 | for (var i = 0; i < validation.length; i++) { 1621 | oneValidation = self._formatValidation(validation[i]); 1622 | validationIndex = $.inArray(oneValidation, validationArray); 1623 | if (validationIndex !== -1) { 1624 | validationArray.splice(validationIndex, 1); 1625 | } 1626 | 1627 | } 1628 | 1629 | if (!validationArray.length) { 1630 | $this.removeAttr(_data.validation); 1631 | return true; 1632 | } 1633 | 1634 | $this.attr(_data.validation, self._joinValidation(validationArray)); 1635 | 1636 | }); 1637 | 1638 | }, 1639 | 1640 | /** 1641 | * API method to manually trigger a form error. 1642 | * Note: The same form jQuery selector MUST be used to recuperate the Validation configuration. 1643 | * 1644 | * @example 1645 | * $('#form-signup_v3').addError({ 1646 | * 'inputName': 'my error message', 1647 | * 'inputName2': [ 1648 | * 'first error message', 1649 | * 'second error message' 1650 | * ] 1651 | * }) 1652 | * 1653 | * @param {Object} node jQuery object 1654 | * @param {Object} error Object of errors to add on the node 1655 | * 1656 | * @returns {*} 1657 | */ 1658 | addError: function (node, error) { 1659 | 1660 | if (!window.Validation.form[node.selector]) { 1661 | 1662 | // {debug} 1663 | window.Debug.log({ 1664 | 'node': node, 1665 | 'function': '$.addError()', 1666 | 'arguments': 'window.Validation.form[' + node.selector + ']', 1667 | 'message': 'ERROR - Invalid node selector' 1668 | }); 1669 | 1670 | window.Debug.print(); 1671 | // {/debug} 1672 | 1673 | return false; 1674 | } 1675 | 1676 | if (typeof error !== "object" || Object.prototype.toString.call(error) !== "[object Object]") { 1677 | 1678 | // {debug} 1679 | window.Debug.log({ 1680 | 'node': node, 1681 | 'function': '$.addError()', 1682 | 'arguments': 'window.Validation.form[' + node.selector + ']', 1683 | 'message': 'ERROR - Invalid argument, must be type object' 1684 | }); 1685 | 1686 | window.Debug.print(); 1687 | // {/debug} 1688 | 1689 | return false; 1690 | } 1691 | 1692 | var input, 1693 | onlyOnce = true; 1694 | for (var inputName in error) { 1695 | 1696 | if (!error.hasOwnProperty(inputName)) { 1697 | continue; 1698 | } 1699 | 1700 | if (!(error[inputName] instanceof Array)) { 1701 | error[inputName] = [error[inputName]]; 1702 | } 1703 | 1704 | input = $(node.selector).find('[name="' + inputName + '"]'); 1705 | if (!input[0]) { 1706 | 1707 | // {debug} 1708 | window.Debug.log({ 1709 | 'node': node, 1710 | 'function': '$.addError()', 1711 | 'arguments': inputName, 1712 | 'message': 'ERROR - Unable to find ' + '$(' + node.selector + ').find("[name="' + inputName + '"]")' 1713 | }); 1714 | 1715 | window.Debug.print(); 1716 | // {/debug} 1717 | 1718 | continue; 1719 | } 1720 | 1721 | if (onlyOnce) { 1722 | window.Validation.hasScrolled = false; 1723 | onlyOnce = false; 1724 | } 1725 | 1726 | window.Validation.form[node.selector].resetOneError(inputName, input); 1727 | 1728 | for (var i = 0; i < error[inputName].length; i++) { 1729 | 1730 | if (typeof error[inputName][i] !== "string") { 1731 | 1732 | // {debug} 1733 | window.Debug.log({ 1734 | 'node': node, 1735 | 'function': '$.addError()', 1736 | 'arguments': error[inputName][i], 1737 | 'message': 'ERROR - Invalid error object property - Accepted format: {"inputName": "errorString"} or {"inputName": ["errorString", "errorString"]}' 1738 | }); 1739 | 1740 | window.Debug.print(); 1741 | // {/debug} 1742 | 1743 | continue; 1744 | } 1745 | 1746 | window.Validation.form[node.selector].registerError(inputName, error[inputName][i]); 1747 | 1748 | } 1749 | 1750 | window.Validation.form[node.selector].displayOneError(inputName); 1751 | 1752 | } 1753 | 1754 | }, 1755 | 1756 | /** 1757 | * API method to manually remove a form error. 1758 | * Note: The same form jQuery selector MUST be used to recuperate the Validation configuration. 1759 | * 1760 | * @example 1761 | * $('#form-signin_v2').removeError([ 1762 | * 'signin_v2[username]', 1763 | * 'signin_v2[password]' 1764 | * ]) 1765 | * 1766 | * @param {Object} node jQuery object 1767 | * @param {Object} inputName Object of errors to remove on the node 1768 | * 1769 | * @returns {*} 1770 | */ 1771 | removeError: function (node, inputName) { 1772 | 1773 | if (!window.Validation.form[node.selector]) { 1774 | 1775 | // {debug} 1776 | window.Debug.log({ 1777 | 'node': node, 1778 | 'function': '$.removeError()', 1779 | 'arguments': 'window.Validation.form[' + node.selector + ']', 1780 | 'message': 'ERROR - Invalid node selector' 1781 | }); 1782 | 1783 | window.Debug.print(); 1784 | // {/debug} 1785 | 1786 | return false; 1787 | } 1788 | 1789 | if (!inputName) { 1790 | window.Validation.form[node.selector].resetErrors(); 1791 | return false; 1792 | } 1793 | 1794 | if (typeof inputName === "object" && Object.prototype.toString.call(inputName) !== "[object Array]") { 1795 | 1796 | // {debug} 1797 | window.Debug.log({ 1798 | 'node': node, 1799 | 'function': '$.removeError()', 1800 | 'arguments': inputName, 1801 | 'message': 'ERROR - Invalid inputName, must be type String or Array' 1802 | }); 1803 | 1804 | window.Debug.print(); 1805 | // {/debug} 1806 | 1807 | return false; 1808 | } 1809 | 1810 | if (!(inputName instanceof Array)) { 1811 | inputName = [inputName]; 1812 | } 1813 | 1814 | var input; 1815 | for (var i = 0; i < inputName.length; i++) { 1816 | 1817 | input = $(node.selector).find('[name="' + inputName[i] + '"]'); 1818 | if (!input[0]) { 1819 | 1820 | // {debug} 1821 | window.Debug.log({ 1822 | 'node': node, 1823 | 'function': '$.removeError()', 1824 | 'arguments': inputName[i], 1825 | 'message': 'ERROR - Unable to find ' + '$(' + node.selector + ').find("[name="' + inputName[i] + '"]")' 1826 | }); 1827 | 1828 | window.Debug.print(); 1829 | // {/debug} 1830 | 1831 | continue; 1832 | } 1833 | 1834 | window.Validation.form[node.selector].resetOneError(inputName[i], input); 1835 | 1836 | } 1837 | 1838 | }, 1839 | 1840 | /** 1841 | * API method to add a validation rule. 1842 | * 1843 | * @example 1844 | * $.alterValidationRules({ 1845 | * rule: 'FILENAME', 1846 | * regex: /^[^\\/:\*\?<>\|\"\']*$/, 1847 | * message: '$ has an invalid filename.' 1848 | * }) 1849 | * 1850 | * @param {Object} ruleObj 1851 | */ 1852 | alterValidationRules: function (ruleObj) { 1853 | 1854 | if (!ruleObj.rule || (!ruleObj.regex && !ruleObj.message)) { 1855 | // {debug} 1856 | window.Debug.log({ 1857 | 'function': '$.alterValidationRules()', 1858 | 'message': 'ERROR - Missing one or multiple parameter(s) {rule, regex, message}' 1859 | }); 1860 | window.Debug.print(); 1861 | // {/debug} 1862 | return false; 1863 | } 1864 | 1865 | ruleObj.rule = ruleObj.rule.toUpperCase(); 1866 | 1867 | if (ruleObj.regex) { 1868 | 1869 | var regex = _buildRegexFromString(ruleObj.regex); 1870 | 1871 | if (!(regex instanceof RegExp)) { 1872 | // {debug} 1873 | window.Debug.log({ 1874 | 'function': '$.alterValidationRules(rule)', 1875 | 'arguments': regex.toString(), 1876 | 'message': 'ERROR - Invalid rule' 1877 | }); 1878 | window.Debug.print(); 1879 | // {/debug} 1880 | return false; 1881 | } 1882 | 1883 | _rules[ruleObj.rule] = regex; 1884 | } 1885 | 1886 | if (ruleObj.message) { 1887 | _messages[ruleObj.rule] = ruleObj.message; 1888 | } 1889 | 1890 | return true; 1891 | } 1892 | 1893 | }; 1894 | 1895 | /** 1896 | * @private 1897 | * Converts string into a regex 1898 | * 1899 | * @param {String|Object} regex 1900 | * @returns {Object|Boolean} rule 1901 | */ 1902 | function _buildRegexFromString(regex) { 1903 | 1904 | if (!regex || (typeof regex !== "string" && !(regex instanceof RegExp))) { 1905 | _regexDebug(); 1906 | return false; 1907 | } 1908 | 1909 | if (typeof regex !== 'string') { 1910 | regex = regex.toString(); 1911 | } 1912 | 1913 | var separator = regex.charAt(0), 1914 | index = regex.length - 1, 1915 | pattern, 1916 | modifier, 1917 | rule; 1918 | 1919 | while (index > 0) { 1920 | if (/[gimsxeU]/.test(regex.charAt(index))) { 1921 | index--; 1922 | } else { 1923 | break; 1924 | } 1925 | } 1926 | 1927 | if (regex.charAt(index) !== separator) { 1928 | separator = null; 1929 | } 1930 | 1931 | if (separator && index !== regex.length - 1) { 1932 | modifier = regex.substr(index + 1, regex.length - 1); 1933 | } 1934 | 1935 | if (separator) { 1936 | pattern = regex.substr(1, index - 1); 1937 | } else { 1938 | pattern = regex; 1939 | } 1940 | 1941 | try { 1942 | rule = new RegExp(pattern, modifier); 1943 | } catch (error) { 1944 | _regexDebug(); 1945 | return false; 1946 | } 1947 | 1948 | return rule; 1949 | 1950 | function _regexDebug() { 1951 | // {debug} 1952 | window.Debug.log({ 1953 | 'function': '_buildRegexFromString()', 1954 | 'arguments': '{pattern: {' + (pattern || '') + '}, modifier: {' + (modifier || '') + '}', 1955 | 'message': 'WARNING - Invalid regex given: ' + regex 1956 | }); 1957 | window.Debug.print(); 1958 | // {/debug} 1959 | } 1960 | 1961 | } 1962 | 1963 | // {debug} 1964 | window.Debug = { 1965 | 1966 | table: {}, 1967 | log: function (debugObject) { 1968 | 1969 | if (!debugObject.message || typeof debugObject.message !== "string") { 1970 | return false; 1971 | } 1972 | 1973 | this.table[debugObject.message] = $.extend( 1974 | Object.preventExtensions( 1975 | { 1976 | 'node': '', 1977 | 'function': '', 1978 | 'arguments': '' 1979 | } 1980 | ), debugObject 1981 | ); 1982 | 1983 | }, 1984 | print: function () { 1985 | 1986 | if (isEmpty(this.table)) { 1987 | return false; 1988 | } 1989 | 1990 | if (console.group !== undefined || console.table !== undefined) { 1991 | 1992 | console.groupCollapsed('--- jQuery Form Validation Debug ---'); 1993 | 1994 | if (console.table) { 1995 | console.table(this.table); 1996 | } else { 1997 | $.each(this.table, function (index, data) { 1998 | console.log(data['Name'] + ': ' + data['Execution Time'] + 'ms'); 1999 | }); 2000 | } 2001 | 2002 | console.groupEnd(); 2003 | 2004 | } else { 2005 | console.log('Debug is not available on your current browser, try the most recent version of Chrome or Firefox.'); 2006 | } 2007 | 2008 | this.table = {}; 2009 | 2010 | } 2011 | 2012 | }; 2013 | // {/debug} 2014 | 2015 | String.prototype.capitalize = function () { 2016 | return this.charAt(0).toUpperCase() + this.slice(1); 2017 | }; 2018 | 2019 | function isEmpty (obj) { 2020 | for (var prop in obj) { 2021 | if (obj.hasOwnProperty(prop)) 2022 | return false; 2023 | } 2024 | 2025 | return true; 2026 | } 2027 | 2028 | if (!Array.prototype.indexOf) { 2029 | Array.prototype.indexOf = function (elt /*, from*/) { 2030 | var len = this.length >>> 0; 2031 | 2032 | var from = Number(arguments[1]) || 0; 2033 | from = (from < 0) 2034 | ? Math.ceil(from) 2035 | : Math.floor(from); 2036 | if (from < 0) 2037 | from += len; 2038 | 2039 | for (; from < len; from++) { 2040 | if (from in this && 2041 | this[from] === elt) 2042 | return from; 2043 | } 2044 | return -1; 2045 | }; 2046 | } 2047 | 2048 | // {debug} 2049 | if (!JSON && !JSON.stringify) { 2050 | JSON.stringify = function (obj) { 2051 | var t = typeof (obj); 2052 | if (t !== "object" || obj === null) { 2053 | // simple data type 2054 | if (t === "string") { 2055 | obj = '"' + obj + '"'; 2056 | } 2057 | return String(obj); 2058 | } 2059 | else { 2060 | var n, v, json = [], arr = (obj && obj.constructor === Array); 2061 | for (n in obj) { 2062 | if (true) { 2063 | v = obj[n]; 2064 | t = typeof(v); 2065 | if (t === "string") { 2066 | v = '"' + v + '"'; 2067 | } 2068 | else if (t === "object" && v !== null) { 2069 | v = JSON.stringify(v); 2070 | } 2071 | json.push((arr ? "" : '"' + n + '": ') + String(v)); 2072 | } 2073 | } 2074 | return (arr ? "[" : "{") + String(json) + (arr ? "]" : "}"); 2075 | } 2076 | }; 2077 | } 2078 | // {/debug} 2079 | 2080 | }(window, document, window.jQuery)); --------------------------------------------------------------------------------