FormBuilder - Conditional Field Visibility
The FormBuilder class provides powerful methods to show or hide form fields based on the value of other fields. This allows you to create dynamic, user-friendly forms that only display relevant fields based on user selections.
💡 Hiding Fields Permanently
If you need to hide a field completely (not conditionally), you have two options:
- In the Controller: Use
modifyField()to set the field type as hidden:->modifyField('field_name', ['form-type' => 'hidden']) - In the Model: Use
hideFromEdit()to hide the field from edit forms:->string('field_name', 100)->hideFromEdit()
Overview
Conditional visibility allows you to:
- Show/hide fields based on dropdown selections
- Display additional fields when a checkbox is checked
- Create multi-step forms with conditional logic
- Reduce form complexity by hiding irrelevant fields
Basic Methods
showIf() - Single Field or Container
Shows a field or container when a milk expression evaluates to true.
// Syntax
$formBuilder->showIf($field_or_container_id, $expression)
// Parameters:
// - $field_or_container_id: The field name or container id to show/hide
// - $expression: Milk expression that evaluates to true/false
Simple Example
User Status Form
This example shows different fields based on the selected status value.
namespace Modules;
use App\Abstracts\{AbstractModule, AbstractModel};
use App\Attributes\RequestAction;
use App\Response;
class UserStatusModule extends AbstractModule {
protected function configure($rule): void
{
$rule->page('userStatus')
->title('User Status Form')
->menu('User Status')
->access('registered');
}
#[RequestAction('home')]
public function home() {
$form = \Builders\FormBuilder::create($this->model, $this->page)
->showIf('activation_date', '[status] == "active"')
->showIf('activated_by', '[status] == "active"')
->showIf('reason_inactive', '[status] == "inactive"')
->showIf('archive_date', '[status] == "archived"')
->showIf('archive_notes', '[status] == "archived"')
->addStandardActions()
->render();
Response::render($form, ['title' => $this->title, 'form' => $form]);
}
}
class UserStatusModel extends AbstractModel {
protected function configure($rule): void
{
$rule
->table('user_status')
->id()
->string('name', 100)
// Status dropdown - controls visibility of other fields
->string('status', 50)->options([
'pending' => 'Pending',
'active' => 'Active',
'inactive' => 'Inactive',
'archived' => 'Archived'
])->formType('list')
->string('description', 255)
// Conditional fields - shown only when status = 'active'
->date('activation_date', false)
->string('activated_by', 100, false)
// Conditional field - shown only when status = 'inactive'
->text('reason_inactive', false)
// Conditional fields - shown only when status = 'archived'
->date('archive_date', false)
->text('archive_notes', false);
}
}
How It Works
Behind the Scenes
When you use showIf(), the FormBuilder:
- Adds
data-milk-showattribute to the field wrapper or container - Applies
style="display:none"to hide the field initially - JavaScript evaluates the expression on each recalculation
- When the expression is true, the field is shown; otherwise it is hidden
Generated HTML Example
<!-- The control field (status) -->
<div class="mb-3">
<label for="status">Status</label>
<select name="status" id="status" class="form-control">
<option value="pending">Pending</option>
<option value="active">Active</option>
<option value="inactive">Inactive</option>
<option value="archived">Archived</option>
</select>
</div>
<!-- Conditional field - initially hidden -->
<div class="mb-3"
data-milk-show="[status] == "active""
style="display:none">
<label for="activation_date">Activation Date</label>
<input type="date" name="activation_date" id="activation_date" class="form-control">
</div>
Common Use Cases
1. Dropdown-Based Conditional Fields
$form = \Builders\FormBuilder::create($this->model, $this->page)
// Show shipping address fields only when shipping type is 'custom'
->showIf('shipping_address', '[shipping_type] == "custom"')
->showIf('shipping_city', '[shipping_type] == "custom"')
->showIf('shipping_zip', '[shipping_type] == "custom"')
// Show billing fields only when billing type is 'different'
->showIf('billing_address', '[billing_type] == "different"')
->showIf('billing_city', '[billing_type] == "different"')
->showIf('billing_zip', '[billing_type] == "different"')
->render();
2. Checkbox-Based Conditional Fields
$form = \Builders\FormBuilder::create($this->model, $this->page)
// Show company fields only when 'is_company' checkbox is checked
// Note: Checkbox value is typically '1' when checked
->showIf('company_name', '[is_company] == 1')
->showIf('vat_number', '[is_company] == 1')
->showIf('registration_number', '[is_company] == 1')
->render();
3. Multiple Conditions for Same Field
// Show different fields for different payment methods
$form = \Builders\FormBuilder::create($this->model, $this->page)
// Show card fields when payment method is 'credit_card'
->showIf('card_number', '[payment_method] == "credit_card"')
->showIf('card_expiry', '[payment_method] == "credit_card"')
->showIf('card_cvv', '[payment_method] == "credit_card"')
// Show bank fields when payment method is 'bank_transfer'
->showIf('bank_name', '[payment_method] == "bank_transfer"')
->showIf('account_number', '[payment_method] == "bank_transfer"')
->showIf('swift_code', '[payment_method] == "bank_transfer"')
// Show PayPal email when payment method is 'paypal'
->showIf('paypal_email', '[payment_method] == "paypal"')
->render();
4. User Type Based Fields
$form = \Builders\FormBuilder::create($this->model, $this->page)
// Show admin-specific fields
->showIf('admin_level', '[user_type] == "admin"')
->showIf('permissions', '[user_type] == "admin"')
->showIf('department', '[user_type] == "admin"')
// Show customer-specific fields
->showIf('customer_type', '[user_type] == "customer"')
->showIf('discount_level', '[user_type] == "customer"')
->showIf('credit_limit', '[user_type] == "customer"')
// Show vendor-specific fields
->showIf('vendor_category', '[user_type] == "vendor"')
->showIf('commission_rate', '[user_type] == "vendor"')
->showIf('contract_date', '[user_type] == "vendor"')
->render();
Complete Working Example
E-commerce Product Form
namespace Modules;
use App\Abstracts\{AbstractModule, AbstractModel};
use App\Attributes\RequestAction;
use App\Response;
class ProductModule extends AbstractModule {
protected function configure($rule): void
{
$rule->page('products')
->title('Product Management')
->menu('Products')
->access('registered');
}
#[RequestAction('edit')]
public function edit() {
$id = _absint($_REQUEST['id'] ?? 0);
$product = $this->model->getByIdForEdit($id);
$form = \Builders\FormBuilder::create($this->model, $this->page)
->addFieldsFromObject($product, 'edit')
// Show digital product fields when type is 'digital'
->showIf('download_url', '[product_type] == "digital"')
->showIf('file_size', '[product_type] == "digital"')
->showIf('download_limit', '[product_type] == "digital"')
// Show physical product fields when type is 'physical'
->showIf('weight', '[product_type] == "physical"')
->showIf('dimensions', '[product_type] == "physical"')
->showIf('shipping_class', '[product_type] == "physical"')
// Show subscription fields when type is 'subscription'
->showIf('billing_period', '[product_type] == "subscription"')
->showIf('trial_days', '[product_type] == "subscription"')
->showIf('renewal_price', '[product_type] == "subscription"')
// Show discount fields only when 'has_discount' is checked
->showIf('discount_percentage', '[has_discount] == 1')
->showIf('discount_start_date', '[has_discount] == 1')
->showIf('discount_end_date', '[has_discount] == 1')
->addStandardActions('?page=' . $this->page, true)
->render();
Response::render(['form' => $form], [
'title' => $id > 0 ? 'Edit Product' : 'Add Product',
'form' => $form
]);
}
}
class ProductModel extends AbstractModel {
protected function configure($rule): void
{
$rule
->table('products')
->id()
->string('name', 200)
->text('description', false)
->float('price')
// Product type selector
->string('product_type', 50)->options([
'physical' => 'Physical Product',
'digital' => 'Digital Product',
'subscription' => 'Subscription'
])->formType('list')
// Physical product fields
->float('weight', false)
->string('dimensions', 100, false)
->string('shipping_class', 50, false)
// Digital product fields
->string('download_url', 255, false)
->string('file_size', 50, false)
->int('download_limit', false)
// Subscription fields
->string('billing_period', 50, false)
->int('trial_days', false)
->float('renewal_price', false)
// Discount checkbox and fields
->checkbox('has_discount', false)
->float('discount_percentage', false)
->date('discount_start_date', false)
->date('discount_end_date', false);
}
}
Best Practices
1. Field Order Matters
Place the control field (the field being watched) before the conditional fields in your form for better UX.
// Good - control field comes first
->fieldOrder(['id', 'status', 'activation_date', 'activated_by'])
// Not recommended - conditional fields appear before control
->fieldOrder(['id', 'activation_date', 'activated_by', 'status'])
2. Make Conditional Fields Optional
Fields that are conditionally shown should typically be optional in the database schema.
// Good - conditional fields are optional (false parameter)
->date('activation_date', false) // Second parameter = false means nullable
->string('activated_by', 100, false)
// Avoid - conditional required fields can cause validation issues
->date('activation_date') // Required field that might be hidden
3. Group Related Conditional Fields
Use showIf() on a container id to show/hide a full section at once.
// Good - grouped related fields via container
->addContainer('CNT_ACTIVATION', ['activation_date', 'activated_by', 'activation_notes'], 3, '', 'Activation')
->showIf('CNT_ACTIVATION', '[status] == "active"')
4. Clear Field Labels
Make field labels descriptive so users understand why fields appear/disappear.
// Good - descriptive labels
->string('activation_date')->formLabel('Date when user was activated')
->string('reason_inactive')->formLabel('Reason for deactivation')
// Less clear
->string('activation_date')->formLabel('Date')
->string('reason_inactive')->formLabel('Reason')
Removing Conditional Visibility
If you need to remove conditional visibility from a field:
$form = \Builders\FormBuilder::create($this->model, $this->page)
->showIf('activation_date', '[status] == "active"')
// Later, remove the condition
->removeFieldCondition('activation_date')
->render();
Technical Details
JavaScript Implementation
The conditional visibility is handled by the MilkForm JavaScript class in milk-form.js. It:
- Automatically detects elements with
data-milk-showattributes - Evaluates expressions on each recalculation
- Shows/hides fields with smooth transitions
- Manages required field validation when fields are hidden
Browser Compatibility
The conditional visibility feature works in all modern browsers that support:
- ES6 JavaScript classes
- CSS transitions
- DOM dataset API
Troubleshooting
Fields Not Showing/Hiding
- Verify field names inside the expression match the real form names (case-sensitive)
- Check that the expression evaluates to true/false as expected
- Ensure JavaScript is loaded (check browser console)
- Verify field is added to form before applying conditional visibility
Validation Issues
If required fields are causing validation errors when hidden:
- Make conditional fields optional in the model (use
falseparameter) - The JavaScript automatically disables required validation for hidden fields
- Ensure you're using the latest version of
milk-form.js
Next Steps
Now that you understand conditional field visibility, explore:
- Form Validation: Custom validation rules and error handling
- Complex Field Types: Working with milkSelect, file uploads, and editors
- Form Actions: Custom callbacks and action handling
- AJAX Forms: Dynamic form submission without page reload