Displaying forms

As we have already shown in the first chapter, the defined form is displayed in view as follows:

use Jet\MVC_View;
use 
Jet\Form;
use 
Jet\UI;

/**
 * @var MVC_View $this
 * @var Form $form
 */

$form $this->getRaw('registration_form');
?>

<?=$form->start()?>
    <?=$form->message()?>

    <?=$form->field('name')?>
    <?=$form->field('birthdate')?>

    <?=UI::button_save()?>

<?=$form->end()?>

As can be seen for the form element display (form start, form message, individual fields and form end) they are displayed simply by converting them to a string. So ultimately it's actually quite simple, but of course this sample usage is just the very basics. Above all, it's important to show how the whole form display system works and ultimately what it all allows.

Before we get into that, let's get a few important things straight:

  • Jet (meaning the Jet library) doesn't actually display anything itself.
    It just provides an interface (in terms of the MVC paradigm, it's actually a Model) to display everything in view scripts.
  • All view scripts are part of the application space.
    This means that the view is completely under your control. Of course, the sample application includes ready-made view scripts that you may or may not use. Thus, even the fact that the sample application uses the Bootstrap framework is nothing definite and fixed.
  • The form view system can do a lot more things than what is shown in the demo.
    This means that you can fully control everything, change everything, pass HTML tags with attributes, have completely unique view scripts for certain forms, and so on. We'll show you everything.
  • Despite the unlimited flexibility of the view, it remains a unified interface.
    Simply put, this means that you can completely "rebuild" the view, but the forms are still defined the same way. Thus, you have separated logic from views (even the forms system is simply designed according to the MVC paradigm).
  • The purpose of this system is to unify the forms in the application and at the same time to make your work easier and more uniform.

Renderer - what is it?

Pojem renderer se bude v táto kapitolo vyskytovat často. Aby ne, je to vlastně podstata celého systému zobrazení formulářů. Ale abychom si ujasnili co to je, tak se ještě jednou vraťme k příkladu zobrazení formulář.

Zobrazení formulářového pole je provedeno takto:

<?=$form->field('name')?>

Of course, this is really just an abbreviated notation. The real call (and in practice the unused full notation) has this form:

<?=$form->field('name')->renderer()->render()?>

A renderer is an instance of one of the children of the Jet\Form_Renderer class, which is held by each element defining the form - whether it is the form itself (to display the start and end of the form), or individual input fields.

Each form element has its own renderer of a specific concrete class. That is, not directly of the Jet\Form_Renderer class, but of its descendants directly intended for a specific purpose. This means that, for example, a generic Input input field has an instance of the Jet\Form_Renderer_Field_Input_Common class as the renderer of the input field itself, a Select field uses a Jet\Form_Renderer_Field_Input_Select class as the renderer, and so on.

As we will show in more detail later, for example, the form field renderer is actually composed of other sub-renders (there is a separate renderer for the field row, a separate renderer for the field description, a separate renderer for the input element, and so on).

For now it is important that the render is the model according to which the view will display the element. Thus, with the renderer we not only display the element, but we can further configure it.

Let's get practical. Again, let's go back to our example. Suppose we want the input for entering a name to be cleared after the user "clicks" into it (or accesses it using the keyboard), even if there was a value in it. It is done like this:

<?php 
$form
->field('name')
    ->
input()
        ->
addJsAction('onfocus'"this.value='';")
?>

<?=$form->field('name')?>

Warning! Again, this is a shortened notation that is used in practice. But for the sake of completeness and better understanding, let's show the full notation.

<?php 
$form
->field('name')
    ->
renderer()
        ->
input()
            ->
addJsAction('onfocus'"this.value='';")
?>

<?=$form->field('name')?>

What code does:

  • From the definition of the form element we took an instance of the renderer.
  • From the renderer we took a sub-renderer that takes care of the input element itself (HTML tag input).
  • We passed the onfocus event handler to Render.
  • In the final, they displayed everything. The display is done by the renderer passing all the information to the view scripts, so the model becomes concrete HTML.

We'll show you another practical application right away. Not only do we need the onfocus event handler. For some reason, we also need to pass a custom CSS class to the input tag, and to make matters worse, a data-* attribute (specifically data-my-attribute="Value"):

<?php 
$form
->field('name')
    ->
input()
        ->
addJsAction('onfocus'"this.value='';")
        ->
addCustomCssClass('my-css-class')
        ->
setDataAttribute('my-attribute''Value')
?>

<?=$form->field('name')?>

You can find out what the renderer can do in the references to the Jet\Form_Renderer class, and last but not least in all other Jet\Form_Renderer_* classes with a specific purpose (special renderers like the one for WYSIWYG editor have other specific methods).

In the context of renderers, it's also important to note that while they inherit from a common base class Jet\Form_Renderer, there are two basic kinds:

  • Unpaired Jet\Form_Renderer_Single
    Designed for unpaired elements such as field description, error message, input element and so on (see below).
  • Pair Jet\Form_Renderer_Pair
    Designed, for example, for the form tag itself (the form has a start and end), the form field row, and the input element container (see below).

Renderer and view

We said that the renderer is the model by which view displays the element. Now let's show exactly how to do that.

I assume you already know how view works in Jet. So I'll leave out the full details here.

As you know, for view to work, it needs these basic things:

  • The root directory where to look for view scripts.
  • The name of the view script to be displayed.
  • And, of course, some data to show what the view should show.
Now let's describe it in more detail.

Root directory of view scripts

You will have noticed that no view directory has been set up anywhere in the examples so far. By default, the forms system assumes that this directory is set by system configuration, specifically, for example, as follows:

//... ... ...
public static function initMVC_Router $router ): void
{
    
//... ... ...
    
SysConf_Jet_Form::setDefaultViewsDir$router->getBase()->getViewsPath() . 'form/' );
    
//... ... ...
}
//... ... ...

I deliberately used a piece of the MVC base initializer as an example, which is the appropriate place to do this setup.

But of course it is not always necessary to use the whole MVC system and the root directory can be set up simply and easily as follows:

SysConf_Jet_Form::setDefaultViewsDir$some_dir.'/views/form/' );

The important thing is that if you don't want to set the root path of the view extra for each form, you must do this before using the forms.

View directories in the sample application

You have probably already noticed that there are four directories with view scripts in the sample application:

  • ~/_installer/views/form/
    Installer forms
  • ~/_tools/studio/application/views/form/
    Jet Studio forms
  • ~/application/bases/admin/views/form/
    Administration forms
  • ~/application/bases/web/views/form
    Sample website forms

You may be thinking, "Why is that? One common directory would be enough, wouldn't it?" And you're right. It was. Of course, for good reason, it's like this. The first reason is to demonstrate the technical possibilities. The second and more important reason is the so-called good habit. Each of those parts of the demo application is actually unique and distinct. And it's very likely that you'll need to make the forms display completely differently for the web, perhaps purely using your CSS and so on. But at the same time, you'll want to leave the administration as it is, and I'm guessing you won't want to break Jet Studio ;-). That's why the directories are straight separated and it's possible to customize the view according to your needs with everything else remaining intact. If the sample application had only one directory, it would be unintuitive and not actually reflect the real world - you would have to separate the view like that anyway.

Extra directory view for a specific form or element

And what if you need a custom set of view scripts for a single very specific form? Of course it is possible. And not just for the whole form, but for any element. Let's show it practically.

If you want to set up an extra view directory for the whole form (that means all its elements), you simply set the view directory on the form renderer:

$form->renderer()->setViewDir$my_view_dir );

If you want an extra view directory for one particular entire form field (for all elements that make up the field - see below), then you do this:

$form->field('name')->renderer()->setViewDir$my_view_dir );

And if for some reason you want an extra view directory for the input field itself, that's no problem:

$form->field('name')->renderer()->input()->setViewDir$my_view_dir );

The same applies to the field label, help and so on:

$form->field('name')->renderer()->label()->setViewDir$my_view_dir );
$form->field('name')->renderer()->help()->setViewDir$my_view_dir );

Or, in short, like this:

$form->field('name')->label()->setViewDir$my_view_dir );
$form->field('name')->help()->setViewDir$my_view_dir );

View Script Name

The principle is actually almost the same as for directories. The only difference is that each element already has predefined view script names and unlike a directory, these do not need to be set.

However, also here it is true that the system configuration is used, specifically SysConf_Jet_Form_DefaultViews. Exactly how it is used and what the default view script names are is described for each renderer class. Importantly, you have the ability to change the defaults in exactly the same way as you specify the view directory.

And of course you have the possibility to change not only the default settings, but the names of view scripts of specific elements. Let's say that for a certain use you need a completely different WYSIWYG editor than the one that is integrated in the default view script. There is no problem to do it like this:

$form->field('article_text')->input()->setViewScript('field/input/wysiwyg-another');

You can do this with any element.

Instance of view

For the view to work, it needs an instance of Jet\MVC_View. This is created and held by each renderer and is accessible to you:

$form->field('name')->renderer()->getView()

And of course it's a common view like any other. So you have the option to pass additional information to the view when you need it. For example, you can do this:

$form->field('name')
    ->
renderer()
        ->
getView()
            ->
setVar('extra_help'Tr::_('Lorem ipsum dolor sit amet, consectetuer adipiscing elit.'));

Example view script

We have already shown how to set up the renderer - i.e. the model. Now let's show an example of a simple view script. Specifically, it is a form field for entering the date. (Caution! Only the input field itself, not the label, help and so on - see below), this is a view script field/input/date script:

<?php
use Jet\MVC_View;
use 
Jet\Form_Renderer_Field_Input;


/**
 * @var MVC_View $this
 * @var Form_Renderer_Field_Input $r
 */
$r $this->getRaw'renderer' );
$field $r->getField();

$r->setInputType('date');

if( !
$r->getBaseCssClasses() ) {
    
$r->setBaseCssClass'form-control' );
}

if( 
$field->getLastErrorCode() ) {
    
$r->addCustomCssClass'is-invalid' );
}

?>
<input <?=$r->renderTagAttributes()?>/>

Of course, there is no space to discuss every view here - so please explore the view scripts as you wish. However, this sample shows the function of view scripts and other purpose of the renderer. What the View script does is that it sets the renderer. That is, it sets the base CSS classes and any other attributes of the future HTML tag. And when everything is set, all that's left is to simply display the HTML, but again using a renderer that can generate the tag attributes based on the settings (and believe me, composing attributes "by hand" is a real pain). So the renderer is helpful until the last moment.

Of course, view can be implemented in a completely different way - purely according to your needs. Anyway, the renderer is always available in the view and it is up to you how you want to use it.

And since view is not only about input fields, but also about form field labels, let's see one more example and that is view field label - view script field/label:

<?php

use Jet\MVC_View;
use 
Jet\Form_Renderer_Field_Label;

/**
 * @var MVC_View $this
 * @var Form_Renderer_Field_Label $r
 */
$r $this->getRaw'renderer' );

if( !
$r->getBaseCssClasses() ) {
    
$r->setBaseCssClass'col-form-label' );
}

$r->setWidthCssClassesCreator(function( $size$width ) {
    return 
'col-' $size '-' $width;
});

$r->setCustomTagAttribute'for'$r->getField()->getId() );

?>
<label <?=$r->renderTagAttributes()?>><?php
    
    
if( $r->getField()->getIsRequired() ):
        
?><em class="form-required">*</em><?php
    
endif;
    
    echo 
$r->getField()->getLabel();
    
?></label>

Elements of which the form element consists

Before you dive into a thorough exploration of specific renderers, let's see one more thing I mention in the previous text. It has already been mentioned that a renderer has its sub-renderers that, for example, a form field consists of several elements. Let's illustrate this for the sake of clarity. A normal form element has this structure, or consists of the following parts:

Row - start
Container - start

Help
Error message
Container - end
Row - end

(Arrangement / placement of elements is only illustrative - in practice it may be quite different)

The main renderer of the form element has to prepare the renderers of the subparts and then assemble the whole element from them.

The main renderer is approached as follows:

$form->field('name')->renderer()

To get a better idea, let's show the view script of this main renderer. So view script field:

<?php

use Jet\MVC_View;
use 
Jet\Form_Renderer_Field;

/**
 * @var MVC_View $this
 * @var Form_Renderer_Field $r
 */
$r $this->getRaw'renderer' );

echo
    
$r->row()->start() .
        
$r->label() .
        
$r->container()->start() .
            
$r->input() .
            
$r->help() .
            
$r->error() .
        
$r->container()->end() .
    
$r->row()->end();

This shows how the main renderer composes the whole result from the subparts.

And now we just need to recap how to approach the renderers of the parts. This is what is important when you want to pass parameters to these subparts, or even display them separately (yes, this is also possible - for example, display only the input field without everything else):

  • Row
    Short form: $form->field('name')->row() Full form: $form->field('name')->renderer()->row()
  • Label
    Short form: $form->field('name')->label() Full form: $form->field('name')->renderer()->label()
  • Container
    Short from: $form->field('name')->container() Full form: $form->field('name')->renderer()->container()
  • Input
    Short from: $form->field('name')->input() Full form: $form->field('name')->renderer()->input()
  • Help
    Short from: $form->field('name')->help() Full form: $form->field('name')->renderer()->help()
  • Error
    Short from: $form->field('name')->error() Full form: $form->field('name')->renderer()->error()
Previous chapter
Capture, validation and transfer of data
Next chapter
Jet\Form_Renderer