Adding Pre-loaded Select with Relationships
Revision: 2025/10/28
This guide shows you how to add a select field that loads all data immediately (no autocomplete fetch), perfect for small datasets like categories, departments, or roles.
- How to add a relationship field with pre-loaded options
- How to create a second model and link it to the first
- How to display related data in tables using dot notation
- How to create multiple select fields for many-to-many relationships
- Pre-loaded (this guide): Small datasets (up to ~100 items) - categories, departments, roles, statuses
- Autocomplete fetch: Large datasets (hundreds/thousands) - users, products, customers. See Autocomplete Search Guide
Example: Employees with Categories
We'll create a Categories model and link it to Employees. This pattern works for:
- Employees → Categories (departments, roles)
- Products → Categories
- Posts → Categories/Tags
- Tasks → Priorities/Statuses
Step 1: Create the Second Model (Categories)
First, create a simple model for the data you want to select from (e.g., categories).
File: CategoryModel.php
<?php
namespace Modules\Employees;
use App\Abstracts\AbstractModel;
class CategoryModel extends AbstractModel
{
protected function configure($rule): void
{
$rule->table('#__employee_categories')
->id()
->string('name', 100)->required()->label('Category Name')
->text('description')->nullable();
}
/**
* Get list of categories for select options
* Returns array: [id => name]
*/
public function getList()
{
$list = $this->query()->order('name')->getResults();
$array_return = [];
foreach ($list as $value) {
$array_return[$value->id] = $value->name;
}
return $array_return;
}
}
- The
getList()method is essential - it returns[id => name]for the select - Keep it in the same namespace as the main model (e.g.,
Modules\Employees) - Use
->order('name')to sort alphabetically
Step 2: Register the Model in the Module
Before creating the database table, you need to register the CategoryModel in your module's configuration.
File: EmployeesModule.php
<?php
namespace Modules\Employees;
use App\Abstracts\AbstractModule;
class EmployeesModule extends AbstractModule
{
protected function configure($rule): void
{
$rule->page('employees')
->title('Employees')
->menu('Employees', '', 'bi bi-people-fill', 20)
// ========================================
// ADD THIS LINE TO REGISTER SECONDARY MODELS
// ========================================
->addModels(['category' => CategoryModel::class])
->access('public')
->version(20251028);
}
}
->addModels() method tells the system to manage these secondary models during installation and updates.
Step 3: Add Relationship Field to Main Model
Now add the category_id field to your main model (e.g., EmployeesModel).
Example: EmployeesModel.php
protected function configure($rule): void
{
$rule->table('#__employees')
->id()
->string('name', 100)->required()
->string('surname', 100)->required()
// ========================================
// ADD THIS CATEGORY FIELD
// ========================================
->int('category_id')
->belongsTo('category', CategoryModel::class, 'id')
->formType('milkSelect')
->options((new CategoryModel())->getList())
->label('Category')
->nullable()
->timestamp('created_at')->hideFromEdit()->saveValue(time());
}
Understanding Each Method
| Method | Purpose |
|---|---|
->int('category_id') |
Creates an integer field in the database to store the category ID |
->belongsTo('category', CategoryModel::class, 'id') |
Defines the relationship: "this employee belongs to a category"
|
->formType('milkSelect') |
Uses MilkSelect component (dropdown with search) |
->options(...) |
Pre-loads all options at page load Difference from apiUrl: No AJAX fetch, all data loaded immediately |
->nullable() |
Makes the field optional (use ->required() if mandatory) |
->options(...)= Pre-loads all data (this guide)->apiUrl(...)= Fetches via AJAX while typing (autocomplete guide)
Step 4: Create/Update the Database Tables
Run the CLI command to create all tables and update the schema:
php milkadmin/cli.php employees:update
This command will:
- Create the
#__employee_categoriestable if it doesn't exist - Add the
category_idcolumn to the#__employeestable - Execute the
afterCreateTable()method to populate initial data (if defined)
afterCreateTable() method in your CategoryModel:
protected function afterCreateTable(): void
{
$sql = "INSERT INTO `" . $this->table . "` (`name`, `description`) VALUES
('Engineering', 'Engineering department'),
('Sales', 'Sales department'),
('Marketing', 'Marketing department');";
$this->db->query($sql);
}
Step 5: Display Category in Table
Update your controller's list method to show the category name using dot notation.
Example: EmployeesController.php - list() method
#[RequestAction('home')]
public function list()
{
// ... your existing code ...
$tableBuilder = TableBuilder::create($this->model, 'tblEmployees')
->column('name', 'Name', 'text')
->column('surname', 'Surname', 'text')
// ========================================
// ADD THIS LINE TO SHOW CATEGORY
// ========================================
->column('category.name', 'Category', 'text')
// ... rest of your table configuration ...
->render();
// ... rest of your code ...
}
category.name automatically loads the category relationship and displays the name instead of the ID.
Multiple Select - Many-to-Many Relationships
Now let's add a multiple select field where employees can be assigned to multiple categories (or doctors, skills, etc.).
Example: Appointments with Multiple Doctors
The Appointments module already has this implemented. Here's how it works:
// In AppointmentsModel.php
->array('doctor_ids')
->options((new DoctorsModel())->getList())
->label('Doctors')
->formType('milkSelect')
->formParams(['type' => 'multiple'])
Understanding Multiple Select
| Method | Purpose |
|---|---|
->array('doctor_ids') |
Creates a field that stores multiple values as array (JSON in database) |
->options(...) |
Pre-loads all available options |
->formType('milkSelect') |
Uses MilkSelect component |
->formParams(['type' => 'multiple']) |
This makes it multiple! Allows selecting multiple items |
- Multiple values are stored as JSON array in the database
- Example:
["1", "3", "5"]for IDs 1, 3, and 5 - The framework automatically handles JSON encoding/decoding
Complete Example: Employees with Multiple Skills
Let's add a multiple select for employee skills:
1. Create SkillModel.php
<?php
namespace Modules\Employees;
use App\Abstracts\AbstractModel;
class SkillModel extends AbstractModel
{
protected function configure($rule): void
{
$rule->table('#__employee_skills')
->id()
->string('name', 100)->required()->label('Skill Name');
}
public function getList()
{
$list = $this->query()->order('name')->getResults();
$array_return = [];
foreach ($list as $value) {
$array_return[$value->id] = $value->name;
}
return $array_return;
}
}
2. Update EmployeesModel.php
protected function configure($rule): void
{
$rule->table('#__employees')
->id()
->string('name', 100)->required()
->string('surname', 100)->required()
// Single select (one category)
->int('category_id')
->belongsTo('category', CategoryModel::class, 'id')
->formType('milkSelect')
->options((new CategoryModel())->getList())
->label('Category')
// ========================================
// MULTIPLE SELECT (many skills)
// ========================================
->array('skill_ids')
->options((new SkillModel())->getList())
->label('Skills')
->formType('milkSelect')
->formParams(['type' => 'multiple'])
->nullable()
->timestamp('created_at')->hideFromEdit()->saveValue(time());
}
3. Register SkillModel in EmployeesModule.php
protected function configure($rule): void
{
$rule->page('employees')
->title('Employees')
->menu('Employees', '', 'bi bi-people-fill', 20)
// ========================================
// REGISTER BOTH SECONDARY MODELS
// ========================================
->addModels([
'category' => CategoryModel::class,
'skill' => SkillModel::class
])
->access('public')
->version(20251028);
}
4. Create/Update Tables
php milkadmin/cli.php employees:update
This single command will:
- Create the
#__employee_skillstable - Add the
skill_idscolumn (TEXT type for JSON storage) to#__employees - Execute
afterCreateTable()for initial data population
- Category: Single select dropdown (one choice)
- Skills: Multiple select dropdown (select many)
Displaying Multiple Values in Tables
To display multiple selected values in tables, you need custom formatting:
// In EmployeesController.php
$tableBuilder = TableBuilder::create($this->model, 'tblEmployees')
->column('name', 'Name', 'text')
->column('category.name', 'Category', 'text')
// For multiple values, you need custom formatting
->column('skill_ids', 'Skills', 'callback', function($value, $record) {
if (empty($value)) return '-';
$skillModel = new SkillModel();
$skills = [];
foreach ($value as $skillId) {
$skill = $skillModel->find($skillId);
if ($skill) $skills[] = $skill->name;
}
return implode(', ', $skills);
})
->render();
Comparison: Single vs Multiple vs Autocomplete
| Feature | Single Pre-loaded | Multiple Pre-loaded | Autocomplete Fetch |
|---|---|---|---|
| Field Type | ->int('category_id') |
->array('skill_ids') |
->int('user_id') |
| Selection | One item only | Multiple items | One item only |
| Data Loading | All at page load | All at page load | AJAX fetch on typing |
| Configuration | ->options(...) |
->options(...) |
->apiUrl(...) |
| Best For | Small lists (10-100 items) | Small lists (10-100 items) | Large lists (100+ items) |
| Database Storage | Integer (single ID) | JSON array of IDs | Integer (single ID) |
Troubleshooting
Problem: Dropdown shows IDs instead of names
Solution:
- Check that
getList()method exists in the related model - Verify
getList()returns[id => name]format - Ensure you're calling
(new CategoryModel())->getList()correctly
Problem: Multiple select not working
Check:
- Field type is
->array('field_name')not->int() ->formParams(['type' => 'multiple'])is present- Database field type supports JSON storage (TEXT or JSON type)
Problem: Empty dropdown
Solutions:
- Check that the related table has data
- Verify
getList()method returns non-empty array - Check for PHP errors in browser console
Summary: Quick Reference
->int('category_id')
->belongsTo('category', CategoryModel::class, 'id')
->formType('milkSelect')
->options((new CategoryModel())->getList())
->array('skill_ids')
->options((new SkillModel())->getList())
->formType('milkSelect')
->formParams(['type' => 'multiple'])
->int('user_id')
->belongsTo('user', UserModel::class, 'id')
->formType('milkSelect')
->apiUrl('?page=module&action=related-search-field&f=user_id', 'username')