Created
September 17, 2011 17:55
-
-
Save pavelkucera/1224180 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* Beta | |
* | |
* Sibling of netteForms.js "rewritten to jQuery" | |
* @author Pavel Kučera | |
* @author David Grudl author of netteForms.js, of the idea and the one who I copied from | |
* | |
* Disclaimer: I'm not a javascript programmer, actually I hate javascript, so consider that please if you want to complain about quality of this utility. | |
* | |
* Dependencies | |
* jQuery - http://jquery.com/ 1.6.4+ | |
*/ | |
(function($) { | |
// Nette | |
$.extend({ | |
Nette: { | |
// Provides support for events "forgotten" by jQuery | |
// todo: register needed events to jQuery | |
addEvent: function(element, on, callback) { | |
var original = element['on' + on]; | |
element['on' + on] = function () { | |
if (typeof original === 'function' && original.apply(element, arguments) === false) { | |
return false; | |
} | |
return callback.apply(element, arguments); | |
}; | |
}, | |
getValue: function(elem) { | |
elem = $(elem); | |
if (elem.is('input[type=checkbox]')) { | |
return !!elem.attr('checked'); | |
} else if (elem.is('input[type=radio]') && elem.length != 1) { | |
var el, val; | |
$(elem).each(function() { | |
el = $(this); | |
if (el.is(':checked')) { | |
val = el.val(); | |
} | |
}); | |
return val; | |
} else { | |
return elem.val(); | |
} | |
} | |
} | |
}); | |
// Nette form constructor | |
$.Nette.form = function(form, options) { | |
// Store form for internal usage, initialize storages | |
this.form = form; | |
this.invalidElements = []; | |
this.errors = []; | |
// Settings | |
this.settings = $.extend(true, {}, $.Nette.form.defaults, options); | |
// Init everything possible and impossible | |
this.init(); | |
}; | |
// Nette form | |
$.extend($.Nette.form, { | |
// Default settings | |
defaults: { | |
onSubmit: true, | |
debug: false, | |
focusInvalid: true, | |
rules: [], | |
errorContainer: $('<ul class="error"/>'), | |
errorElement: $('<li/>'), | |
onKeyUp: true | |
}, | |
// Prototype | |
prototype: { | |
// Form is stored right here | |
form: undefined, | |
// Settings storage | |
settings: undefined, | |
// List of invalid elements | |
invalidElements: undefined, | |
// Storage of errors | |
errors: undefined, | |
init: function() { | |
var i; | |
// Set nette-submittedBy 'field' | |
this.form.click(function(e) { | |
var target = e.target || e.srcElement; | |
this['nette-submittedBy'] = (target.type in {submit:1, image:1}) ? target : null; | |
}); | |
// Force IE to behave as a good man | |
if ($.browser.msie) { | |
this._makeIEBetter(); | |
} | |
// Turn off browser validation | |
this.form.attr('noValidate', true); | |
// Turn on "live" validation if allowed | |
if (this.settings.onKeyUp) { | |
this.form.delegate('input, select, textarea', 'keyup', function() { | |
var el = $(this); | |
$(el[0].form).netteForm().validateElement(el); | |
}); | |
} | |
}, | |
_makeIEBetter: function() { | |
var labels = {}, | |
wheelHandler = function() { return false; }, | |
clickHandler = function() { document.getElementById(this.htmlFor).focus(); return false; }, | |
i, elms; | |
for (i = 0, elms = this.form.find('label'); i < elms.length; i++) { | |
labels[elms.eq(i).attr('for')] = elms.eq(i); | |
} | |
for (i = 0, elms = this.form.find('select'); i < elms.length; i++) { | |
// Prevent accidental change | |
$.Nette.addEvent(elms[i], 'mousewheel', wheelHandler); | |
// Prevent deselect in IE 5 - 6 | |
if (labels[elms.eq(i).attr('id')]) { | |
labels[elms.eq(i).attr('id')].click(clickHandler); | |
} | |
} | |
}, | |
validatableElements: function() { | |
var elements = [], | |
i, elem; | |
for (i = 0; i < this.form[0].elements.length; i++) { | |
elem = this.form[0].elements[i]; | |
if (!this.isValidatable(elem)) { | |
continue; | |
} | |
elements.push($(elem)); | |
} | |
return elements; | |
}, | |
isValidatable: function(elem) { | |
return (elem.nodeName.toLowerCase() in {input:1, select:1, textarea:1}) && !(elem.type in {hidden:1, submit:1, image:1, reset: 1}) && !elem.disabled && !elem.readonly; | |
}, | |
// Rules handling | |
rebuildRules: function() { | |
var i, elms; | |
elms = this.validatableElements(); | |
for (i = 0; i < elms.length; i++) { | |
this.buildElemRules(elms[i]); | |
} | |
}, | |
buildElemRules: function(elem) { | |
var id = this.elemIdentificator(elem), | |
rules = this.parseRules(eval('[' + (elem.attr('data-nette-rules') || '') + ']')); | |
this.settings.rules[id] = rules; | |
return rules; | |
}, | |
elemRules: function(elem) { | |
var id = this.elemIdentificator(elem), | |
rules = this.settings.rules[id]; | |
if (typeof rules == 'undefined') { | |
rules = this.buildElemRules(elem); | |
} | |
return rules; | |
}, | |
parseRules: function(rules) { | |
var result = [], | |
rule, tmp, i; | |
for (i = 0; i < rules.length; i++) { | |
rule = rules[i]; | |
// Process negation, condition | |
tmp = rule.op.match(/(~)?([^?]+)/); | |
rule.neg = tmp[1]; | |
rule.condition = !!rule.rules; | |
// Process operation | |
if (tmp[2].charAt(0) === ':') { | |
tmp[2] = tmp[2].substr(1); | |
} | |
rule.op = tmp[2]; | |
// If rule carries information about control, replace it by object | |
if (rule.control) { | |
rule.control = $(this.form[0].elements[rule.control]); | |
} | |
// Process conditional rules | |
if (rule.condition) { | |
rule.rules = this.parseRules(rule.rules); | |
} | |
// Append rule to the set | |
result.push(rule); | |
} | |
return result; | |
}, | |
// Validation itself | |
validate: function(onlyCheck) { | |
onlyCheck = onlyCheck || false; | |
var elms = this.validatableElements(), | |
i; | |
this.invalidElements = []; | |
for (i = 0; i < elms.length; i++) { | |
if (!this.validateElement(elms[i], onlyCheck)) { | |
this.invalidElements.push(elms[i]); | |
} | |
} | |
if (this.settings.focusInvalid == true && this.invalidElements.length > 0) { | |
this.invalidElements[0].focus(); | |
} | |
return this.invalidElements.length == 0; | |
}, | |
validateElement: function(elem, onlyCheck) { | |
var rules = this.elemRules(elem), | |
currentErrors = this.elemErrors(elem), | |
oldErrors, newErrors, errors; | |
// Check validity | |
errors = this.validateRules(elem, rules); | |
// Hide old errors, display new ones | |
if (!onlyCheck) { | |
// Find old errors & hide them | |
oldErrors = $.grep(currentErrors, function(error) { | |
return $.inArray(error, errors) == -1; | |
}); | |
this.hideElemErrors(elem, oldErrors); | |
this.removeElemErrors(elem, oldErrors); | |
// Find new errors & show them | |
newErrors = $.grep(errors, function(error) { | |
return $.inArray(error, currentErrors) == -1 | |
}); | |
if (newErrors.length > 0) { | |
this.addElemErrors(elem, newErrors); | |
this.showErrors(elem, newErrors); | |
} | |
} | |
return errors.length == 0; | |
}, | |
validateRules: function(elem, rules) { | |
var errors = [], | |
i, j, tmp, rule, op, arg, success; | |
for (i = 0; i < rules.length; i++) { | |
rule = rules[i]; | |
success = this.validateRule(elem, rule); | |
if (rule.condition) { | |
if (success) { | |
tmp = this.validateRules(elem, rule.rules); | |
for (j = 0; j < tmp.length; j++) { | |
errors.push(tmp[j]); | |
} | |
} | |
} else if (!rule.condition && !success) { | |
errors.push(rule.msg.replace('%value', $.Nette.getValue(elem))); | |
} | |
} | |
return errors; | |
}, | |
validateRule: function (elem, rule) { | |
var el = rule.control ? rule.control[0] : elem[0], | |
val = $.Nette.getValue(el), | |
op = rule.op, | |
arg = rule.arg, | |
success = $.Nette.form.validators[op] ? $.Nette.form.validators[op](el, arg, val) : null; | |
if (success !== null && rule.neg) { | |
success = !success; | |
} | |
return success; | |
}, | |
// Errors handling | |
addElemErrors: function(elem, errors) { | |
var id = this.elemIdentificator(elem), | |
i; | |
if (!this.errors[id]) { | |
this.errors[id] = []; | |
} | |
for (i = 0; i < errors.length; i++) { | |
this.errors[id].push(errors[i]); | |
} | |
}, | |
removeElemErrors: function(elem, errors) { | |
var id = this.elemIdentificator(elem); | |
if (!errors) { | |
this.errors[id] = []; | |
} else { | |
this.errors[id] = $.grep(this.elemErrors(elem), function(error) { | |
return $.inArray(error, errors) == -1; | |
}); | |
} | |
}, | |
elemErrors: function(elem, errors) { | |
var id = this.elemIdentificator(elem); | |
if (typeof errors != 'undefined') { | |
this.removeElemErrors(elem); | |
this.addElemErrors(elem, errors); | |
} | |
return this.errors[id] || []; | |
}, | |
showErrors: function(elem, errors) { | |
var container = this.elemErrorContainer(elem), | |
i, error; | |
errors = errors || this.elemErrors(elem); | |
container.insertAfter(elem); | |
for (i = 0; i < errors.length; i++) { | |
error = this.settings.errorElement.clone().html(errors[i]); | |
error.hide(); | |
error.appendTo(container); | |
error.slideDown(); | |
} | |
}, | |
hideElemErrors: function(elem, errors) { | |
var container = this.elemErrorContainer(elem), | |
children; | |
errors = errors || this.elemErrors(elem); | |
if (errors.length > 0) { | |
children = container.children(); | |
children.each(function() { | |
var el = $(this); | |
if ($.inArray(el.html(), errors) != -1) { | |
el.slideUp('normal', function() { | |
el.remove(); | |
if (container.children().length == 0) { | |
container.remove(); | |
} | |
}); | |
} | |
}); | |
} | |
}, | |
// Toggling | |
toggleForm: function(firsttime) { | |
firsttime = firsttime || false; | |
var i; | |
for (i = 0; i < this.form[0].elements.length; i++) { | |
this.toggleElement($(this.form[0].elements[i]), undefined, firsttime); | |
} | |
}, | |
toggleElement: function(elem, rules, firsttime) { | |
if (typeof rules == 'undefined') { | |
rules = this.elemRules(elem); | |
} | |
var has = false, | |
__hasProp = Object.prototype.hasOwnProperty, | |
handler = function() { | |
$(this.form).netteForm().toggleForm(); | |
}, | |
i, j, rule, el, success; | |
for (i = 0; i < rules.length; i++) { | |
rule = rules[i]; | |
if (!rule.condition) { | |
continue; | |
} | |
el = rule.control ? rule.control : elem; | |
success = this.validateRule(el, rule); | |
if (this.toggleElement(elem, rule.rules, firsttime) || rule.toggle) { | |
has = true; | |
if (firsttime) { | |
if (el.is('select')) { | |
el.change(handler); | |
} else { | |
el.click(handler); | |
} | |
} | |
for (j in rule.toggle || {}) { | |
if (__hasProp.call(rule.toggle, j)) { | |
this.toggle($('#' + j), success ? rule.toggle[j] : !rule.toggle[j]); | |
} | |
} | |
} | |
} | |
return has; | |
}, | |
toggle: function(elem, visible) { | |
if (elem.length) { | |
if (visible) { | |
elem.slideDown(); | |
} else { | |
elem.slideUp(); | |
} | |
} | |
}, | |
// Helpers | |
elemErrorContainer: function(elem) { | |
var container = elem.data('form-validation-error-container'); | |
if (!container) { | |
container = this.settings.errorContainer.clone(); | |
elem.data('form-validation-error-container', container) | |
} | |
return container; | |
}, | |
elemIdentificator: function(elem) { | |
return elem.attr('id'); | |
} | |
}, | |
// Validator methods | |
validators: { | |
filled: function(elem, arg, val) { | |
return val !== '' && val !== false && val !== null; | |
}, | |
valid: function(elem, arg, val) { | |
return $(elem.form).netteForm().validateElement($(elem), true); | |
}, | |
equal: function(elem, arg, val) { | |
if (arg === undefined) { | |
return null; | |
} | |
arg = $.isArray(arg) ? arg : [arg]; | |
for (var i = 0, len = arg.length; i < len; i++) { | |
if (val == (arg[i].control ? $.Nette.getValue($(elem.form.elements[arg[i].control])) : arg[i])) { | |
return true; | |
} | |
} | |
return false; | |
}, | |
minLength: function(elem, arg, val) { | |
return val.length >= arg; | |
}, | |
maxLength: function(elem, arg, val) { | |
return val.length <= arg; | |
}, | |
length: function(elem, arg, val) { | |
arg = $.isArray(arg) ? arg : [arg, arg]; | |
return (arg[0] === null || val.length >= arg[0]) && (arg[1] === null || val.length <= arg[1]); | |
}, | |
email: function(elem, arg, val) { | |
return (/^[^@\s]+@[^@\s]+\.[a-z]{2,10}$/i).test(val); | |
}, | |
url: function(elem, arg, val) { | |
return (/^.+\.[a-z]{2,6}(\/.*)?$/i).test(val); | |
}, | |
regexp: function(elem, arg, val) { | |
var parts = typeof arg === 'string' ? arg.match(/^\/(.*)\/([imu]*)$/) : false; | |
if (parts) { try { | |
return (new RegExp(parts[1], parts[2].replace('u', ''))).test(val); | |
} catch (e) {} } | |
}, | |
pattern: function(elem, arg, val) { | |
try { | |
return typeof arg === 'string' ? (new RegExp('^(' + arg + ')$')).test(val) : null; | |
} catch (e) {} | |
}, | |
integer: function(elem, arg, val) { | |
return (/^-?[0-9]+$/).test(val); | |
}, | |
float: function(elem, arg, val) { | |
return (/^-?[0-9]*[.,]?[0-9]+$/).test(val); | |
}, | |
range: function(elem, arg, val) { | |
return $.isArray(arg) ? ((arg[0] === null || parseFloat(val) >= arg[0]) && (arg[1] === null || parseFloat(val) <= arg[1])) : null; | |
}, | |
submitted: function(elem, arg, val) { | |
return elem.form['nette-submittedBy'] === elem; | |
} | |
} | |
}); | |
// $('form').netteForm(); | |
$.extend($.fn, { | |
netteForm: function(options) { | |
var netteForm; | |
// Nothing here, move along and notice the security if owner has it | |
if (!this.length || !this[0] instanceof HTMLFormElement) { | |
options && options.debug && window.console && console.warn("No form selected."); | |
} | |
// If form has been already "netted", don't do it again | |
netteForm = this.data('netteForm'); | |
if (netteForm) { | |
return netteForm; | |
} | |
// Otherwise do all the needed stuff | |
netteForm = new $.Nette.form(this, options); | |
this.data('netteForm', netteForm); | |
// For example initialize validation if allowed | |
if (netteForm.settings.onSubmit) { | |
this.submit(function(e) { | |
// In debug mode, don't send form | |
if (netteForm.settings.debug) { | |
e.preventDefault(); | |
} | |
// Don't let anyone to send invalid form | |
return netteForm.validate(false); | |
}); | |
} | |
// Toggle controls | |
netteForm.toggleForm(true); | |
// Finally a bit of peace in the soul | |
return netteForm; | |
} | |
}); | |
// Init forms | |
$(document).ready(function() { | |
$('form').each(function() { | |
$(this).netteForm(); | |
}); | |
}); | |
}(jQuery)); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment