Multiplier: Dynamic Components

A tool for dynamical creation of interactive components

Let's start with a typical problem: we have a list of products on an e-commerce site and we want to accompany each product with an add to cart form. One way is to wrap the whole listing in a single form. A more convenient way is to use Nette\Application\UI\Multiplier.

Multiplier allows you to define a factory for multiple components. It is based on the principle of nested components – each component inheriting from Nette\ComponentModel\Container may contain other components.

Multiplier poses as a parent component which can dynamically create its children using the callback passed in the constructor. See example:

protected function createComponentShopForm()
{
	return new Multiplier(function () {
		$form = new Nette\Application\UI\Form;
		$form->addInteger('amount', 'Amount:')
			->setRequired();
		$form->addSubmit('send', 'Add to cart');
		return $form;
	});
}

In the template we can render a form for each product – and each form will indeed be a unique component.

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

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

Argument passed to {control} tag says:

  1. get a component shopForm
  2. and return its child $item->id

During the first call of 1. the shopForm component does not yet exist, so the method createComponentShopForm is called to create it. An anonymous function passed as a parameter to Multiplier, is then called and a form is created.

In the subsequent iterations of the foreach the method createComponentShopForm is no longer called because the component already exists. But since we reference another child ($item->id varies between iterations), an anonymous function is called again and a new form is created.

The last thing is to ensure that the form actually adds the correct product to the cart because in the current state all the forms are equal and we cannot distinguish to which products they belong. For this we can use the property of Multiplier (and in general of any component factory method in Nette Framework) that every component factory method receives the name of the created component as the first argument. In our case that would be $item->id, which is exactly what we need to distinguish individual products. All you need to do is modify the code for creating the form:

protected function createComponentShopForm()
{
	return new Multiplier(function ($itemId) {
		$form = new Nette\Application\UI\Form;
		$form->addInteger('amount', 'Amount:')
			->setRequired();
		$form->addHidden('itemId', $itemId);
		$form->addSubmit('send', 'Add to cart');
		return $form;
	});
}
version: 4.0 3.x 2.x