Migrate $name from Form_Field to input components Refactor form inputs: $name moves from Form_Field to input components 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
376 lines
12 KiB
Plaintext
Executable File
376 lines
12 KiB
Plaintext
Executable File
DATETIME_INPUTS(7) RSX Framework Manual DATETIME_INPUTS(7)
|
|
|
|
NAME
|
|
datetime_inputs - Composite date/time input handling with Schedule_Input
|
|
|
|
SYNOPSIS
|
|
Client-side (template):
|
|
<Form_Field $label="Date & Time">
|
|
<Schedule_Input $name="schedule" />
|
|
</Form_Field>
|
|
|
|
Server-side:
|
|
use App\RSpade\Core\Schedule_Field_Helper;
|
|
|
|
$schedule = Schedule_Field_Helper::parse($params['schedule']);
|
|
$errors = $schedule->validate();
|
|
|
|
$schedule->apply_to($model, [
|
|
'date' => 'event_date',
|
|
'start_time' => 'start_time',
|
|
'duration_minutes' => 'duration_minutes',
|
|
'is_all_day' => 'is_all_day',
|
|
'timezone' => 'timezone',
|
|
]);
|
|
|
|
DESCRIPTION
|
|
RSX provides a standardized pattern for handling date/time form inputs
|
|
through composite components. Rather than managing multiple separate
|
|
fields (date, time, duration, timezone), a single Schedule_Input
|
|
component encapsulates all scheduling logic and submits as one JSON value.
|
|
|
|
The Problem
|
|
|
|
Traditional date/time handling has several issues:
|
|
- Multiple fields to validate independently
|
|
- All-day toggle requires hiding/showing time fields
|
|
- Timezone handling is often forgotten or inconsistent
|
|
- Empty strings submitted for optional time fields cause DB errors
|
|
- Time field interdependencies (end must be after start)
|
|
|
|
The Solution
|
|
|
|
Schedule_Input combines all scheduling fields into one component:
|
|
|
|
<Form_Field $label="Schedule">
|
|
<Schedule_Input $name="schedule" />
|
|
</Form_Field>
|
|
|
|
Submits as JSON:
|
|
{
|
|
"date": "2025-12-23",
|
|
"start_time": "09:00",
|
|
"duration_minutes": 60,
|
|
"is_all_day": false,
|
|
"timezone": "America/Chicago"
|
|
}
|
|
|
|
Schedule_Field_Helper parses and validates on the server, with
|
|
automatic handling of all-day events (nulls time fields) and
|
|
dot-notation error support.
|
|
|
|
CONFIGURATION
|
|
Framework Configuration
|
|
|
|
In system/config/rsx.php or rsx/resource/config/rsx.php:
|
|
|
|
'datetime' => [
|
|
// Default timezone (IANA identifier)
|
|
'default_timezone' => 'America/Chicago',
|
|
|
|
// Time dropdown interval in minutes
|
|
'time_interval' => 15,
|
|
|
|
// Default duration for new events
|
|
'default_duration' => 60,
|
|
],
|
|
|
|
Environment Variables
|
|
|
|
RSX_DEFAULT_TIMEZONE=America/Chicago
|
|
|
|
SCHEDULE_INPUT COMPONENT
|
|
Arguments
|
|
|
|
$name Field name for form submission (required when in Form_Field)
|
|
$required Whether date is required (default: true)
|
|
$show_timezone Show timezone picker (default: true)
|
|
$default_duration Default duration in minutes (default: 60)
|
|
|
|
Template Usage
|
|
|
|
Basic usage:
|
|
<Form_Field $label="Date & Time" $required=true>
|
|
<Schedule_Input $name="schedule" />
|
|
</Form_Field>
|
|
|
|
Without timezone picker:
|
|
<Schedule_Input $name="schedule" $show_timezone=false />
|
|
|
|
JavaScript API
|
|
|
|
Get/set value:
|
|
const schedule = this.sid('schedule_input').val();
|
|
// Returns: {date, start_time, duration_minutes, is_all_day, timezone}
|
|
|
|
this.sid('schedule_input').val({
|
|
date: '2025-12-23',
|
|
start_time: '14:00',
|
|
duration_minutes: 90,
|
|
is_all_day: false,
|
|
timezone: 'America/New_York',
|
|
});
|
|
|
|
Apply validation errors (called automatically by Form_Utils):
|
|
schedule_input.apply_errors({
|
|
date: 'Date is required',
|
|
start_time: 'Invalid time format',
|
|
});
|
|
|
|
Visual Elements
|
|
|
|
The component displays:
|
|
- Date picker (native HTML date input)
|
|
- All-day toggle (hides time fields when checked)
|
|
- Start time dropdown (15-minute intervals)
|
|
- Duration dropdown (15min to 8hrs)
|
|
- Timezone selector (US timezones)
|
|
|
|
SCHEDULE_FIELD_HELPER CLASS
|
|
Location
|
|
|
|
App\RSpade\Core\Schedule_Field_Helper
|
|
|
|
Parsing
|
|
|
|
$schedule = Schedule_Field_Helper::parse($params['schedule']);
|
|
|
|
// With custom field name for error messages
|
|
$schedule = Schedule_Field_Helper::parse($params['schedule'], 'event_schedule');
|
|
|
|
Validation
|
|
|
|
Returns array of dot-notation errors:
|
|
|
|
$errors = $schedule->validate(
|
|
date_required: true,
|
|
time_required: false
|
|
);
|
|
|
|
// Returns:
|
|
// ['schedule.date' => 'Date is required', ...]
|
|
|
|
Merge with other errors:
|
|
|
|
$errors = [];
|
|
$errors = array_merge($errors, $schedule->validate());
|
|
|
|
Applying to Model
|
|
|
|
Use apply_to() for clean model assignment:
|
|
|
|
$schedule->apply_to($event, [
|
|
'date' => 'event_date',
|
|
'start_time' => 'start_time',
|
|
'duration_minutes' => 'duration_minutes',
|
|
'is_all_day' => 'is_all_day',
|
|
'timezone' => 'timezone',
|
|
]);
|
|
|
|
This automatically:
|
|
- Sets time fields to null when is_all_day is true
|
|
- Maps schedule properties to model columns
|
|
|
|
Direct Property Access
|
|
|
|
$schedule->date // "2025-12-23" or null
|
|
$schedule->start_time // "09:00" or null
|
|
$schedule->duration_minutes // 60 or null
|
|
$schedule->is_all_day // true/false
|
|
$schedule->timezone // "America/Chicago"
|
|
|
|
Carbon Helpers
|
|
|
|
$schedule->get_date_carbon() // Carbon date or null
|
|
$schedule->get_start_datetime() // Carbon datetime or null
|
|
$schedule->get_end_datetime() // Carbon datetime (start + duration)
|
|
|
|
Loading from Model
|
|
|
|
For edit forms, convert model data back to component format:
|
|
|
|
$schedule_data = Schedule_Field_Helper::from_model($event, [
|
|
'date' => 'event_date',
|
|
'start_time' => 'start_time',
|
|
'duration_minutes' => 'duration_minutes',
|
|
'is_all_day' => 'is_all_day',
|
|
'timezone' => 'timezone',
|
|
]);
|
|
|
|
// Returns array suitable for Schedule_Input val()
|
|
|
|
DOT-NOTATION ERRORS
|
|
Schedule_Field_Helper returns errors with dot notation:
|
|
|
|
schedule.date -> Error for date field
|
|
schedule.start_time -> Error for start time
|
|
schedule.duration_minutes -> Error for duration
|
|
schedule.timezone -> Error for timezone
|
|
|
|
Form_Utils.apply_form_errors() automatically:
|
|
1. Detects dot-notation error keys
|
|
2. Groups by parent field (schedule)
|
|
3. Calls component.apply_errors({subfield: message})
|
|
4. Component highlights individual sub-fields
|
|
|
|
Creating Custom Composite Inputs
|
|
|
|
To create your own composite input with dot-notation errors:
|
|
|
|
1. Extend Form_Input_Abstract
|
|
2. Implement val() returning/accepting an object
|
|
3. Include hidden input with JSON value
|
|
4. Implement apply_errors(sub_errors) method
|
|
|
|
JavaScript:
|
|
apply_errors(errors) {
|
|
this.$.find('.is-invalid').removeClass('is-invalid');
|
|
this.$.find('.invalid-feedback').remove();
|
|
|
|
for (const subfield in errors) {
|
|
const $input = this.$sid(subfield + '_input');
|
|
if ($input.exists()) {
|
|
$input.addClass('is-invalid');
|
|
$('<div class="invalid-feedback">').text(errors[subfield])
|
|
.insertAfter($input);
|
|
}
|
|
}
|
|
}
|
|
|
|
DATABASE SCHEMA
|
|
Recommended column types for schedule data:
|
|
|
|
event_date DATE NOT NULL
|
|
start_time TIME NULL -- NULL when all-day
|
|
duration_minutes BIGINT NULL -- NULL when all-day
|
|
is_all_day TINYINT(1) NOT NULL DEFAULT 0
|
|
timezone VARCHAR(50) DEFAULT 'America/Chicago'
|
|
|
|
Note: Do NOT use empty strings for TIME columns. The Schedule_Field_Helper
|
|
automatically converts to NULL when is_all_day is true.
|
|
|
|
COMPLETE EXAMPLE
|
|
Controller
|
|
|
|
use App\RSpade\Core\Schedule_Field_Helper;
|
|
|
|
#[Ajax_Endpoint]
|
|
public static function save(Request $request, array $params = [])
|
|
{
|
|
$schedule = Schedule_Field_Helper::parse($params['schedule']);
|
|
|
|
$errors = [];
|
|
|
|
if (empty($params['title'])) {
|
|
$errors['title'] = 'Title is required';
|
|
}
|
|
|
|
// Validate schedule (date required, time optional)
|
|
$schedule_errors = $schedule->validate(
|
|
date_required: true,
|
|
time_required: false
|
|
);
|
|
$errors = array_merge($errors, $schedule_errors);
|
|
|
|
if (!empty($errors)) {
|
|
return response_form_error('Please fix errors', $errors);
|
|
}
|
|
|
|
$event = new Event_Model();
|
|
$event->title = $params['title'];
|
|
|
|
$schedule->apply_to($event, [
|
|
'date' => 'event_date',
|
|
'start_time' => 'start_time',
|
|
'duration_minutes' => 'duration_minutes',
|
|
'is_all_day' => 'is_all_day',
|
|
'timezone' => 'timezone',
|
|
]);
|
|
|
|
$event->save();
|
|
|
|
return ['success' => true, 'id' => $event->id];
|
|
}
|
|
|
|
Action JavaScript
|
|
|
|
on_create() {
|
|
this.data.form_data = {
|
|
title: '',
|
|
schedule: {
|
|
date: new Date().toISOString().split('T')[0],
|
|
start_time: '',
|
|
duration_minutes: 60,
|
|
is_all_day: false,
|
|
timezone: 'America/Chicago',
|
|
},
|
|
};
|
|
}
|
|
|
|
async on_load() {
|
|
if (!this.data.is_edit) return;
|
|
|
|
const event = await Event_Model.fetch(this.args.id);
|
|
|
|
this.data.form_data = {
|
|
id: event.id,
|
|
title: event.title,
|
|
schedule: {
|
|
date: event.event_date,
|
|
start_time: event.start_time,
|
|
duration_minutes: event.duration_minutes,
|
|
is_all_day: event.is_all_day,
|
|
timezone: event.timezone,
|
|
},
|
|
};
|
|
}
|
|
|
|
on_ready() {
|
|
if (this.data.form_data.schedule) {
|
|
this.sid('schedule_input').val(this.data.form_data.schedule);
|
|
}
|
|
}
|
|
|
|
Template
|
|
|
|
<Rsx_Form $data=this.data.form_data $controller="Events_Controller" $method="save">
|
|
<Form_Field $label="Title" $required=true>
|
|
<Text_Input $name="title" />
|
|
</Form_Field>
|
|
|
|
<Form_Field $label="Date & Time" $required=true>
|
|
<Schedule_Input $name="schedule" $sid="schedule_input" />
|
|
</Form_Field>
|
|
|
|
<button type="submit" class="btn btn-primary">Save</button>
|
|
</Rsx_Form>
|
|
|
|
TIMEZONE SUPPORT
|
|
Available Timezones
|
|
|
|
The default timezone picker includes US timezones:
|
|
- America/New_York (Eastern)
|
|
- America/Chicago (Central)
|
|
- America/Denver (Mountain)
|
|
- America/Los_Angeles (Pacific)
|
|
- America/Anchorage (Alaska)
|
|
- Pacific/Honolulu (Hawaii)
|
|
- UTC
|
|
|
|
Customizing Timezone Options
|
|
|
|
To customize available timezones, override the static method:
|
|
|
|
Schedule_Input.get_timezone_options = function() {
|
|
return [
|
|
{ value: 'Europe/London', label: 'London (GMT)' },
|
|
{ value: 'Europe/Paris', label: 'Paris (CET)' },
|
|
// ...
|
|
];
|
|
};
|
|
|
|
SEE ALSO
|
|
polymorphic(7), form_conventions(7), ajax_error_handling(7)
|
|
|
|
RSX Framework 2025-12-23 DATETIME_INPUTS(7)
|