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(): PollControl
	{
		$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(): void
{
	// 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(int $id, string $message): void
{
	// ...
}

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(): void
{
	// ...
}

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 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(int $x, int $y): void
{
	// ... 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 30 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 text or the stdClass object representing the message. Optional second parameter is its type (error, warning, info, etc.). The method flashMessage() returns an instance of flash message as object stdClass 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

Persistent parameters are used to maintain state in components between different requests. Their value remains the same even after a link is clicked. Unlike session data, they are transferred in the URL. And they are transferred automatically, including links created in other components on the same page.

For example, you have a content paging component. There can be several such components on a page. And you want all components to stay on their current page when you click on the link. Therefore, we make the page number (page) a persistent parameter.

Creating a persistent parameter is extremely easy in Nette. Just create a public property and tag it with the attribute: (previously /** @persistent */ was used)

use Nette\Application\Attributes\Persistent; // this line is important

class PaginatingControl extends Control
{
	#[Persistent]
	public int $page = 1; // must be public
}

We recommend that you include the data type (e.g. int) with the property, and you can also include a default value. Parameter values can be validated.

You can change the value of a persistent parameter when creating a link:

<a n:href="this page: $page + 1">next</a>

Or it can be reset, i.e. removed from the URL. It will then take its default value:

<a n:href="this page: null">reset</a>

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.

In PHP 8, you can also use attributes to mark persistent components:

use Nette\Application\Attributes\Persistent;

#[Persistent('calendar', 'poll')]
class DefaultPresenter extends Nette\Application\UI\Presenter
{
}

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
{
	public function __construct(
		private int $id, //  Id of a poll, for which the component is created
		private PollFacade $facade,
	) {
	}

	public function handleVote(int $voteId): void
	{
		$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
{
	public function __construct(
		private PollFacade $facade,
	) {
	}

	public function create(int $id): PollControl
	{
		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
{
	public function __construct(
		private PollControlFactory $pollControlFactory,
	) {
	}

	protected function createComponentPollControl(): PollControl
	{
		$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
{
	public function create(int $id): PollControl;
}

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.

Components in Depth

Components in a Nette Application are the reusable parts of a web application that we embed in pages, which is the subject of this chapter. What exactly are the capabilities of such a component?

  1. it is renderable in a template
  2. it knows which part of itself to render during an AJAX request (snippets)
  3. it has the ability to store its state in a URL (persistent parameters)
  4. has the ability to respond to user actions (signals)
  5. creates a hierarchical structure (where the root is the presenter)

Each of these functions is handled by one of the inheritance lineage classes. Rendering (1 + 2) is handled by Nette\Application\UI\Control, incorporation into the lifecycle (3, 4) by the Nette\Application\UI\Component class, and the creation of the hierarchical structure (5) by the Container and Component classes.

Nette\ComponentModel\Component  { IComponent }
|
+- Nette\ComponentModel\Container  { IContainer }
	|
	+- Nette\Application\UI\Component  { SignalReceiver, StatePersistent }
		|
		+- Nette\Application\UI\Control  { Renderable }
			|
			+- Nette\Application\UI\Presenter  { IPresenter }

Life Cycle of Component

Life cycle of component

Validation of Persistent Parameters

The values of persistent parameters received from URLs are written to properties by the loadState() method. It also checks if the data type specified for the property matches, otherwise it will respond with a 404 error and the page will not be displayed.

Never blindly trust persistent parameters because they can easily be overwritten by the user in the URL. For example, this is how we check if the page number $this->page is greater than 0. A good way to do this is to override the loadState() method mentioned above:

class PaginatingControl extends Control
{
	#[Persistent]
	public int $page = 1;

	public function loadState(array $params): void
	{
		parent::loadState($params); // here is set the $this->page
		// follows the user value check:
		if ($this->page < 1) {
			$this->error();
		}
	}
}

The opposite process, that is, collecting values from persistent properites, is handled by the saveState() method.

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 SignalReceiver 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.

version: 4.0 3.x 2.x