Multiplier: dynamiczne komponenty

Narzędzie do dynamicznego tworzenia interaktywnych komponentów

Wyjdźmy od typowego przykładu: mamy listę towarów w sklepie internetowym, przy czym przy każdym będziemy chcieli wyświetlić formularz do dodania towaru do koszyka. Jedną z możliwych opcji jest opakowanie całego listingu w jeden formularz. Znacznie wygodniejszy sposób oferuje nam jednak Nette\Application\UI\Multiplier.

Multiplier umożliwia wygodne definiowanie fabryczki dla wielu komponentów. Działa na zasadzie zagnieżdżonych komponentów – każdy komponent dziedziczący po Nette\ComponentModel\Container może zawierać kolejne komponenty.

Zobacz rozdział o modelu komponentowym w dokumentacji lub wykład Honzy Tvrdíka.

Istotą Multipliera jest to, że występuje w pozycji rodzica, który potrafi dynamicznie tworzyć swoje potomstwo za pomocą callbacku przekazanego w konstruktorze. Zobacz przykład:

protected function createComponentShopForm(): Multiplier
{
	return new Multiplier(function () {
		$form = new Nette\Application\UI\Form;
		$form->addInteger('count', 'Ilość towaru:')
			->setRequired();
		$form->addSubmit('send', 'Dodaj do koszyka');
		return $form;
	});
}

Teraz możemy w szablonie po prostu przy każdym towarze wyświetlić formularz – i każdy będzie rzeczywiście unikalnym komponentem.

{foreach $items as $item}
	<h2>{$item->title}</h2>
	{$item->description}

	{control "shopForm-$item->id"}
{/foreach}

Argument przekazany w znaczniku {control} jest w formacie, który mówi:

  1. pobierz komponent shopForm
  2. a z niego pobierz potomka $item->id

Przy pierwszym wywołaniu punktu 1. shopForm jeszcze nie istnieje, więc wywołana zostanie jego fabryka createComponentShopForm. Na uzyskanym komponencie (instancji Multipliera) jest następnie wywoływana fabryka konkretnego formularza – co jest anonimową funkcją, którą przekazaliśmy Multiplierowi w konstruktorze.

W kolejnej iteracji foreache już metoda createComponentShopForm nie będzie wywoływana (komponent istnieje), ale ponieważ szukamy jego innego potomka ($item->id będzie w każdej iteracji inne), ponownie zostanie wywołana anonimowa funkcja i zwróci nam nowy formularz.

Jedyne, co pozostaje, to zapewnić, aby formularz dodał do koszyka rzeczywiście ten towar, który ma – obecnie formularz przy każdym towarze jest całkowicie identyczny. Pomoże nam właściwość Multipliera (i ogólnie każdej fabryki komponentu w Nette Framework), a mianowicie ta, że każda fabryka jako swój pierwszy argument otrzymuje nazwę tworzonego komponentu. W naszym przypadku będzie to $item->id, co jest dokładnie tą informacją, której potrzebujemy. Wystarczy więc lekko zmodyfikować tworzenie formularza:

protected function createComponentShopForm(): Multiplier
{
	return new Multiplier(function ($itemId) {
		$form = new Nette\Application\UI\Form;
		$form->addInteger('count', 'Ilość towaru:')
			->setRequired();
		$form->addHidden('itemId', $itemId);
		$form->addSubmit('send', 'Dodaj do koszyka');
		return $form;
	});
}
wersja: 4.0