Custom DGS Fields
This page tells you how to add your own field to a Data Grid Store (DGS). You do not change the framework, the theme, or the system pages.
There are four methods. Use the first method that is sufficient.
| Method | Use it when | Code that you write |
|---|---|---|
| Cell view handler | Only the value in the list must look different. | One plugin method and one template. |
| Handler field | One store needs a special input, and the plugin has the logic. | Plugin methods and one template. |
| Edit view event | You must replace the input of a field at run time. | One listener. |
| Field class | Many stores or many projects use the field. | One class and its templates. |
How the DGS finds the class of a field
Each field of a schema has a type. The DGS makes an object of a field class from the type.
- If
typeis the name of a class that implementsIStoreField, the DGS uses this class. - If not, the DGS makes the class name: first letter as a capital +
Field. ThustextgivesTextField, andforeignKeygivesForeignKeyField.
There is no list of registered types. A class that the autoloader can load is sufficient.
<!-- A built-in type -->
<field type="text" name="title" caption="Title" />
<!-- A field class of a plugin -->
<field type="plugin\Orders\Field\ColorField" name="color" caption="Color" />
In a class schema, give the class name:
$fields->add('color')->type(ColorField::class)->caption(__('Color'));
See Class schema.
How a field renders
A field class extends AbstractField, and AbstractField extends Template. Thus each field is a template object with its own template directory.
| Where | Method of the field | Template |
|---|---|---|
Form (insert, edit, info) |
getEditInput($value, $row). It fires the edit view event and then calls fetchEditInput($value, $row). |
edit.phtml |
| List cell | getDisplayValue($value, $row) |
cell_value.phtml |
List cell with the attribute trim |
getDisplayValue($value, $row) |
cell_value_trim.phtml |
| Filter | fetchFilter() |
filters/<filter type>.phtml |
The method getTemplatePath() returns the template directory of the field. The built-in fields use the directory fields/ of the Theme package:
| Field class | Template directory |
|---|---|
AbstractField (the base) |
<ENGINE_TEMPLATE_PATH>fields/ |
SelectField |
<ENGINE_TEMPLATE_PATH>fields/select/ |
ForeignKeyField |
<ENGINE_TEMPLATE_PATH>fields/foreign_key/ |
Important facts:
- A field looks in one directory only. It does not use the lookup of the plugin or of the DGS view.
- Thus a file in the theme or in the plugin does not replace a field template.
- To use different templates, make a field class and override
getTemplatePath()(method 4).
In a field template, $this is the field object.
| In the template | Content |
|---|---|
$this->getName() |
The field name. |
$this->getCaption() |
The caption. |
$this->get('<attribute>') |
The value of a schema attribute of the field. |
$this->getElementAttributes() |
The HTML attributes from the schema (required, readonly, maxlength, and others) as text. |
$this->getCssClassName() |
The CSS classes of the input. |
$this->getStore() |
The store. |
$value |
In edit.phtml: the value, with HTML characters as entities. |
$currentValue |
In cell_value.phtml: the formatted value. |
$rowValues |
In cell_value.phtml: all values of the row. |
Method 1: Cell view handler
The attribute cellViewHandler gives the list cell to a plugin method. See Cell View Handler.
Method 2: Handler field
The type handler gives the input, the list cell, and the value to plugin methods. See Handler Field.
Method 3: Edit view event
The event StoreFieldEvent::EVENT_ON_GET_EDIT_VIEW lets a listener replace the input of one field.
use core\store\event\StoreFieldEvent;
$store = $this->createStoreInstance('orders');
$fields = &$store->getModel()->getFields();
$fields['color']->addEventListener(
StoreFieldEvent::EVENT_ON_GET_EDIT_VIEW,
function (StoreFieldEvent &$event) {
$content = $this->fetch('field_color.phtml', ['value' => $event->getValue()]);
$event->setContent($content);
}
);
$store->onRequest($response);
A field of each type fires this event, each time a form draws the field. A listener that gives HTML to setContent() replaces the input. A listener that sets no content can change the value, and the field then draws its own input. See StoreFieldEvent::EVENT_ON_GET_EDIT_VIEW for the methods of the event and for the value that a listener gets.
Caution: The public method
getEditInput()ofAbstractFieldfires the event. A field class that overridesgetEditInput()does not fire it. OverridefetchEditInput()in your field class.
Method 4: Field class
1. Make the class
Put the class in your plugin. Extend AbstractField, or extend a built-in field that is almost correct (for example TextField or SelectField).
plugins/
`-- Orders/
|-- Field/
| `-- ColorField.php
|-- static/
| `-- js/
| `-- color_field.js
`-- templates/
`-- fields/
`-- color/
|-- edit.phtml
`-- cell_value.phtml
<?php
namespace plugin\Orders\Field;
use AbstractField;
use Core;
use FieldModel;
class ColorField extends AbstractField
{
public function onInit(FieldModel $scheme): void
{
parent::onInit($scheme);
$plugin = Core::getInstance()->getPluginInstance('Orders');
$plugin->includeStatic('js/color_field.js');
}
protected function getTemplatePath(): string
{
return __DIR__.'/../templates/fields/color/';
}
public function isValidValue(mixed $value): bool
{
if ($value && !preg_match('/^#[0-9a-f]{6}$/i', (string) $value)) {
$this->setErrorMessage(__('Use the format #rrggbb.'));
return false;
}
return parent::isValidValue($value);
}
}
What each method does:
| Method | Use |
|---|---|
onInit() |
Runs when the DGS makes the field. Register the JS and CSS files of the field here. |
getTemplatePath() |
Returns the template directory of the field, with a slash at the end. |
isValidValue() |
Examines the value from the request. Return false and set a message to stop the save. |
2. Make the templates
templates/fields/color/edit.phtml:
<?php
/**
* @var \plugin\Orders\Field\ColorField $this
* @var string $value
*/
?>
<input type="color"
class="form-control e-field-color"
name="<?php echo $this->getName(); ?>"
data-palette="<?php echo $this->get('palette'); ?>"
value="<?php echo $value; ?>" <?php echo $this->getElementAttributes(); ?> />
templates/fields/color/cell_value.phtml:
<?php
/**
* @var \plugin\Orders\Field\ColorField $this
* @var string $currentValue
*/
?>
<span class="e-color-cell" style="background: <?php echo $currentValue; ?>"><?php echo $currentValue; ?></span>
- The
nameattribute of the input must be the field name. The DGS reads the value from the request with this name. - A field class that extends
AbstractFieldneedsedit.phtmlandcell_value.phtml. If the schema uses the attributetrim, it also needscell_value_trim.phtml. - A field class that extends a built-in field can return the directory of the parent for the templates that it does not change.
3. Use the field in a schema
<field type="plugin\Orders\Field\ColorField"
name="color"
caption="<?php echo __('Color'); ?>"
palette="basic"
isnull="true"
width="10%" />
- All general attributes of a field work:
caption,required,width,filter,hide, and the others. See Fields. - You can add your own attributes (
palettein the example). Read them with$this->get('palette').
4. Validation
When isValidValue() returns false, the DGS does not save the row. The browser shows the message of setErrorMessage():
{"messages": ["Use the format #rrggbb."], "storeAction": {}, "type": "alert"}
Always call parent::isValidValue(). The parent examines the attributes required, regexp, and valueFilter.
5. JavaScript and CSS of the field
Register the files in onInit(). See Static Files.
A DGS form opens in a dialog. The browser loads the form with a second request, and that response has no <head>. Thus:
- The page gets the files when it renders the list. The schema of the list contains the field, so
onInit()runs. - When the form shows, the inputs are new elements. Start the JavaScript of the field with the event
Jimbo.EVENT_ON_DISPLAY_STORE_FORM:
Jimbo.addListener(Jimbo.EVENT_ON_DISPLAY_STORE_FORM, function (event, params) {
jQuery('.e-field-color').each(function () {
// Start the component of the field here.
});
});
If a page shows only the form (not the list), register the files in the plugin method of that page.
6. Other methods that you can override
| Method | Override it to |
|---|---|
fetchEditInput($value, $row) |
Give more variables to edit.phtml. Call $this->assign() and then $this->fetch('edit.phtml'). The method is protected; getEditInput() calls it when no listener of the edit view event set the content. |
getEditInputValue($value) |
Change the value that the listeners of the edit view event and fetchEditInput() get. |
getDisplayValue($value, $row) |
Change the list cell. |
getPreviewValue($value, $row) |
Change the formatted value that shows outside of an input. |
getValueFromRequest($request) |
Change how the field reads its value from the request (for example, join two inputs). |
getFormattedValue($value) |
Change the format of the value for display. |
getQueryWhere() |
Change the SQL condition of the filter. |
A field as a Composer package
Put the field class and its templates into a package when more than one project uses the field. The package MarkdownField is an example:
- The class extends
TextareaField. getBaseTemplatePath()returns thetemplatesdirectory of the package.onInit()registers the files of the editor withCore::includeJs($url, false)andCore::includeCss($url, false).- The schema uses the full class name as the type.
Rules
- Do not edit the field templates in
vendor/. - Do not add a field class to the Core. Put it in a plugin or in a package.
- Use the full class name as the type. A short type name works only for a class without a namespace.
- Keep the logic in the field class and the HTML in the templates.
- Do not write
<script>blocks with logic in a field template. Put the logic in a static file. - Use
__()for each text that the user sees.