Files
rspade_system/app/RSpade/man/modals.txt
root f6ac36c632 Enhance refactor commands with controller-aware Route() updates and fix code quality violations
Add semantic token highlighting for 'that' variable and comment file references in VS Code extension
Add Phone_Text_Input and Currency_Input components with formatting utilities
Implement client widgets, form standardization, and soft delete functionality
Add modal scroll lock and update documentation
Implement comprehensive modal system with form integration and validation
Fix modal component instantiation using jQuery plugin API
Implement modal system with responsive sizing, queuing, and validation support
Implement form submission with validation, error handling, and loading states
Implement country/state selectors with dynamic data loading and Bootstrap styling
Revert Rsx::Route() highlighting in Blade/PHP files
Target specific PHP scopes for Rsx::Route() highlighting in Blade
Expand injection selector for Rsx::Route() highlighting
Add custom syntax highlighting for Rsx::Route() and Rsx.Route() calls
Update jqhtml packages to v2.2.165
Add bundle path validation for common mistakes (development mode only)
Create Ajax_Select_Input widget and Rsx_Reference_Data controller
Create Country_Select_Input widget with default country support
Initialize Tom Select on Select_Input widgets
Add Tom Select bundle for enhanced select dropdowns
Implement ISO 3166 geographic data system for country/region selection
Implement widget-based form system with disabled state support

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-30 06:21:56 +00:00

725 lines
21 KiB
Plaintext
Executable File

================================================================================
MODAL SYSTEM
================================================================================
The Modal system provides a consistent, queue-managed interface for displaying
dialogs throughout the application. All modals are managed by the static Modal
class, which handles queuing, backdrop management, and user interactions.
================================================================================
BASIC DIALOGS
================================================================================
ALERT
-----
Show a simple notification message with an OK button.
await Modal.alert(message)
await Modal.alert(title, message)
await Modal.alert(title, message, button_label)
Examples:
await Modal.alert("File saved successfully");
await Modal.alert("Success", "Your changes have been saved");
await Modal.alert("Notice", "Operation complete", "Got it");
Parameters:
message - Message text (if only 1 arg) or jQuery element
title - Optional title (default: "Notice")
button_label - Optional button text (default: "OK")
Returns: Promise<void>
CONFIRM
-------
Show a confirmation dialog with Cancel and Confirm buttons.
let result = await Modal.confirm(message)
let result = await Modal.confirm(title, message)
let result = await Modal.confirm(title, message, confirm_label, cancel_label)
Examples:
if (await Modal.confirm("Delete this item?")) {
// User confirmed
}
if (await Modal.confirm("Delete Item", "This cannot be undone")) {
// User confirmed
}
Parameters:
message - Message text (if 1-2 args) or jQuery element
title - Optional title (default: "Confirm")
confirm_label - Confirm button text (default: "Confirm")
cancel_label - Cancel button text (default: "Cancel")
Returns: Promise<boolean> - true if confirmed, false if cancelled
PROMPT
------
Show an input dialog for text entry.
let value = await Modal.prompt(message)
let value = await Modal.prompt(title, message)
let value = await Modal.prompt(title, message, default_value)
let value = await Modal.prompt(title, message, default_value, multiline)
let value = await Modal.prompt(title, message, default_value, multiline, error)
Examples:
let name = await Modal.prompt("What is your name?");
if (name) {
console.log("Hello, " + name);
}
let email = await Modal.prompt("Email", "Enter your email:", "user@example.com");
let feedback = await Modal.prompt("Feedback", "Enter your feedback:", "", true);
Rich Content Example:
const $rich = $('<div>')
.append($('<h5 style="color: #2c3e50;">').text('Registration'))
.append($('<p>').html('Enter your <strong>full name</strong>'));
let name = await Modal.prompt($rich);
Validation Pattern (Lazy Re-prompting):
let email = '';
let error = null;
let valid = false;
while (!valid) {
email = await Modal.prompt('Email', 'Enter email:', email, false, error);
if (email === false) return; // Cancelled
// Validate
if (!email.includes('@')) {
error = 'Please enter a valid email address';
} else {
valid = true;
}
}
// email is now valid
Parameters:
message - Prompt message text or jQuery element
title - Optional title (default: "Input")
default_value - Default input value (default: "")
multiline - Show textarea instead of input (default: false)
error - Optional error message to display as validation feedback
Returns: Promise<string|false> - Input value or false if cancelled
Input Constraints:
Standard input: 245px minimum width
Textarea input: 315px minimum width
Spacing: 36px between message and input field
Error Display:
When error parameter is provided:
- Input field marked with .is-invalid class (red border)
- Error message displayed below input as .invalid-feedback
- Input retains previously entered value
- User can correct and resubmit
ERROR
-----
Show an error message dialog.
await Modal.error(error)
await Modal.error(error, title)
Examples:
await Modal.error("File not found");
await Modal.error(exception, "Upload Failed");
await Modal.error({message: "Invalid format"}, "Error");
Parameters:
error - String, error object, or {message: string}
title - Optional title (default: "Error")
Handles various error formats:
- String: "Error message"
- Object: {message: "Error"}
- Laravel response: {responseJSON: {message: "Error"}}
- Field errors: {field: "Error", field2: "Error2"}
Returns: Promise<void>
================================================================================
CUSTOM MODALS
================================================================================
SHOW
----
Display a custom modal with specified content and buttons.
let result = await Modal.show(options)
Options:
title - Modal title (default: "Modal")
body - String, HTML, or jQuery element
buttons - Array of button definitions (see below)
max_width - Maximum width in pixels (default: 800)
closable - Allow ESC/backdrop/X to close (default: true)
Button Definition:
{
label: "Button Text",
value: "return_value",
class: "btn-primary", // Bootstrap button class
default: true, // Make this the default button
callback: async function() {
// Optional: perform action and return result
return custom_value;
}
}
Examples:
// Two button modal
const result = await Modal.show({
title: "Choose Action",
body: "What would you like to do?",
buttons: [
{label: "Cancel", value: false, class: "btn-secondary"},
{label: "Continue", value: true, class: "btn-primary", default: true}
]
});
// Three button modal
const result = await Modal.show({
title: "Save Changes",
body: "How would you like to save?",
buttons: [
{label: "Cancel", value: false, class: "btn-secondary"},
{label: "Save Draft", value: "draft", class: "btn-info"},
{label: "Publish", value: "publish", class: "btn-success", default: true}
]
});
// jQuery content
const $content = $('<div>')
.append($('<p>').text('Custom content'))
.append($('<ul>').append($('<li>').text('Item 1')));
await Modal.show({
title: "Custom Content",
body: $content,
buttons: [{label: "Close", value: true, class: "btn-primary"}]
});
Returns: Promise<any> - Value from clicked button (false if cancelled)
================================================================================
FORM MODALS
================================================================================
FORM
----
Display a form component in a modal with validation support.
let result = await Modal.form(options)
Options:
component - Component class name (string)
component_args - Arguments to pass to component
title - Modal title (default: "Form")
max_width - Maximum width in pixels (default: 800)
closable - Allow ESC/backdrop to close (default: true)
submit_label - Submit button text (default: "Submit")
cancel_label - Cancel button text (default: "Cancel")
on_submit - Callback function (receives form component)
The on_submit callback pattern:
- Receives the form component instance
- Call form.vals() to get current values
- Perform validation/submission
- Return false to keep modal open (for errors)
- Return data to close modal and resolve promise
Simple Example:
const result = await Modal.form({
title: "New User",
component: "User_Form",
on_submit: async (form) => {
const values = form.vals();
if (!values.name) {
await Modal.alert("Name is required");
return false; // Keep modal open
}
await sleep(500); // Simulate save
return values; // Close modal with data
}
});
if (result) {
console.log("Saved:", result);
}
Validation Example:
const result = await Modal.form({
title: "Edit Profile",
component: "Profile_Form",
component_args: {data: user_data},
submit_label: "Update",
on_submit: async (form) => {
const values = form.vals();
// Server-side validation
const response = await User_Controller.update_profile(values);
if (response.errors) {
// Show errors and keep modal open
Form_Utils.apply_form_errors(form.$, response.errors);
return false;
}
// Success - close and return
return response.data;
}
});
Creating Form Components:
Your form component must:
- Extend Jqhtml_Component
- Implement vals() method for getting/setting values
- Use standard form HTML with name attributes
- Include error container: <div $id="error_container"></div>
Example form component (my_form.jqhtml):
<Define:My_Form tag="div">
<div $id="error_container"></div>
<div class="mb-3">
<label class="form-label">Name</label>
<input type="text" class="form-control" $id="name_input" name="name">
</div>
<div class="mb-3">
<label class="form-label">Email</label>
<input type="email" class="form-control" $id="email_input" name="email">
</div>
</Define:My_Form>
Example form component class (my_form.js):
class My_Form extends Jqhtml_Component {
on_create() {
this.data.values = this.args.data || {};
}
on_ready() {
if (this.data.values) {
this.vals(this.data.values);
}
}
vals(values) {
if (values) {
// Setter
this.$id('name_input').val(values.name || '');
this.$id('email_input').val(values.email || '');
return null;
} else {
// Getter
return {
name: this.$id('name_input').val(),
email: this.$id('email_input').val()
};
}
}
}
Validation Error Handling:
Form_Utils.apply_form_errors() automatically handles:
- Field-specific errors (matched by name attribute)
- General error messages
- Multiple error formats (string, array, object)
- Animated error display
- Bootstrap 5 validation classes
Error format examples:
// Field errors
{
name: "Name is required",
email: "Invalid email format"
}
// General errors
"An error occurred"
// Array of errors
["Error 1", "Error 2"]
// Laravel format
{
name: ["Name is required", "Name too short"],
email: ["Invalid format"]
}
Returns: Promise<Object|false> - Form data or false if cancelled
================================================================================
SPECIAL MODALS
================================================================================
UNCLOSABLE
----------
Display a modal that cannot be closed by user (no ESC, backdrop, or X button).
Must be closed programmatically.
Modal.unclosable(message)
Modal.unclosable(title, message)
Examples:
Modal.unclosable("Processing", "Please wait...");
setTimeout(() => {
Modal.close();
}, 3000);
Parameters:
message - Message text
title - Optional title (default: "Please Wait")
Returns: void (does not wait for close)
Note: Call Modal.close() to dismiss the modal programmatically.
================================================================================
MODAL STATE MANAGEMENT
================================================================================
IS_OPEN
-------
Check if a modal is currently displayed.
if (Modal.is_open()) {
console.log("Modal is open");
}
Returns: boolean
GET_CURRENT
-----------
Get the currently displayed modal instance.
const modal = Modal.get_current();
if (modal) {
console.log("Modal instance exists");
}
Returns: Rsx_Modal instance or null
CLOSE
-----
Programmatically close the current modal.
await Modal.close();
Typically used with unclosable modals or to force-close from external code.
Returns: Promise<void>
APPLY_ERRORS
------------
Apply validation errors to the current modal (if it contains a form).
Modal.apply_errors({
field1: "Error message",
field2: "Another error"
});
This is a convenience method that calls Form_Utils.apply_form_errors() on
the current modal's body element.
Parameters:
errors - Error object (field: message pairs)
Returns: void
================================================================================
MODAL QUEUING
================================================================================
The Modal system automatically queues multiple simultaneous modal requests
and displays them sequentially:
// All three modals are queued and shown one after another
const p1 = Modal.alert("First");
const p2 = Modal.alert("Second");
const p3 = Modal.alert("Third");
await Promise.all([p1, p2, p3]);
Queuing Behavior:
- Single shared backdrop persists across queued modals
- 500ms delay between modals (backdrop stays visible)
- Backdrop fades in at start of queue
- Backdrop fades out when queue is empty
- Each modal appears instantly (no fade animation)
Current Limitations:
- All modals treated equally (no priority levels)
- No concept of "modal sessions" or grouped interactions
- FIFO queue order (first requested, first shown)
Future Considerations:
When implementing real-time notifications or background events, you may
need to distinguish between:
- User-initiated modal sequences (conversational flow)
- Background notifications (should wait for user flow to complete)
Planned features:
- Priority levels for different modal types
- Modal sessions to group related interactions
- External event blocking during active user sessions
================================================================================
MODAL SIZING
================================================================================
Responsive Sizing:
- Desktop: 60% viewport width preferred, max 80%
- Mobile: 90% viewport width
- Minimum width: 400px desktop, 280px mobile
- Minimum height: 260px
Maximum Width:
- Default: 800px
- Configurable via max_width option
- Examples: 500px (forms), 1200px (data tables)
Scrolling:
- Triggers when content exceeds 80% viewport height
- Modal body becomes scrollable
- Header and footer remain fixed
Manual Control:
Modal.show({
max_width: 1200, // Wide modal for tables
body: content
});
================================================================================
STYLING AND UX
================================================================================
Modal Appearance:
- Centered vertically and horizontally
- Gray header background (#f8f9fa)
- Smaller title font (1rem)
- Shorter header padding (0.75rem)
- Subtle drop shadow (0 4px 12px rgba(0,0,0,0.15))
- Buttons centered horizontally as a group
- Modal body text centered (for simple dialogs)
Animations:
- Modal appears instantly (no fade)
- Backdrop fades in/out over 250ms
- Validation errors fade in over 300ms
Body Scroll Lock:
- Page scrolling disabled when modal open
- Scrollbar width calculated and compensated via padding
- Prevents layout shift when scrollbar disappears
- Original body state restored when modal closes
- Managed at backdrop level (first modal locks, last unlocks)
Accessibility:
- ESC key closes modal (if closable)
- Backdrop click closes modal (if closable)
- Focus management (input fields auto-focus)
- Keyboard navigation support
================================================================================
BEST PRACTICES
================================================================================
1. Use Appropriate Dialog Type
- alert() - Notifications, information
- confirm() - Yes/no decisions
- prompt() - Simple text input
- form() - Complex forms with validation
- show() - Custom requirements
2. Handle Cancellations
Always check for false (cancelled) return values:
const result = await Modal.confirm("Delete?");
if (result === false) {
return; // User cancelled
}
3. Validation Feedback
Keep modal open for validation errors:
if (errors) {
Form_Utils.apply_form_errors(form.$, errors);
return false; // Keep open
}
4. Avoid Nested Modals
While technically possible, nested modals create poor UX.
Close the first modal before showing a second:
await Modal.alert("Step 1");
await Modal.alert("Step 2"); // Shows after first closes
5. Loading States
For long operations, use unclosable modals:
Modal.unclosable("Saving", "Please wait...");
await save_operation();
await Modal.close();
6. Rich Content
Use jQuery elements for formatted content:
const $content = $('<div>')
.append($('<h5>').text('Title'))
.append($('<p>').html('<strong>Bold</strong> text'));
await Modal.alert($content);
7. Form Component Design
- Keep vals() method simple and synchronous
- Put async logic in on_submit callback
- Use standard HTML form structure
- Include error_container div for validation
- Match field name attributes to error keys
================================================================================
COMMON PATTERNS
================================================================================
Delete Confirmation:
const confirmed = await Modal.confirm(
"Delete Item",
"This action cannot be undone. Are you sure?",
"Delete Forever",
"Cancel"
);
if (confirmed) {
await Item_Controller.delete(item_id);
await Modal.alert("Item deleted successfully");
}
Save with Validation:
const result = await Modal.form({
title: "Edit Profile",
component: "Profile_Form",
component_args: {data: user},
on_submit: async (form) => {
const values = form.vals();
const response = await User_Controller.save(values);
if (response.errors) {
Form_Utils.apply_form_errors(form.$, response.errors);
return false;
}
return response.data;
}
});
if (result) {
await Modal.alert("Profile updated successfully");
}
Multi-Step Process:
const name = await Modal.prompt("What is your name?");
if (!name) return;
const email = await Modal.prompt("Enter your email:");
if (!email) return;
const confirmed = await Modal.confirm(
"Confirm Registration",
`Register ${name} with ${email}?`
);
if (confirmed) {
await register({name, email});
}
Progressive Disclosure:
const result = await Modal.show({
title: "Choose Action",
body: "What would you like to do?",
buttons: [
{label: "View Details", value: "view"},
{label: "Edit", value: "edit"},
{label: "Delete", value: "delete", class: "btn-danger"}
]
});
if (result === "view") {
// Show details modal
} else if (result === "edit") {
// Show edit form
} else if (result === "delete") {
// Confirm and delete
}
================================================================================
TROUBLESHOOTING
================================================================================
Modal Won't Close
- Check if callback returns false (intentionally keeping open)
- Verify closable: true option is set
- Check for JavaScript errors in callback
- Use Modal.close() to force close
Validation Errors Not Showing
- Ensure form has <div $id="error_container"></div>
- Verify field name attributes match error keys
- Check that fields are wrapped in .form-group containers
- Use Form_Utils.apply_form_errors(form.$, errors)
Form Values Not Saving
- Verify vals() method returns correct object
- Check that on_submit returns data (not false)
- Ensure callback doesn't throw unhandled errors
- Test vals() method independently
Queue Not Working
- All modals automatically queue
- If backdrop flickers, check for multiple backdrop creation
- Verify using Modal.* static methods (not creating instances)
Component Not Found
- Ensure component class name is correct (case-sensitive)
- Check that component files are in manifest
- Verify component extends Jqhtml_Component
- Component must be in /rsx/ directory tree
================================================================================