DATETIME_INPUTS(7) RSX Framework Manual DATETIME_INPUTS(7) NAME datetime_inputs - Composite date/time input handling with Schedule_Input SYNOPSIS Client-side (template): 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: 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: Without timezone picker: 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'); $('
').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 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)