Interactive Components
Components are separate reusable objects that we place into pages. They can be forms, datagrids, polls, in fact anything that makes sense to use repeatedly. We will show:
- how to use components?
- how to write them?
- What are signals?
Nette has a built-in component system. Older of you may remember something similar from Delphi or ASP.NET Web Forms. React or Vue.js is built on something remotely similar. However, in the world of PHP frameworks, this is a completely unique feature.
At the same time, components fundamentally change the approach to application development. You can compose pages from pre-prepared units. Do you need datagrid in administration? You can find it at Componette, a repository of open-source add-ons (not just components) for Nette, and simply paste it into the presenter.
You can incorporate any number of components into the presenter. And you can insert other components into some components. This creates a component tree with a presenter as a root.
Factory Methods
How are components placed and subsequently used in the presenter? Usually using factory methods.
The component factory is an elegant way to create components only when they are really needed (lazy / on-demand). The whole
magic is in implementation of a method called createComponent<Name>()
, where <Name>
is the
name of the component, that will create and return.
class DefaultPresenter extends Nette\Application\UI\Presenter
{
protected function createComponentPoll()
{
$poll = new PollControl;
$poll->items = $this->item;
return $poll;
}
}
Because all components are created in separate methods, the code is cleaner and easier to read.
Component names always start with a lowercase letter, although they are capitalized in the method name.
We never call factories directly, they get called automatically, when we use components for the first time. Thanks to that, a component is created at the right moment, and only if it's really needed. If we wouldn't use the component (for example on some AJAX request, where we return only part of the page, or when parts are cached), it wouldn't even be created and we save performance of the server.
// we access the component and if it was the first time,
// it calls createComponentPoll() to create it
$poll = $this->getComponent('poll');
// alternative syntax: $poll = $this['poll'];
In the template, you can render a component using tag {control}. So there is no need of manually passing the components to template.
<h2>Please Vote</h2>
{control poll}
Hollywood Style
Components commonly use one cool technique, which we like to call Hollywood style. Surely you know the cliché that actors hear often at the casting calls: “Don't call us, we'll call you.” And that's what this is about.
In Nette, instead of having to constantly ask questions (“was the form submitted?”, “was it valid?” or “did anyone press this button?”), you tell the framework “when this happens, call this method” and leave further work on it. If you program in JavaScript, you are familiar with this style of programming. You write functions that are called when a certain event occurs. And the engine passes the appropriate parameters to them.
This completely changes the way you write applications. The more tasks you can delegate to the framework, the less work you have. And the less you can forget.
How to Write a Component
By component we usually mean descendants of the class Nette\Application\UI\Control. The presenter Nette\Application\UI\Presenter itself is also a descendant of the` Control` class.
use Nette\Application\UI\Control;
class PollControl extends Control
{
}
Rendering
We already know that the {control componentName}
tag is used to draw a component. It actually calls the method
render()
of the component, in which we take care of the rendering. We have, just like in the presenter, a Latte template in the variable $this->template
, to which we pass the
parameters. Unlike use in a presenter, we must specify a template file and let it render:
public function render()
{
// we will put some parameters into the template
$this->template->param = $value;
// and draw it
$this->template->render(__DIR__ . '/poll.latte');
}
The tag {control}
allows to pass parameters to the method render()
:
{control poll $id, $message}
public function render($id, $message)
{
// ...
}
Sometimes a component can consist of several parts that we want to render separately. For each of them we will create own
rendering method, here is for example renderPaginator()
:
public function renderPaginator()
{
// ...
}
And in the template we then call it using:
{control poll:paginator}
For better understanding it's good to know how the tag is compiled to PHP code.
{control poll}
{control poll:paginator 123, 'hello'}
This compiles to:
$control->getComponent('poll')->render();
$control->getComponent('poll')->renderPaginator(123, 'hello');
getComponent()
method returns the poll
component and then the render()
or
renderPaginator()
method, respectively, is called on it.
If anywhere in the parameter part =>
is used, all parameters will be wrapped with an
array and passed as the first argument:
{control poll, id => 123, message => 'hello'}
compiles to:
$control->getComponent('poll')->render(['id' => 123, 'message' => 'hello']);
Rendering of sub-component:
{control cartControl-someForm}
compiles to:
$control->getComponent("cartControl-someForm")->render();
Components, like presenters, pass several useful variables to templates automatically:
$basePath
is an absolute URL path to root dir (for example/CD-collection
)$baseUrl
is an absolute URL to root dir (for examplehttp://localhost/CD-collection
)$user
is an object representing the user$presenter
is the current presenter$control
is the current component$flashes
list of messages sent by methodflashMessage()
Signal
We already know that navigation in the Nette application consists of linking or redirecting to pairs
Presenter:action
. But what if we just want to perform an action on the current page? For example, change the
sorting order of the column in the table; delete item; switch light/dark mode; submit the form; vote in the poll; etc.
This type of request is called a signal. And like actions invoke methods action<Action>()
or
render<Action>()
, signals call methods handle<Signal>()
. While the concept of action (or
view) relates only to presenters, signals apply to all components. And therefore also to presenters, because
UI\Presenter
is a descendant of UI\Control
.
public function handleClick($x, $y)
{
// ... processing of signal ...
}
The link that calls the signal is created in the usual way, i.e. in the template by the attribute n:href
or the
tag {link}
, in the code by the method link()
. More in the chapter Creating URL links.
<a n:href="click! $x, $y">click here</a>
The signal is always called on the current presenter and view, so it is not possible to link to signal in different presenter / action.
Thus, the signal causes the page to be reloaded in exactly the same way as in the original request, only in addition it calls the signal handling method with the appropriate parameters. If the method does not exist, exception Nette\Application\UI\BadSignalException is thrown, which is displayed to the user as error page 403 Forbidden.
Snippets and AJAX
The signals may remind you a little bit AJAX: handlers that are called on the current page. And you're right, signals are really often called using AJAX, and then we only transmit changed parts of the page to the browser. They are called snippets. More information can be found on the page about AJAX.
Flash Messages
A component has its own storage of flash messages independent of the presenter. These are messages that, for example, inform about the result of the operation. An important feature of flash messages is that they are available in the template even after redirection. Even after being displayed, they will remain alive for another three seconds – for example, in case the user would unintentionally refresh the page – the message will not be lost.
Sending is done by the method flashMessage. The first
parameter is the message's text and the second (optional) parameter is its type (error, warning, info, etc.). The method
flashMessage()
returns an instance of flash message to which you can pass information.
$this->flashMessage('Item was deleted.');
$this->redirect(/* ... */); // and redirect
In the template, these messages are available in the variable $flashes
as objects stdClass
, which
contain the properties message
(message text), type
(message type) and can contain the already mentioned
user information. We draw them as follows:
{foreach $flashes as $flash}
<div class="flash {$flash->type}">{$flash->message}</div>
{/foreach}
Persistent Parameters
It is often needed to keep some parameter in a component for the whole time of working with the component. It can be for
example the number of the page in pagination. This parameter should be marked as persistent using the annotation
@persistent
.
class PollControl extends Control
{
/** @persistent */
public $page = 1;
}
This parameter will be automatically passed in every link as a GET
parameter until the user leaves the page with
this component.
Never trust persistent parameters blindly because they can be faked easily (by overwriting the URL). Verify, for example, if the page number is within the correct interval.
Persistent Components
Not only parameters but also components can be persistent. Their persistent parameters are also transferred between different
actions or between different presenters. We mark persistent components with this annotations for the presenter class. For example
here we mark components calendar
and poll
as follows:
/**
* @persistent(calendar, poll)
*/
class DefaultPresenter extends Nette\Application\UI\Presenter
{
}
You don't have to mark subcomponents as persistent, they are persistent automatically.
Components with Dependencies
How to create components with dependencies without “messing up” the presenters that will use them? Thanks to the clever features of the DI container in Nette, as with using traditional services, we can leave most of the work to the framework.
Let's take as an example a component that has a dependency on the PollFacade
service:
class PollControl extends Control
{
/** @var PollFacade */
private $facade;
/** @var int Id of a poll, for which the component is created */
private $id;
public function __construct($id, PollFacade $facade)
{
$this->facade = $facade;
$this->id = $id;
}
public function handleVote($voteId)
{
$this->facade->vote($id, $voteId);
//...
}
}
If we were writing a classic service, there would be nothing to worry about. The DI container would invisibly take care of
passing all the dependencies. But we usually handle components by creating a new instance of them directly in the presenter in factory methods createComponent...()
. But passing all the dependencies of all the
components to the presenter to then pass them to the components is cumbersome. And the amount of code written…
The logical question is, why don't we just register the component as a classic service, pass it to the presenter, and then
return it in the createComponent...()
method? But this approach is inappropriate because we want to be able to create
the component multiple times.
The correct solution is to write a factory for the component, i.e. a class that creates the component for us:
class PollControlFactory
{
/** @var PollFacade */
private $facade;
public function __construct(PollFacade $facade)
{
$this->facade = $facade;
}
/**
* @return PollControl
*/
public function create($id)
{
return new PollControl($id, $this->facade);
}
}
Now we register our service to DI container to configuration:
services:
- PollControlFactory
Finally, we will use this factory in our presenter:
class PollPresenter extends Nette\UI\Application\Presenter
{
/** @var PollControlFactory */
private $pollControlFactory;
public function __construct(PollControlFactory $pollControlFactory)
{
$this->pollControlFactory = $pollControlFactory;
}
protected function createComponentPollControl()
{
$pollId = 1; // we can pass our parameter
return $this->pollControlFactory->create($pollId);
}
}
The great thing is that Nette DI can generate such simple factories, so instead of writing the whole code, you just need to write its interface:
interface PollControlFactory
{
/**
* @return PollControl
*/
public function create($id);
}
That's all. Nette internally implements this interface and injects it to our presenter, where we can use it. It also magically
passes our parameter $id
and instance of class PollFacade
into our component.
Advanced Use of Components
Components are mostly renderable. But also unrenderable components exist. Some components can have descendants some not. Nette Framework introduces several classes and interfaces for all these types of components.
Object inheritance allows us to have a hierarchic structure of classes like in real world. We can create new classes by extending. These extended classes are descendants of the original class and inherit its parameters and methods. Extended class can add its own parameters and methods to the inherited ones.
Knowledge class hierarchy is required for proper understanding how things work.
Nette\ComponentModel\Component { IComponent }
|
+- Nette\ComponentModel\Container { IContainer }
|
+- Nette\Application\UI\Component { ISignalReceiver, IStatePersistent }
|
+- Nette\Application\UI\Control { IPartiallyRenderable }
|
+- Nette\Application\UI\Presenter { IPresenter }
Nette\ComponentModel\IComponent
Interface Nette\ComponentModel\IComponent has
to be implemented by every component. It requires method getName()
which is returning it's name and
getParent()
returning it's parent. Both name and parent can be set using method setParent()
– first
argument is parent and the second one is name.
Nette\ComponentModel\Component
Nette\ComponentModel\Component
is a standard implementation of IComponent
. It is common ancestor of all components including form elements. It has
several methods for traversing:
lookup($type)
looks up object of given class or interface up in the hierarchy. For example
$component->lookup(Nette\Application\UI\Presenter::class)
returns presenter if the components is bound to it (even
deep in the hierarchy).
lookupPath($type)
returns path – a string made by concatenating names of all components on the way between current and component of
$type
. For example $component->lookupPath(Nette\Application\UI\Presenter::class)
returns unique
identifier of component toward presenter.
Nette\ComponentModel\IContainer
Parent components implement not just IComponent
but also Nette\ComponentModel\IContainer.
Interface which contains methods for adding, removing, getting and iteration over components. Such components can create
hierarchy – presenters can contain forms and these forms can contain form inputs. Whole tree hierarchy of components is created
by branches of IContainer
objects and IComponent
leafs.
Nette\ComponentModel\Container
Nette\ComponentModel\Container
is a standard implementation of IContainer
interface. It is an ancestor of for example form or classes
Control
and Presenter
.
It offers methods for correct adding, getting and removing objects and of course iteration over its contents. Attempt to call
undefined child causes invoking of factory createComponent($name).
Method createComponent($name)
invokes method createComponent<component name>
in current component
and it passes name of the component as a parameter. Created component is then passed to current component as its child. We call
theese component factories, they can be implemented in classes inherited from Container
.
Nette\Application\UI\Component
Class Nette\Application\UI\Component is an ancestor of all components used in presenter. Components in presenter are object which are kept by presenter during its life cycle.
They have ability to influence each other, save their states into URL and respond to user commands (signals) and does not have to be renderable.
Nette\Application\UI\Control
Control is a renderable component. It is a reusable part of web application which is this whole chapter about. When talking about components this is the class we usually have on mind. It remembers which it's part should be rendered in AJAX request, which we've already talked about.
Control
isn't representing visual sector of webpage, but it's logical part. It is possible to render it
repeatedly, conditionally and every time with different template.
Monitoring of Ancestors
Component model in Nette offers very dynamical tree workflow (components can be removed, added, moved). It would be a mistake to rely on knowing parent (in constructor) when the component is created. In most cases, parent is not not known when the component is created.
How to find out when was component added to presenter's tree? Watching the change of parent is not enough because a parent of
parent might have been added to presenter. Method monitor($type) is here
to help. Every component can monitor any number of classes and interfaces. Adding or removing is reported by invoking method
attached($obj)
(detached($obj)
respectivelly), where $obj
is the object of
monitored class.
An example: Class UploadControl
, representing form element for uploading files in Nette Forms, has to set
form's attribute enctype
to value multipart/form-data
. But in the time of the creation of the object it
does not have to be attached to any form. When to modify the form? The solution is simple – we create a request for monitoring
in the constructor:
class UploadControl extends Nette\Forms\Controls\BaseControl
{
public function __construct($label)
{
$this->monitor(Nette\Forms\Form::class);
// ...
}
// ...
}
and method attached
is called when the form is available:
protected function attached($form)
{
parent::attached($form);
if ($form instanceof Nette\Forms\Form) {
$form->getElementPrototype()->enctype = 'multipart/form-data';
}
}
Since nette/component-model
v2.4, the preferred way is to pass the callbacks directly to
monitor($type, $attached = null, $detached = null)
:
class UploadControl extends Nette\Forms\Controls\BaseControl
{
public function __construct($label)
{
$this->monitor(Nette\Forms\Form::class, function ($form) {
$form->getElementPrototype()->enctype = 'multipart/form-data';
});
}
}
Iterating over Children
There is a method getComponents($deep =
false, $type = null) for that. First parameter determines if the components should be looked up in depth (recursively). With
true
, it not only iterates all it's children, but also all children of it's children, etc. Second parameter servers
as an optional filter by class or interface.
For example, this is the way how validation of forms is internally performed:
$valid = true;
foreach ($form->getComponents(true, Nette\Forms\IControl::class) as $control) {
if (!$control->getRules()->validate()) {
$valid = false;
break;
}
}
Signals in Depth
A signal causes a page reload like the original request (with the exception of AJAX) and invokes the method
signalReceived($signal)
whose default implementation in class Nette\Application\UI\Component
tries to
call a method composed of the words handle{Signal}
. Further processing relies on the given object. Objects which are
descendants of Component
(i.e. Control
and Presenter
) try to call
handle{Signal}
with relevant parameters.
In other words: the definition of the method handle{Signal}
is taken and all parameters which were received in the
request are matched with the method's parameters. It means that the parameter id
from the URL is matched to the
method's parameter $id
, something
to $something
and so on. And if the method doesn't
exist, the method signalReceived
throws an exception.
Signal can be received by any component, presenter of object which implements interface ISignalReceiver
if
it's connected to component tree.
The main receivers of signals are Presenters
and visual components extending Control
. A signal is a
sign for an object that it has to do something – poll counts in a vote from user, box with news has to unfold, form was sent
and has to process data and so on.
The URL for the signal is created using the method Component::link(). As
parameter $destination
we pass string {signal}!
and as $args
an array of arguments which we
want to pass to the signal handler. Signal parameters are attached to the URL of the current presenter/view. The parameter
?do
in the URL determines the signal called.
Its format is {signal}
or {signalReceiver}-{signal}
. {signalReceiver}
is the name of the
component in the presenter. This is why hyphen (inaccurately dash) can't be present in the name of components – it is used to
divide the name of the component and signal, but it's possible to compose several components.
The method isSignalReceiver()
verifies whether a component (first argument) is a receiver of a signal (second argument). The second argument can be omitted –
then it finds out if the component is a receiver of any signal. If the second parameter is true
it verifies whether
the component or its descendants are receivers of a signal.
In any phase preceding handle{Signal}
can be signal performed manually by calling the method processSignal() which
takes responsibility for signal execution. Takes receiver component (if not set it is presenter itself) and sends it the
signal.
Example:
if ($this->isSignalReceiver($this, 'paging') || $this->isSignalReceiver($this, 'sorting')) {
$this->processSignal();
}
The signal is executed prematurely and it won't be called again.