Upravit chování validace formulářů?
- David Grudl
- Nette Core | 8218
Uvažuju nad tím, že by bylo vhodné upravit chování validace.
1) Zastavit validaci po první chybě
Zatímco (výchozí) JavaScriptová validace končí u první chyby hláškou, validace na straně serveru proběhne u každého prvku do konce, může tedy vygenerovat více hlášení. Například tento kód
$form->addPassword('password', 'Choose password:', 20)
->addRule(Form::FILLED, 'Choose your password')
->addRule(Form::MIN_LENGTH, 'The password is too short: it must be at least %d characters', 3);
vygeneruje dvě hlášky ‚Choose your password‘ & ‚The password is too short: it must be at least 3 characters‘. Pokud chci druhou hlášku eliminovat, musím kód upravit:
$form->addPassword('password', 'Choose password:', 20)
->addRule(Form::FILLED, 'Choose your password')
->addCondition(Form::FILLED)
->addRule(Form::MIN_LENGTH, 'The password is too short: it must be at least %d characters', 3);
Současné chování je logické a dobře tvárné, ale analýzou svých kódů jsem zjistil, že v drtivé většině případů je žádoucí po první chybě (každého prvku zvlášť!) validaci ukončit. Tedy maximálně bude tolik chyb, kolik je prvků ve formuláři.
Zkuste se nad tím zamyslet a kdyžtak věc připomínkovat.
2) Hybrid mezi validátorem a filtrem
Protože validátor dostává jako parametr prvek FormControl, může nejen
ověřit jeho hodnotu, ale zároveň ji i změnit. Uvažuju, že bych takto
rozšířil textové validátory :integer
a :float
.
V případně úspěchu by hodnotu přetypovaly na odpovídající typ. Pak by
formulář s prvkem
$form->addText('age', 'Your age:', 5)
->addRule(Form::FILLED, 'Enter your age')
->addRule(Form::INTEGER, 'Age must be numeric value');
vracel v poli $form->getValues()
prvek age
jako
integer. To má velký význam při následném ukládání dat do
databáze.
Přetypování by bylo prováděno inteligentně, takže třeba prvek definovaný jako
$form->addText('age', 'Your age:', 5)
->addRule(Form::INTEGER, 'Age must be numeric value');
by vrátil buď integer nebo NULL, když by uživatel nezadal nic (tj. prázdný řetězec by se nepřetypoval na nulu, ale na NULL).
- soundake
- Člen | 24
David Grudl napsal(a):
- Zastavit validaci po první chybě
Prosím ne. Z hlediska použitelnosti je opruz, když uživatel musí odeslat formulář třikrát, když udělá dvě chyby. Je pro něj rozhodně příjemnější, když hned napoprvé vidí co všechno udělal blbě, než aby byl postupně deprimován.
Ps: nejlepší teda je, když je formulář tak srozumitelný, aby chyby nedělal, ale to je jiná debata :)
Editoval soundake (1. 11. 2008 14:01)
- David Grudl
- Nette Core | 8218
S tím zcela souhlasím, ale zrovna takováto série chyb mi uživatelsky příjemnější nepřipadá. Tj. pokud zapomenu vyplnit věk, nevidím přínos v tom, že se vypíší hlášky ‚věk není číslo‘, ‚věk musí být větší než 0‘ apod. Naopak tím uživatele zmatu.
- kravčo
- Člen | 721
David Grudl napsal(a):
1) Zastavit validaci po první chybě
Ja som za, avšak zastaviť validáciu konkrétneho prvku, nie celého formulára. Tým sa eliminujú práve duplicitné chyby ku konkrétnemu prvku, ale taktiež sa zobrazia chyby k ostatným prvkom. Teda ku každému prvku sa zobrazí najviac jedna chyba.
Zastaviť validáciu celého formulára má asi zmysel pri AJAXových formulároch, no pri bežných sa mi to vidí kontraproduktívne. Alebo mi niečo uniká?
2) Hybrid mezi validátorem a filtrem
Hybrid alebo mutant, hlavné je že to uľahčí robotu :)
Skvelý nápad… a keby sa ešte dal dátum…
- David Grudl
- Nette Core | 8218
kravco napsal(a):
Ja som za, avšak zastaviť validáciu konkrétneho prvku, nie celého formulára.
Přesně tak a máš pravdu, že jsem to zapomněl zdůraznit, první post jsem doplnil.
- dmajda
- Člen | 22
David Grudl napsal(a):
1) Zastavit validaci po první chybě
Souhlas s variantou max. jedna hláška per control.
2) Hybrid mezi validátorem a filtrem
Ale fuj :-)
Validace a přetypování jsou IMO dvě různé věci. Přesněji řečeno, přetypování může zároveň obsahovat validaci, že uživatel zadal něco přetypovatelného, ale validace sama by neměla s hodnotou nic dělat.
Byl bych pro úplně novou metodu
setType($type, $message = NULL)
, která by zařídila přidání
odpovídající validace (tj. zavolala by
addRule(Form::WHATEVER, $message)
a následné přetypování
hodnoty controlu v případě, že by byl validní. Tím by se ty dvě
myšlenky od sebe odlišily.
Určitě by to taky chtělo mít přístupnou hodnotu před přetypováním i po něm.
- David Grudl
- Nette Core | 8218
dmajda napsal(a):
Validace a přetypování jsou IMO dvě různé věci. Přesněji řečeno, přetypování může zároveň obsahovat validaci, že uživatel zadal něco přetypovatelného, ale validace sama by neměla s hodnotou nic dělat.
Zkus se odprostit od zažitých představ, s nimi ten framework nevybudujeme :-))
Zapomeňme chvíli na filtry a validátory a vraťme se k nejprostšímu způsobu zpracování vstupních dat. Ono zpracování je řada úkonů, kde na počátku máme hodnotu zadanou uživatelem a na konci je buď chybová zpráva nebo nativní hodnota. Podstatné je, že úkony následují v daném pořádí a že řada je právě jedna (byť třeba rozvětvená pomocí podmínek).
V tom se věřím shodneme.
Zároveň z toho vyplývá důležitá věc: implementace pomocí dvou řad (filtry + validátory) je zcela špatná. Nelze říct, která řada se má provádět dřív. A lze vymyslet řadu jednoduchých příkladů, ze kterých je nesmyslnost této implementace hned patrná.
Řada, úkony. Je spíše terminologická záležitost, jestli „řadu“ budeme nazývat „validace“ a „úkony“ slovem „validátory“ (nebo třeba „pravidla“?). Můžeme ony úkony rozdělit do dvou typů, „validátory“ a „filtry“. Jeví se mi ale pragmatičtější je nerozdělovat. (Nebo je důvod je rozdělovat ten, že je rozdělují v nějakém jiném frameworku?)
Byl bych pro úplně novou metodu
setType($type, $message = NULL)
, která by zařídila přidání odpovídající validace (tj. zavolala byaddRule(Form::WHATEVER, $message)
a následné přetypování hodnoty controlu v případě, že by byl validní. Tím by se ty dvě myšlenky od sebe odlišily.
Tady nejde jen o přetypování, ale jakoukoliv jinou konverzi, třeba na malá písmenka apod.
Určitě by to taky chtělo mít přístupnou hodnotu před přetypováním i po něm.
To spíš raději ne…
- David Grudl
- Nette Core | 8218
Možná pro lepší pochopení přidám příklad:
// prvek buď nesmí být vyplněn, nebo musí obsahovat desetinné číslo.
$form->addText('float', 'Float:', 5)
->addCondition(Form::FILLED)
->addRule(Form::FLOAT, 'Must be float');
Zároveň chceme, aby se nám vracel typ float. Vytvoříme hypoteticku metodu addFilter a filtry rozmístíme takto:
$form->addText('float', 'Float:', 5)
->addFilter('trim')
->addCondition(Form::FILLED)
->addFilter("replace ',' with '.'") // aby 3,14 byl platný vstup
->addRule(Form::FLOAT, 'Must be float')
->addFilter('convert to float');
Z toho je patrné, že filtry mají své místo v řadě společně s validátory. Kdybychom je chtěli vyčlenit do samostatné řady (například prvně aplikovat všechny filtry a pak teprve provést validaci), nefungovalo by to.
Souhlasím s Davidem Majdou, že validace a filtry jsou různé věci, ale vidím v tom spíš problém terminologický – prostě neříkejme tomu, co se přidá metodou addRule(), validace.
- phx
- Člen | 651
ad 1) Souhlas. Ale asi bych nechal moznost zapnout i soucasne chovani (vypis vsechn erroru prvku) i kdyz me ted nenapada zda to vubec nekdo nekdy vyuzije.
ad 2) Ja bych dany problem rozclenil na 2 veci:
- co chceme po uzivateli (pravidla)
- co za uzivatele udelame mi (prevod, zariznuti, nahrada)
Jinak napad zajimavy, ale urcite budou problem s kompatibilitou. Napr ted muze nekde testovat
if($form['prvek']->value != '') {
}
ale ted by musel testovat vuci null.
Pokud by se jadnalo napriklad jen o INT a FLOAT tak jsem urcite pro. Dale by bylo zajimave napriklad vstup pro datum, cas, datum a cas. Vystupem by mohl byt UnixTimeStamp nebo nejaky jiny dany format. Zajimave by bylo i aby uzivatel mohl definovat nejake svoje prevody.
- xificurk
- Člen | 121
ad 1) Souhlasím se zastavováním validace po prvním erroru. Ideální by však bylo, kdyby se dal u každého pravidla zapnout příznak, který by říkal „pokračuj ve validaci i v případě, že já vyhazuju error“, tak by myslím byl spokojený každý a daly by se díky tomu dělat doslova kouzla s validačními pravidly :-)
ad 2) Souhlas, začlenit to do stejné vrstvy dává smysl, z uvedeného příkladu je myslím jasné v čem spočívají výhody, nevýhody jsem žádné neodhalil.
- David Grudl
- Nette Core | 8218
Implementoval bych tedy následující:
- po první validační chybě se proces zastaví, nebude-li na pravidlu
určeno jinak (způsob určení není zatím zvolen – použít třeba
čtvrtý parametr
addRule()
?) - přidám možnost zastavit proces kdykoliv; buď metodou
addTerminate()
,addTerminator()
neboterminate()
- v důsledku této změny budu muset odstranit metodu
addRuleFor()
. Lze ji ovšem nahradit konstrukcí využívajícíaddConditionOn()
, viz příklad:
$form['send']->addCondition(Form::EQUAL, TRUE)
->toggle('sendBox')
->addRuleFor($form['city'], Form::FILLED, 'Enter your shipping address')
->addRuleFor($form['country'], Form::FILLED, 'Select your country');
// se změní na
$form['send']->addCondition(Form::EQUAL, TRUE)
->toggle('sendBox');
$form['city']->addConditionOn($form['send'], Form::EQUAL, TRUE)
->addRule(Form::FILLED, 'Enter your shipping address');
$form['country']->addConditionOn($form['send'], Form::EQUAL, TRUE)
->addRule(Form::FILLED, 'Select your country');
p.s. nahradit toggle()
za addToggle()
?
- kravčo
- Člen | 721
Len taký nápad, jedna z vecí, ktorú som si ja dorobil:
keď potrebujem validovať zložitejší input, napr. dátum:
$form->addText('datum', 'Dátum:')
->addRule(Form::FILLED, 'Nevyplnený dátum!')
->addRule(Form::REGEXP, 'Zlý formát dátumu!')
->addRule('Validate::date', 'Neplatný dátum!');
V prípade, že ho potrebujem použiť na viacerých miestach, aj s hláškami, vzniká nepríjemná duplicita a nábeh na nejednoznačnosť – zmením jednu hlášku a druhá zostane po starom…
Spravil som si takú fičúrku zvanú Validation
:
$form->addText('datum', 'Dátum:')
->addValidation(new DateValidation); // DateValidation implements IValidation
medzičasom mi napadlo aj iné riešenie:
$form->addText('datum', 'Dátum:')
->addValidation('Validate::dateComplete'); // ale bez interfejsu IValidation
Čo myslíte, má to miesto v Nette, či skôr v čoraz častejšie spomínaných Nextras?
- edke
- Člen | 198
David Grudl wrote:
$form['send']->addCondition(Form::EQUAL, TRUE) ->toggle('sendBox') ->addRuleFor($form['city'], Form::FILLED, 'Enter your shipping address') ->addRuleFor($form['country'], Form::FILLED, 'Select your country'); // se změní na $form['send']->addCondition(Form::EQUAL, TRUE) ->toggle('sendBox'); $form['city']->addConditionOn($form['send'], Form::EQUAL, TRUE) ->addRule(Form::FILLED, 'Enter your shipping address'); $form['country']->addConditionOn($form['send'], Form::EQUAL, TRUE) ->addRule(Form::FILLED, 'Select your country');
Len chcem reagovat na ten tvoj priklad, priamo s temou to nesuvisi. Riesim podobny formular, kde sa cast formularu skryva na zaklade toggle pre checkbox. A rovnako cez podmienku ak je checkbox checked su tie polia required. Ale ani po odoslani formulara a jeho neuspesnom validovani a znovuvykresleni nie su tie polia oznacene rendererom ako required (class=„required“).
- David Grudl
- Nette Core | 8218
1) Zastavit validaci po první chybě
Implementováno. (a trvalo to jenom půl roku!)
- ATom
- Člen | 16
Pokud není prvek vyplněný asi nemá smysl vypisovat nic dalšího. Takže bych určitě skončil na kritériu vyplněnosti. Pokud ale vyplněný je a selže více dalších kritérii už by se asi mohlo vypsat více hlášek, aby uživatel vědět, co vše je potřeba splnit.
Navrhoval bych, aby se JS validace chovala vizuálně stejně, tedy aby se nepoužíval alert, ale chybové hlášky se objevovali na formulářem, ve stejném tvaru jako po odeslání. Volitelně by bylo hezké zobrazovat chybové hlášky přímo k jednotlivým polím, obalené do nastaveného elementu.
- Šaman
- Člen | 2659
Tak se taky zeptám, jestli se v této věci něco chystá?
Je to poměrně častý problém: uživatel vyplňuje telefonní číslo s mezerama, rodné číslo s lomítkem apod. Já ale chci, abych po validaci dostal hodnoty v použitelném tvaru. Při vlastní validaci si hodnotu převedu, zvaliduji, ale pak dostanu to, co uživatel zadal a po getValues() abych to filtroval znovu.
I když zrovna ve výše uvedených případech by nastal problém s délkou inputu – rodné číslo bude mít nastaveno rule length = 10, ale pokud uživatel použije lomítko, tak se mu tam celé nevejde.. a musí to opravit ručně. Řešením by byla možnost nastavit délku inputu nezávisle na validačních pravidlech a toto nastavení by mělo větší prioritu.
- JakubS
- Člen | 15
Je to poměrně častý problém: uživatel vyplňuje telefonní číslo s mezerama, rodné číslo s lomítkem apod.
Osobně to řeším filtováním znaků už při jejich zadávání:
$form->addtext('psc', 'PSČ:')
->addConditionOn($orderForm['fakturacni_udaje'], Form::EQUAL, TRUE)
->addRule(Form::FILLED, 'Zadejte PSČ.')
->addRule(Form::MAX_LENGTH, 'PSČ je příli dlouhé.', 5)
->addRule(Form::NUMERIC, 'PSČ není číslo.');
$form['psc']->getControlPrototype()
->onkeydown = 'this.value = this.value.replace(" ", "");';