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

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

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

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

См. модель компонента в документации.

Multiplier представляет собой родительский компонент, который может динамически создавать свои дочерние компоненты, используя обратный вызов, переданный в конструкторе. См. пример:

protected function createComponentShopForm(): Multiplier
{
	return new Multiplier(function () {
		$form = new Nette\Application\UI\Form;
		$form->addInteger('amount', 'Amount:')
			->setRequired();
		$form->addSubmit('send', 'Add to cart');
		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, и создается форма.

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

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

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