Components and Controls
Components are the foundation of reusable code. They make your work easier and allow you to profit from community work. Components are wonderful. We'll explain
- how to write components
- what are signals
- how to send flash messages
- how to use AJAX
A component is a renderable object. It can be a form, menu, poll etc. Within one page there can be as many components as you wish. At https://addons.nette.org/cs you can find open-source components which were put there by volunteers from the Nette Framework community.
You can find an example of a component and it's use in a page in the example Fifteen.
A component is a descendant of class Nette\Application\UI\Control:
use Nette\Application\UI\Control;
class PollControl extends Control
{
}
When talking about components, we usually mean descendants of class Control. Components are basically controls in the framework's terminology.
Templates
A component has factory for its template. It creates a template, passes several variables
(shown below) and registers default helpers. Rendering is our job and it is done in method
render()
. This is where we choose the file from which the template will be loaded. render()
method is
also the right place to register variables used in the template. The template can be put inside the same folder as the component
and with the same file name.
public function render()
{
$template = $this->template;
$template->setFile(dirname(__FILE__) . '/PollControl.latte');
// insert some parameters to the template
$template->param = 'some value';
// render it
$template->render();
}
Links
The method link()
is made for linking to signals. Links are rendered using
the macro {link}
in the template, or by macro {plink}
if we are in a component's template and the
target is a presenter.
Use in a component:
$url = $this->link('click!', $x, $y);
Use in a template:
<a n:href="click! $x, $y"> ... </a>
Flash messages
A component has its own storage of flash messages independent of the presenter. These messages inform about the result of operations which are followed by redirection.
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.
Example:
public function deleteFormSubmitted(Form $form)
{
... delete record using model ...
// pass flash message
$this->flashMessage('Item was deleted.');
$this->redirect(...); // and redirect
}
Template receives this messages automatically in variable $flashes
. This variable contains an array of objects
(stdClass
) which contains properties message
, type
and can contain extra information
mentioned above.
Example:
{foreach $flashes as $flash}
<div class="flash {$flash->type}">{$flash->message}</div>
{/foreach}
If flashMessage()
is saved and redirection occurs, the same parameter $flashes
will be present in the
template. Messages then stay alive for the next three seconds – to prevent their loss in case that page was not loaded
successfully and the user refreshed the request. If someone refreshes the page (F5) twice in a row the flash message persists, if
s/he clicks elsewhere it disappears.
Signal (subrequest)
A signal (subrequest) is a communication with the server under the level of normal view. Signals are actions without a change
of view. Only a presenter can change the view therefore components work only with signals. $component->link()
links to a signal, $presenter->link()
usually links to a view (unless an exclamation mark is used –
$presenter->link('signal!')
). But a component can call $component->presenter->link('view')
.
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 PresenterComponent
tries to call a method
composed of the words handle{Signal}
. Further processing relies on the given object. Objects which are descendants of
PresenterComponent
(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.
An example signal handler:
public function handleClick($x, $y)
{
if (!$this->isClickable($x, $y)) {
throw new Nette\Application\UI\BadSignalException('Action not allowed.');
}
// ... processing of signal ...
}
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
(these are
automatically invalidated which is important for AJAX). 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.
Signal is always called on current presenter and view therefore it is impossible to point it elsewhere.
The URL for the signal is created using the method PresenterComponent::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.
Subrequest vs. request
The difference between a signal and a request:
- a subrequest keeps all components
- a request keeps only persistent components
Invalidation and snippets
During the processing of a signal changes can occur which require re-rendering of a component. These methods takes care of this: invalidateControl(), validateControl() and isControlInvalid(). They are basic elements of using AJAX with Nette.
Nette also offers even higher granularity of validity, than only at the component level, with use of snippets.
Snippets can be invalidated one by one (any component can have as many snippets as needed). If the whole component is invalidated than each of its snippets is invalidated too. A component is considered invalid if any of its children (components) is invalid.
More information can be found on the page about AJAX.
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.
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\Object
|
+- Nette\ComponentModel\Component { IComponent }
|
+- Nette\ComponentModel\Container { IContainer }
|
+- Nette\Application\UI\PresenterComponent { 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')
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')
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\PresenterComponent
Class Nette\Application\UI\PresenterComponent 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.
Component tree
The same component can be rendered more than once on page if given different names (this can be done dynamically). Parent can
be presenter, component or other object implementing IContainer
.
Hierarchy can look like following:
Nette\Application\UI\Presenter { root of tree of components is always a presenter }
|
--Nette\Application\UI\Control { implements IContainer => can be parent }
|
--Nette\ComponentModel\Component
|
--Nette\ComponentModel\Component { does not implement IContainer => cannot be parent }
|
--Nette\Application\UI\Control
|
--Nette\ComponentModel\Component
Delayed composition
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.
$control = new NewsControl;
// ...
$parent->addComponent($control, 'shortNews');
Monitoring changes
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');
// ...
}
// ...
}
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';
}
}
Monitoring and lookup of components or paths using lookup
is very precisely optimized for maximal
performance.
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') as $control) {
if (!$control->getRules()->validate()) {
$valid = FALSE;
break;
}
}