Files
rspade_system/app/RSpade/man/datetime_inputs.txt
root 1b57ec2785 Add datetime system (Rsx_Time/Rsx_Date) and .expect file documentation system
Tighten CLAUDE.dist.md for LLM audience - 15% size reduction
Add Repeater_Simple_Input component for managing lists of simple values
Add Polymorphic_Field_Helper for JSON-encoded polymorphic form fields
Fix incorrect data-sid selector in route-debug help example
Fix Form_Utils to use component.$sid() instead of data-sid selector
Add response helper functions and use _message as reserved metadata key

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-24 21:47:53 +00:00

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 $name="schedule" $label="Date & Time">
<Schedule_Input />
</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 $name="schedule">
<Schedule_Input />
</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 $name="schedule" $label="Date & Time" $required=true>
<Schedule_Input />
</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 $name="title" $label="Title" $required=true>
<Text_Input />
</Form_Field>
<Form_Field $name="schedule" $label="Date & Time" $required=true>
<Schedule_Input $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)