Rename Checkbox_Multiselect to Checkbox_Multiselect_Input
Implement template method pattern for Form_Input_Abstract 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
349
app/RSpade/man/form_input.txt
Executable file
349
app/RSpade/man/form_input.txt
Executable file
@@ -0,0 +1,349 @@
|
|||||||
|
FORM_INPUT(3) RSX Framework Manual FORM_INPUT(3)
|
||||||
|
|
||||||
|
NAME
|
||||||
|
form_input - Form input component contract and implementation
|
||||||
|
|
||||||
|
SYNOPSIS
|
||||||
|
// Creating a custom input component (template method pattern)
|
||||||
|
class My_Custom_Input extends Form_Input_Abstract {
|
||||||
|
_get_value() {
|
||||||
|
return this.$sid('input').val();
|
||||||
|
}
|
||||||
|
|
||||||
|
_set_value(value) {
|
||||||
|
this.$sid('input').val(value || '');
|
||||||
|
}
|
||||||
|
|
||||||
|
on_ready() {
|
||||||
|
this._mark_ready();
|
||||||
|
|
||||||
|
const that = this;
|
||||||
|
this.$sid('input').on('input', function() {
|
||||||
|
const value = that.val();
|
||||||
|
that.trigger('input', value);
|
||||||
|
that.trigger('val', value);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DESCRIPTION
|
||||||
|
Form input components are the building blocks of RSX forms. They provide
|
||||||
|
a consistent interface for getting and setting values, handling user
|
||||||
|
interaction, and integrating with the form system.
|
||||||
|
|
||||||
|
The base class (Form_Input_Abstract) implements a template method pattern
|
||||||
|
that handles all common logic:
|
||||||
|
- Pre-initialization value buffering
|
||||||
|
- val() getter/setter implementation
|
||||||
|
- Automatic trigger('val') on value changes
|
||||||
|
- Applying buffered values when ready
|
||||||
|
|
||||||
|
Concrete input classes only need to implement:
|
||||||
|
- _get_value() - How to read the current value
|
||||||
|
- _set_value(value) - How to write a value
|
||||||
|
- on_ready() - Call _mark_ready() and setup event handlers
|
||||||
|
|
||||||
|
Key difference from Laravel:
|
||||||
|
- Laravel: Form inputs are standard HTML elements, values read via request
|
||||||
|
- RSX: Form inputs are components with rich behavior and event system
|
||||||
|
|
||||||
|
Unlike regular JQHTML components:
|
||||||
|
- Input components have NO on_load() method
|
||||||
|
- Input components do not use this.data (it's always empty)
|
||||||
|
- Values are managed via the base class val() method
|
||||||
|
- Templates render EMPTY elements (no value attributes)
|
||||||
|
|
||||||
|
TEMPLATE METHOD PATTERN
|
||||||
|
|
||||||
|
The base class handles the complex logic, concrete classes provide simple
|
||||||
|
hooks for getting and setting values.
|
||||||
|
|
||||||
|
Base Class Provides:
|
||||||
|
on_create() Initializes _pending_value and _is_ready
|
||||||
|
val() Full getter/setter with buffering and events
|
||||||
|
_mark_ready() Apply buffered value, mark component ready
|
||||||
|
_transform_value() Default pass-through (override if needed)
|
||||||
|
|
||||||
|
Concrete Class Implements:
|
||||||
|
_get_value() Return current DOM/component value (REQUIRED)
|
||||||
|
_set_value(value) Set DOM/component value (REQUIRED)
|
||||||
|
_transform_value() Transform buffered value for getter (OPTIONAL)
|
||||||
|
on_ready() Call _mark_ready(), setup events (REQUIRED)
|
||||||
|
|
||||||
|
Benefits:
|
||||||
|
- Cannot forget trigger('val') - base class handles it
|
||||||
|
- Cannot forget buffering - base class handles it
|
||||||
|
- Reduced boilerplate: ~10 lines instead of ~40
|
||||||
|
- Consistent behavior across all inputs
|
||||||
|
|
||||||
|
MINIMAL IMPLEMENTATION
|
||||||
|
|
||||||
|
class My_Input extends Form_Input_Abstract {
|
||||||
|
_get_value() {
|
||||||
|
return this.$sid('input').val();
|
||||||
|
}
|
||||||
|
|
||||||
|
_set_value(value) {
|
||||||
|
this.$sid('input').val(value || '');
|
||||||
|
}
|
||||||
|
|
||||||
|
on_ready() {
|
||||||
|
this._mark_ready();
|
||||||
|
|
||||||
|
const that = this;
|
||||||
|
this.$sid('input').on('input', function() {
|
||||||
|
const value = that.val();
|
||||||
|
that.trigger('input', value);
|
||||||
|
that.trigger('val', value);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
That's it. No on_create(), no val() method, no buffering logic.
|
||||||
|
|
||||||
|
VALUE TRANSFORMATION
|
||||||
|
|
||||||
|
Some inputs need to transform the buffered value when returning it.
|
||||||
|
Override _transform_value() for this:
|
||||||
|
|
||||||
|
class Checkbox_Input extends Form_Input_Abstract {
|
||||||
|
on_create() {
|
||||||
|
super.on_create();
|
||||||
|
this.checked_value = this.args.checked_value || '1';
|
||||||
|
this.unchecked_value = this.args.unchecked_value || '0';
|
||||||
|
}
|
||||||
|
|
||||||
|
_get_value() {
|
||||||
|
return this.$sid('input').prop('checked')
|
||||||
|
? this.checked_value
|
||||||
|
: this.unchecked_value;
|
||||||
|
}
|
||||||
|
|
||||||
|
_set_value(value) {
|
||||||
|
const should_check = (value === this.checked_value ||
|
||||||
|
value === '1' || value === 1 || value === true);
|
||||||
|
this.$sid('input').prop('checked', should_check);
|
||||||
|
}
|
||||||
|
|
||||||
|
_transform_value(value) {
|
||||||
|
const should_check = (value === this.checked_value ||
|
||||||
|
value === '1' || value === 1 || value === true);
|
||||||
|
return should_check ? this.checked_value : this.unchecked_value;
|
||||||
|
}
|
||||||
|
|
||||||
|
on_ready() {
|
||||||
|
this._mark_ready();
|
||||||
|
// setup change handler...
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ASYNC INITIALIZATION
|
||||||
|
|
||||||
|
Some components initialize asynchronously (e.g., waiting for external
|
||||||
|
libraries). Call _mark_ready() when actually ready, not at the start
|
||||||
|
of on_ready().
|
||||||
|
|
||||||
|
class Wysiwyg_Input extends Form_Input_Abstract {
|
||||||
|
_get_value() {
|
||||||
|
if (!this.quill) return '';
|
||||||
|
return safe_html(this.quill.root.innerHTML);
|
||||||
|
}
|
||||||
|
|
||||||
|
_set_value(value) {
|
||||||
|
if (value) {
|
||||||
|
this.quill.root.innerHTML = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_transform_value(value) {
|
||||||
|
return safe_html(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
on_ready() {
|
||||||
|
const that = this;
|
||||||
|
quill_ready(function() {
|
||||||
|
that._initialize_quill();
|
||||||
|
that._mark_ready(); // Called AFTER quill is ready
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
EVENT SYSTEM
|
||||||
|
|
||||||
|
Two events serve different purposes:
|
||||||
|
|
||||||
|
'input' Event:
|
||||||
|
Fires only on user interaction. Use for dependent fields, live search,
|
||||||
|
or any logic that should respond to user changes but not programmatic
|
||||||
|
population.
|
||||||
|
|
||||||
|
this.sid('country').on('input', (component, value) => {
|
||||||
|
this.reload_states(value);
|
||||||
|
});
|
||||||
|
|
||||||
|
'val' Event:
|
||||||
|
Fires on ALL value changes. Because jqhtml triggers already-fired
|
||||||
|
events when .on() is registered late, this provides immediate callback
|
||||||
|
with the current value plus all future changes.
|
||||||
|
|
||||||
|
this.sid('amount').on('val', (component, value) => {
|
||||||
|
// Fires immediately with current value
|
||||||
|
// Then fires on every subsequent change
|
||||||
|
this.update_total(value);
|
||||||
|
});
|
||||||
|
|
||||||
|
Event Triggering:
|
||||||
|
- Base class val() setter automatically triggers 'val'
|
||||||
|
- Concrete classes trigger BOTH 'input' and 'val' on user interaction
|
||||||
|
- Never trigger 'input' from the val() setter
|
||||||
|
|
||||||
|
PRE-INITIALIZATION
|
||||||
|
|
||||||
|
Why Required:
|
||||||
|
Forms call vals() during on_ready() to populate all inputs with data.
|
||||||
|
Child input components may still be loading (e.g., Select_Input
|
||||||
|
fetching options via Ajax). The base class buffers the value and
|
||||||
|
applies it when _mark_ready() is called.
|
||||||
|
|
||||||
|
Example Scenario:
|
||||||
|
1. Form on_ready() calls this.vals(data)
|
||||||
|
2. Select_Input is still loading options from server
|
||||||
|
3. Form sets val('123') on the Select_Input
|
||||||
|
4. Base class buffers '123' in _pending_value
|
||||||
|
5. Select_Input on_ready() calls _mark_ready()
|
||||||
|
6. Base class applies buffered '123' via _set_value()
|
||||||
|
|
||||||
|
The base class handles all this automatically. Concrete classes just
|
||||||
|
need to implement _get_value() and _set_value().
|
||||||
|
|
||||||
|
BUILT-IN INPUTS
|
||||||
|
|
||||||
|
Text_Input
|
||||||
|
Text, email, url, tel, number, textarea inputs.
|
||||||
|
<Text_Input $type="email" $placeholder="user@example.com" />
|
||||||
|
<Text_Input $type="textarea" $rows="5" />
|
||||||
|
|
||||||
|
Select_Input
|
||||||
|
Dropdown with static options.
|
||||||
|
<Select_Input $options="<%= JSON.stringify(options) %>" />
|
||||||
|
|
||||||
|
Select_Ajax_Input
|
||||||
|
Dropdown that loads options via Ajax.
|
||||||
|
<Select_Ajax_Input $controller="Controller" $method="get_options" />
|
||||||
|
|
||||||
|
Checkbox_Input
|
||||||
|
Boolean checkbox input.
|
||||||
|
<Checkbox_Input $label="I agree to terms" />
|
||||||
|
|
||||||
|
Checkbox_Multiselect
|
||||||
|
Multiple checkbox selection as array value.
|
||||||
|
|
||||||
|
Wysiwyg_Input
|
||||||
|
Rich text editor (Quill-based).
|
||||||
|
|
||||||
|
EXTENDING OTHER INPUTS
|
||||||
|
|
||||||
|
When extending another input (e.g., Select_Ajax_Input extends Select_Input),
|
||||||
|
call super methods appropriately:
|
||||||
|
|
||||||
|
class Select_Ajax_Input extends Select_Input {
|
||||||
|
async on_load() {
|
||||||
|
this.data.select_values = await Controller.get_options();
|
||||||
|
}
|
||||||
|
|
||||||
|
on_ready() {
|
||||||
|
// Parent sets up TomSelect and calls _mark_ready()
|
||||||
|
super.on_ready();
|
||||||
|
}
|
||||||
|
|
||||||
|
// _get_value() and _set_value() inherited from Select_Input
|
||||||
|
}
|
||||||
|
|
||||||
|
CREATING CUSTOM INPUTS
|
||||||
|
|
||||||
|
1. Create JavaScript class extending Form_Input_Abstract
|
||||||
|
2. Create jqhtml template (NO class="Widget" needed)
|
||||||
|
3. Implement _get_value() and _set_value()
|
||||||
|
4. Implement on_ready() calling _mark_ready() and setting up events
|
||||||
|
|
||||||
|
Template Example:
|
||||||
|
<Define:My_Custom_Input>
|
||||||
|
<div class="my-input-wrapper">
|
||||||
|
<input type="text" $sid="input" class="form-control" />
|
||||||
|
</div>
|
||||||
|
</Define:My_Custom_Input>
|
||||||
|
|
||||||
|
JavaScript Example:
|
||||||
|
class My_Custom_Input extends Form_Input_Abstract {
|
||||||
|
_get_value() {
|
||||||
|
return this.$sid('input').val();
|
||||||
|
}
|
||||||
|
|
||||||
|
_set_value(value) {
|
||||||
|
this.$sid('input').val(value || '');
|
||||||
|
}
|
||||||
|
|
||||||
|
on_ready() {
|
||||||
|
this._mark_ready();
|
||||||
|
|
||||||
|
const that = this;
|
||||||
|
this.$sid('input').on('input', function() {
|
||||||
|
const val = that.val();
|
||||||
|
that.trigger('input', val);
|
||||||
|
that.trigger('val', val);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
COMMON MISTAKES
|
||||||
|
|
||||||
|
Implementing val():
|
||||||
|
Don't override val(). Implement _get_value() and _set_value() instead.
|
||||||
|
The base class handles all the buffering and event logic.
|
||||||
|
|
||||||
|
Forgetting _mark_ready():
|
||||||
|
Must call in on_ready() to apply buffered values and mark ready.
|
||||||
|
|
||||||
|
Calling _mark_ready() too early:
|
||||||
|
For async initialization, wait until actually ready before calling.
|
||||||
|
|
||||||
|
Using this.data:
|
||||||
|
Input components have no on_load(), so this.data is always {}.
|
||||||
|
Never use this.data.value or similar patterns.
|
||||||
|
|
||||||
|
Adding class="Widget":
|
||||||
|
No longer required. The .Form_Input_Abstract class is automatic.
|
||||||
|
|
||||||
|
Not triggering both events:
|
||||||
|
User interaction must trigger both 'input' and 'val'.
|
||||||
|
|
||||||
|
Setting value in template:
|
||||||
|
Templates must render empty. Forms populate via val().
|
||||||
|
|
||||||
|
TROUBLESHOOTING
|
||||||
|
|
||||||
|
Value Not Appearing:
|
||||||
|
- Check _set_value() actually sets DOM element
|
||||||
|
- Verify _mark_ready() is called in on_ready()
|
||||||
|
- Confirm template has $sid="input" or equivalent
|
||||||
|
|
||||||
|
Events Not Firing:
|
||||||
|
- Verify on_ready() sets up DOM event listeners
|
||||||
|
- Confirm both 'input' and 'val' triggered on user interaction
|
||||||
|
- Base class handles trigger('val') in setter automatically
|
||||||
|
|
||||||
|
Form Not Finding Input:
|
||||||
|
- Input must extend Form_Input_Abstract
|
||||||
|
- Check for typos in class name
|
||||||
|
- Verify component is inside Rsx_Form
|
||||||
|
|
||||||
|
Pre-Init Values Lost:
|
||||||
|
- Verify _mark_ready() is called in on_ready()
|
||||||
|
- For async init, call _mark_ready() after initialization completes
|
||||||
|
- Check _get_value() returns correct value
|
||||||
|
|
||||||
|
SEE ALSO
|
||||||
|
jqhtml(3) - Component lifecycle and templates
|
||||||
|
form_conventions(3) - Form patterns and data flow
|
||||||
|
spa(3) - SPA action forms
|
||||||
|
rsx/theme/components/inputs/CLAUDE.md - Implementation details
|
||||||
437
app/RSpade/upstream_changes/form_input_abstract_12_29.txt
Executable file
437
app/RSpade/upstream_changes/form_input_abstract_12_29.txt
Executable file
@@ -0,0 +1,437 @@
|
|||||||
|
FORM INPUT ABSTRACT - MIGRATION GUIDE
|
||||||
|
Date: 2025-12-29
|
||||||
|
|
||||||
|
SUMMARY
|
||||||
|
|
||||||
|
This update standardizes form input component identification and establishes
|
||||||
|
a formal event contract for value changes. The manual `.Widget` CSS class
|
||||||
|
marker is replaced by the automatic `.Form_Input_Abstract` class that jqhtml
|
||||||
|
adds to all components extending that base class.
|
||||||
|
|
||||||
|
Additionally, this update moves common boilerplate into the base class using
|
||||||
|
a template method pattern. Concrete input classes now implement simple
|
||||||
|
_get_value() and _set_value() methods instead of the full val() logic.
|
||||||
|
|
||||||
|
Key changes:
|
||||||
|
- Remove class="Widget" from templates (automatic via inheritance)
|
||||||
|
- Base class handles val() logic, buffering, and event triggers
|
||||||
|
- Concrete classes implement _get_value(), _set_value(), _transform_value()
|
||||||
|
- Call _mark_ready() in on_ready() to apply buffered values
|
||||||
|
|
||||||
|
Reference implementations are provided alongside this document:
|
||||||
|
- form_input_abstract_12_29_reference.js - Complete base class
|
||||||
|
- text_input_12_29_reference.js - Simple input example
|
||||||
|
|
||||||
|
AFFECTED FILES
|
||||||
|
|
||||||
|
Input component templates (remove class="Widget"):
|
||||||
|
- rsx/theme/components/inputs/text/text_input.jqhtml
|
||||||
|
- rsx/theme/components/inputs/select/select_input.jqhtml
|
||||||
|
- rsx/theme/components/inputs/select/Select_Ajax_Input.jqhtml
|
||||||
|
- rsx/theme/components/inputs/select/Select_Country_Input.jqhtml
|
||||||
|
- rsx/theme/components/inputs/select/Select_State_Input.jqhtml
|
||||||
|
- rsx/theme/components/inputs/select/select_with_description_input.jqhtml
|
||||||
|
- rsx/theme/components/inputs/checkbox/checkbox_input.jqhtml
|
||||||
|
- rsx/theme/components/inputs/checkbox_multiselect/checkbox_multiselect.jqhtml
|
||||||
|
- rsx/theme/components/inputs/wysiwyg/wysiwyg_input.jqhtml
|
||||||
|
- rsx/theme/components/inputs/photo/profile_photo_input.jqhtml
|
||||||
|
- rsx/theme/components/inputs/repeater/repeater_simple_input.jqhtml
|
||||||
|
- rsx/theme/components/inputs/select_user_role/select_user_role_input.jqhtml
|
||||||
|
- Any custom input components in your application
|
||||||
|
|
||||||
|
Base class (implement template method pattern):
|
||||||
|
- rsx/theme/components/inputs/form_input_abstract.js
|
||||||
|
|
||||||
|
Input component JavaScript (convert to template method):
|
||||||
|
- All input component .js files that implement val()
|
||||||
|
|
||||||
|
Code that searches for .Widget:
|
||||||
|
- rsx/theme/components/forms/rsx_form.js (vals() method)
|
||||||
|
- Any custom code using .shallowFind('.Widget') or similar
|
||||||
|
|
||||||
|
CHANGES REQUIRED
|
||||||
|
|
||||||
|
1. Remove class="Widget" from Input Component Templates
|
||||||
|
|
||||||
|
All input components currently add class="Widget" manually. This is now
|
||||||
|
redundant because jqhtml automatically adds the class name of any extended
|
||||||
|
class. Components extending Form_Input_Abstract will automatically have
|
||||||
|
the .Form_Input_Abstract class.
|
||||||
|
|
||||||
|
BEFORE:
|
||||||
|
<Define:Text_Input class="Widget">
|
||||||
|
|
||||||
|
AFTER:
|
||||||
|
<Define:Text_Input>
|
||||||
|
|
||||||
|
Search pattern to find affected files:
|
||||||
|
grep -r 'class="Widget"' --include="*.jqhtml" rsx/
|
||||||
|
|
||||||
|
2. Update Code Searching for .Widget to Use .Form_Input_Abstract
|
||||||
|
|
||||||
|
Any code that finds input components via .Widget selector must be updated.
|
||||||
|
|
||||||
|
BEFORE:
|
||||||
|
this.$.shallowFind('.Widget').each(function () {
|
||||||
|
let component = $(this).component();
|
||||||
|
if (component && 'val' in component) {
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
AFTER:
|
||||||
|
this.$.shallowFind('.Form_Input_Abstract').each(function () {
|
||||||
|
let component = $(this).component();
|
||||||
|
// val() is guaranteed by Form_Input_Abstract
|
||||||
|
// ...
|
||||||
|
});
|
||||||
|
|
||||||
|
Note: The 'val' in component check is no longer needed since all
|
||||||
|
Form_Input_Abstract subclasses are required to implement _get_value()
|
||||||
|
and _set_value(), and the base class provides val().
|
||||||
|
|
||||||
|
3. Update Form_Input_Abstract Base Class
|
||||||
|
|
||||||
|
The base class now implements the template method pattern, handling all
|
||||||
|
common logic for buffering, events, and val() getter/setter.
|
||||||
|
|
||||||
|
See: form_input_abstract_12_29_reference.js
|
||||||
|
|
||||||
|
Key methods provided by base class:
|
||||||
|
- on_create() - Initializes _pending_value and _is_ready
|
||||||
|
- val() - Full getter/setter with buffering and event triggers
|
||||||
|
- _mark_ready() - Apply buffered value, call from concrete on_ready()
|
||||||
|
|
||||||
|
Methods to override in concrete classes:
|
||||||
|
- _get_value() - Return current DOM/component value (REQUIRED)
|
||||||
|
- _set_value(value) - Set DOM/component value (REQUIRED)
|
||||||
|
- _transform_value(value) - Transform pending value for getter (OPTIONAL)
|
||||||
|
|
||||||
|
4. Convert Concrete Input Classes to Template Method
|
||||||
|
|
||||||
|
Replace full val() implementation with simple _get_value() and _set_value().
|
||||||
|
|
||||||
|
BEFORE (full boilerplate in each class):
|
||||||
|
class Text_Input extends Form_Input_Abstract {
|
||||||
|
on_create() {
|
||||||
|
this._pending_value = null;
|
||||||
|
this._is_ready = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
val(value) {
|
||||||
|
if (arguments.length === 0) {
|
||||||
|
if (this._pending_value !== null) {
|
||||||
|
return this._pending_value;
|
||||||
|
}
|
||||||
|
return this.$sid('input').val();
|
||||||
|
}
|
||||||
|
if (this._is_ready) {
|
||||||
|
this.$sid('input').val(value || '');
|
||||||
|
this._pending_value = null;
|
||||||
|
} else {
|
||||||
|
this._pending_value = value || '';
|
||||||
|
}
|
||||||
|
this.trigger('val', value);
|
||||||
|
}
|
||||||
|
|
||||||
|
on_ready() {
|
||||||
|
this._is_ready = true;
|
||||||
|
if (this._pending_value !== null) {
|
||||||
|
this.$sid('input').val(this._pending_value);
|
||||||
|
this._pending_value = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const that = this;
|
||||||
|
this.$sid('input').on('input', function() {
|
||||||
|
const value = that.val();
|
||||||
|
that.trigger('input', value);
|
||||||
|
that.trigger('val', value);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
AFTER (template method pattern):
|
||||||
|
class Text_Input extends Form_Input_Abstract {
|
||||||
|
_get_value() {
|
||||||
|
return this.$sid('input').val();
|
||||||
|
}
|
||||||
|
|
||||||
|
_set_value(value) {
|
||||||
|
this.$sid('input').val(value || '');
|
||||||
|
}
|
||||||
|
|
||||||
|
on_ready() {
|
||||||
|
this._mark_ready();
|
||||||
|
|
||||||
|
const that = this;
|
||||||
|
this.$sid('input').on('input', function() {
|
||||||
|
const value = that.val();
|
||||||
|
that.trigger('input', value);
|
||||||
|
that.trigger('val', value);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Note: The concrete class no longer needs:
|
||||||
|
- on_create() for _pending_value/_is_ready (handled by base)
|
||||||
|
- val() method (handled by base)
|
||||||
|
- Buffering logic (handled by base)
|
||||||
|
- trigger('val') calls (handled by base)
|
||||||
|
|
||||||
|
The concrete class ONLY needs:
|
||||||
|
- _get_value() - How to read the current value
|
||||||
|
- _set_value() - How to write a value
|
||||||
|
- on_ready() - Call _mark_ready() and setup user interaction events
|
||||||
|
|
||||||
|
5. Handle Value Transformation (Optional)
|
||||||
|
|
||||||
|
Some inputs need to transform the buffered value when returning it.
|
||||||
|
For example, Checkbox_Input needs to convert boolean/string to
|
||||||
|
checked_value/unchecked_value.
|
||||||
|
|
||||||
|
Override _transform_value() for this:
|
||||||
|
|
||||||
|
class Checkbox_Input extends Form_Input_Abstract {
|
||||||
|
on_create() {
|
||||||
|
super.on_create();
|
||||||
|
this.checked_value = this.args.checked_value || '1';
|
||||||
|
this.unchecked_value = this.args.unchecked_value || '0';
|
||||||
|
}
|
||||||
|
|
||||||
|
_get_value() {
|
||||||
|
const is_checked = this.$sid('input').prop('checked');
|
||||||
|
return is_checked ? this.checked_value : this.unchecked_value;
|
||||||
|
}
|
||||||
|
|
||||||
|
_set_value(value) {
|
||||||
|
let should_check = false;
|
||||||
|
if (typeof value === 'boolean') {
|
||||||
|
should_check = value;
|
||||||
|
} else if (value === this.checked_value || value === '1' || value === 1) {
|
||||||
|
should_check = true;
|
||||||
|
}
|
||||||
|
this.$sid('input').prop('checked', should_check);
|
||||||
|
}
|
||||||
|
|
||||||
|
_transform_value(value) {
|
||||||
|
// Convert pending value to return format
|
||||||
|
let should_check = false;
|
||||||
|
if (typeof value === 'boolean') {
|
||||||
|
should_check = value;
|
||||||
|
} else if (value === this.checked_value || value === '1' || value === 1) {
|
||||||
|
should_check = true;
|
||||||
|
}
|
||||||
|
return should_check ? this.checked_value : this.unchecked_value;
|
||||||
|
}
|
||||||
|
|
||||||
|
on_ready() {
|
||||||
|
this._mark_ready();
|
||||||
|
|
||||||
|
const that = this;
|
||||||
|
this.$sid('input').on('change', function() {
|
||||||
|
const value = that.val();
|
||||||
|
that.trigger('input', value);
|
||||||
|
that.trigger('val', value);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
6. Handle Async Initialization
|
||||||
|
|
||||||
|
Some components initialize asynchronously (e.g., Wysiwyg_Input waits
|
||||||
|
for Quill library). Call _mark_ready() when actually ready, not at
|
||||||
|
the start of on_ready().
|
||||||
|
|
||||||
|
class Wysiwyg_Input extends Form_Input_Abstract {
|
||||||
|
_get_value() {
|
||||||
|
if (!this.quill) return '';
|
||||||
|
return safe_html(this.quill.root.innerHTML);
|
||||||
|
}
|
||||||
|
|
||||||
|
_set_value(value) {
|
||||||
|
if (value) {
|
||||||
|
this.quill.root.innerHTML = value;
|
||||||
|
this.$sid('hidden_input').val(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_transform_value(value) {
|
||||||
|
return safe_html(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
on_ready() {
|
||||||
|
const that = this;
|
||||||
|
|
||||||
|
// Quill loads asynchronously
|
||||||
|
quill_ready(function() {
|
||||||
|
that._initialize_quill();
|
||||||
|
that._mark_ready(); // Called AFTER quill is ready
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
_initialize_quill() {
|
||||||
|
this.quill = new Quill(this.$sid('editor')[0], { ... });
|
||||||
|
|
||||||
|
this.quill.on('text-change', () => {
|
||||||
|
this.$sid('hidden_input').val(this.quill.root.innerHTML);
|
||||||
|
const value = this.val();
|
||||||
|
this.trigger('input', value);
|
||||||
|
this.trigger('val', value);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
7. Components with Complex State
|
||||||
|
|
||||||
|
Some components (like Profile_Photo_Input) use this.state for internal
|
||||||
|
tracking. These work fine with the template method pattern:
|
||||||
|
|
||||||
|
class Profile_Photo_Input extends Form_Input_Abstract {
|
||||||
|
on_create() {
|
||||||
|
super.on_create();
|
||||||
|
this.state = {
|
||||||
|
attachment_key: '',
|
||||||
|
thumbnail_url: ''
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
_get_value() {
|
||||||
|
return this.state.attachment_key || '';
|
||||||
|
}
|
||||||
|
|
||||||
|
_set_value(value) {
|
||||||
|
this.state.attachment_key = value || '';
|
||||||
|
// Update thumbnail URL, trigger re-render, etc.
|
||||||
|
this.render();
|
||||||
|
}
|
||||||
|
|
||||||
|
on_ready() {
|
||||||
|
this._mark_ready();
|
||||||
|
// Setup upload handlers...
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
8. Components that Extend Other Inputs
|
||||||
|
|
||||||
|
Components that extend other inputs (like Select_Ajax_Input extends
|
||||||
|
Select_Input) should call super methods appropriately:
|
||||||
|
|
||||||
|
class Select_Ajax_Input extends Select_Input {
|
||||||
|
async on_load() {
|
||||||
|
// Load options from Ajax
|
||||||
|
this.data.select_values = await Controller.get_options();
|
||||||
|
}
|
||||||
|
|
||||||
|
on_ready() {
|
||||||
|
// Parent sets up TomSelect and calls _mark_ready()
|
||||||
|
super.on_ready();
|
||||||
|
}
|
||||||
|
|
||||||
|
// _get_value() and _set_value() inherited from Select_Input
|
||||||
|
}
|
||||||
|
|
||||||
|
EVENT CONTRACT
|
||||||
|
|
||||||
|
Two events for different use cases:
|
||||||
|
|
||||||
|
'input' Event:
|
||||||
|
- Fires ONLY on user interaction (typing, clicking, selecting)
|
||||||
|
- Does NOT fire when val() setter is called programmatically
|
||||||
|
- Use for: Reacting to user changes, live search, dependent dropdowns
|
||||||
|
|
||||||
|
'val' Event:
|
||||||
|
- Fires on ALL value changes (user interaction AND val() setter)
|
||||||
|
- Because jqhtml triggers already-fired events on late .on() registration,
|
||||||
|
calling component.on('val', callback) immediately invokes callback with
|
||||||
|
the current value
|
||||||
|
- Use for: Always knowing the current value, initialization patterns
|
||||||
|
|
||||||
|
The base class automatically triggers 'val' in the val() setter. Concrete
|
||||||
|
classes must still trigger both 'input' and 'val' on user interaction:
|
||||||
|
|
||||||
|
this.$sid('input').on('input', function() {
|
||||||
|
const value = that.val();
|
||||||
|
that.trigger('input', value); // User interaction
|
||||||
|
that.trigger('val', value); // All changes (user fired, setter is in base)
|
||||||
|
});
|
||||||
|
|
||||||
|
Example Usage:
|
||||||
|
// React only to user changes
|
||||||
|
this.sid('my_input').on('input', (component, value) => {
|
||||||
|
console.log('User changed value to:', value);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Get immediate callback with current value, plus all future changes
|
||||||
|
this.sid('my_input').on('val', (component, value) => {
|
||||||
|
console.log('Value is:', value); // Fires immediately with current value
|
||||||
|
});
|
||||||
|
|
||||||
|
TEMPLATE METHOD BENEFITS
|
||||||
|
|
||||||
|
The template method pattern provides:
|
||||||
|
|
||||||
|
1. Reduced Boilerplate
|
||||||
|
Before: ~30-40 lines of val() logic per component
|
||||||
|
After: ~5-10 lines implementing _get_value() and _set_value()
|
||||||
|
|
||||||
|
2. Enforced Contract
|
||||||
|
- Cannot forget trigger('val') - base class handles it
|
||||||
|
- Cannot forget buffering - base class handles it
|
||||||
|
- Cannot mess up the getter/setter pattern
|
||||||
|
|
||||||
|
3. Consistent Behavior
|
||||||
|
All inputs behave identically for buffering and events because
|
||||||
|
the logic is centralized in one place.
|
||||||
|
|
||||||
|
4. Easier Maintenance
|
||||||
|
Bug fixes or enhancements to buffering/events only need to change
|
||||||
|
the base class, not every input component.
|
||||||
|
|
||||||
|
CONFIGURATION
|
||||||
|
|
||||||
|
No configuration required. These are code changes.
|
||||||
|
|
||||||
|
VERIFICATION
|
||||||
|
|
||||||
|
1. Search for remaining .Widget references:
|
||||||
|
grep -r '\.Widget' --include="*.js" --include="*.jqhtml" rsx/
|
||||||
|
|
||||||
|
2. Verify input components extend Form_Input_Abstract:
|
||||||
|
grep -r "extends Form_Input_Abstract" --include="*.js" rsx/
|
||||||
|
|
||||||
|
3. Verify base class methods are implemented:
|
||||||
|
grep -r "_get_value\|_set_value" --include="*.js" rsx/theme/components/inputs/
|
||||||
|
|
||||||
|
4. Test form value population:
|
||||||
|
- Load a form with data
|
||||||
|
- Verify all fields populate correctly
|
||||||
|
- Check console for errors
|
||||||
|
|
||||||
|
5. Test event triggers:
|
||||||
|
- In console: component.on('val', (c, v) => console.log('val:', v))
|
||||||
|
- Should immediately log current value
|
||||||
|
- Change input, should log new value
|
||||||
|
|
||||||
|
6. Test pre-initialization:
|
||||||
|
- Call val('test') on a Select_Ajax_Input before options load
|
||||||
|
- Verify value is selected after options load
|
||||||
|
|
||||||
|
REFERENCE IMPLEMENTATIONS
|
||||||
|
|
||||||
|
Complete reference implementations are provided alongside this document:
|
||||||
|
|
||||||
|
form_input_abstract_12_29_reference.js
|
||||||
|
The complete base class with template method pattern.
|
||||||
|
Copy this to: rsx/theme/components/inputs/form_input_abstract.js
|
||||||
|
|
||||||
|
text_input_12_29_reference.js
|
||||||
|
Example of a simple input using the new pattern.
|
||||||
|
Shows minimal implementation with _get_value(), _set_value(), on_ready().
|
||||||
|
|
||||||
|
Use these as reference when converting existing input components.
|
||||||
|
|
||||||
|
SEE ALSO
|
||||||
|
|
||||||
|
php artisan rsx:man form_input
|
||||||
|
rsx/theme/components/inputs/CLAUDE.md
|
||||||
|
rsx/theme/components/inputs/form_input_abstract.js
|
||||||
153
app/RSpade/upstream_changes/form_input_abstract_12_29_reference.js
Executable file
153
app/RSpade/upstream_changes/form_input_abstract_12_29_reference.js
Executable file
@@ -0,0 +1,153 @@
|
|||||||
|
/**
|
||||||
|
* Form_Input_Abstract - Base class for all form input widgets
|
||||||
|
*
|
||||||
|
* Reference implementation for form_input_abstract_12_29.txt migration guide.
|
||||||
|
* Copy this to: rsx/theme/components/inputs/form_input_abstract.js
|
||||||
|
*
|
||||||
|
* This class implements the template method pattern, handling:
|
||||||
|
* - Pre-initialization value buffering
|
||||||
|
* - val() getter/setter logic
|
||||||
|
* - Event triggers (trigger('val') on all changes)
|
||||||
|
*
|
||||||
|
* CONCRETE CLASS CONTRACT:
|
||||||
|
*
|
||||||
|
* REQUIRED - Implement these methods:
|
||||||
|
* - _get_value() Return the current DOM/component value
|
||||||
|
* - _set_value(value) Set the DOM/component value
|
||||||
|
*
|
||||||
|
* OPTIONAL - Override if needed:
|
||||||
|
* - _transform_value(value) Transform buffered value for getter return
|
||||||
|
*
|
||||||
|
* REQUIRED - In on_ready():
|
||||||
|
* - Call this._mark_ready() when component is fully initialized
|
||||||
|
* - Setup user interaction event handlers that trigger both 'input' and 'val'
|
||||||
|
*
|
||||||
|
* Example Minimal Implementation:
|
||||||
|
*
|
||||||
|
* class My_Input extends Form_Input_Abstract {
|
||||||
|
* _get_value() {
|
||||||
|
* return this.$sid('input').val();
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
* _set_value(value) {
|
||||||
|
* this.$sid('input').val(value || '');
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
* on_ready() {
|
||||||
|
* this._mark_ready();
|
||||||
|
*
|
||||||
|
* const that = this;
|
||||||
|
* this.$sid('input').on('input', function() {
|
||||||
|
* const value = that.val();
|
||||||
|
* that.trigger('input', value);
|
||||||
|
* that.trigger('val', value);
|
||||||
|
* });
|
||||||
|
* }
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
* EVENT CONTRACT:
|
||||||
|
* - 'val' event fires on ALL value changes (handled by base val() setter)
|
||||||
|
* - 'input' event fires on user interaction ONLY (handled by concrete class)
|
||||||
|
* - Concrete classes trigger BOTH 'input' and 'val' on user interaction
|
||||||
|
*
|
||||||
|
* All widgets automatically have .Form_Input_Abstract class via jqhtml.
|
||||||
|
* Form_Field sets data-name attribute for form integration.
|
||||||
|
*
|
||||||
|
* See: rsx/theme/components/inputs/CLAUDE.md for implementation details
|
||||||
|
*/
|
||||||
|
class Form_Input_Abstract extends Component {
|
||||||
|
/**
|
||||||
|
* Initialize buffering state.
|
||||||
|
* Concrete classes should call super.on_create() if they override this.
|
||||||
|
*/
|
||||||
|
on_create() {
|
||||||
|
this._pending_value = null;
|
||||||
|
this._is_ready = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* val() - Get or set the current value
|
||||||
|
*
|
||||||
|
* This method handles all buffering and event logic. Concrete classes
|
||||||
|
* should NOT override this method. Instead, implement _get_value(),
|
||||||
|
* _set_value(), and optionally _transform_value().
|
||||||
|
*
|
||||||
|
* @param {*} [value] - If provided, sets the value. If omitted, returns the value.
|
||||||
|
* @returns {*} The current value when called as getter
|
||||||
|
*/
|
||||||
|
val(value) {
|
||||||
|
if (arguments.length === 0) {
|
||||||
|
// Getter
|
||||||
|
if (this._pending_value !== null) {
|
||||||
|
return this._transform_value(this._pending_value);
|
||||||
|
}
|
||||||
|
return this._get_value();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Setter
|
||||||
|
if (this._is_ready) {
|
||||||
|
this._set_value(value);
|
||||||
|
this._pending_value = null;
|
||||||
|
} else {
|
||||||
|
this._pending_value = value;
|
||||||
|
}
|
||||||
|
this.trigger('val', value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mark the component as ready and apply any buffered value.
|
||||||
|
* Call this in on_ready() when the component is fully initialized.
|
||||||
|
*
|
||||||
|
* For components with async initialization (e.g., waiting for external
|
||||||
|
* libraries like Quill or TomSelect), call this AFTER initialization
|
||||||
|
* completes, not at the start of on_ready().
|
||||||
|
*/
|
||||||
|
_mark_ready() {
|
||||||
|
this._is_ready = true;
|
||||||
|
if (this._pending_value !== null) {
|
||||||
|
this._set_value(this._pending_value);
|
||||||
|
this._pending_value = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the current value from DOM/component.
|
||||||
|
* Concrete classes MUST implement this method.
|
||||||
|
*
|
||||||
|
* @returns {*} The current value
|
||||||
|
*/
|
||||||
|
_get_value() {
|
||||||
|
throw new Error(`${this.constructor.name} must implement _get_value()`);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the value on DOM/component.
|
||||||
|
* Concrete classes MUST implement this method.
|
||||||
|
*
|
||||||
|
* @param {*} value - The value to set
|
||||||
|
*/
|
||||||
|
_set_value(value) {
|
||||||
|
throw new Error(`${this.constructor.name} must implement _set_value()`);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Transform a pending value for getter return.
|
||||||
|
* Override this if the value needs transformation when returned.
|
||||||
|
*
|
||||||
|
* Example: Checkbox_Input transforms boolean/string to checked_value/unchecked_value
|
||||||
|
*
|
||||||
|
* @param {*} value - The pending value to transform
|
||||||
|
* @returns {*} The transformed value
|
||||||
|
*/
|
||||||
|
_transform_value(value) {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Seed - Fill with random test data (optional)
|
||||||
|
* Subclasses MAY implement this method
|
||||||
|
*/
|
||||||
|
async seed() {
|
||||||
|
// Optional - widgets can override if they support seeding
|
||||||
|
}
|
||||||
|
}
|
||||||
59
app/RSpade/upstream_changes/text_input_12_29_reference.js
Executable file
59
app/RSpade/upstream_changes/text_input_12_29_reference.js
Executable file
@@ -0,0 +1,59 @@
|
|||||||
|
/**
|
||||||
|
* Text_Input - Reference implementation using template method pattern
|
||||||
|
*
|
||||||
|
* Reference implementation for form_input_abstract_12_29.txt migration guide.
|
||||||
|
* Shows the minimal implementation required for a simple input component.
|
||||||
|
*
|
||||||
|
* Key points:
|
||||||
|
* - No on_create() needed (base class handles _pending_value/_is_ready)
|
||||||
|
* - No val() needed (base class handles it)
|
||||||
|
* - Just implement _get_value(), _set_value(), and on_ready()
|
||||||
|
* - Call _mark_ready() in on_ready() to apply buffered values
|
||||||
|
* - Trigger both 'input' and 'val' on user interaction
|
||||||
|
*/
|
||||||
|
class Text_Input extends Form_Input_Abstract {
|
||||||
|
/**
|
||||||
|
* Return the current input value.
|
||||||
|
* Called by base class val() getter.
|
||||||
|
*/
|
||||||
|
_get_value() {
|
||||||
|
return this.$sid('input').val();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the input value.
|
||||||
|
* Called by base class val() setter and _mark_ready().
|
||||||
|
*/
|
||||||
|
_set_value(value) {
|
||||||
|
this.$sid('input').val(value || '');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize component after render.
|
||||||
|
* - Call _mark_ready() to apply any buffered value
|
||||||
|
* - Setup user interaction events
|
||||||
|
*/
|
||||||
|
on_ready() {
|
||||||
|
// Apply any buffered value from pre-ready val() calls
|
||||||
|
this._mark_ready();
|
||||||
|
|
||||||
|
// Trigger events on user interaction
|
||||||
|
const that = this;
|
||||||
|
this.$sid('input').on('input', function() {
|
||||||
|
const value = that.val();
|
||||||
|
that.trigger('input', value); // User interaction only
|
||||||
|
that.trigger('val', value); // All changes
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Seed - Fill with random test data
|
||||||
|
*/
|
||||||
|
async seed() {
|
||||||
|
if (this.args.seeder) {
|
||||||
|
// TODO: Implement Rsx_Random_Values endpoint
|
||||||
|
let value = 'Test ' + (this.args.seeder || 'Value');
|
||||||
|
this.val(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
268
docs/skills/form-input/SKILL.md
Executable file
268
docs/skills/form-input/SKILL.md
Executable file
@@ -0,0 +1,268 @@
|
|||||||
|
---
|
||||||
|
name: form-input
|
||||||
|
description: Creating custom form input components that extend Form_Input_Abstract. Use when building custom input widgets, implementing _get_value()/_set_value(), handling input events, or integrating inputs with Rsx_Form.
|
||||||
|
---
|
||||||
|
|
||||||
|
# Form Input Component Contract
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
Form input components extend `Form_Input_Abstract` which implements a template method pattern. The base class handles value buffering, events, and the val() interface. Concrete classes only implement how to get and set values.
|
||||||
|
|
||||||
|
## Template Method Pattern
|
||||||
|
|
||||||
|
**Base class handles:**
|
||||||
|
- Pre-initialization value buffering (`_pending_value`, `_is_ready`)
|
||||||
|
- Complete `val()` getter/setter logic
|
||||||
|
- Automatic `trigger('val', value)` on all setter calls
|
||||||
|
- Applying buffered values via `_mark_ready()`
|
||||||
|
|
||||||
|
**Concrete classes implement:**
|
||||||
|
- `_get_value()` - Return current DOM/component value (REQUIRED)
|
||||||
|
- `_set_value(value)` - Set DOM/component value (REQUIRED)
|
||||||
|
- `_transform_value(value)` - Transform buffered value for getter (OPTIONAL)
|
||||||
|
- `on_ready()` - Call `_mark_ready()` and setup user interaction events
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Minimal Implementation
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
class My_Custom_Input extends Form_Input_Abstract {
|
||||||
|
_get_value() {
|
||||||
|
return this.$sid('input').val();
|
||||||
|
}
|
||||||
|
|
||||||
|
_set_value(value) {
|
||||||
|
this.$sid('input').val(value || '');
|
||||||
|
}
|
||||||
|
|
||||||
|
on_ready() {
|
||||||
|
this._mark_ready();
|
||||||
|
|
||||||
|
const that = this;
|
||||||
|
this.$sid('input').on('input', function() {
|
||||||
|
const value = that.val();
|
||||||
|
that.trigger('input', value);
|
||||||
|
that.trigger('val', value);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
That's it. No `on_create()`, no `val()` method, no buffering logic.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Key Methods
|
||||||
|
|
||||||
|
### _get_value() (REQUIRED)
|
||||||
|
|
||||||
|
Return the current value from DOM/component:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
_get_value() {
|
||||||
|
return this.$sid('input').val();
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### _set_value(value) (REQUIRED)
|
||||||
|
|
||||||
|
Set the value on DOM/component:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
_set_value(value) {
|
||||||
|
this.$sid('input').val(value || '');
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### _mark_ready()
|
||||||
|
|
||||||
|
Call in `on_ready()` to apply buffered values and mark component ready:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
on_ready() {
|
||||||
|
this._mark_ready(); // REQUIRED - apply buffered value
|
||||||
|
// ... setup event handlers
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
For async initialization, call `_mark_ready()` when actually ready:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
on_ready() {
|
||||||
|
const that = this;
|
||||||
|
quill_ready(function() {
|
||||||
|
that._initialize_quill();
|
||||||
|
that._mark_ready(); // Called AFTER quill is ready
|
||||||
|
});
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### _transform_value(value) (OPTIONAL)
|
||||||
|
|
||||||
|
Transform buffered value for getter return. Used when value needs conversion:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// Example: Checkbox converts to checked_value/unchecked_value
|
||||||
|
_transform_value(value) {
|
||||||
|
const should_check = (value === this.checked_value || value === '1' || value === 1);
|
||||||
|
return should_check ? this.checked_value : this.unchecked_value;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Event Triggers
|
||||||
|
|
||||||
|
On user interaction, trigger BOTH events:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
this.$sid('input').on('input', function() {
|
||||||
|
const value = that.val();
|
||||||
|
that.trigger('input', value); // User interaction only
|
||||||
|
that.trigger('val', value); // All changes
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
**`input` event**: Fires on user interaction ONLY
|
||||||
|
**`val` event**: Fires on ALL value changes (base class handles setter, you handle user interaction)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Event Usage Patterns
|
||||||
|
|
||||||
|
### React to User Changes Only
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
this.sid('country').on('input', (component, value) => {
|
||||||
|
// Only fires when user selects, not when form populates
|
||||||
|
this.reload_states(value);
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
### Get Current Value + All Future Changes
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// Because jqhtml triggers already-fired events on late .on() registration,
|
||||||
|
// this callback fires IMMEDIATELY with current value, then on every change
|
||||||
|
this.sid('amount').on('val', (component, value) => {
|
||||||
|
this.update_total(value);
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Template Requirements
|
||||||
|
|
||||||
|
Templates render elements EMPTY. Forms populate via `val()`:
|
||||||
|
|
||||||
|
```jqhtml
|
||||||
|
<%-- CORRECT --%>
|
||||||
|
<Define:My_Input>
|
||||||
|
<input type="text" $sid="input" class="form-control" />
|
||||||
|
</Define:My_Input>
|
||||||
|
|
||||||
|
<%-- WRONG - never set value in template --%>
|
||||||
|
<Define:My_Input>
|
||||||
|
<input type="text" $sid="input" value="<%= this.data.value %>" />
|
||||||
|
</Define:My_Input>
|
||||||
|
```
|
||||||
|
|
||||||
|
**Note**: Input components have NO `on_load()`, so `this.data` is always `{}`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Value Transformation Example
|
||||||
|
|
||||||
|
For inputs that need to convert values (like Checkbox):
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
class Checkbox_Input extends Form_Input_Abstract {
|
||||||
|
on_create() {
|
||||||
|
super.on_create();
|
||||||
|
this.checked_value = this.args.checked_value || '1';
|
||||||
|
this.unchecked_value = this.args.unchecked_value || '0';
|
||||||
|
}
|
||||||
|
|
||||||
|
_get_value() {
|
||||||
|
return this.$sid('input').prop('checked')
|
||||||
|
? this.checked_value
|
||||||
|
: this.unchecked_value;
|
||||||
|
}
|
||||||
|
|
||||||
|
_set_value(value) {
|
||||||
|
const should_check = (value === this.checked_value ||
|
||||||
|
value === '1' || value === 1 || value === true);
|
||||||
|
this.$sid('input').prop('checked', should_check);
|
||||||
|
}
|
||||||
|
|
||||||
|
_transform_value(value) {
|
||||||
|
const should_check = (value === this.checked_value ||
|
||||||
|
value === '1' || value === 1 || value === true);
|
||||||
|
return should_check ? this.checked_value : this.unchecked_value;
|
||||||
|
}
|
||||||
|
|
||||||
|
on_ready() {
|
||||||
|
this._mark_ready();
|
||||||
|
|
||||||
|
const that = this;
|
||||||
|
this.$sid('input').on('change', function() {
|
||||||
|
const value = that.val();
|
||||||
|
that.trigger('input', value);
|
||||||
|
that.trigger('val', value);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Extending Other Inputs
|
||||||
|
|
||||||
|
When extending another input:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
class Select_Ajax_Input extends Select_Input {
|
||||||
|
async on_load() {
|
||||||
|
this.data.select_values = await Controller.get_options();
|
||||||
|
}
|
||||||
|
|
||||||
|
on_ready() {
|
||||||
|
// Parent sets up TomSelect and calls _mark_ready()
|
||||||
|
super.on_ready();
|
||||||
|
}
|
||||||
|
|
||||||
|
// _get_value() and _set_value() inherited from Select_Input
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Common Mistakes
|
||||||
|
|
||||||
|
| Mistake | Correct Approach |
|
||||||
|
|---------|-----------------|
|
||||||
|
| Implementing `val()` | Use `_get_value()` and `_set_value()` instead |
|
||||||
|
| Forgetting `_mark_ready()` | Must call in `on_ready()` to apply buffered values |
|
||||||
|
| Using `this.data` | Inputs have no `on_load()`, use `val()` only |
|
||||||
|
| Adding `class="Widget"` | Not needed, `.Form_Input_Abstract` is automatic |
|
||||||
|
| Not triggering both events | User interaction must trigger both 'input' and 'val' |
|
||||||
|
| Calling `_mark_ready()` too early | For async init, wait until actually ready |
|
||||||
|
| Setting value in template | Templates render empty, forms populate via `val()` |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Built-in Reference Implementations
|
||||||
|
|
||||||
|
| Component | Notes |
|
||||||
|
|-----------|-------|
|
||||||
|
| `text/text_input.js` | Simple text input - minimal implementation |
|
||||||
|
| `select/select_input.js` | Dropdown with TomSelect |
|
||||||
|
| `checkbox/checkbox_input.js` | Uses `_transform_value()` |
|
||||||
|
| `wysiwyg/wysiwyg_input.js` | Async initialization |
|
||||||
|
|
||||||
|
## More Information
|
||||||
|
|
||||||
|
- `php artisan rsx:man form_input` - Complete documentation
|
||||||
|
- `rsx/theme/components/inputs/CLAUDE.md` - Implementation details
|
||||||
25
node_modules/.package-lock.json
generated
vendored
25
node_modules/.package-lock.json
generated
vendored
@@ -2211,9 +2211,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@jqhtml/core": {
|
"node_modules/@jqhtml/core": {
|
||||||
"version": "2.3.32",
|
"version": "2.3.33",
|
||||||
"resolved": "http://privatenpm.hanson.xyz/@jqhtml/core/-/core-2.3.32.tgz",
|
"resolved": "http://privatenpm.hanson.xyz/@jqhtml/core/-/core-2.3.33.tgz",
|
||||||
"integrity": "sha512-g2BZLfNI4vGS35eR+qVP3WJHMcvYsun8ZFLMlVOB58CnJfdCMeXLhiXZrGr3OFFoxAPaitOG2rmzwqMXKfCvOQ==",
|
"integrity": "sha512-ahGiUKvXTNq7qLT85uNMicKDmrS3BKy43DG7xPuY9iAzPR/Bg03Z1XBkx5VSLWG3U7xstM5/KCA1W2KsZDXXmw==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@rollup/plugin-node-resolve": "^16.0.1",
|
"@rollup/plugin-node-resolve": "^16.0.1",
|
||||||
@@ -2237,9 +2237,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@jqhtml/parser": {
|
"node_modules/@jqhtml/parser": {
|
||||||
"version": "2.3.32",
|
"version": "2.3.33",
|
||||||
"resolved": "http://privatenpm.hanson.xyz/@jqhtml/parser/-/parser-2.3.32.tgz",
|
"resolved": "http://privatenpm.hanson.xyz/@jqhtml/parser/-/parser-2.3.33.tgz",
|
||||||
"integrity": "sha512-YCycfJkxFyr8PsWH8o0wJBImcCi6Eq4m1XFlMDXwAYlBre3H/GkeYpe3JN7xdYP5f/T4Q8RolBW2Y0vzywl27g==",
|
"integrity": "sha512-afQao3d8N06wb3tgLJdhz1leaEK61fXcTD4GEcbidfS1EHn7FXcmC52FWRdJllz7/Y+2iToDZLrdD4Ez6p7/Eg==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/jest": "^29.5.11",
|
"@types/jest": "^29.5.11",
|
||||||
@@ -2277,9 +2277,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@jqhtml/ssr": {
|
"node_modules/@jqhtml/ssr": {
|
||||||
"version": "2.3.32",
|
"version": "2.3.33",
|
||||||
"resolved": "http://privatenpm.hanson.xyz/@jqhtml/ssr/-/ssr-2.3.32.tgz",
|
"resolved": "http://privatenpm.hanson.xyz/@jqhtml/ssr/-/ssr-2.3.33.tgz",
|
||||||
"integrity": "sha512-VFz3rPSdPn8ZZg36Cb8XujALNXXIyTxMdj8kNhNNhcqBWvKn8XxygyZrGCPCpO9cyRJKlhzDpEX+tW4qMqvPng==",
|
"integrity": "sha512-dCO0Z9gedEbNzRMjAqmIXYEdH0jGIaELYCkPRvGT3T76ejmyI9mzkmyfUFDDfCqQDwjaAfF1JZbacOXuTeijdw==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"jquery": "^3.7.1",
|
"jquery": "^3.7.1",
|
||||||
@@ -2373,9 +2373,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@jqhtml/vscode-extension": {
|
"node_modules/@jqhtml/vscode-extension": {
|
||||||
"version": "2.3.32",
|
"version": "2.3.33",
|
||||||
"resolved": "http://privatenpm.hanson.xyz/@jqhtml/vscode-extension/-/vscode-extension-2.3.32.tgz",
|
"resolved": "http://privatenpm.hanson.xyz/@jqhtml/vscode-extension/-/vscode-extension-2.3.33.tgz",
|
||||||
"integrity": "sha512-UL/7JpIi2BS1QLzj60kYg/Womf7O+UHjK0c2/JnSowl813FUmDFgA4DWog1DlUllbUnf4cRvY5MX302FibqGHw==",
|
"integrity": "sha512-Gihx7w61l8zerwAC1xGRVX6J7e+6dL5RjUUQ6Jxiu2nKD1o5CH1zA8hD3+DQFKKT8Uf/rMD5NeRL/rKz3IbSvA==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"vscode": "^1.74.0"
|
"vscode": "^1.74.0"
|
||||||
@@ -12979,6 +12979,7 @@
|
|||||||
"version": "3.1.1",
|
"version": "3.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-3.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-3.1.1.tgz",
|
||||||
"integrity": "sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==",
|
"integrity": "sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==",
|
||||||
|
"deprecated": "Use @exodus/bytes instead for a more spec-conformant and faster implementation",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"iconv-lite": "0.6.3"
|
"iconv-lite": "0.6.3"
|
||||||
|
|||||||
30
node_modules/@jqhtml/core/dist/index.cjs
generated
vendored
30
node_modules/@jqhtml/core/dist/index.cjs
generated
vendored
@@ -100,6 +100,22 @@ class LifecycleManager {
|
|||||||
// If data changed during load, re-render
|
// If data changed during load, re-render
|
||||||
// Note: _render() now calls on_render() internally after DOM update
|
// Note: _render() now calls on_render() internally after DOM update
|
||||||
if (component._should_rerender()) {
|
if (component._should_rerender()) {
|
||||||
|
// Disable animations during re-render to prevent visual artifacts
|
||||||
|
// from CSS transitions/animations firing on newly rendered elements
|
||||||
|
const $el = component.$;
|
||||||
|
if ($el && $el[0]) {
|
||||||
|
$el[0].classList.add('__jqhtml_disable_animations');
|
||||||
|
// Re-enable animations after animation frame + 5ms
|
||||||
|
// This ensures the browser has painted the new DOM before animations resume
|
||||||
|
requestAnimationFrame(() => {
|
||||||
|
setTimeout(() => {
|
||||||
|
// Check component still exists and element is still in DOM
|
||||||
|
if (!component._stopped && $el[0].isConnected) {
|
||||||
|
$el[0].classList.remove('__jqhtml_disable_animations');
|
||||||
|
}
|
||||||
|
}, 5);
|
||||||
|
});
|
||||||
|
}
|
||||||
render_id = component._render();
|
render_id = component._render();
|
||||||
// Check if stopped during re-render
|
// Check if stopped during re-render
|
||||||
if (component._stopped)
|
if (component._stopped)
|
||||||
@@ -5011,7 +5027,7 @@ function init(jQuery) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Version - will be replaced during build with actual version from package.json
|
// Version - will be replaced during build with actual version from package.json
|
||||||
const version = '2.3.32';
|
const version = '2.3.33';
|
||||||
// Default export with all functionality
|
// Default export with all functionality
|
||||||
const jqhtml = {
|
const jqhtml = {
|
||||||
// Core
|
// Core
|
||||||
@@ -5119,6 +5135,18 @@ if (typeof window !== 'undefined' && !window.jqhtml) {
|
|||||||
window.Jqhtml_Component = Jqhtml_Component;
|
window.Jqhtml_Component = Jqhtml_Component;
|
||||||
window.Component = Jqhtml_Component; // For backwards compatibility
|
window.Component = Jqhtml_Component; // For backwards compatibility
|
||||||
window.Jqhtml_LifecycleManager = LifecycleManager;
|
window.Jqhtml_LifecycleManager = LifecycleManager;
|
||||||
|
// Inject animation-disable style into document head
|
||||||
|
// Used to prevent animations during re-render after on_load() data changes
|
||||||
|
const style = document.createElement('style');
|
||||||
|
style.id = '__jqhtml_styles';
|
||||||
|
style.textContent = `
|
||||||
|
.__jqhtml_disable_animations *,
|
||||||
|
.__jqhtml_disable_animations *::before,
|
||||||
|
.__jqhtml_disable_animations *::after {
|
||||||
|
animation: none !important;
|
||||||
|
transition: none !important;
|
||||||
|
}`;
|
||||||
|
document.head.appendChild(style);
|
||||||
// Log in debug mode
|
// Log in debug mode
|
||||||
if (jqhtml.debug?.enabled) {
|
if (jqhtml.debug?.enabled) {
|
||||||
console.log('[JQHTML] Auto-registered window.jqhtml global for template compatibility');
|
console.log('[JQHTML] Auto-registered window.jqhtml global for template compatibility');
|
||||||
|
|||||||
2
node_modules/@jqhtml/core/dist/index.cjs.map
generated
vendored
2
node_modules/@jqhtml/core/dist/index.cjs.map
generated
vendored
File diff suppressed because one or more lines are too long
2
node_modules/@jqhtml/core/dist/index.d.ts.map
generated
vendored
2
node_modules/@jqhtml/core/dist/index.d.ts.map
generated
vendored
@@ -1 +1 @@
|
|||||||
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
||||||
30
node_modules/@jqhtml/core/dist/index.js
generated
vendored
30
node_modules/@jqhtml/core/dist/index.js
generated
vendored
@@ -96,6 +96,22 @@ class LifecycleManager {
|
|||||||
// If data changed during load, re-render
|
// If data changed during load, re-render
|
||||||
// Note: _render() now calls on_render() internally after DOM update
|
// Note: _render() now calls on_render() internally after DOM update
|
||||||
if (component._should_rerender()) {
|
if (component._should_rerender()) {
|
||||||
|
// Disable animations during re-render to prevent visual artifacts
|
||||||
|
// from CSS transitions/animations firing on newly rendered elements
|
||||||
|
const $el = component.$;
|
||||||
|
if ($el && $el[0]) {
|
||||||
|
$el[0].classList.add('__jqhtml_disable_animations');
|
||||||
|
// Re-enable animations after animation frame + 5ms
|
||||||
|
// This ensures the browser has painted the new DOM before animations resume
|
||||||
|
requestAnimationFrame(() => {
|
||||||
|
setTimeout(() => {
|
||||||
|
// Check component still exists and element is still in DOM
|
||||||
|
if (!component._stopped && $el[0].isConnected) {
|
||||||
|
$el[0].classList.remove('__jqhtml_disable_animations');
|
||||||
|
}
|
||||||
|
}, 5);
|
||||||
|
});
|
||||||
|
}
|
||||||
render_id = component._render();
|
render_id = component._render();
|
||||||
// Check if stopped during re-render
|
// Check if stopped during re-render
|
||||||
if (component._stopped)
|
if (component._stopped)
|
||||||
@@ -5007,7 +5023,7 @@ function init(jQuery) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Version - will be replaced during build with actual version from package.json
|
// Version - will be replaced during build with actual version from package.json
|
||||||
const version = '2.3.32';
|
const version = '2.3.33';
|
||||||
// Default export with all functionality
|
// Default export with all functionality
|
||||||
const jqhtml = {
|
const jqhtml = {
|
||||||
// Core
|
// Core
|
||||||
@@ -5115,6 +5131,18 @@ if (typeof window !== 'undefined' && !window.jqhtml) {
|
|||||||
window.Jqhtml_Component = Jqhtml_Component;
|
window.Jqhtml_Component = Jqhtml_Component;
|
||||||
window.Component = Jqhtml_Component; // For backwards compatibility
|
window.Component = Jqhtml_Component; // For backwards compatibility
|
||||||
window.Jqhtml_LifecycleManager = LifecycleManager;
|
window.Jqhtml_LifecycleManager = LifecycleManager;
|
||||||
|
// Inject animation-disable style into document head
|
||||||
|
// Used to prevent animations during re-render after on_load() data changes
|
||||||
|
const style = document.createElement('style');
|
||||||
|
style.id = '__jqhtml_styles';
|
||||||
|
style.textContent = `
|
||||||
|
.__jqhtml_disable_animations *,
|
||||||
|
.__jqhtml_disable_animations *::before,
|
||||||
|
.__jqhtml_disable_animations *::after {
|
||||||
|
animation: none !important;
|
||||||
|
transition: none !important;
|
||||||
|
}`;
|
||||||
|
document.head.appendChild(style);
|
||||||
// Log in debug mode
|
// Log in debug mode
|
||||||
if (jqhtml.debug?.enabled) {
|
if (jqhtml.debug?.enabled) {
|
||||||
console.log('[JQHTML] Auto-registered window.jqhtml global for template compatibility');
|
console.log('[JQHTML] Auto-registered window.jqhtml global for template compatibility');
|
||||||
|
|||||||
2
node_modules/@jqhtml/core/dist/index.js.map
generated
vendored
2
node_modules/@jqhtml/core/dist/index.js.map
generated
vendored
File diff suppressed because one or more lines are too long
32
node_modules/@jqhtml/core/dist/jqhtml-core.esm.js
generated
vendored
32
node_modules/@jqhtml/core/dist/jqhtml-core.esm.js
generated
vendored
@@ -1,5 +1,5 @@
|
|||||||
/**
|
/**
|
||||||
* JQHTML Core v2.3.32
|
* JQHTML Core v2.3.33
|
||||||
* (c) 2025 JQHTML Team
|
* (c) 2025 JQHTML Team
|
||||||
* Released under the MIT License
|
* Released under the MIT License
|
||||||
*/
|
*/
|
||||||
@@ -101,6 +101,22 @@ class LifecycleManager {
|
|||||||
// If data changed during load, re-render
|
// If data changed during load, re-render
|
||||||
// Note: _render() now calls on_render() internally after DOM update
|
// Note: _render() now calls on_render() internally after DOM update
|
||||||
if (component._should_rerender()) {
|
if (component._should_rerender()) {
|
||||||
|
// Disable animations during re-render to prevent visual artifacts
|
||||||
|
// from CSS transitions/animations firing on newly rendered elements
|
||||||
|
const $el = component.$;
|
||||||
|
if ($el && $el[0]) {
|
||||||
|
$el[0].classList.add('__jqhtml_disable_animations');
|
||||||
|
// Re-enable animations after animation frame + 5ms
|
||||||
|
// This ensures the browser has painted the new DOM before animations resume
|
||||||
|
requestAnimationFrame(() => {
|
||||||
|
setTimeout(() => {
|
||||||
|
// Check component still exists and element is still in DOM
|
||||||
|
if (!component._stopped && $el[0].isConnected) {
|
||||||
|
$el[0].classList.remove('__jqhtml_disable_animations');
|
||||||
|
}
|
||||||
|
}, 5);
|
||||||
|
});
|
||||||
|
}
|
||||||
render_id = component._render();
|
render_id = component._render();
|
||||||
// Check if stopped during re-render
|
// Check if stopped during re-render
|
||||||
if (component._stopped)
|
if (component._stopped)
|
||||||
@@ -5012,7 +5028,7 @@ function init(jQuery) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Version - will be replaced during build with actual version from package.json
|
// Version - will be replaced during build with actual version from package.json
|
||||||
const version = '2.3.32';
|
const version = '2.3.33';
|
||||||
// Default export with all functionality
|
// Default export with all functionality
|
||||||
const jqhtml = {
|
const jqhtml = {
|
||||||
// Core
|
// Core
|
||||||
@@ -5120,6 +5136,18 @@ if (typeof window !== 'undefined' && !window.jqhtml) {
|
|||||||
window.Jqhtml_Component = Jqhtml_Component;
|
window.Jqhtml_Component = Jqhtml_Component;
|
||||||
window.Component = Jqhtml_Component; // For backwards compatibility
|
window.Component = Jqhtml_Component; // For backwards compatibility
|
||||||
window.Jqhtml_LifecycleManager = LifecycleManager;
|
window.Jqhtml_LifecycleManager = LifecycleManager;
|
||||||
|
// Inject animation-disable style into document head
|
||||||
|
// Used to prevent animations during re-render after on_load() data changes
|
||||||
|
const style = document.createElement('style');
|
||||||
|
style.id = '__jqhtml_styles';
|
||||||
|
style.textContent = `
|
||||||
|
.__jqhtml_disable_animations *,
|
||||||
|
.__jqhtml_disable_animations *::before,
|
||||||
|
.__jqhtml_disable_animations *::after {
|
||||||
|
animation: none !important;
|
||||||
|
transition: none !important;
|
||||||
|
}`;
|
||||||
|
document.head.appendChild(style);
|
||||||
// Log in debug mode
|
// Log in debug mode
|
||||||
if (jqhtml.debug?.enabled) {
|
if (jqhtml.debug?.enabled) {
|
||||||
console.log('[JQHTML] Auto-registered window.jqhtml global for template compatibility');
|
console.log('[JQHTML] Auto-registered window.jqhtml global for template compatibility');
|
||||||
|
|||||||
2
node_modules/@jqhtml/core/dist/jqhtml-core.esm.js.map
generated
vendored
2
node_modules/@jqhtml/core/dist/jqhtml-core.esm.js.map
generated
vendored
File diff suppressed because one or more lines are too long
2
node_modules/@jqhtml/core/dist/lifecycle-manager.d.ts.map
generated
vendored
2
node_modules/@jqhtml/core/dist/lifecycle-manager.d.ts.map
generated
vendored
@@ -1 +1 @@
|
|||||||
{"version":3,"file":"lifecycle-manager.d.ts","sourceRoot":"","sources":["../src/lifecycle-manager.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AAEvD,MAAM,MAAM,cAAc,GAAG,QAAQ,GAAG,QAAQ,GAAG,MAAM,GAAG,OAAO,CAAC;AAEpE,qBAAa,gBAAgB;IAC3B,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAmB;IAC1C,OAAO,CAAC,iBAAiB,CAAoC;IAE7D,MAAM,CAAC,YAAY,IAAI,gBAAgB;;IAevC;;;;;;;;;OASG;IACG,cAAc,CAAC,SAAS,EAAE,gBAAgB,GAAG,OAAO,CAAC,IAAI,CAAC;IAwHhE;;OAEG;IACH,oBAAoB,CAAC,SAAS,EAAE,gBAAgB,GAAG,IAAI;IAIvD;;OAEG;IACG,cAAc,IAAI,OAAO,CAAC,IAAI,CAAC;CAetC"}
|
{"version":3,"file":"lifecycle-manager.d.ts","sourceRoot":"","sources":["../src/lifecycle-manager.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AAEvD,MAAM,MAAM,cAAc,GAAG,QAAQ,GAAG,QAAQ,GAAG,MAAM,GAAG,OAAO,CAAC;AAEpE,qBAAa,gBAAgB;IAC3B,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAmB;IAC1C,OAAO,CAAC,iBAAiB,CAAoC;IAE7D,MAAM,CAAC,YAAY,IAAI,gBAAgB;;IAevC;;;;;;;;;OASG;IACG,cAAc,CAAC,SAAS,EAAE,gBAAgB,GAAG,OAAO,CAAC,IAAI,CAAC;IA0IhE;;OAEG;IACH,oBAAoB,CAAC,SAAS,EAAE,gBAAgB,GAAG,IAAI;IAIvD;;OAEG;IACG,cAAc,IAAI,OAAO,CAAC,IAAI,CAAC;CAetC"}
|
||||||
2
node_modules/@jqhtml/core/package.json
generated
vendored
2
node_modules/@jqhtml/core/package.json
generated
vendored
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@jqhtml/core",
|
"name": "@jqhtml/core",
|
||||||
"version": "2.3.32",
|
"version": "2.3.33",
|
||||||
"description": "Core runtime library for JQHTML",
|
"description": "Core runtime library for JQHTML",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "./dist/index.js",
|
"main": "./dist/index.js",
|
||||||
|
|||||||
2
node_modules/@jqhtml/parser/dist/codegen.js
generated
vendored
2
node_modules/@jqhtml/parser/dist/codegen.js
generated
vendored
@@ -1377,7 +1377,7 @@ export class CodeGenerator {
|
|||||||
for (const [name, component] of this.components) {
|
for (const [name, component] of this.components) {
|
||||||
code += `// Component: ${name}\n`;
|
code += `// Component: ${name}\n`;
|
||||||
code += `jqhtml_components.set('${name}', {\n`;
|
code += `jqhtml_components.set('${name}', {\n`;
|
||||||
code += ` _jqhtml_version: '2.3.32',\n`; // Version will be replaced during build
|
code += ` _jqhtml_version: '2.3.33',\n`; // Version will be replaced during build
|
||||||
code += ` name: '${name}',\n`;
|
code += ` name: '${name}',\n`;
|
||||||
code += ` tag: '${component.tagName}',\n`;
|
code += ` tag: '${component.tagName}',\n`;
|
||||||
code += ` defaultAttributes: ${this.serializeAttributeObject(component.defaultAttributes)},\n`;
|
code += ` defaultAttributes: ${this.serializeAttributeObject(component.defaultAttributes)},\n`;
|
||||||
|
|||||||
2
node_modules/@jqhtml/parser/package.json
generated
vendored
2
node_modules/@jqhtml/parser/package.json
generated
vendored
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@jqhtml/parser",
|
"name": "@jqhtml/parser",
|
||||||
"version": "2.3.32",
|
"version": "2.3.33",
|
||||||
"description": "JQHTML template parser - converts templates to JavaScript",
|
"description": "JQHTML template parser - converts templates to JavaScript",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "dist/index.js",
|
"main": "dist/index.js",
|
||||||
|
|||||||
2
node_modules/@jqhtml/ssr/package.json
generated
vendored
2
node_modules/@jqhtml/ssr/package.json
generated
vendored
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@jqhtml/ssr",
|
"name": "@jqhtml/ssr",
|
||||||
"version": "2.3.32",
|
"version": "2.3.33",
|
||||||
"description": "Server-Side Rendering for JQHTML components - renders components to HTML for SEO",
|
"description": "Server-Side Rendering for JQHTML components - renders components to HTML for SEO",
|
||||||
"main": "src/index.js",
|
"main": "src/index.js",
|
||||||
"bin": {
|
"bin": {
|
||||||
|
|||||||
2
node_modules/@jqhtml/vscode-extension/.version
generated
vendored
2
node_modules/@jqhtml/vscode-extension/.version
generated
vendored
@@ -1 +1 @@
|
|||||||
2.3.32
|
2.3.33
|
||||||
|
|||||||
Binary file not shown.
2
node_modules/@jqhtml/vscode-extension/package.json
generated
vendored
2
node_modules/@jqhtml/vscode-extension/package.json
generated
vendored
@@ -2,7 +2,7 @@
|
|||||||
"name": "@jqhtml/vscode-extension",
|
"name": "@jqhtml/vscode-extension",
|
||||||
"displayName": "JQHTML",
|
"displayName": "JQHTML",
|
||||||
"description": "Syntax highlighting and language support for JQHTML template files",
|
"description": "Syntax highlighting and language support for JQHTML template files",
|
||||||
"version": "2.3.32",
|
"version": "2.3.33",
|
||||||
"publisher": "jqhtml",
|
"publisher": "jqhtml",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"publishConfig": {
|
"publishConfig": {
|
||||||
|
|||||||
25
package-lock.json
generated
25
package-lock.json
generated
@@ -2660,9 +2660,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@jqhtml/core": {
|
"node_modules/@jqhtml/core": {
|
||||||
"version": "2.3.32",
|
"version": "2.3.33",
|
||||||
"resolved": "http://privatenpm.hanson.xyz/@jqhtml/core/-/core-2.3.32.tgz",
|
"resolved": "http://privatenpm.hanson.xyz/@jqhtml/core/-/core-2.3.33.tgz",
|
||||||
"integrity": "sha512-g2BZLfNI4vGS35eR+qVP3WJHMcvYsun8ZFLMlVOB58CnJfdCMeXLhiXZrGr3OFFoxAPaitOG2rmzwqMXKfCvOQ==",
|
"integrity": "sha512-ahGiUKvXTNq7qLT85uNMicKDmrS3BKy43DG7xPuY9iAzPR/Bg03Z1XBkx5VSLWG3U7xstM5/KCA1W2KsZDXXmw==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@rollup/plugin-node-resolve": "^16.0.1",
|
"@rollup/plugin-node-resolve": "^16.0.1",
|
||||||
@@ -2686,9 +2686,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@jqhtml/parser": {
|
"node_modules/@jqhtml/parser": {
|
||||||
"version": "2.3.32",
|
"version": "2.3.33",
|
||||||
"resolved": "http://privatenpm.hanson.xyz/@jqhtml/parser/-/parser-2.3.32.tgz",
|
"resolved": "http://privatenpm.hanson.xyz/@jqhtml/parser/-/parser-2.3.33.tgz",
|
||||||
"integrity": "sha512-YCycfJkxFyr8PsWH8o0wJBImcCi6Eq4m1XFlMDXwAYlBre3H/GkeYpe3JN7xdYP5f/T4Q8RolBW2Y0vzywl27g==",
|
"integrity": "sha512-afQao3d8N06wb3tgLJdhz1leaEK61fXcTD4GEcbidfS1EHn7FXcmC52FWRdJllz7/Y+2iToDZLrdD4Ez6p7/Eg==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/jest": "^29.5.11",
|
"@types/jest": "^29.5.11",
|
||||||
@@ -2726,9 +2726,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@jqhtml/ssr": {
|
"node_modules/@jqhtml/ssr": {
|
||||||
"version": "2.3.32",
|
"version": "2.3.33",
|
||||||
"resolved": "http://privatenpm.hanson.xyz/@jqhtml/ssr/-/ssr-2.3.32.tgz",
|
"resolved": "http://privatenpm.hanson.xyz/@jqhtml/ssr/-/ssr-2.3.33.tgz",
|
||||||
"integrity": "sha512-VFz3rPSdPn8ZZg36Cb8XujALNXXIyTxMdj8kNhNNhcqBWvKn8XxygyZrGCPCpO9cyRJKlhzDpEX+tW4qMqvPng==",
|
"integrity": "sha512-dCO0Z9gedEbNzRMjAqmIXYEdH0jGIaELYCkPRvGT3T76ejmyI9mzkmyfUFDDfCqQDwjaAfF1JZbacOXuTeijdw==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"jquery": "^3.7.1",
|
"jquery": "^3.7.1",
|
||||||
@@ -2822,9 +2822,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@jqhtml/vscode-extension": {
|
"node_modules/@jqhtml/vscode-extension": {
|
||||||
"version": "2.3.32",
|
"version": "2.3.33",
|
||||||
"resolved": "http://privatenpm.hanson.xyz/@jqhtml/vscode-extension/-/vscode-extension-2.3.32.tgz",
|
"resolved": "http://privatenpm.hanson.xyz/@jqhtml/vscode-extension/-/vscode-extension-2.3.33.tgz",
|
||||||
"integrity": "sha512-UL/7JpIi2BS1QLzj60kYg/Womf7O+UHjK0c2/JnSowl813FUmDFgA4DWog1DlUllbUnf4cRvY5MX302FibqGHw==",
|
"integrity": "sha512-Gihx7w61l8zerwAC1xGRVX6J7e+6dL5RjUUQ6Jxiu2nKD1o5CH1zA8hD3+DQFKKT8Uf/rMD5NeRL/rKz3IbSvA==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"vscode": "^1.74.0"
|
"vscode": "^1.74.0"
|
||||||
@@ -13969,6 +13969,7 @@
|
|||||||
"version": "3.1.1",
|
"version": "3.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-3.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-3.1.1.tgz",
|
||||||
"integrity": "sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==",
|
"integrity": "sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==",
|
||||||
|
"deprecated": "Use @exodus/bytes instead for a more spec-conformant and faster implementation",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"iconv-lite": "0.6.3"
|
"iconv-lite": "0.6.3"
|
||||||
|
|||||||
Reference in New Issue
Block a user