Multiplier: динамические компоненты

Инструмент для динамического создания интерактивных компонентов

Начнем с типичного примера: у нас есть список товаров в интернет-магазине, и для каждого мы хотим вывести форму для добавления товара в корзину. Один из возможных вариантов — обернуть весь список в одну форму. Однако гораздо более удобный способ предлагает нам Nette\Application\UI\Multiplier.

Multiplier позволяет удобно определить фабрику для нескольких компонентов. Он работает по принципу вложенных компонентов — каждый компонент, наследующий от Nette\ComponentModel\Container, может содержать другие компоненты.

См. главу о модели компонентов в документации или лекцию Яна Тврдика.

Суть Multiplier заключается в том, что он выступает в роли родителя, который может динамически создавать своих потомков с помощью callback-функции, переданной в конструкторе. См. пример:

protected function createComponentShopForm(): Multiplier
{
	return new Multiplier(function () {
		$form = new Nette\Application\UI\Form;
		$form->addInteger('count', 'Количество товара:')
			->setRequired();
		$form->addSubmit('send', 'Добавить в корзину');
		return $form;
	});
}

Теперь мы можем в шаблоне просто для каждого товара отрисовать форму — и каждая будет действительно уникальным компонентом.

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

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

Аргумент, переданный в теге {control}, имеет формат, который говорит:

  1. получи компонент shopForm
  2. и из него получи потомка $item->id

При первом вызове пункта 1. shopForm еще не существует, поэтому вызывается его фабрика createComponentShopForm. На полученном компоненте (экземпляре Multiplier) затем вызывается фабрика конкретной формы — это анонимная функция, которую мы передали Multiplier в конструкторе.

В следующей итерации foreach метод createComponentShopForm уже не будет вызван (компонент существует), но поскольку мы ищем другого его потомка ($item->id будет разным в каждой итерации), снова будет вызвана анонимная функция и вернет нам новую форму.

Единственное, что остается, — это убедиться, что форма добавит в корзину действительно тот товар, который нужно — в настоящее время форма для каждого товара абсолютно одинакова. Нам поможет свойство Multiplier (и вообще любой фабрики компонентов в Nette Framework), а именно то, что каждая фабрика в качестве своего первого аргумента получает имя создаваемого компонента. В нашем случае это будет $item->id, что является именно той информацией, которая нам нужна. Достаточно немного изменить создание формы:

protected function createComponentShopForm(): Multiplier
{
	return new Multiplier(function ($itemId) {
		$form = new Nette\Application\UI\Form;
		$form->addInteger('count', 'Количество товара:')
			->setRequired();
		$form->addHidden('itemId', $itemId);
		$form->addSubmit('send', 'Добавить в корзину');
		return $form;
	});
}
версия: 4.0