Creating a custom form field type

Except for prepared form field types, you can of course create your own field - a custom type, or a better implementation for an existing type. The only requirement is that you use inheritance. Each form field (or its class) must inherit from the Jet\Form_Field class. So your classes must inherit from that class, but of course they can inherit from any existing class. Just standard object-oriented programming ❤️

Beware of one important thing. If you are using automatic generation of forms mapped to classes and you want to use your new class (or a whole new type) for these forms, you must let the appropriate factory know that there is a new type, or that an existing field type is represented by a new class.

New implementation of an existing form field type

As mentioned, it is sufficient to create a new class that can inherit from an existing class representing the form field type:

namespace JetApplication;

use 
Jet\Form;
use 
Jet\Form_Field_Tel;

class 
MyForm_Field_Tel extends Form_Field_Tel {
    
//.. ... ..    
}

WARNING! It is necessary to forward the information to the factory. The factory call is required to be placed in the application initialization. That is, in the script ~/application/Init/Factory.php

use Jet\Factory_Form;

Factory_Form::setFieldClassNameForm::TYPE_TELMyForm_Field_Tel::class );

A completely new type of field

Now let's create a new class and, most importantly, a brand new type of array. The class can have its own methods implemented for capture and validation (and possibly any other methods). Importantly, its property $_type must inform what type of form field it represents. This information will be used for further operations. It is a good idea to create a new constant for the type. Here, the imaginary constant MyForm::MY_NEW_FIELD_TYPE is used as an example.

namespace JetApplication;

use 
Jet\Form;
use 
Jet\Form_Field;
use 
Jet\Data_Array;

class 
MyForm_Field_NewType extends Form_Field
{
    
//!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
    
protected string $_type MyForm::MY_NEW_FIELD_TYPE;
    
//!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
    
    
public function catchInputData_Array $data ): void
    
{
        
//... ... ... 
    
}
    
    public function 
validate(): bool
    
{
        
//... ... ...
    
}
    
    public function 
getRequiredErrorCodes(): array
    {
        
//... ...
    
}
}

So we have a class. But there are still a few things to do:

  • Register a new type / class to factory forms (otherwise the type could not be used for auto-generated forms, for example).
  • Register what renderers will be used for the new field type.
  • Register what view scripts will be used for the type.

Type registration to the factory

To register the type to the factory, do the following:

use Jet\Factory_Form;

Factory_Form::setFieldClassNameMyForm::MY_NEW_FIELD_TYPEMyForm_Field_NewType::class );

WARNING! Factory calls must be placed in the application initialization. That is, in the script ~/application/Init/Factory.php.

Registration of renderers

Now you need to tell the factory what renderers will be used for the new array type.

Again we will work with the factory, i.e. in the script ~/application/Init/Factory.php

A complete call can take the following form:

Factory_Form::setRendererFieldClassNameMyForm::MY_NEW_FIELD_TYPE'field'Form_Renderer_Field::class );
Factory_Form::setRendererFieldClassNameMyForm::MY_NEW_FIELD_TYPE'container',Form_Renderer_Field_Container::class );
Factory_Form::setRendererFieldClassNameMyForm::MY_NEW_FIELD_TYPE'error'Form_Renderer_Field_Error::class );
Factory_Form::setRendererFieldClassNameMyForm::MY_NEW_FIELD_TYPE'help'Form_Renderer_Field_Help::class );
Factory_Form::setRendererFieldClassNameMyForm::MY_NEW_FIELD_TYPE'input'Form_Renderer_Field_Input_Common::class );
Factory_Form::setRendererFieldClassNameMyForm::MY_NEW_FIELD_TYPE'label'Form_Renderer_Field_Label::class );
Factory_Form::setRendererFieldClassNameMyForm::MY_NEW_FIELD_TYPE'row'Form_Renderer_Field_Row::class );

Ugh... That's a bit of a long call. But it's good to see it for the sake of illustration and completeness.

But let's show a more practical call, which replaces everything we've shown so far and does everything we need. Factory_Form::registerNewFieldType(
    
field_typeMyForm::MY_NEW_FIELD_TYPE,
    
field_class_nameMyForm_Field_NewType::class,
    
renderers: [
        
'input' => MyForm_Renderer_Field_Input_Special::class
    ] 
);
This piece of code does everything necessary - this is how to register a new type in a real and simple way. No other calls are necessary! This method registers the class and renderers for the new type. Please note that only the input element is listed in the element and renderer lists. If you want to use the default renderers for other elements, it is not necessary to list them and the default values will be added.

We've deliberately shown both ways, although the latter is definitely more practical.

Register view scripts

The last thing we need to do is to tell the system what default view scripts the new array type will have. We need to let the system configuration know.

For the sake of order it is good to do it also in the script ~/application/Init/Factory.php

The principle is the same as for the renderer registration and it is also possible to call several methods for each view separately, but I won't bother you with that here and let's show you the optimal way to do it:

SysConf_Jet_Form_DefaultViews::registerNewFieldType(
    
field_typeMyForm::MY_NEW_FIELD_TYPE,
    
views: [
        
'input' => 'field/input/my-special'
        'label' 
=> 'field/label-my-special'
    
]
);

As you can see, it is not necessary to list all the views here, but only the ones that you want to be set differently from the system defaults.

Thus, the whole registration of the new type takes the following form:

namespace JetApplication;

use 
Jet\Factory_Form;
use 
Jet\SysConf_Jet_Form_DefaultViews;

Factory_Form::registerNewFieldType(
    
field_typeMyForm::MY_NEW_FIELD_TYPE,
    
field_class_nameMyForm_Field_NewType::class,
    
renderers: [
        
'input' => MyForm_Renderer_Field_Input_Special::class
    ]
);

SysConf_Jet_Form_DefaultViews::registerNewFieldType(
    
field_typeMyForm::MY_NEW_FIELD_TYPE,
    
views: [
        
'input' => 'field/input/my-special'
    
]
);

New parameter for your form field

You already know the general principle of creating a new array type, but let's go back to the beginning. The example given so far only assumes that you have your own implementation of data capture and validation.

But that may not be nearly enough for more complex field types. In the real world, you'll need the new field type to have parameters. Similar to numeric types, you can specify a range from - to, for files the allowed upload types, and so on. See form field types.

Basically, it's simple. You just add the necessary properties, getter and setter, to the class representing the form field type and of course implement the logic of using the parameter - especially during validation. Yes, this is enough and it will work.

However, if you've tried Jet Studio, you've already found a tool for mapping forms to classes. And if you want this tool to know your new form field type, you'll need to register it properly. But not only that, you also need to define the parameters of your new field. So that, for example, Jet Studio can recognize that it is a form parameter and can work with it (i.e., so that it can "click" your new input element as well).

How to do it? Give the property that represents the new parameter the appropriate attributes. Let's show a real example straight from Jet. This trait is used for numeric form fields, where a range of valid numeric values may be present:

namespace Jet;

trait 
Form_Field_Part_NumberRangeInt_Trait
{
    
    #[
Form_Definition_FieldOption(
        
typeForm_Definition_FieldOption::TYPE_INT,
        
label'Minimal value',
        
getter'getMinValue',
        
setter'setMinValue',
    )]
    protected ?
int $min_value null;
    
    #[
Form_Definition_FieldOption(
        
typeForm_Definition_FieldOption::TYPE_INT,
        
label'Maximal value',
        
getter'getMaxValue',
        
setter'setMaxValue',
    )]
    protected ?
int $max_value null;
    
    #[
Form_Definition_FieldOption(
        
typeForm_Definition_FieldOption::TYPE_INT,
        
label'Step',
        
getter'getStep',
        
setter'setStep',
    )]
    protected ?
int $step null;

    
// ... ... ...
}

As you can see, the attributes are used to define it, and it's a fairly trivial definition.

Definition parameters

Parameter Meaning of
type What type is it.
See below for a list of types.
label Parameter description. The description is intended for a tool like Jet Studio.
setter Name of the method - setter within the class representing the form field that can be used to set the parameter.
getter The name of the method - getter within the class representing the form field that can be used to find the set value of the parameter.

Types of parameters

Type Meaning of
Form_Definition_FieldOption::TYPE_STRING Value of type string
Form_Definition_FieldOption::TYPE_INT Value of type int
Form_Definition_FieldOption::TYPE_FLOAT Value of float type
Form_Definition_FieldOption::TYPE_BOOL Value of type bool
Form_Definition_FieldOption::TYPE_CALLABLE Call - effectively a field of two positions. The first position (index 0) can be:
  • self::class
    it is then a static call of the self.
  • řetězec 'this'
    it is a call to itself via $this.
  • The specific name of the class (either as a string or as NameSpace\Class:class).
Form_Definition_FieldOption::TYPE_ARRAY A simple array.
Form_Definition_FieldOption::TYPE_ASSOC_ARRAY Associated array.
Previous chapter
Jet\Form_Definition_SubForms
Next chapter
Jet\Form_Definition_FieldOption