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>
725 lines
21 KiB
Plaintext
Executable File
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
|
|
|
|
================================================================================
|