Creating a custom form field type
Besides prebuilt form field types, you can naturally create your own field – a custom type, or a better implementation for an existing type. The only condition is using inheritance. Every form field (or rather its class) must inherit from the Jet\Form_Field class. Thus, your classes must inherit from this class, but they can naturally inherit from any existing class. Just standard object-oriented programming ❤️
Pay attention to one important detail. If you use automatic generation of forms mapped to classes and want to use your new class (or an entire new type) for these forms as well, you must notify the relevant factory that a new type exists, or that a given 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 the existing class representing the given form field type:
namespace JetApplication;
ATTENTION! It is necessary to pass this information to the factory. Factory calls must be placed in application initialization, specifically into the script ~/application/Init/Factory.php:
use Jet\Form;
use Jet\Form_Field_Tel;
class MyForm_Field_Tel extends Form_Field_Tel {
//.. ... ..
}
use Jet\Factory_Form;
Factory_Form::setFieldClassName( Form::TYPE_TEL, MyForm_Field_Tel::class );
A Brand New Field Type
Now let's create a new class and, most importantly, a completely new field type. The class can implement custom methods for input capturing and validation (and optionally any other methods). Crucially, its $_type property must specify what form field type it represents, as this information will be used further in the system. It is good practice to create a new constant for the type. The imaginary constant MyForm::MY_NEW_FIELD_TYPE is used here as an example.
Furthermore, it is necessary to connect the field to an InputCatcher and a Validator. Of course, you can either select one of the prebuilt input catchers and validators, or create custom ones as well.
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;
protected string $_validator_type = Validator::TYPE_INT;
protected string $_input_catcher_type = InputCatcher::TYPE_INT;
//!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
}
So we have the class ready. However, several additional steps are required:
- Register the new type / new class into the form factory (otherwise the type could not be used, for example, for automatically generated forms).
- Register which renderers will be used for the new field type.
- Register which view scripts will be used for the given type.
Registering the Type into the Factory
Registering the type into the factory is performed as follows:
use Jet\Factory_Form;
Factory_Form::setFieldClassName( MyForm::MY_NEW_FIELD_TYPE, MyForm_Field_NewType::class );
ATTENTION! Factory calls must be placed in application initialization, specifically in the script ~/application/Init/Factory.php.
Registering Renderers
Now it is necessary to inform the factory which renderers will be used for the new field type.
Again, we will work with the factory in the script ~/application/Init/Factory.php
The full series of calls can look like this:
Factory_Form::setRendererFieldClassName( MyForm::MY_NEW_FIELD_TYPE, 'field', Form_Renderer_Field::class );
Phew ... That is a rather lengthy set of calls. However, it is useful to demonstrate it for illustration and completeness.
Factory_Form::setRendererFieldClassName( MyForm::MY_NEW_FIELD_TYPE, 'container', Form_Renderer_Field_Container::class );
Factory_Form::setRendererFieldClassName( MyForm::MY_NEW_FIELD_TYPE, 'error', Form_Renderer_Field_Error::class );
Factory_Form::setRendererFieldClassName( MyForm::MY_NEW_FIELD_TYPE, 'help', Form_Renderer_Field_Help::class );
Factory_Form::setRendererFieldClassName( MyForm::MY_NEW_FIELD_TYPE, 'input', Form_Renderer_Field_Input_Common::class );
Factory_Form::setRendererFieldClassName( MyForm::MY_NEW_FIELD_TYPE, 'label', Form_Renderer_Field_Label::class );
Factory_Form::setRendererFieldClassName( MyForm::MY_NEW_FIELD_TYPE, 'row', Form_Renderer_Field_Row::class );
Let's look at a more practical method that replaces everything shown so far and performs all necessary actions.
Factory_Form::registerNewFieldType(
This snippet performs everything required – this is how a new type is registered simply and realistically. No other calls are needed! This method registers both the class and the renderers for the new type. Please note that only the input element is specified in the list of elements and renderers. If you want to use default renderers for the remaining elements, you do not need to list them and default values will be supplied.
field_type: MyForm::MY_NEW_FIELD_TYPE,
field_class_name: MyForm_Field_NewType::class,
renderers: [
'input' => MyForm_Renderer_Field_Input_Special::class
]
);
We intentionally showed both approaches, though the second is certainly more practical.
Registering View Scripts
The last thing remaining is to tell the system which default view scripts the new field type will have. We need to inform system configuration about this.
For consistency, it is best to place this in the script ~/application/Init/Factory.php as well.
The principle is identical to registering renderers and it is also possible to call individual methods for each view separately, but I won't burden you with that here and will jump straight to the optimal approach:
SysConf_Jet_Form_DefaultViews::registerNewFieldType(
As you can see, here too it is unnecessary to enumerate all views; you only specify those you want configured differently from the system defaults.
field_type: MyForm::MY_NEW_FIELD_TYPE,
views: [
'input' => 'field/input/my-special',
'label' => 'field/label-my-special'
]
);
Thus, the entire registration of a new type ultimately looks like this:
namespace JetApplication;
use Jet\Factory_Form;
use Jet\SysConf_Jet_Form_DefaultViews;
Factory_Form::registerNewFieldType(
field_type: MyForm::MY_NEW_FIELD_TYPE,
field_class_name: MyForm_Field_NewType::class,
renderers: [
'input' => MyForm_Renderer_Field_Input_Special::class
]
);
SysConf_Jet_Form_DefaultViews::registerNewFieldType(
field_type: MyForm::MY_NEW_FIELD_TYPE,
views: [
'input' => 'field/input/my-special'
]
);
A New Parameter for Your Form Field
You now know the general principle for creating a new field type, but let's return to the beginning for a moment. The example above assumed you would only have custom data capture and validation implementations.
However, for more complex field types, that may be far from sufficient. In the real world, you will need the new field type to have its own parameters as well. Similar to how numeric types can specify a min-max range, or file fields specify allowed upload types, and so on. See form field types.
In essence, it is simple. You just add the required property, getter, and setter to the class representing the form field type, and naturally implement the logic for using that parameter – especially during validation. Yes, that is enough and it will work.
However, if you have tried Jet Studio, you found the tool for mapping forms to classes. If you want this tool to recognize your new form field type as well, you must register it properly. Not only that, you must also define the parameters of your new field so that Jet Studio recognizes them as form parameters and can work with them (i.e., so your new input element can be configured visually).
How to do this? Attach the appropriate attributes to the property representing the new parameter. Let's look at a real example directly from Jet. This trait is used for numeric form fields where a range of valid numeric values can apply:
namespace Jet;
trait Form_Field_Part_NumberRangeInt_Trait
{
#[Form_Definition_FieldOption(
type: Form_Definition_FieldOption::TYPE_INT,
label: 'Minimal value',
getter: 'getMinValue',
setter: 'setMinValue',
)]
protected ?int $min_value = null;
#[Form_Definition_FieldOption(
type: Form_Definition_FieldOption::TYPE_INT,
label: 'Maximal value',
getter: 'getMaxValue',
setter: 'setMaxValue',
)]
protected ?int $max_value = null;
#[Form_Definition_FieldOption(
type: Form_Definition_FieldOption::TYPE_INT,
label: 'Step',
getter: 'getStep',
setter: 'setStep',
)]
protected ?int $step = null;
// ... ... ...
}
As you can see, attributes are used for the definition, making it quite a straightforward process.
However, based on these parameters, it is necessary to configure elements such as the validator. This is done by overriding the getValidator method as follows:
public function getValidator() : Validator
{
if(!$this->validator) {
$this->validator = $this->validatorFactory();
}
/**
* @var Validator_Int $validator
*/
$validator = $this->validator;
$validator->setMinValue( $this->getMinValue() );
$validator->setMaxValue( $this->getMaxValue() );
return $validator;
}
Definition Parameters
| Parameter | Meaning |
|---|---|
| type | What type it is. See below for the list of types. |
| label | Parameter description. The description is intended for tools like Jet Studio. |
| setter | Name of the setter method in the class representing the form field, used to set the parameter value. |
| getter | Name of the getter method in the class representing the form field, used to retrieve the parameter value. |
Parameter Types
| Type | Meaning |
|---|---|
| Form_Definition_FieldOption::TYPE_STRING | String type value |
| Form_Definition_FieldOption::TYPE_INT | Integer type value |
| Form_Definition_FieldOption::TYPE_FLOAT | Float type value |
| Form_Definition_FieldOption::TYPE_BOOL | Bool type value |
| Form_Definition_FieldOption::TYPE_CALLABLE | Callable – effectively a two-element array. The first position (index 0) can be:
|
| Form_Definition_FieldOption::TYPE_ARRAY | Indexed array. |
| Form_Definition_FieldOption::TYPE_ASSOC_ARRAY | Associative array. |