-
-
Save redhead/552273 to your computer and use it in GitHub Desktop.
/** | |
* Live Form Validation for Nette 2.0 | |
* | |
* @author Radek Ježdík, MartyIX, David Grudl | |
*/ | |
var LiveForm = { | |
options: { | |
controlErrorClass: 'form-control-error', // CSS class for an invalid control | |
errorMessageClass: 'form-error-message', // CSS class for an error message | |
validMessageClass: 'form-valid-message', // CSS class for a valid message | |
noLiveValidation: 'no-live-validation', // CSS class for a valid message | |
showValid: false, // show message when valid | |
dontShowWhenValidClass: 'dont-show-when-valid', // control with this CSS class will not show valid message | |
messageTag: 'span', // tag that will hold the error/valid message | |
messageIdPostfix: '_message', // message element id = control id + this postfix | |
wait: 300 // delay in ms before validating on keyup/keydown | |
}, | |
forms: { } | |
}; | |
/** | |
* Handlers for all the events that trigger validation | |
* YOU CAN CHANGE these handlers (ie. to use jQuery events instead) | |
*/ | |
LiveForm.setUpHandlers = function(el) { | |
if (this.hasClass(el, this.options.noLiveValidation)) return; | |
var handler = function(event) { | |
event = event || window.event; | |
Nette.validateControl(event.target ? event.target : event.srcElement); | |
}; | |
var self = this; | |
el.onchange = handler; | |
el.onblur = handler; | |
el.onkeydown = function (event) { | |
if (self.options.wait >= 200) { | |
// Hide validation span tag. | |
self.removeClass(this, self.options.controlErrorClass); | |
self.removeClass(this, self.options.validMessageClass); | |
var error = self.getMessageElement(this); | |
error.innerHTML = ''; | |
error.className = ''; | |
// Cancel timeout to run validation handler | |
if (self.timeout) { | |
clearTimeout(self.timeout); | |
} | |
} | |
}; | |
el.onkeyup = function(event) { | |
event = event || window.event; | |
if (event.keyCode !== 9) { | |
if (self.timeout) clearTimeout(self.timeout); | |
self.timeout = setTimeout(function() { | |
handler(event); | |
}, self.options.wait); | |
} | |
}; | |
} | |
LiveForm.addError = function(el, message) { | |
this.forms[el.form.id].hasError = true; | |
this.addClass(el, this.options.controlErrorClass); | |
if (!message) { | |
message = ' '; | |
} | |
var error = this.getMessageElement(el); | |
error.innerHTML = message; | |
} | |
LiveForm.removeError = function(el) { | |
this.removeClass(el, this.options.controlErrorClass); | |
var err_el = document.getElementById(el.id + this.options.messageIdPostfix); | |
if (this.options.showValid && this.showValid(el)) { | |
err_el = this.getMessageElement(el); | |
err_el.className = this.options.validMessageClass; | |
return; | |
} | |
if (err_el) { | |
err_el.parentNode.removeChild(err_el); | |
} | |
} | |
LiveForm.showValid = function(el) { | |
if(el.type) { | |
var type = el.type.toLowerCase(); | |
if(type == 'checkbox' || type == 'radio') { | |
return false; | |
} | |
} | |
var rules = Nette.getRules(null, el); | |
if(rules.length == 0) { | |
return false; | |
} | |
if (this.hasClass(el, this.options.dontShowWhenValidClass)) { | |
return false; | |
} | |
return true; | |
} | |
LiveForm.getMessageElement = function(el) { | |
var id = el.id + this.options.messageIdPostfix; | |
var error = document.getElementById(id); | |
if (!error) { | |
error = document.createElement(this.options.messageTag); | |
error.id = id; | |
el.parentNode.appendChild(error); | |
} | |
if (el.style.display == 'none') { | |
error.style.display = 'none'; | |
} | |
error.className = this.options.errorMessageClass; | |
error.innerHTML = ''; | |
return error; | |
} | |
LiveForm.addClass = function(el, className) { | |
if (!el.className) { | |
el.className = className; | |
} else if (!this.hasClass(el, className)) { | |
el.className += ' ' + className; | |
} | |
} | |
LiveForm.hasClass = function(el, className) { | |
if (el.className) | |
return el.className.match(new RegExp('(\\s|^)' + className + '(\\s|$)')); | |
return false; | |
} | |
LiveForm.removeClass = function(el, className) { | |
if (this.hasClass(el, className)) { | |
var reg = new RegExp('(\\s|^)'+ className + '(\\s|$)'); | |
var m = el.className.match(reg); | |
el.className = el.className.replace(reg, (m[1] == ' ' && m[2] == ' ') ? ' ' : ''); | |
} | |
} | |
/////////////////////////////////////////////////////////////////////////////////////////// | |
var Nette = Nette || { }; | |
Nette.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); | |
}; | |
}; | |
Nette.getValue = function(elem) { | |
var i, len; | |
if (!elem) { | |
return null; | |
} else if (!elem.nodeName) { // radio | |
for (i = 0, len = elem.length; i < len; i++) { | |
if (elem[i].checked) { | |
return elem[i].value; | |
} | |
} | |
return null; | |
} else if (elem.nodeName.toLowerCase() === 'select') { | |
var index = elem.selectedIndex, options = elem.options; | |
if (index < 0) { | |
return null; | |
} else if (elem.type === 'select-one') { | |
return options[index].value; | |
} | |
for (i = 0, values = [], len = options.length; i < len; i++) { | |
if (options[i].selected) { | |
values.push(options[i].value); | |
} | |
} | |
return values; | |
} else if (elem.type === 'checkbox') { | |
return elem.checked; | |
} else if (elem.type === 'radio') { | |
return Nette.getValue(elem.form.elements[elem.name].nodeName ? [elem] : elem.form.elements[elem.name]); | |
} else { | |
return elem.value.replace(/^\s+|\s+$/g, ''); | |
} | |
}; | |
Nette.validateControl = function(elem, rules, onlyCheck) { | |
rules = Nette.getRules(rules, elem); | |
for (var id in rules) { | |
var rule = rules[id], op = rule.op.match(/(~)?([^?]+)/); | |
rule.neg = op[1]; | |
rule.op = op[2]; | |
rule.condition = !!rule.rules; | |
var el = rule.control ? elem.form.elements[rule.control] : elem; | |
var success = Nette.validateRule(el, rule.op, rule.arg); | |
if (success === null) continue; | |
if (rule.neg) success = !success; | |
if (rule.condition && success) { | |
if (!Nette.validateControl(elem, rule.rules, onlyCheck)) { | |
return false; | |
} | |
} else if (!rule.condition && !success) { | |
if (el.disabled) continue; | |
if (!onlyCheck) { | |
Nette.addError(el, rule.msg.replace('%value', Nette.getValue(el))); | |
} | |
return false; | |
} | |
} | |
if (!onlyCheck) { | |
LiveForm.removeError(elem); | |
} | |
return true; | |
}; | |
Nette.getRules = function(rules, elem) { | |
return rules || eval('[' + (elem.getAttribute('data-nette-rules') || '') + ']') | |
}; | |
Nette.validateForm = function(sender) { | |
var form = sender.form || sender; | |
LiveForm.forms[form.id].hasError = false; | |
if (form['nette-submittedBy'] && form['nette-submittedBy'].getAttribute('formnovalidate') !== null) { | |
return true; | |
} | |
var ok = true; | |
for (var i = 0; i < form.elements.length; i++) { | |
var elem = form.elements[i]; | |
if (!(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) { | |
continue; | |
} | |
if (!Nette.validateControl(elem)) { | |
ok = false; | |
} | |
} | |
return ok; | |
}; | |
Nette.addError = function(elem, message) { | |
if (elem.focus && !LiveForm.forms[elem.form.id].hasError) { | |
elem.focus(); | |
} | |
LiveForm.addError(elem, message); | |
}; | |
Nette.validateRule = function(elem, op, arg) { | |
var val = Nette.getValue(elem); | |
if (elem.getAttribute) { | |
if (val === elem.getAttribute('data-nette-empty-value')) { | |
val = ''; | |
} | |
} | |
if (op.charAt(0) === ':') { | |
op = op.substr(1); | |
} | |
op = op.replace('::', '_'); | |
op = op.replace(/\\/g,''); | |
return Nette.validators[op] ? Nette.validators[op](elem, arg, val) : null; | |
}; | |
Nette.validators = { | |
filled: function(elem, arg, val) { | |
return val !== '' && val !== false && val !== null; | |
}, | |
valid: function(elem, arg, val) { | |
return Nette.validateControl(elem, null, true); | |
}, | |
equal: function(elem, arg, val) { | |
if (arg === undefined) { | |
return null; | |
} | |
arg = Nette.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 = Nette.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 (/^("([ !\x23-\x5B\x5D-\x7E]*|\\[ -~])+"|[-a-z0-9!#$%&'*+/=?^_`{|}~]+(\.[-a-z0-9!#$%&'*+/=?^_`{|}~]+)*)@([0-9a-z\u00C0-\u02FF\u0370-\u1EFF]([-0-9a-z\u00C0-\u02FF\u0370-\u1EFF]{0,61}[0-9a-z\u00C0-\u02FF\u0370-\u1EFF])?\.)+[a-z\u00C0-\u02FF\u0370-\u1EFF][-0-9a-z\u00C0-\u02FF\u0370-\u1EFF]{0,17}[a-z\u00C0-\u02FF\u0370-\u1EFF]$/i).test(val); | |
}, | |
url: function(elem, arg, val) { | |
return (/^(https?:\/\/|(?=.*\.))([0-9a-z\u00C0-\u02FF\u0370-\u1EFF](([-0-9a-z\u00C0-\u02FF\u0370-\u1EFF]{0,61}[0-9a-z\u00C0-\u02FF\u0370-\u1EFF])?\.)*[a-z\u00C0-\u02FF\u0370-\u1EFF][-0-9a-z\u00C0-\u02FF\u0370-\u1EFF]{0,17}[a-z\u00C0-\u02FF\u0370-\u1EFF]|\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})(:\d{1,5})?(\/\S*)?$/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 Nette.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; | |
} | |
}; | |
Nette.toggleForm = function(form) { | |
for (var i = 0; i < form.elements.length; i++) { | |
if (form.elements[i].nodeName.toLowerCase() in { | |
input:1, | |
select:1, | |
textarea:1, | |
button:1 | |
}) { | |
Nette.toggleControl(form.elements[i]); | |
} | |
} | |
}; | |
Nette.toggleControl = function(elem, rules, firsttime) { | |
rules = Nette.getRules(rules, elem); | |
var has = false, __hasProp = Object.prototype.hasOwnProperty, handler = function() { | |
Nette.toggleForm(elem.form); | |
}; | |
for (var id in rules) { | |
var rule = rules[id], op = rule.op.match(/(~)?([^?]+)/); | |
rule.neg = op[1]; | |
rule.op = op[2]; | |
rule.condition = !!rule.rules; | |
if (!rule.condition) continue; | |
var el = rule.control ? elem.form.elements[rule.control] : elem; | |
var success = Nette.validateRule(el, rule.op, rule.arg); | |
if (success === null) continue; | |
if (rule.neg) success = !success; | |
if (Nette.toggleControl(elem, rule.rules, firsttime) || rule.toggle) { | |
has = true; | |
if (firsttime) { | |
if (!el.nodeName) { // radio | |
for (var i = 0; i < el.length; i++) { | |
Nette.addEvent(el[i], 'click', handler); | |
} | |
} else if (el.nodeName.toLowerCase() === 'select') { | |
Nette.addEvent(el, 'change', handler); | |
} else { | |
Nette.addEvent(el, 'click', handler); | |
} | |
} | |
for (var id2 in rule.toggle || []) { | |
if (__hasProp.call(rule.toggle, id2)) { | |
Nette.toggle(id2, success ? rule.toggle[id2] : !rule.toggle[id2]); | |
} | |
} | |
} | |
} | |
return has; | |
}; | |
Nette.toggle = function(id, visible) { | |
var elem = document.getElementById(id); | |
if (elem) { | |
elem.style.display = visible ? "" : "none"; | |
} | |
}; | |
Nette.initForm = function(form) { | |
form.noValidate = 'novalidate'; | |
LiveForm.forms[form.id] = { | |
hasError: false | |
}; | |
Nette.addEvent(form, 'submit', function() { | |
return Nette.validateForm(form); | |
}); | |
Nette.addEvent(form, 'click', function(e) { | |
e = e || event; | |
var target = e.target || e.srcElement; | |
form['nette-submittedBy'] = (target.type in { | |
submit:1, | |
image:1 | |
}) ? target : null; | |
}); | |
for (var i = 0; i < form.elements.length; i++) { | |
Nette.toggleControl(form.elements[i], null, true); | |
LiveForm.setUpHandlers(form.elements[i]); | |
} | |
if (/MSIE/.exec(navigator.userAgent)) { | |
var labels = {}, | |
wheelHandler = function() { | |
return false; | |
}, | |
clickHandler = function() { | |
document.getElementById(this.htmlFor).focus(); | |
return false; | |
}; | |
for (i = 0, elms = form.getElementsByTagName('label'); i < elms.length; i++) { | |
labels[elms[i].htmlFor] = elms[i]; | |
} | |
for (i = 0, elms = form.getElementsByTagName('select'); i < elms.length; i++) { | |
Nette.addEvent(elms[i], 'mousewheel', wheelHandler); // prevents accidental change in IE | |
if (labels[elms[i].htmlId]) { | |
Nette.addEvent(labels[elms[i].htmlId], 'click', clickHandler); // prevents deselect in IE 5 - 6 | |
} | |
} | |
} | |
}; | |
Nette.isArray = function(arg) { | |
return Object.prototype.toString.call(arg) === '[object Array]'; | |
}; | |
Nette.addEvent(window, 'load', function () { | |
for (var i = 0; i < document.forms.length; i++) { | |
Nette.initForm(document.forms[i]); | |
} | |
}); |
Jasne, zkusim to pridat do issues na github.
Jeste k tomu Firefoxu: "Vsiml jsem si, ze kdyz se objevi chyba a pak se vstup zmeni na validni, tak nezmizi ve Firefoxu 7 chybova hlaska." -> Je to jenom u me nebo tam je opravdu chyba? V kodu se mi nedari dohledat chybu :(
Aha, nevšiml jsem si editu, podívám se na to.
MartyIX/nette@5ef52bb - radek op = op.replace('', ''); se dostal do Nette
fork https://gist.github.com/1267868 opravujici tu chybu ve Firefoxu - chyba je v innerText, ktere staci zmenit na innerHTML :-)
Paráda, díky.
Ta chyba s tím zobrazením chyb. hlášky ve FF se tím spravila?
:) Jo, alespon v poslednim stable Firefoxu, vic jsem netestoval.
https://gist.github.com/1267868#comments - drobná nová featura. Koukal jsem, že se to řešilo i na fóru http://forum.nette.org/cs/5167-vypnuti-js-validace-u-konkretnich-prvku
na riadku 45 je chyba, namiesto err_el.parentNode.removeChild(el); by malo byt err_el.parentNode.removeChild(err_el); ak mam checkbox a zobrazenu chybovu hlasku tak po kliknuti nan, mi zmizne checkbox namiesto chyby
Díky!
Nevim jestli je to znamy problem. Ale pri pouziti live validace mi nefunguje toggle. Normalne se skryje, ale uz se nezobrazi. Stejny priklad pouziti pri netteForms.js funguje.
Potvrzuji nefunčnost "toggle".
Nefunkční toggle je dán zřejmě přenastavením onchange metody při validaci. Nejdříve se jako onchange handler pro select nastaví toggle a potom se přepíše na validaci. Podle mě stačí řádek 37 nahradit tímto: Nette.addEvent(el, 'change', handler); Samozřejmě by se mělo nahradit i ostatní nastavování handlerů.
Ahoj, při použití minifikátoru JavaScriptPacker vznikl problém u funkcí, které nebyly zakončeny středníkem. Konkrétně mezi řádky 27-153. Viz můj fork.
Jak se Vám chová validace formuláře v IE 10 ? Kliknutím na submit u se mě form pokaždé refreshne a přidá chyby na začátek formu.. ovšem kliknutím na např. povinný input a následně jinam, vyskočí hláška vedle inputu správně.
Ve FF je to ok.
Přidám řešení problémů, na které jsem narazil:
- V IE8 funkce getAttribute pro neexistující atribut vrací prázdný řetězec a ne null, takže na řádku 253 je potřeba upravit podmínku, jinak se formulář odešle bez validace (nette/nette@30ac03a).
- IE < 9 špatně zachází s events ve funkci setTimeout, takže dochází k chybě member not found v handleru onkeyup.
//za řádek 58 (http://stackoverflow.com/questions/3531751/member-not-found-ie-error-ie-6-7-8-9)
var eventCopy = {};
for (var i in event) {
eventCopy[i] = event[i];
}
//vyměnit původní self.timeout za tento (event je změněn za eventCopy)
self.timeout = setTimeout(function() {
handler(eventCopy);
}, self.options.wait);
- Pokud používáte k vykreslování formulářů bootstrap-renderer, tak se hodí využít označení celého chybného ,,řádku" s labelem, inputem a chybovou zprávou. Lze toho docílit přidáním:
//za řádek 75 (funkce LiveForm.addError)
if (document.getElementById(el.id + '-pair') !== null)
this.addClass(document.getElementById(el.id + '-pair'), 'error');
//za řádek 90 (funkce LiveForm.removeError)
if (document.getElementById(el.id + '-pair') !== null)
this.removeClass(document.getElementById(el.id + '-pair'), 'error');
//řádek 120 - obecnější vkládání chybové zprávy (řeší problém se skrytou zprávou u datepickeru)
//el.parentNode.appendChild(error);
var blockControl = document.getElementById(el.id + '-pair');
if (blockControl) {
for (var i = 0; i < blockControl.childNodes.length; i++) {
if (blockControl.childNodes[i].className === "controls") {
blockControl.childNodes[i].appendChild(error);
break;
}
}
}
Paradni pluginek, akorat zlobi pri pouziti twitter bootstrapu pri pouziti .input-prepend a .input-append. udelal jsem jednoduchy fix
Stary radek 120:
el.parentNode.appendChild(error);
Nahradit za toto:
if (el.parentNode.className.indexOf("input-prepend") != -1 || el.parentNode.className.indexOf("input-append") != -1) {
el.parentNode.parentNode.appendChild(error);
} else {
el.parentNode.appendChild(error);
}
Je to spise workaround nez realny fix, kazdopadne pomohl. Budu rad za pripadnou dalsi upravu ;). Jinak diky, fakt dobra prace.
Tak jsem objevil dalsi chybku. S live validation nefunguje toggle. Staci opravit v metode LiveForm.setUpHandlers zpusob, jakymi se ukladaji handlery na radcich 37, 38, 39 a 55 tak, aby se vyuzivalo Nette.addEvent,
Prepsat zminene radky v tomto stylu:
el.onchange = handler;
prepsat na
Nette.addEvent(el, "change", handler);
Toggly pak zacnou fungovat, duvod je, ze se prepisuje toggle event, misto toho aby se pridaval.
Na řádcích 215 a 402 je chyba. Namísto "rules[id]" tam musí být "rules[id][0]". Tedy při použití s Nette > 2.2, nižší verze jsem nezkoušel.
Hele to asi ne, když tak dej pull request na ofiko od Nette, já totiž kód, co je v Nette, přebírám 1:1.
Jinak prozatím, si ten kód můžeš samozřejmě upravit.