Upravit chování validace formulářů?

Upozornění: Tohle vlákno je hodně staré a informace nemusí být platné pro současné Nette.
David Grudl
Nette Core | 8218
+
0
-

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
+
0
-

David Grudl napsal(a):

  1. 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
+
0
-

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
+
0
-

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
+
0
-

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
+
0
-

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
+
0
-

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 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.

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
+
0
-

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
+
0
-

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
+
0
-

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
+
0
-

Implementoval bych tedy následující:

  1. 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()?)
  2. přidám možnost zastavit proces kdykoliv; buď metodou addTerminate(), addTerminator() nebo terminate()
  3. 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()?

pmg
Člen | 372
+
0
-

Mně se to líbí! :)

_Martin_
Generous Backer | 679
+
0
-

Jsem pro hybrida, najde uplatnění třeba při koverzi dat do tvaru pro databázi (volitelně vyplnitelná políčka nastavovat na NULL, pokud jsou prázdná, konvertovat datum do tvaru RRRR-MM-DD,…)

kravčo
Člen | 721
+
0
-

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
+
0
-

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
+
0
-

1) Zastavit validaci po první chybě

Implementováno. (a trvalo to jenom půl roku!)

Ola
Člen | 385
+
0
-

Díky :-)

ATom
Člen | 16
+
0
-

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.

zakjan
Člen | 9
+
0
-
  1. Hybrid mezi validátorem a filtrem

Nastal nějaký postup v této věci?

Šaman
Člen | 2658
+
0
-

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
+
0
-

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(" ", "");';
David Grudl
Nette Core | 8218
+
0
-

Tohle bude vyřešené ve verzi 1.

(o tři roky později: haha…)