├── .gitignore ├── component.json ├── bower.json ├── README.md ├── composer.json └── h5validate.js /.gitignore: -------------------------------------------------------------------------------- 1 | # Application 2 | composer.lock 3 | composer.phar 4 | vendor/ 5 | node_modules/ 6 | bower_components/ 7 | public/_/ 8 | 9 | # IDEs 10 | .idea/ 11 | .settings/ 12 | .project/ 13 | .buildpath/ 14 | 15 | # Miscellaneous 16 | .DS_Store 17 | *~ 18 | -------------------------------------------------------------------------------- /component.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "h5validate", 3 | "repo": "leodido/h5validate", 4 | "version": "0.9.0", 5 | "homepage": "http://github.com/leodido/h5validate", 6 | "description": "Repository for the h5validate jQuery component", 7 | "keywords": [ 8 | "jquery", 9 | "form", 10 | "validation", 11 | "input", 12 | "h5validate", 13 | "component" 14 | ], 15 | "main": "h5validate.js", 16 | "scripts": [ 17 | "h5validate.js" 18 | ], 19 | "license": "GPL", 20 | "dependencies": { 21 | "component/jquery": "*" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "h5validate", 3 | "main": "h5validate.js", 4 | "version": "0.9.0", 5 | "homepage": "https://github.com/leodido/h5validate", 6 | "authors": [ 7 | { "name": "Eric Elliot", "homepage": "http://ericleads.com" }, 8 | { "name": "Leo Di Donato", "homepage": "http://github.com/leodido", "email": "leodidonato@gmail.com" } 9 | ], 10 | "moduleType": [ 11 | "globals" 12 | ], 13 | "description": "Shim repository for the h5validate jQuery plugin", 14 | "keywords": [ 15 | "validation", 16 | "jquery", 17 | "form", 18 | "input", 19 | "h5validate" 20 | ], 21 | "license": "GPL", 22 | "ignore": [ 23 | "**/.*", 24 | "node_modules", 25 | "bower_components", 26 | "test", 27 | "tests" 28 | ], 29 | "dependencies": { 30 | "jquery": ">=1.9.0" 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | h5validate 2 | ========== 3 | 4 | Shim repository for the [h5validate](https://github.com/dilvie/h5Validate) jQuery plugin. 5 | 6 | Original demo page can be found [here](http://ericleads.com/h5validate/). 7 | 8 | _Note: function `h5Validate` as been lowercased (i.e., `h5validate`)._ 9 | 10 | Installation 11 | ------------ 12 | 13 | ### Using [composer](https://github.com/composer/composer) 14 | 15 | To install with [composer](http://packagist.org/packages/components/h5validate) add `components/h5validate` to your `composer.json`. E.g., 16 | 17 | ``` 18 | { 19 | "require": { 20 | "components/h5validate": "*" 21 | } 22 | } 23 | ``` 24 | 25 | ### Using [component](http://component.io) 26 | 27 | To install as a [component](https://github.com/component/component) just run `component install leodido/h5validate`. 28 | 29 | Or add `leodido/h5validate` to the dependencies element into your `component.json`. 30 | 31 | ### Using [bower](http://bower.io) 32 | 33 | To install with [bower]() just run `bower install h5validate`. 34 | 35 | Or add `h5validate` to the dependencies element into you `bower.json`. 36 | 37 | --- 38 | 39 | [![Analytics](https://ga-beacon.appspot.com/UA-49657176-1/h5validate)](https://github.com/igrigorik/ga-beacon) 40 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "components/h5validate", 3 | "description": "Shim repository for the h5validate jQuery plugin", 4 | "type": "component", 5 | "homepage": "http://github.com/leodido/h5validate", 6 | "license": "GPL", 7 | "keywords": [ 8 | "jquery", 9 | "form", 10 | "validation", 11 | "input", 12 | "h5validate", 13 | "component" 14 | ], 15 | "support": { 16 | "wiki": "http://ericleads.com/h5validate", 17 | "source": "http://github.com/dilvie/h5Validate" 18 | }, 19 | "authors": [ 20 | { 21 | "name": "Eric Hamilton", 22 | "homepage": "http://ericleads.com", 23 | "role": "Developer" 24 | }, 25 | { 26 | "name": "Leo Di Donato", 27 | "email": "leodidonato@gmail.com", 28 | "homepage": "http://github.com/leodido", 29 | "role": "Maintainer" 30 | } 31 | ], 32 | "require": { 33 | "robloach/component-installer": "*" 34 | }, 35 | "extra": { 36 | "component": { 37 | "scripts": [ 38 | "h5validate.js" 39 | ], 40 | "shim": { 41 | "deps": ["jquery"] 42 | } 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /h5validate.js: -------------------------------------------------------------------------------- 1 | /** 2 | * h5validate 3 | * @version v0.9.0 4 | * Using semantic versioning: http://semver.org 5 | * @author Eric Hamilton http://ericleads.com 6 | * @maintainer Leo Di Donato http://github.com/leodido 7 | * @copyright 2010 - 2012 Eric Hamilton 8 | * Dual licensed under the MIT and GPL licenses: 9 | * http://www.opensource.org/licenses/mit-license.php 10 | * http://www.gnu.org/licenses/gpl.html 11 | * 12 | * Developed under the sponsorship of RootMusic, Zumba Fitness, LLC, and Rese Property Management 13 | */ 14 | 15 | /* global jquery, window, console */ 16 | (function ($) { 17 | 'use strict'; 18 | var console = window.console || function () { 19 | }, 20 | h5 = { 21 | // Public API 22 | defaults: { 23 | debug: false, 24 | 25 | RODom: false, 26 | 27 | // HTML5-compatible validation pattern library that can be extended and/or overriden 28 | patternLibrary: { 29 | // TODO: Test the new regex patterns. Should I apply these to the new input types? 30 | 31 | // TODO: password 32 | 33 | phone: /([\+][0-9]{1,3}([ \.\-])?)?([\(][0-9]{1,6}[\)])?([0-9A-Za-z \.\-]{1,32})(([A-Za-z \:]{1,11})?[0-9]{1,4}?)/, 34 | 35 | // Shamelessly lifted from Scott Gonzalez via the Bassistance Validation plugin http://projects.scottsplayground.com/email_address_validation/ 36 | email: /((([a-zA-Z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+(\.([a-zA-Z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+)*)|((\x22)((((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(([\x01-\x08\x0b\x0c\x0e-\x1f\x7f]|\x21|[\x23-\x5b]|[\x5d-\x7e]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(\\([\x01-\x09\x0b\x0c\x0d-\x7f]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]))))*(((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(\x22)))@((([a-zA-Z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-zA-Z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-zA-Z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-zA-Z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)+(([a-zA-Z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-zA-Z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-zA-Z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-zA-Z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.?/, 37 | 38 | // Shamelessly lifted from Scott Gonzalez via the Bassistance Validation plugin http://projects.scottsplayground.com/iri/ 39 | url: /(https?|ftp):\/\/(((([a-zA-Z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:)*@)?(((\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5]))|((([a-zA-Z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-zA-Z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-zA-Z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-zA-Z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)+(([a-zA-Z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-zA-Z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-zA-Z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-zA-Z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.?)(:\d*)?)(\/((([a-zA-Z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)+(\/(([a-zA-Z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)*)*)?)?(\?((([a-zA-Z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)|[\uE000-\uF8FF]|\/|\?)*)?(\#((([a-zA-Z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)|\/|\?)*)?/, 40 | 41 | // Number, including positive, negative, and floating decimal. Credit: bassistance 42 | number: /-?(?:\d+|\d{1,3}(?:,\d{3})+)?(?:\.\d+)?/, 43 | 44 | // Date in ISO format. Credit: bassistance 45 | dateISO: /\d{4}[\/\-]\d{1,2}[\/\-]\d{1,2}/, 46 | 47 | alpha: /[a-zA-Z]+/, 48 | 49 | alphaNumeric: /\w+/, 50 | 51 | integer: /-?\d+/ 52 | }, 53 | 54 | // The prefix to use for dynamically-created class names 55 | classPrefix: 'h5-', 56 | 57 | errorClass: 'ui-state-error', // No prefix for these 58 | validClass: 'ui-state-valid', 59 | activeClass: 'active', // Prefix will get prepended 60 | requiredClass: 'required', 61 | requiredAttribute: 'required', 62 | patternAttribute: 'pattern', 63 | 64 | // Attribute which stores the ID of the error container element (without the hash) 65 | errorAttribute: 'data-h5-errorid', 66 | 67 | // Events API 68 | customEvents: { 69 | 'validate': true 70 | }, 71 | 72 | // Setup KB event delegation 73 | kbSelectors: ':input:not(:button):not(:disabled):not(.novalidate)', 74 | focusout: true, 75 | focusin: false, 76 | change: true, 77 | keyup: false, 78 | activeKeyup: true, 79 | 80 | // Setup mouse event delegation 81 | mSelectors: '[type="range"]:not(:disabled):not(.novalidate), :radio:not(:disabled):not(.novalidate), :checkbox:not(:disabled):not(.novalidate), select:not(:disabled):not(.novalidate), option:not(:disabled):not(.novalidate)', 82 | click: true, 83 | 84 | // What do we name the required .data variable? 85 | requiredVar: 'h5-required', 86 | 87 | // What do we name the pattern .data variable? 88 | patternVar: 'h5-pattern', 89 | stripMarkup: true, 90 | 91 | // Run submit related checks and prevent form submission if any fields are invalid? 92 | submit: true, 93 | 94 | // Move focus to the first invalid field on submit? 95 | focusFirstInvalidElementOnSubmit: true, 96 | 97 | // When submitting, validate elements that haven't been validated yet? 98 | validateOnSubmit: true, 99 | 100 | // Callback stubs 101 | invalidCallback: function () { 102 | }, 103 | validCallback: function () { 104 | }, 105 | 106 | // Elements to validate with allValid (only validating visible elements) 107 | allValidSelectors: ':input:visible:not(:button):not(:disabled):not(.novalidate)', 108 | 109 | // Mark field invalid. 110 | // TODO: Highlight labels 111 | // TODO: Implement setCustomValidity as per the spec: 112 | // http://www.whatwg.org/specs/web-apps/current-work/multipage/association-of-controls-and-forms.html#dom-cva-setcustomvalidity 113 | markInvalid: function markInvalid(options) { 114 | var $element = $(options.element), 115 | $errorID = $(options.errorID); 116 | $element.addClass(options.errorClass).removeClass(options.validClass); 117 | 118 | // User needs help. Enable active validation. 119 | $element.addClass(options.settings.activeClass); 120 | 121 | if ($errorID.length) { // These ifs are technically not needed, but improve server-side performance 122 | if ($element.attr('title')) { 123 | $errorID.text($element.attr('title')); 124 | } 125 | $errorID.show(); 126 | } 127 | $element.data('valid', false); 128 | options.settings.invalidCallback.call(options.element, options.validity); 129 | return $element; 130 | }, 131 | 132 | // Mark field valid. 133 | markValid: function markValid(options) { 134 | var $element = $(options.element), 135 | $errorID = $(options.errorID); 136 | 137 | $element.addClass(options.validClass).removeClass(options.errorClass); 138 | if ($errorID.length) { 139 | $errorID.hide(); 140 | } 141 | $element.data('valid', true); 142 | options.settings.validCallback.call(options.element, options.validity); 143 | return $element; 144 | }, 145 | 146 | // Unmark field 147 | unmark: function unmark(options) { 148 | var $element = $(options.element); 149 | $element.removeClass(options.errorClass).removeClass(options.validClass); 150 | $element.form.find("#" + options.element.id).removeClass(options.errorClass).removeClass(options.validClass); 151 | return $element; 152 | } 153 | } 154 | }, 155 | // Aliases 156 | defaults = h5.defaults, 157 | patternLibrary = defaults.patternLibrary, 158 | createValidity = function createValidity(validity) { 159 | return $.extend({ 160 | customError: validity.customError || false, 161 | patternMismatch: validity.patternMismatch || false, 162 | rangeOverflow: validity.rangeOverflow || false, 163 | rangeUnderflow: validity.rangeUnderflow || false, 164 | stepMismatch: validity.stepMismatch || false, 165 | tooLong: validity.tooLong || false, 166 | typeMismatch: validity.typeMismatch || false, 167 | valid: validity.valid || true, 168 | valueMissing: validity.valueMissing || false 169 | }, validity); 170 | }, 171 | methods = { 172 | /** 173 | * Check the validity of the current field 174 | * @param {object} settings instance settings 175 | * @param {object} options 176 | * .revalidate - trigger validation function first? 177 | * @return {Boolean} 178 | */ 179 | isValid: function (settings, options) { 180 | var $this = $(this); 181 | 182 | options = (settings && options) || {}; 183 | 184 | // Revalidate defaults to true 185 | if (options.revalidate !== false) { 186 | $this.trigger('validate'); 187 | } 188 | 189 | return $this.data('valid'); // Get the validation result 190 | }, 191 | allValid: function (config, options) { 192 | var valid = true, 193 | formValidity = [], 194 | $this = $(this), 195 | $allFields, 196 | $filteredFields, 197 | radioNames = [], 198 | getValidity = function getValidity(e, data) { 199 | data.e = e; 200 | formValidity.push(data); 201 | }, 202 | settings = $.extend({}, config, options); // allow options to override settings 203 | 204 | options = options || {}; 205 | 206 | $this.trigger('formValidate', {settings: $.extend(true, {}, settings)}); 207 | 208 | // Make sure we're not triggering handlers more than we need to. 209 | $this.undelegate(settings.allValidSelectors, 210 | '.allValid', getValidity); 211 | $this.delegate(settings.allValidSelectors, 212 | 'validated.allValid', getValidity); 213 | 214 | $allFields = $this.find(settings.allValidSelectors); 215 | 216 | // Filter radio buttons with the same name and keep only one, 217 | // since they will be checked as a group by isValid() 218 | $filteredFields = $allFields.filter(function (index) { 219 | var name; 220 | 221 | if (this.tagName === "INPUT" 222 | && this.type === "radio") { 223 | name = this.name; 224 | if (radioNames[name] === true) { 225 | return false; 226 | } 227 | radioNames[name] = true; 228 | } 229 | return true; 230 | }); 231 | 232 | $filteredFields.each(function () { 233 | var $this = $(this); 234 | valid = $this.h5validate('isValid', options) && valid; 235 | }); 236 | 237 | $this.trigger('formValidated', {valid: valid, elements: formValidity}); 238 | return valid; 239 | }, 240 | validate: function (settings) { 241 | // Get the HTML5 pattern attribute if it exists. 242 | // ** TODO: If a pattern class exists, grab the pattern from the patternLibrary, but the pattern attrib should override that value. 243 | var $this = $(this), 244 | pattern = $this.filter('[pattern]')[0] ? $this.attr('pattern') : false, 245 | 246 | // The pattern attribute must match the whole value, not just a subset: 247 | // "...as if it implied a ^(?: at the start of the pattern and a )$ at the end." 248 | re = new RegExp('^(?:' + pattern + ')$'), 249 | $radiosWithSameName = null, 250 | value = ($this.is('[type=checkbox]')) ? 251 | $this.is(':checked') : ($this.is('[type=radio]') ? 252 | // Cache all radio buttons (in the same form) with the same name as this one 253 | ($radiosWithSameName = $this.parents('form') 254 | // **TODO: escape the radio buttons' name before using it in the jQuery selector 255 | .find('input[name="' + $this.attr('name') + '"]')) 256 | .filter(':checked') 257 | .length > 0 : $this.val()), 258 | errorClass = settings.errorClass, 259 | validClass = settings.validClass, 260 | errorIDbare = $this.attr(settings.errorAttribute) || false, // Get the ID of the error element. 261 | errorID = errorIDbare ? '#' + errorIDbare.replace(/(:|\.|\[|\])/g, '\\$1') : false, // Add the hash for convenience. This is done in two steps to avoid two attribute lookups. 262 | required = false, 263 | validity = createValidity({element: this, valid: true}), 264 | $checkRequired = $(''), 265 | maxlength; 266 | 267 | /* If the required attribute exists, set it required to true, unless it's set 'false'. 268 | * This is a minor deviation from the spec, but it seems some browsers have falsey 269 | * required values if the attribute is empty (should be true). The more conformant 270 | * version of this failed sanity checking in the browser environment. 271 | * This plugin is meant to be practical, not ideologically married to the spec. 272 | */ 273 | // Feature fork 274 | if ($checkRequired.filter('[required]') && $checkRequired.filter('[required]').length) { 275 | required = ($this.filter('[required]').length && $this.attr('required') !== 'false'); 276 | } else { 277 | required = ($this.attr('required') !== undefined); 278 | } 279 | 280 | if (settings.debug && window.console) { 281 | console.log('Validate called on "' + value + '" with regex "' + re + '". Required: ' + required); // **DEBUG 282 | console.log('Regex test: ' + re.test(value) + ', Pattern: ' + pattern); // **DEBUG 283 | } 284 | 285 | maxlength = parseInt($this.attr('maxlength'), 10); 286 | if (!isNaN(maxlength) && value.length > maxlength) { 287 | validity.valid = false; 288 | validity.tooLong = true; 289 | } 290 | 291 | if (required && !value) { 292 | validity.valid = false; 293 | validity.valueMissing = true; 294 | } else if (pattern && !re.test(value) && value) { 295 | validity.valid = false; 296 | validity.patternMismatch = true; 297 | } else { 298 | if (!settings.RODom) { 299 | settings.markValid({ 300 | element: this, 301 | validity: validity, 302 | errorClass: errorClass, 303 | validClass: validClass, 304 | errorID: errorID, 305 | settings: settings 306 | }); 307 | } 308 | } 309 | 310 | if (!validity.valid) { 311 | if (!settings.RODom) { 312 | settings.markInvalid({ 313 | element: this, 314 | validity: validity, 315 | errorClass: errorClass, 316 | validClass: validClass, 317 | errorID: errorID, 318 | settings: settings 319 | }); 320 | } 321 | } 322 | $this.trigger('validated', validity); 323 | 324 | // If it's a radio button, also validate the other radio buttons with the same name 325 | // (while making sure the call is not recursive) 326 | if ($radiosWithSameName !== null 327 | && settings.alreadyCheckingRelatedRadioButtons !== true) { 328 | 329 | settings.alreadyCheckingRelatedRadioButtons = true; 330 | 331 | $radiosWithSameName 332 | .not($this) 333 | .trigger('validate'); 334 | 335 | settings.alreadyCheckingRelatedRadioButtons = false; 336 | 337 | } 338 | }, 339 | /** 340 | * Take the event preferences and delegate the events to selected 341 | * objects. 342 | * 343 | * @param {object} eventFlags The object containing event flags. 344 | * 345 | * @returns {element} The passed element (for method chaining). 346 | */ 347 | delegateEvents: function (selectors, eventFlags, element, settings) { 348 | var events = {}, 349 | key = 0, 350 | validate = function () { 351 | settings.validate.call(this, settings); 352 | }; 353 | $.each(eventFlags, function (key, value) { 354 | if (value) { 355 | events[key] = key; 356 | } 357 | }); 358 | // key = 0; 359 | for (key in events) { 360 | if (events.hasOwnProperty(key)) { 361 | $(element).delegate(selectors, events[key] + '.h5validate', validate); 362 | } 363 | } 364 | return element; 365 | }, 366 | /** 367 | * Prepare for event delegation. 368 | * 369 | * @param {object} settings The full plugin state, including 370 | * options. 371 | * 372 | * @returns {object} jQuery object for chaining. 373 | */ 374 | bindDelegation: function (settings) { 375 | var $this = $(this), 376 | $forms; 377 | // Attach patterns from the library to elements. 378 | // **TODO: pattern / validation method matching should 379 | // take place inside the validate action. 380 | $.each(patternLibrary, function (key, value) { 381 | var pattern = value.toString(); 382 | pattern = pattern.substring(1, pattern.length - 1); 383 | $('.' + settings.classPrefix + key).attr('pattern', pattern); 384 | }); 385 | 386 | $forms = $this.filter('form') 387 | .add($this.find('form')) 388 | .add($this.parents('form')); 389 | 390 | $forms 391 | .attr('novalidate', 'novalidate') 392 | .submit(checkValidityOnSubmitHandler); 393 | 394 | $forms.find("input[formnovalidate][type='submit']").click(function () { 395 | $(this).closest("form").unbind('submit', checkValidityOnSubmitHandler); 396 | }); 397 | 398 | return this.each(function () { 399 | var kbEvents = { 400 | focusout: settings.focusout, 401 | focusin: settings.focusin, 402 | change: settings.change, 403 | keyup: settings.keyup 404 | }, 405 | mEvents = { 406 | click: settings.click 407 | }, 408 | activeEvents = { 409 | keyup: settings.activeKeyup 410 | }; 411 | 412 | settings.delegateEvents(':input', settings.customEvents, this, settings); 413 | settings.delegateEvents(settings.kbSelectors, kbEvents, this, settings); 414 | settings.delegateEvents(settings.mSelectors, mEvents, this, settings); 415 | settings.delegateEvents(settings.activeClassSelector, activeEvents, this, settings); 416 | settings.delegateEvents('textarea[maxlength]', {keyup: true}, this, settings); 417 | }); 418 | } 419 | }, 420 | /** 421 | * Event handler for the form submit event. 422 | * When settings.submit is enabled: 423 | * - prevents submission if any invalid fields are found. 424 | * - Optionally validates all fields. 425 | * - Optionally moves focus to the first invalid field. 426 | * 427 | * @param {object} evt The jQuery Event object as from the submit event. 428 | * 429 | * @returns {object} undefined if no validation was done, true if validation passed, false if validation didn't. 430 | */ 431 | checkValidityOnSubmitHandler = function (evt) { 432 | var $this, 433 | settings = getInstance.call(this), 434 | allValid; 435 | 436 | if (settings.submit !== true) { 437 | return; 438 | } 439 | 440 | $this = $(this); 441 | allValid = $this.h5validate('allValid', {revalidate: settings.validateOnSubmit === true}); 442 | 443 | if (allValid !== true) { 444 | evt.preventDefault(); 445 | 446 | if (settings.focusFirstInvalidElementOnSubmit === true) { 447 | var $invalid = $(settings.allValidSelectors, $this) 448 | .filter(function (index) { 449 | return $(this).h5validate('isValid', {revalidate: false}) !== true; 450 | }); 451 | 452 | $invalid.first().focus(); 453 | } 454 | } 455 | 456 | return allValid; 457 | }, 458 | instances = [], 459 | buildSettings = function buildSettings(options) { 460 | // Combine defaults and options to get current settings. 461 | var settings = $.extend({}, defaults, options, methods), 462 | activeClass = settings.classPrefix + settings.activeClass; 463 | 464 | return $.extend(settings, { 465 | activeClass: activeClass, 466 | activeClassSelector: '.' + activeClass, 467 | requiredClass: settings.classPrefix + settings.requiredClass, 468 | el: this 469 | }); 470 | }, 471 | getInstance = function getInstance() { 472 | var $parent = $(this).closest('[data-h5-instanceId]'); 473 | return instances[$parent.attr('data-h5-instanceId')]; 474 | }, 475 | setInstance = function setInstance(settings) { 476 | var instanceId = instances.push(settings) - 1; 477 | if (settings.RODom !== true) { 478 | $(this).attr('data-h5-instanceId', instanceId); 479 | } 480 | $(this).trigger('instance', {'data-h5-instanceId': instanceId}); 481 | }; 482 | $.h5validate = { 483 | /** 484 | * Take a map of pattern names and HTML5-compatible regular 485 | * expressions, and add them to the patternLibrary. Patterns in 486 | * the library are automatically assigned to HTML element pattern 487 | * attributes for validation. 488 | * 489 | * @param {Object} patterns A map of pattern names and HTML5 compatible 490 | * regular expressions. 491 | * 492 | * @returns {Object} patternLibrary The modified pattern library 493 | */ 494 | addPatterns: function (patterns) { 495 | var patternLibrary = defaults.patternLibrary, 496 | key; 497 | for (key in patterns) { 498 | if (patterns.hasOwnProperty(key)) { 499 | patternLibrary[key] = patterns[key]; 500 | } 501 | } 502 | return patternLibrary; 503 | }, 504 | /** 505 | * Take a valid jQuery selector, and a list of valid values to 506 | * validate against. 507 | * If the user input isn't in the list, validation fails. 508 | * 509 | * @param {String} selector Any valid jQuery selector. 510 | * 511 | * @param {Array} values A list of valid values to validate selected 512 | * fields against. 513 | */ 514 | validValues: function (selector, values) { 515 | var i = 0, 516 | ln = values.length, 517 | pattern = '', 518 | re; 519 | // Build regex pattern 520 | for (i = 0; i < ln; i += 1) { 521 | pattern = pattern ? pattern + '|' + values[i] : values[i]; 522 | } 523 | re = new RegExp('^(?:' + pattern + ')$'); 524 | $(selector).data('regex', re); 525 | } 526 | }; 527 | $.fn.h5validate = function h5validate(options) { 528 | var action, 529 | args, 530 | settings; 531 | 532 | if (typeof options === 'string' && typeof methods[options] === 'function') { 533 | // Whoah, hold on there! First we need to get the instance: 534 | settings = getInstance.call(this); 535 | 536 | args = [].slice.call(arguments, 0); 537 | action = options; 538 | args.shift(); 539 | args = $.merge([settings], args); 540 | 541 | // Use settings here so we can plug methods into the instance dynamically? 542 | return settings[action].apply(this, args); 543 | } 544 | 545 | settings = buildSettings.call(this, options); 546 | setInstance.call(this, settings); 547 | 548 | // Returning the jQuery object allows for method chaining. 549 | return methods.bindDelegation.call(this, settings); 550 | }; 551 | }(jQuery)); 552 | --------------------------------------------------------------------------------