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('amount', 'Количество:')
			->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): каждая фабрика получает первым аргументом имя создаваемого компонента. Кроме того, фабрика Multiplier получает вторым аргументом сам экземпляр Multiplier. В нашем случае первым аргументом будет $item->id, а это ровно те сведения, которые нам нужны. Значит, достаточно немного изменить создание формы:

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