Skip to content

Instantly share code, notes, and snippets.

@pavelkucera
Created September 17, 2011 17:55
Show Gist options
  • Save pavelkucera/1224180 to your computer and use it in GitHub Desktop.
Save pavelkucera/1224180 to your computer and use it in GitHub Desktop.
/**
* 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