Profíci, jak tvoříte vlastní formulářové komponenty?

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

Představte si, že chcete udělat formulářový prvek pro zadávání data, podobný jako používá Facebook, tedy s odděleným políčkem pro den, měsíc a rok (mohou to být selectboxy):

Api by mělo přijímat a vracet jako hodnotu objekt DateTime.

Jakým způsobem v současné verzi Nette podobný prvek navrhnete?

Šaman
Člen | 2662
+
0
-

Vlastní kontejner, který obsahuje tři selektítka. Jen to API jsem zatím kontejneru dopsat nezkoušel.

enumag
Člen | 2118
+
0
-

Jsem na tom stejně jako Šaman, už jsem nad tím přemýšlel dříve a také bych to řešil jako Container, ale nezkoušel jsem to v praxi.

level 2: Nechť se optiony v selectboxu „Den“ živě updatují dle vybraného měsíce (a v případě února i roku).

Editoval enumag (26. 6. 2013 19:09)

Čelo
Člen | 42
+
0
-

Snažil bych se dostat k něčemu takovému:

<?php
$form->addDateSelect('geburtstag','Geburtstag');
?>
Filip Procházka
Moderator | 4668
+
+1
-

Podědím BaseControl a napíšu metodu getControl tak, aby vytvořila 3 inputy (selecty). Potom setValue a getValue naučím pracovat s datetime.

Už tu třídu nepoužívám, ale pro představu cca takhle.

Editoval Filip Procházka (26. 6. 2013 23:19)

Milo
Nette Core | 1283
+
0
-

Podobné inputy řeším cca takto. Jen dědím z LatteBaseControl, abych mohl getControl() provádět v Latte.

class DateInput extends Nette\Forms\Controls\BaseControl
{
	private $emptyValue = array('y' => '', 'm' => '', 'd' => '');



	public function getControl()
	{
		$value = $this->getHtmlValue();

		$d = Html::el('input')->name($this->name . '[d]')->value($value['d']);
		$m = Html::el('input')->name($this->name . '[m]')->value($value['m']);
		$y = Html::el('input')->name($this->name . '[y]')->value($value['y']);

		return Html::el()->add($d)->add($m)->add($y);
	}



	public function setValue($value)
	{
		if (is_array($value)) {
			$this->value = $value + $this->emptyValue;

		} elseif ($value instanceof \DateTime) {
			$this->value = array(
				'y' => $value->format('Y'),
				'm' => $value->format('m'),
				'd' => $value->format('d'),
			);

		} else {
			$this->value = NULL;
		}
		return $this;
	}



	protected function getHtmlValue()
	{
		if (is_array($this->value)) {
			return $this->value;
		}

		return $this->emptyValue;
	}



	public function getValue()
	{
		$value = $this->getHtmlValue();
		$value = \DateTime::createFromFormat('Y-m-d', "$value[y]-$value[m]-$value[d]");

		if ($value === FALSE) {
			return NULL;
		}

		return $value;
	}

}
Felix
Nette Core | 1245
+
0
-

@Milo: to mi prijde celkem pekne, ale dava to skrytou zavislost do $values, co udelat nejaky typehint do setValues()? Neco jako InputDatetimeFormat (vim, dlouhe, divne..)

Milo
Nette Core | 1283
+
0
-

@Felix Skrytou závislostí myslíš ty indexy pole? To se používá pouze interně, když Nette nastavuje přijaté hodnoty. A typehint stejně nemůžeš, protože dědíš.

hrach
Člen | 1838
+
0
-

Na signálech Jan Tvrdík před více jak 3 lety zavedl abstraktní formulářovou komponentu, ktera implementuje standardní komponentové rozhraní.

Jednotlivé formulářové komponenty pak mohou mít interně kolik chcou komponent.

V rámci nextras jsem před měsíci udělal WIP, ještě není dodělané, vypadá to takto:

Je to neuvěřitelně mocný nástroj, asi nejsložitější komponentu v tom máme takovou, který dokonce obsahuje popup. Komponenta saba o sobě umí nastavovat přístupová oprávnění pro skupiny, etc, ala Facebook: všechno žlutý je jedna formulářová komponenta $form->addAuthUtil(). Popup se zobrazí po vybrání položky upřesnit v hlavnim inputu…

Editoval hrach (27. 6. 2013 8:26)

hrach
Člen | 1838
+
0
-

Jeste bych zapomnel: nejdulezitejsi featurou toho celeho je to, ze ta formularova komponenta, respektive jeji potomci muzou vytvaret odkazy na sebe a volat signaly, tzn. menit se ajaxem, etc… :)

enumag
Člen | 2118
+
0
-

@hrach: Hezký, dělal jsem něco podobného když jsem upravoval TagsControl aby si suggest řídil ajaxem sám od sebe pomocí signálu. Tohle je ale propracovanější a vypadá to jako velmi dobrý základ pro budoucí formulářové elementy.

Milo
Nette Core | 1283
+
0
-

Mě by akorát zajímalo, s čím vyrukuje David :-)

enumag
Člen | 2118
+
0
-

@Milo: To já taky, ale nejvíc jsem zvědavej jestli opraví to co mi ve formulářích chybí:

  • praktická nemožnost přidávání vlastních validátorů včetně JS varianty
  • signál validate pro live validaci callback validátorů (např. kontrola zda uživatel již existuje)
  • možnost změny chybových zpráv automaticky přidaných pravidel (např. selectbox invalid value)

Editoval enumag (27. 6. 2013 13:14)

Filip Procházka
Moderator | 4668
+
0
-

<ot>Největší bolest formulářů je v současnosti pevně svázaný validátor :)</ot>

Editoval Filip Procházka (27. 6. 2013 12:47)

David Grudl
Nette Core | 8227
+
0
-

enumag: cože? Tohle je co? https://github.com/…alidator.php#L18. V té custom JS validaci si pak snadno můžeš třeba ověřit přes AJAX data.

Select invalidate message se změní v Nette\Forms\Rules::$defaultMessages. Navíc to není potřeba, ta zpráva se uživateli nezobrazí, jde o zabránění hackingu.

Filip: Tam přece žádný pevně svázaný validátor není. Všechny validace jsou ve třídě Validators. Ve formulářích je pak jen minimální obálka nad nimi. Lze to řešit líp?

enumag
Člen | 2118
+
0
-

@David Grudl: Ok tak jsem to přehlédl. Když jsem to naposled zkoušel tak mi to nešlo, nevím už přesně proč.

David Matějka
Moderator | 6445
+
0
-

@David Grudl: jednotlive validace oddelene jsou, ale snadno konfigurovatelny validator (ci seznam pravidel) je jen u formularu, se kterym je pevne svazany. nevim, jak to myslel Filip, ale mne by se libilo, aby validator sel napsat oddelene a byl by aplikovatelny nejen na form, ale treba jen na pole (napr. vstup z api), nedavno se to tu na foru i resilo, videl jsem i nejaky funkcni reseni, ale mohlo by to byt primo v nette :)
treba takto:

$validator = new Validator();
$validator->addText('foo')->addRule(Validator::FILLED)->addRule(Validator::MIN_LENGTH, 'Minimalni delka je %d znaku', 5);
$validator->addBoolean('bar');
....

$form = new Form();
$form->addText('foo', 'Foo');
$form->addCheckbox('bar', 'Bar');
$form->setValidator($validator);

//nebo treba z API ziskam nejaka data, tak
$result = $validator->validate($data);
//a v result bude stav a pripadne seznam chyb
Filip Procházka
Moderator | 4668
+
0
-

@David Grudl: Formuláře je můžou využívat, ale měly by jít použít samostatně. Což teď jdou velice težko.

V ideálním případě bych měl nějak nadefinované validace (fluentem nad nějakým rules objektem, annotacemi, …), přes validátor který tyto definice bude mít načtené bych prohnal strukturu (formulář, entitu, pole, …) a vypadl by mi výsledek (violations) který by obsahoval chybové zprávy a ke kterému prvku patří ($violations[email] = "neobsahuje @") a tyto pravidla by se aplikovaly na formulář (protože vím jméno prvku, můžu chybu přiřadit ke správnému políčku a správně vykreslit).

// pseudokód
$form->onValidate[] = function ($form) use ($entity) {
	$entity->setValues($form->values);
	$validator = new Validator(new MetadataReader($entity));
	$result = $validator->validate($entity);
	$form->addErrors($result);
};

Inspirace

Co tím získám? Zcela jasné oddělení validátoru, možnost plně jej využít i v modelových třídách nad entitami, validovat json strukturu co přijde v API, …

Editoval Filip Procházka (28. 6. 2013 2:40)

llook
Člen | 407
+
0
-

Doteď jsem tohle dělal zhruba tak, že jsem si těch několik HTML inputů definoval v getControl() a jako name jsem jim dal $this->htmlName . '["year"]' apod. A potom překlad z array na cílový objekt a zpět v getValue()/setValue().

Ale to řešení s controlem implementujícím IContainer se mi líbí víc.

Šaman
Člen | 2662
+
0
-

Nějak to tu utichlo, neznamená to doufám, že se David skutečně ptal jak na to? :)
Plánuje do 2.1 nějaké kchůl řešení, nebo dokonce poradit best practise na již existující verzi?

David Matějka
Moderator | 6445
+
0
-

David neco planuje s formularema, rikal to na posobote, nechtel ale prozradit co :)

enumag
Člen | 2118
+
0
-

@David Grudl: Tak už jsem si vzpomněl co mi u vlastních validátorů tak zásadně chybí. Není možné přes config nastavit default message. Nebo aspoň ne pomocí NetteExtension. Když nad tím tak přemýšlím tak mne nenapadá jak to kamkoli do configu napsat (bez použití vlastní extension).

Editoval enumag (25. 7. 2013 19:51)

Filip Procházka
Moderator | 4668
+
0
-

@enumag: to není pravda

enumag
Člen | 2118
+
0
-

@Filip Procházka: Přečti si znovu můj příspěvek a ten kód. ;-)

Filip Procházka
Moderator | 4668
+
0
-

@enumag: nechápu, pletu se v nečem? :)

enumag
Člen | 2118
+
0
-

@Filip Procházka: Všimni si tohoto řádku z NetteExtension:

$initialize->addBody('Nette\Forms\Rules::$defaultMessages[Nette\Forms\Form::?] = ?;', array($name, $text));

Kvůli tomu Nette\Forms\Form::? tohle nelze použít na žádný validátor který není jako konstanta přímo v Nette\Forms\Form. Když si napíšu vlastní validátor na dejme tomu telefonní číslo tak ten tam tu konstantu nemá a default msg přes config nenastavím aniž bych psal své Extension.

Filip Procházka
Moderator | 4668
+
0
-

@enumag: good point, máš pravdu :)

Milo
Nette Core | 1283
+
0
-

@David Grudl Právě jsem shlédl video z 56. Poslední soboty kde jsi nechal nakouknout do formulářů ve 2.1 a je to super. U jedné aplikace potřebuji přepsat systém přidělování oprávnění a tam se to hodí perfektně.

ViPEr*CZ*
Člen | 817
+
0
-

Milo napsal(a):

@David Grudl Právě jsem shlédl video z 56. Poslední soboty kde jsi nechal nakouknout do formulářů ve 2.1 a je to super. U jedné aplikace potřebuji přepsat systém přidělování oprávnění a tam se to hodí perfektně.

+1