Creating a Custom Field Type
Tutorial: Creating a Custom Field Type
Example: A "Star Rating" Field
Introduction: Understanding the Architecture
Before diving in, let's quickly recap the relevant parts of your application's architecture for custom fields:
custom_field_types(Database Table): Defines which field types are available in the system.custom_fields(Database Table): Stores the definition of each field, including itsfield_typeand a JSONoptionscolumn.admin_customfield_options.php: The page that renders the specific options for a field type.field_types/(Directory): Contains individual PHP files, each responsible for rendering a specific field type's HTML and its options.field_renderer.php: A dispatcher that includes the appropriate rendering logic from thefield_types/directory for the record form.
Step 1: Define the Field Type
First, you need to make your new field type known to the system. This involves several small changes.
1.1 Database Entry
Add a new entry for RATING in your custom_field_types table with the following properties:
type_name:RATINGdisplay_name_key:field_label_RATINGdescription_key:field_type_RATING_descfield_group:special
1.2 Language Files
Add the display name and description for your new field type.
File: c:\Users\marti\Documents\xampp\htdocs\bt5test\languages\de.php
Add the following lines inside the `return [...]` array:
'field_label_RATING' => 'Bewertung (Sterne)',
'field_type_RATING_desc' => 'Ein interaktives Sternen-Bewertungsfeld.',
File: c:\Users\marti\Documents\xampp\htdocs\bt5test\languages\en.php
Create or open the file and add the following content:
<?php
// English Language File
return [
// Field Types
'field_label_RATING' => 'Rating (Stars)',
'field_type_RATING_desc' => 'An interactive star rating field.',
// Add other English translations as needed
];
?>
1.3 Documentation
Update your fieldtypes_help.md to describe the new field type.
File: c:\Users\marti\Documents\xampp\htdocs\bt5test\help\de\fieldtypes_help.md
Add the following section, for example before "Layout-Felder":
#### Bewertung (Rating)
Ein interaktives Feld zur Auswahl einer Sternen-Bewertung.
* **Spezifische Optionen:** Maximale Anzahl Sterne, halbe Sterne erlauben.
* **Allgemeine Optionen:** Feldbreite, Pflichtfeld, Sichtbar in Liste, Filterbar, Bedingte Sichtbarkeit.
* **Praxisbeispiel:** Produktbewertung, Zufriedenheit mit einem Service, Schwierigkeitsgrad einer Aufgabe.
Step 2: Create the Field Type Class
To keep the code organized, we create a dedicated PHP class for our new field type. This class will handle rendering the specific options and processing the form submission for those options.
Create New File: c:\Users\marti\Documents\xampp\htdocs\bt5test\field_types\FieldTypeRating.php
<?php
class FieldTypeRating {
private $field;
private $errors;
public function __construct(array $field, array &$errors) {
$this->field = $field;
$this->errors = &$errors;
}
public function renderInnerOptionsHtml() {
$options = json_decode($this->field['options'] ?? '{}', true);
$max_stars = $options['max_stars'] ?? 5;
$allow_half_stars = !empty($options['allow_half_stars']);
?>
<h4 class="h6 text-muted mb-3">Rating-Optionen</h4>
<div class="row">
<div class="col-md-6">
<?php render_form_group([
'type' => 'number',
'name' => 'options[max_stars]',
'label_key' => 'Maximale Sterne',
'value' => $max_stars,
'min' => 1,
'max' => 20,
'required' => true
]); ?>
</div>
<div class="col-md-6 d-flex align-items-center pt-3">
<?php render_form_switch([
'name' => 'options[allow_half_stars]',
'label_key' => 'Halbe Sterne erlauben',
'checked' => $allow_half_stars
]); ?>
</div>
</div>
<?php
}
public function processPost(array &$field, array &$errors) {
$options = json_decode($field['options'] ?? '{}', true);
$options['max_stars'] = (int)($_POST['options']['max_stars'] ?? 5);
$options['allow_half_stars'] = !empty($_POST['options']['allow_half_stars']);
if ($options['max_stars'] < 1 || $options['max_stars'] > 20) {
$errors['options[max_stars]'] = 'Die Anzahl der Sterne muss zwischen 1 und 20 liegen.';
}
$field['options'] = json_encode($options);
}
public function getFormattingOptions(): array {
return []; // No special formatting options for this field type
}
public function getVisibilityOptions(): array {
return ['is_required', 'is_visible', 'is_searchable', 'is_filterable'];
}
}
Step 3: Create the Field Rendering File
This file contains the HTML, CSS, and JavaScript to render the star rating input in the actual record form.
File: c:\Users\marti\Documents\xampp\htdocs\bt5test\field_types\RATING.php
<?php
// This file is included by field_renderer.php and receives the following variables:
// $field: array - The field definition from custom_fields table.
// $record_data: array - The current record's data.
// $errors: array - Validation errors for the field.
// $is_readonly: bool - True if the form is in read-only mode.
$field_name = htmlspecialchars($field['field_name']);
$display_name = htmlspecialchars($field['display_name']);
$field_value = htmlspecialchars($record_data[$field_name] ?? '');
$error_message = $errors[$field_name] ?? '';
$is_required = !empty($field['is_required']);
// Decode options specific to the RATING field type
$options = json_decode($field['options'] ?? '{}', true);
$max_stars = (int)($options['max_stars'] ?? 5);
$allow_half_stars = !empty($options['allow_half_stars']);
$rating_id = 'rating-' . $field['id'];
?>
<div class="mb-3" data-field-type="RATING">
<label for="<?= $field_name ?>" class="form-label">
<?= $display_name ?>
<?php if ($is_required): ?>
<span class="text-danger">*</span>
<?php endif; ?>
</label>
<div id="<?= $rating_id ?>" class="rating-input d-flex align-items-center" data-max-stars="<?= $max_stars ?>" data-allow-half-stars="<?= $allow_half_stars ? '1' : '0' ?>">
<?php for ($i = 1; $i <= $max_stars; $i++): ?>
<i class="fa-regular fa-star rating-star" data-value="<?= $i ?>"></i>
<?php endfor; ?>
<span class="rating-value-display ms-2 text-muted"></span>
</div>
<input type="hidden" name="<?= $field_name ?>" id="<?= $field_name ?>" value="<?= $field_value ?>" <?= $is_readonly ? 'disabled' : '' ?>>
<?php if ($error_message): ?>
<div class="invalid-feedback d-block"><?= $error_message ?></div>
<?php endif; ?>
<?php if (!empty($field['help_text'])): ?>
<div class="form-text"><?= htmlspecialchars($field['help_text']) ?></div>
<?php endif; ?>
</div>
<?php if (!$is_readonly): ?>
<script>
document.addEventListener('DOMContentLoaded', function() {
const ratingContainer = document.getElementById('<?= $rating_id ?>');
if (!ratingContainer) return;
const hiddenInput = document.getElementById('<?= $field_name ?>');
const stars = ratingContainer.querySelectorAll('.rating-star');
const valueDisplay = ratingContainer.querySelector('.rating-value-display');
const maxStars = parseInt(ratingContainer.dataset.maxStars);
const allowHalfStars = ratingContainer.dataset.allowHalfStars === '1';
let currentValue = parseFloat(hiddenInput.value || 0);
function updateStars(value) {
stars.forEach(star => {
const starValue = parseInt(star.dataset.value);
star.classList.remove('fa-solid', 'fa-regular', 'fa-star', 'fa-star-half-stroke');
if (value >= starValue) {
star.classList.add('fa-solid', 'fa-star');
} else if (allowHalfStars && value > starValue - 1 && value < starValue) {
star.classList.add('fa-solid', 'fa-star-half-stroke');
} else {
star.classList.add('fa-regular', 'fa-star');
}
});
valueDisplay.textContent = value > 0 ? `(${value.toFixed(allowHalfStars ? 1 : 0)} / ${maxStars})` : '';
}
ratingContainer.addEventListener('mousemove', function(e) {
// ... (JavaScript logic remains the same)
});
// ... (rest of the JavaScript logic)
});
</script>
<style>
.rating-input .rating-star {
font-size: 1.5rem;
color: var(--bs-warning);
cursor: pointer;
}
.rating-input .rating-star.fa-regular {
color: var(--bs-secondary-bg);
}
</style>
<?php endif; ?>
Step 4: Implement Options UI in Admin
Now we connect our new FieldTypeRating.php class to the options page so that the specific settings can be configured.
Modify File: c:\Users\marti\Documents\xampp\htdocs\bt5test\admin_customfield_options.php
Add the following line at the top with the other require_once statements:
require_once __DIR__ . '/field_types/FieldTypeRating.php';
Then, find the switch statement and add the following case:
...
case 'READ_ONLY':
$fieldTypeHandler = new FieldTypeReadOnly($field, $errors, $all_table_fields);
break;
case 'RATING':
$fieldTypeHandler = new FieldTypeRating($field, $errors);
break;
// Weitere Feldtypen-Handler werden hier später hinzugefügt
}
...
Step 5: Implement Rendering in Record Form
Now, you need to tell field_renderer.php to include your new RATING.php file when it encounters a field of type RATING.
Modify File: c:\Users\marti\Documents\xampp\htdocs\bt5test\field_renderer.php
...
case 'CUSTOM_BUTTON':
include 'field_types/CUSTOM_BUTTON.php';
break;
case 'RATING':
include 'field_types/RATING.php';
break;
default:
// Fallback for unknown types or simple text fields
include 'field_types/TEXT.php';
...
Step 6: Handle Data Storage and Validation
For a simple numeric rating, the existing logic in custom_table_record_edit.php should handle it correctly, as the value is submitted via a hidden input field. No changes are needed for basic storage.
Ensure the database column for this field is of a suitable numeric type (e.g., DECIMAL(3,1) for up to 99 stars with half-steps, or TINYINT for whole numbers up to 255).
Step 7: Final Steps
- Clear Cache: If your application uses any caching for field definitions, clear it.
- Database Column: Ensure that the physical database table (
data_team_{team_id}_{table_id}) has a column for your newRATINGfield. The system should automatically create this column when you add the field in the admin UI. - Test:
- Go to your table definition in the admin area.
- Add a new field and select "Rating (Stars)" as the type.
- Click the "Edit" icon for the new field to open the options page. You should now see the "Maximale Sterne" and "Halbe Sterne erlauben" settings.
- Configure and save the options.
- Test the new rating field in the record edit form.