Komponenta průvodce

Upozornění: Tohle vlákno je hodně staré a informace nemusí být platné pro současné Nette.
_Martin_
Generous Backer | 679
+
0
-

Jak již někteří vědí, léta letoucí se snažím o dokonalou komponentu pro Průvodce. V jisté zbastlené verzi tato komponenta již existuje a nyní nastává onen okamžik velkého refactoringu. A to je chvíle, kdy se obracím o radu na vás, kolegy Nettisty.

Rozhoduji se, jakým způsobem přidávat do průvodce kroky. V hlavě mám dva nápady a nyní je předkládám před vás, odbornou veřejnost, k posouzení. A samozřejmě můžete přijít i s nápadem vlastním.

Verze 1 – jako formuláře

protected function createComponentRegistrationWizard($name)
{

	$wizard = new Wizard($this, $name);
	$wizard->caption = 'Průvodce registrací';
	$wizard->onProcess[] = array($this, 'process');
	$wizard->onCancel[] = array($this, 'canceled');

	/** sekce: ZÁKAZNÍK *******************************************************/

	$wizard->addStep('zakaznik', 'Informace o zákazníkovi')
		->addDescription('Vyplňte informace o zákazníkovi.');


	/** sekce: DODAVATEL ******************************************************/

	$wizard->addStep('obchodniZalezitosti', 'Obchodní záležitosti')
		->addDescription('Vyplňte informace, které se týkají obchodní stránky věci.');

	$wizard->addStep('dodavatel', 'Základní informace o dodavateli')
		->addDescription('Vyplňte základní informace o dodavateli.')
		->setNext($wizard['obchodniZalezitosti']);


	/** křižovatka: UŽIVATEL **************************************************/

	$wizard->addCrossroad('uzivatel', 'Kdo se registruje')
		->addDescription('Chcete se registrovat jako náš zákazník nebo dodavatel?')
		->addWay('Zákazník', $wizard['zakaznik'], 'Můžete nakupovat, jak je ctěná libost.', 'zakaznik.gif')
		->addWay('Dodavatel', $wizard['dodavatel'], 'Budete nás zásobovat, pane Karfíku.', 'dodavatel.gif');


	/** DOKONČENÍ *************************************************************/

	$wizard->addStep('dokonceni', 'Kontrola údajů')
		->addDescription('Zkontrolujte vyplněné údaje. Registraci dokončíte stisknutím tlačítka Registrovat.');


	$wizard->setFirst($wizard['uzivatel']);
	$wizard['zakaznik']->setNext($wizard['dokonceni']);
	$wizard['obchodniZalezitosti']->setNext($wizard['dokonceni']);

	return $wizard;

}

Výhody:

  • vytváří se stejně jako formuláře
  • jednoduchý a objektově čistý

Nevýhody:

  • kroky, na které chci odkazovat, musím buď vytvořit předem nebo je odkázat dodatečně → v obou případech to dělá kód těžším, než by mohlo být (a nutí mě kupříkladu ručně nastavit první krok)

Verze 2 – kroky jako komponenty z továrničky

// v presenteru
protected function createComponentRegistrationWizard($name)
{
	$wizard = new RegistrationWizard($this, $name);
	$wizard->caption = 'Průvodce registrací';
	$wizard->onProcess[] = array($this, 'process');
	$wizard->onCancel[] = array($this, 'canceled');

	return $wizard;
}

// RegistrationWizard
class RegistrationWizard extends Wizard
{

	/** křižovatka: UŽIVATEL **************************************************/

	protected function createComponentUzivatel($name)
	{
		return $this->addCrossroad($name, 'Kdo se registruje')
			->addDescription('Chcete se registrovat jako náš zákazník nebo dodavatel?')
			->addWay('Zákazník', $this['zakaznik'], 'Můžete nakupovat, jak je ctěná libost.', 'zakaznik.gif')
			->addWay('Dodavatel', $this['dodavatel'], 'Budete nás zásobovat, pane Karfíku.', 'dodavatel.gif');
	}


	/** sekce: ZÁKAZNÍK *******************************************************/

	protected function createComponentZakaznik($name)
	{
		return $this->addStep($name, 'Informace o zákazníkovi')
			->addDescription('Vyplňte informace o zákazníkovi.')
			->setNext($this['dokonceni']);
	}


	/** sekce: DODAVATEL ******************************************************/

	protected function createComponentDodavatel($name)
	{
		return $this->addStep($name, 'Základní informace o dodavateli')
			->addDescription('Vyplňte základní informace o dodavateli.')
			->setNext($this['obchodniZalezitosti']);
	}

	protected function createComponentObchodniZalezitosti($name)
	{
		return $this->addStep($name, 'Obchodní záležitosti')
			->addDescription('Vyplňte informace, které se týkají obchodní stránky věci.')
			->setNext($this['dokonceni']);
	}

	/** DOKONČENÍ *************************************************************/

	protected function createComponentDokonceni($name)
	{
		return $this->addStep($name, 'Kontrola údajů')
			->addDescription('Zkontrolujte vyplněné údaje. Registraci dokončíte stisknutím tlačítka Registrovat.');
	}

}

Výhody:

  • kroky jsou pěkně za sebou a vše ohledně kroku je na jednom místě

Nevýhody:

  • nutnost vytvářet vlastního potomka třídy Wizard
  • více kódu

Tak co vy na to?

Editoval _Martin_ (2. 11. 2009 17:29)

Honza Marek
Člen | 1664
+
0
-

Co dělaj ty metody addCrossroad a addStep, když jsou v továrničce?

_Martin_
Generous Backer | 679
+
0
-

Honza M. napsal(a):

Co dělaj ty metody addCrossroad a addStep, když jsou v továrničce?

Zhruba to samé, co metody addSubmit, addText,… u formuláře. V těch továrničkách jsem je nechal z toho důvodu, že je to uživatelsky přívětivější, než psát pro každý krok:

protected function createComponentUzivatel($name)
{
	$step = new Crossroad;
	$step->caption = 'Kdo se registruje';
	$step->addDescription('Chcete se registrovat jako náš zákazník nebo dodavatel?');
	$step->addWay('Zákazník', $this['zakaznik'], 'Můžete nakupovat, jak je ctěná libost.', 'zakaznik.gif');
	$step->addWay('Dodavatel', $this['dodavatel'], 'Budete nás zásobovat, pane Karfíku.', 'dodavatel.gif');

	if(isset($this->values[$name])) {
		$step->setValues($this->values[$name]);
	}

	return $this[$name] = $step;
}

Edit: ta továrnička je tam především kvůli tomu, aby se mohlo na krok odkazovat, ikdyž ještě nebyl vytvořen.

Editoval _Martin_ (2. 11. 2009 18:16)

Jerry123456789
Člen | 37
+
0
-

A co třeba (jen příklad, inspirace)

<?php
$w = new Wizard(...);
...
$w->addStep($this['wizardStep1'],...);
$w->addStep($this['wizardStep2'],...);
...
?>

přičemž wizardStep1/2 by bylo AppForm (spíše potomek Formu – WizardForm), normálně v továrničce stvořený.

Editoval Jerry123456789 (2. 11. 2009 18:49)

_Martin_
Generous Backer | 679
+
0
-

Jerry123456789 napsal(a):

Pokud ti dobře rozumím, spojil jsi obě dvě mnou uvedené verze do jedné – bohužel v tom nevidím změnu oproti tomu, co jsem psal. A přidávat potomek Formu rozhodně ne (formulář není třeba v každém kroku) – jsou zde třídy pro různé kroky, jako třeba Crossroad pro „rozcestí“ (něco jako výběr typu sítě ve Windows 7).

Honza Kuchař
Člen | 1662
+
0
-

Já myslím, že je to dilema, protože na jednoduché průvodce bude hezčí ten první způsob. Na složitější ten druhý.

JakubKohout
Člen | 92
+
0
-

honzakuchar napsal(a):

Já myslím, že je to dilema, protože na jednoduché průvodce bude hezčí ten první způsob. Na složitější ten druhý.

souhlasím

asi bych preferoval způsob zápisu ala TabControl, je to vlastně příklad co už uvedl kolega Jerry.
Je to lepší z hlediska toho když budu chtít v nějakém kroku vykreslit například dataGrid, či jenom formulář (asi ani jedno bych nevyužil ale třeba se v budoucnu objeví nějaká super componenta která by sem sedla)

_Martin_
Generous Backer | 679
+
0
-

Když se na to dívám znovu, tak z implementačního hlediska to je jedno a to samé – jde spíše o takové Best practice. Na TabControlu se mi líbí definování továrních metod, to je šikovné a umožňovalo by to líné vytváření obsahu jednotlivých kroků (praktické zvláště u velkých formulářů) – takže vím, kde brát inspiraci=)

Z vašich reakcí mám ovšem pocit, že jsem se špatně vyjádřil – řešil jsem především problém, jak jednomu kroku říct, že vede na druhý, když druhý ještě neexistuje.

$a = new Wizard;
$a->addStep('krok1')->setNext(???); // $a['krok2'] dát nemůžu, ještě neexistuje
$a->addStep('krok2');
Honza Marek
Člen | 1664
+
0
-

_Martin_ napsal(a):

protected function createComponentUzivatel($name)
{
	$step = new Crossroad;
	...

	return $this[$name] = $step;
}

Ta továrnička by měla stačit taková:

protected function createComponentUzivatel()
{
	$step = new Crossroad;
	...

	return $step;
}

nebo

protected function createComponentUzivatel($name)
{
	$step = new Crossroad($this, $name);
	...
}

A taky teda myslim, že pokud step i crossroad jsou obě přímými podkomponentami wizardu, tak je to na implementátorovi, jaký způsob použije.

Jerry123456789
Člen | 37
+
0
-

_Martin_:

ad 1) addStep($this[‚…‘]) nemusí dostat jen form, stačí nějaký „embedder“ (může embedovat form, text, apod.)

ad 2) ->setNext(string $name, boolead $need);
to by snad stačilo ne? $need – throw exception if next step doesn't exists?

_Martin_
Generous Backer | 679
+
0
-

Honza M. napsal(a): …

Jj, kopíroval jsem to z metody Wizard::addStep a tam to oproti továrničce bylo třeba.

Jerry123456789 napsal(a):

ad 1) addStep($this[‚…‘]) nemusí dostat jen form, stačí nějaký „embedder“ (může embedovat form, text, apod.)

Já nechci krok vázat jen na jednu komponentu (buď form nebo nějakou jinou), byť by byla upravená. Tady pravděpodobně zvolím způsob podobný TabControlu, čili továrničky na obsah.

ad 2) ->setNext(string $name, boolead $need);
to by snad stačilo ne? $need – throw exception if next step doesn't exists?

Nad tím jsem taky uvažoval. Technicky to jde, ale má to jednu nevýhodu: když zadáš krok špatně (třeba neexistující), tak ti Laděnka nemůže říct, na jakým řádku k chybě došlo (protože se na tu chybu přijde až někdě později při zpracování).


Teď si říkám, že bude lepší mít možnost nastavit následující krok za běhu (například pokud chci krok měnit v závislosti na vyplnění formuláře) – a nastavení kroků přímo v definici průvodce bude jen zjednodušením kvůli triviálním průvodcům.

Jerry123456789
Člen | 37
+
0
-

_Martin_ napsal(a):

Jerry123456789 napsal(a):

ad 1) addStep($this[‚…‘]) nemusí dostat jen form, stačí nějaký „embedder“ (může embedovat form, text, apod.)

Já nechci krok vázat jen na jednu komponentu (buď form nebo nějakou jinou), byť by byla upravená. Tady pravděpodobně zvolím způsob podobný TabControlu, čili továrničky na obsah.

Však jeden embedder by mohl mít v sobě schováno cokoli, ne jen jednu komponentu.

Honza Kuchař
Člen | 1662
+
0
-

Ahoj, přidáte tuto komponentu do extras?