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:
- get a component
shopForm
- 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;
});
}