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>
414 lines
13 KiB
Plaintext
Executable File
414 lines
13 KiB
Plaintext
Executable File
NAME
|
|
jQuery Extensions - RSpade framework extensions and overrides to jQuery
|
|
|
|
SYNOPSIS
|
|
// Standard click handler with automatic preventDefault
|
|
$('.btn').click(function(e) {
|
|
console.log('Button clicked');
|
|
});
|
|
|
|
// Escape hatch for native behavior (rare)
|
|
$('.nav-link').click_allow_default(function(e) {
|
|
analytics.track('click');
|
|
// Link navigation happens
|
|
});
|
|
|
|
// Other jQuery helpers
|
|
if ($('.element').exists()) { ... }
|
|
if ($('.element').is_in_viewport()) { ... }
|
|
$('.form').checkValidity();
|
|
|
|
DESCRIPTION
|
|
RSpade extends jQuery with utility methods and overrides default behaviors
|
|
to provide sensible defaults for modern web applications. The most significant
|
|
change is the .click() override that automatically calls e.preventDefault()
|
|
to prevent accidental page navigation and form submission.
|
|
|
|
Unlike vanilla jQuery where developers must remember to call preventDefault
|
|
in nearly every click handler, RSpade makes the correct behavior automatic.
|
|
This eliminates a common source of bugs where forgotten preventDefault calls
|
|
cause unwanted page reloads or navigation.
|
|
|
|
Philosophy:
|
|
- Prevent default is correct 95% of the time
|
|
- Escape hatch available for the 5% case
|
|
- Fail toward the safe behavior (no navigation)
|
|
- Make the common case trivial
|
|
|
|
CLICK OVERRIDE
|
|
|
|
.click(handler)
|
|
Attach click handler that automatically calls e.preventDefault()
|
|
|
|
This prevents:
|
|
- Link navigation (<a href>)
|
|
- Form submission (<button type="submit">)
|
|
- Default button actions
|
|
|
|
Usage:
|
|
$('.delete-btn').click(function(e) {
|
|
delete_record($(this).data('id'));
|
|
// No need for e.preventDefault() - automatic
|
|
});
|
|
|
|
$('a.modal-trigger').click(function(e) {
|
|
open_modal();
|
|
// Link won't navigate - preventDefault called
|
|
});
|
|
|
|
.click() (no handler)
|
|
Trigger click event - native behavior unchanged
|
|
|
|
Usage:
|
|
$('.hidden-button').click(); // Triggers click
|
|
|
|
.click_allow_default(handler)
|
|
Escape hatch for native click behavior without preventDefault
|
|
|
|
Use this ONLY when you explicitly need default browser behavior.
|
|
Examples: analytics tracking before navigation, conditional preventDefault
|
|
|
|
Usage:
|
|
$('.external-link').click_allow_default(function(e) {
|
|
analytics.track('external_click', this.href);
|
|
// Navigation happens after handler
|
|
});
|
|
|
|
$('button[type=submit]').click_allow_default(function(e) {
|
|
if (!validate_form()) {
|
|
e.preventDefault(); // Conditionally prevent
|
|
}
|
|
// Otherwise allows form submission
|
|
});
|
|
|
|
JQUERY HELPERS
|
|
|
|
Element Existence and State
|
|
|
|
.exists()
|
|
Returns true if jQuery selector matched any elements
|
|
|
|
if ($('.error-message').exists()) {
|
|
console.log('Error message present');
|
|
}
|
|
|
|
.is_visible()
|
|
Returns true if element is visible (not display:none or hidden)
|
|
|
|
if ($('.modal').is_visible()) {
|
|
$('.modal').fadeOut();
|
|
}
|
|
|
|
.is_in_dom()
|
|
Returns true if element exists in and is attached to the DOM
|
|
|
|
if ($('.dynamic-element').is_in_dom()) {
|
|
// Element is live in the page
|
|
}
|
|
|
|
.is_in_viewport()
|
|
Returns true if element is currently visible in the viewport
|
|
|
|
$('.lazy-image').each(function() {
|
|
if ($(this).is_in_viewport()) {
|
|
load_image($(this));
|
|
}
|
|
});
|
|
|
|
Scrolling
|
|
|
|
.scroll_up_to(speed = 0)
|
|
Scrolls page up to bring element into view if it's above viewport
|
|
|
|
$('.error-field').scroll_up_to(300);
|
|
|
|
Element Information
|
|
|
|
.tagname()
|
|
Returns lowercase tag name of element
|
|
|
|
if ($element.tagname() === 'a') {
|
|
// It's a link
|
|
}
|
|
|
|
.is_external()
|
|
Returns true if link href is external to current domain
|
|
Only works on <a> elements
|
|
|
|
if ($('a').is_external()) {
|
|
$(this).attr('target', '_blank');
|
|
}
|
|
|
|
Component-Aware DOM Traversal
|
|
|
|
.shallowFind(selector)
|
|
Finds child elements matching the selector that don't have another
|
|
element of the same class as a parent between them and the component.
|
|
|
|
Useful for finding direct widget children in nested component trees
|
|
without accidentally selecting widgets from nested child components.
|
|
|
|
Example:
|
|
Component_A
|
|
└── div
|
|
└── Widget (found)
|
|
└── span
|
|
└── Widget (not found - has Widget parent)
|
|
|
|
$('.Component_A').shallowFind('.Widget')
|
|
// Returns only the first Widget
|
|
|
|
Use case - Finding form widgets without selecting nested widgets:
|
|
this.$.shallowFind('.Form_Field').each(function() {
|
|
// Only processes fields directly in this form,
|
|
// not fields in nested sub-forms
|
|
});
|
|
|
|
.closest_sibling(selector)
|
|
Searches for elements within progressively higher ancestor containers.
|
|
Similar to .closest() but searches within ancestors instead of
|
|
matching the ancestors themselves. Stops searching at <body> tag.
|
|
|
|
Useful for component-to-component communication when components need
|
|
to find related sibling or cousin components without knowing the
|
|
exact DOM structure.
|
|
|
|
Algorithm:
|
|
1. Get current element's parent
|
|
2. Search within parent using parent.find(selector)
|
|
3. If found, return results
|
|
4. If not found, move to parent's parent and repeat
|
|
5. Stop when reaching <body> (searches body but not beyond)
|
|
6. Return empty jQuery object if nothing found
|
|
|
|
Example DOM structure:
|
|
<body>
|
|
<div class="form-section">
|
|
<div class="row">
|
|
<div class="Country_Select_Input"></div>
|
|
</div>
|
|
<div class="another-row">
|
|
<div class="State_Select_Input"></div>
|
|
</div>
|
|
</div>
|
|
</body>
|
|
|
|
$('.Country_Select_Input').closest_sibling('.State_Select_Input')
|
|
// Finds State_Select_Input by searching up to form-section
|
|
|
|
Use case - Country selector updating state selector:
|
|
on_ready() {
|
|
if (this.tom_select) {
|
|
this.tom_select.on('change', () => {
|
|
const state_input = this.$el.closest_sibling('.State_Select_Input');
|
|
if (state_input.exists()) {
|
|
const widget = state_input.component();
|
|
widget.set_country_code(this.val());
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
Form Validation
|
|
|
|
.checkValidity()
|
|
Returns true if form/field passes HTML5 validation
|
|
|
|
if ($('form').checkValidity()) {
|
|
submit_form();
|
|
}
|
|
|
|
.reportValidity()
|
|
Triggers browser's native validation UI, returns validity
|
|
|
|
if (!$('form').reportValidity()) {
|
|
return; // Browser shows validation errors
|
|
}
|
|
|
|
.requestSubmit()
|
|
Programmatically submit form (triggers validation)
|
|
|
|
$('form').requestSubmit();
|
|
|
|
Focus Selector
|
|
|
|
:focus pseudo-selector
|
|
Select element that currently has focus
|
|
|
|
if ($('input').is(':focus')) {
|
|
// Input is focused
|
|
}
|
|
|
|
$('input:focus').addClass('active');
|
|
|
|
RSPADE VS VANILLA JQUERY
|
|
|
|
Click Handlers
|
|
|
|
Vanilla jQuery:
|
|
$('.btn').click(function(e) {
|
|
e.preventDefault(); // Must remember this
|
|
do_something();
|
|
});
|
|
|
|
RSpade:
|
|
$('.btn').click(function(e) {
|
|
do_something(); // preventDefault automatic
|
|
});
|
|
|
|
Existence Checks
|
|
|
|
Vanilla jQuery:
|
|
if ($('.element').length > 0) { ... }
|
|
|
|
RSpade:
|
|
if ($('.element').exists()) { ... }
|
|
|
|
Form Validation
|
|
|
|
Vanilla jQuery:
|
|
if ($('form')[0].checkValidity()) { ... }
|
|
|
|
RSpade:
|
|
if ($('form').checkValidity()) { ... }
|
|
|
|
WHEN TO USE .click_allow_default()
|
|
|
|
Valid use cases (rare):
|
|
|
|
1. Analytics tracking before navigation
|
|
$('a.external').click_allow_default(function(e) {
|
|
analytics.track('external_link');
|
|
// Let navigation happen
|
|
});
|
|
|
|
2. Conditional preventDefault
|
|
$('button[type=submit]').click_allow_default(function(e) {
|
|
if (!validate_form()) {
|
|
e.preventDefault();
|
|
}
|
|
});
|
|
|
|
3. Progressive enhancement fallbacks
|
|
$('a[href="/fallback"]').click_allow_default(function(e) {
|
|
if (ajax_available()) {
|
|
e.preventDefault();
|
|
load_via_ajax();
|
|
}
|
|
// Otherwise let href work
|
|
});
|
|
|
|
Invalid use cases (use standard .click() instead):
|
|
|
|
- Opening modals (don't need navigation)
|
|
- Triggering actions (don't need navigation)
|
|
- Ajax requests (don't need navigation)
|
|
- Any case where you don't want the browser's default behavior
|
|
|
|
MIGRATION FROM VANILLA JQUERY
|
|
|
|
When porting jQuery code to RSpade:
|
|
|
|
1. Remove explicit preventDefault calls in click handlers
|
|
Before:
|
|
$('.btn').click(function(e) {
|
|
e.preventDefault();
|
|
do_action();
|
|
});
|
|
|
|
After:
|
|
$('.btn').click(function(e) {
|
|
do_action();
|
|
});
|
|
|
|
2. Identify legitimate native behavior needs
|
|
If you have click handlers that SHOULD allow navigation/submission,
|
|
switch to .click_allow_default()
|
|
|
|
3. Replace existence checks
|
|
Before: if ($('.el').length > 0)
|
|
After: if ($('.el').exists())
|
|
|
|
4. Replace form validation
|
|
Before: $('form')[0].checkValidity()
|
|
After: $('form').checkValidity()
|
|
|
|
EXAMPLES
|
|
|
|
Modal Trigger
|
|
// Opens modal without navigating
|
|
$('a.open-modal').click(function(e) {
|
|
const modal_id = $(this).data('modal');
|
|
$(`#${modal_id}`).fadeIn();
|
|
});
|
|
|
|
Delete Button
|
|
// Deletes record without form submission
|
|
$('.delete-btn').click(function(e) {
|
|
const id = $(this).data('id');
|
|
if (confirm('Delete this record?')) {
|
|
delete_record(id);
|
|
}
|
|
});
|
|
|
|
External Link with Tracking
|
|
// Tracks click then allows navigation
|
|
$('a.external').click_allow_default(function(e) {
|
|
analytics.track('external_link', {
|
|
url: this.href,
|
|
text: $(this).text()
|
|
});
|
|
});
|
|
|
|
Conditional Form Submit
|
|
// Only prevents submit if validation fails
|
|
$('form button[type=submit]').click_allow_default(function(e) {
|
|
if (!validate_custom_rules()) {
|
|
e.preventDefault();
|
|
show_errors();
|
|
}
|
|
});
|
|
|
|
Lazy Loading on Scroll
|
|
$(window).on('scroll', function() {
|
|
$('.lazy-image').each(function() {
|
|
if ($(this).is_in_viewport() && !$(this).data('loaded')) {
|
|
const src = $(this).data('src');
|
|
$(this).attr('src', src).data('loaded', true);
|
|
}
|
|
});
|
|
});
|
|
|
|
TROUBLESHOOTING
|
|
|
|
Problem: Links not navigating when they should
|
|
Solution: Use .click_allow_default() instead of .click()
|
|
|
|
Problem: Form submitting unexpectedly
|
|
Solution: This shouldn't happen - .click() prevents submission by default
|
|
If using .click_allow_default(), add explicit e.preventDefault()
|
|
|
|
Problem: .exists() not working
|
|
Solution: Ensure Rsx_Jq_Helpers is loaded - check browser console for errors
|
|
|
|
Problem: Want to use .on('click') to avoid preventDefault
|
|
Solution: Don't do this - it defeats the framework's safety. If you need
|
|
native behavior, use .click_allow_default() to make intent explicit
|
|
|
|
IMPLEMENTATION NOTES
|
|
|
|
The click override is implemented in:
|
|
/app/RSpade/Core/Js/Rsx_Jq_Helpers.js
|
|
|
|
Native jQuery .click() is preserved as:
|
|
$.fn._click_native()
|
|
|
|
The override applies to all elements, not just links and buttons. This is
|
|
intentional - preventDefault is harmless on elements without default actions
|
|
and ensures consistency across the codebase.
|
|
|
|
SEE ALSO
|
|
jqhtml.txt - JQHTML component system
|
|
controller.txt - Controller click handlers
|
|
error_handling.txt - JavaScript error patterns
|