Components and Controls

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 component-name} 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}

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 example http://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 method flashMessage()

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

When our component needs some dependencies to work, like PollControl needs PollManager to manage voting and save polls, it can be done by passing these dependencies into the constructor:

class PollControl extends Control
{
	/** @var PollManager */
	private $pollManager;

	/** @var int Id of a poll, for which the component is created */
	private $pollId;

	public function __construct($pollId, PollManager $pollManager)
	{
		$this->pollManager = $pollManager;
		$this->pollId = $pollId;
	}

	public function handleVote($voteId)
	{
		$this->pollManager->vote($pollId, $voteId);
		//...
	}
}

OK, but how is the PollManager put into the PollControl's constructor? To do that, we need to write a factory class that will create instance of our component. This factory is a service, so we will register it as service to the DI container:

class PollControlFactory
{
	/** @var PollManager */
	private $pollManager;

	public function __construct(PollManager $pollManager)
	{
		$this->pollManager = $pollManager;
	}

	/**
	 * @return PollControl
	 */
	public function create($pollId)
	{
		return new PollControl($pollId, $this->pollManager);
	}
}

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);
	}
}

Luckily, Nette can generate these simple factories, so we can write just interface of this factory, and DI container will generate the implementation:

interface PollControlFactory
{
	/**
	 * @return PollControl
	 */
	public function create($pollId);
}

We will register this interface to our container to configuration:

services:
	- PollControlFactory

and usage in presenter is same as usage of previous factory, we will get the interface by DI and use it as factory.

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 $pollId and instance of class PollManager into our component.

Further usage of components in context of DI is discussed here.

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;
	}
}

Signály do hloubky

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.


Related blog posts