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:
root
2025-12-29 07:05:46 +00:00
parent bded711d1c
commit 88aa57d591
22 changed files with 1391 additions and 39 deletions

349
app/RSpade/man/form_input.txt Executable file
View 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

View 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

View 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
}
}

View 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);
}
}
}