Add Text_Input $max_length requirement and Model.field_length() API
🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -546,6 +546,24 @@ class Database_BundleIntegration extends BundleIntegration_Abstract
|
||||
$content .= " }\n\n";
|
||||
}
|
||||
|
||||
// Generate field_length() method for varchar max lengths
|
||||
$varchar_lengths = [];
|
||||
foreach ($columns as $col_name => $col_data) {
|
||||
if (isset($col_data['max_length']) && $col_data['max_length'] !== null) {
|
||||
$varchar_lengths[$col_name] = $col_data['max_length'];
|
||||
}
|
||||
}
|
||||
|
||||
$content .= " /**\n";
|
||||
$content .= " * Get max length for a varchar/char column.\n";
|
||||
$content .= " * @param {string} column - Column name\n";
|
||||
$content .= " * @returns {number|null} Max length for varchar/char columns, null for other types\n";
|
||||
$content .= " */\n";
|
||||
$content .= " static field_length(column) {\n";
|
||||
$content .= " const lengths = " . json_encode($varchar_lengths, JSON_FORCE_OBJECT) . ";\n";
|
||||
$content .= " return lengths[column] ?? null;\n";
|
||||
$content .= " }\n\n";
|
||||
|
||||
$content .= "}\n";
|
||||
|
||||
return $content;
|
||||
|
||||
@@ -75,6 +75,7 @@ class Model_ManifestSupport extends ManifestSupport_Abstract
|
||||
foreach ($column_results as $column) {
|
||||
$columns[$column->Field] = [
|
||||
'type' => static::__parse_column_type($column->Type),
|
||||
'max_length' => static::__parse_varchar_length($column->Type),
|
||||
'nullable' => ($column->Null === 'YES'),
|
||||
'key' => $column->Key,
|
||||
'default' => $column->Default,
|
||||
@@ -158,6 +159,24 @@ class Model_ManifestSupport extends ManifestSupport_Abstract
|
||||
return $type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract varchar length from MySQL column type
|
||||
*
|
||||
* Returns the max length for varchar/char fields, null for all other types.
|
||||
*
|
||||
* @param string $type MySQL column type (e.g., "varchar(255)", "char(10)", "text")
|
||||
* @return int|null Max length for varchar/char, null otherwise
|
||||
*/
|
||||
protected static function __parse_varchar_length(string $type): ?int
|
||||
{
|
||||
// Match varchar(N) or char(N)
|
||||
if (preg_match('/^(?:varchar|char)\((\d+)\)/i', $type, $matches)) {
|
||||
return (int) $matches[1];
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the name of this support module
|
||||
*
|
||||
|
||||
@@ -220,8 +220,18 @@ 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" />
|
||||
REQUIRES $max_length argument for character limits.
|
||||
|
||||
<Text_Input $name="email" $max_length=User_Model.field_length('email') />
|
||||
<Text_Input $name="notes" $type="textarea" $max_length=-1 $rows="5" />
|
||||
|
||||
$max_length values:
|
||||
- Positive number: Sets HTML maxlength attribute
|
||||
- -1: Unlimited (no maxlength applied)
|
||||
- Undefined: Console error with guidance
|
||||
|
||||
Use Model.field_length('column') for database-driven limits.
|
||||
Subclasses (Phone_Text_Input, Currency_Input) are exempt.
|
||||
|
||||
Select_Input
|
||||
Dropdown with static options.
|
||||
|
||||
@@ -79,12 +79,34 @@ Remove any implementations of:
|
||||
- `_transform_value(value)` - no longer in base class
|
||||
- `seed()` - removed from Form_Input_Abstract contract
|
||||
|
||||
### 6. Update _mark_ready() implementation
|
||||
|
||||
If you've copied `Form_Input_Abstract` or have custom input classes, update `_mark_ready()`:
|
||||
|
||||
```javascript
|
||||
_mark_ready() {
|
||||
this._is_ready = true;
|
||||
if (this._pending_value !== null) {
|
||||
this._set_value(this._pending_value);
|
||||
this.trigger('val', this._pending_value);
|
||||
this._pending_value = null;
|
||||
} else {
|
||||
this.trigger('val', this._get_value());
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The fix: `_mark_ready()` now always triggers the 'val' event on initialization.
|
||||
This ensures listeners registered via `this.sid('input').on('val', ...)` receive
|
||||
the initial value even when no value was buffered.
|
||||
|
||||
## How It Works Now
|
||||
|
||||
1. Input component receives `$name="email"` as argument
|
||||
2. `Form_Input_Abstract.on_create()` sets `data-name="email"` on component root
|
||||
3. `Form_Field` finds child `.Form_Input_Abstract` and reads its `data-name`
|
||||
4. `Rsx_Form.vals()` finds all `[data-name]` elements and calls `val()` on each
|
||||
5. `_mark_ready()` triggers 'val' event with initial value on component ready
|
||||
|
||||
## Validation
|
||||
|
||||
|
||||
139
app/RSpade/upstream_changes/text_field_max_lengths_12_29.txt
Executable file
139
app/RSpade/upstream_changes/text_field_max_lengths_12_29.txt
Executable file
@@ -0,0 +1,139 @@
|
||||
# Text Input $max_length Requirement
|
||||
|
||||
**Date:** 2025-12-29
|
||||
**Affects:** All Text_Input and textarea components
|
||||
|
||||
## Breaking Change
|
||||
|
||||
`Text_Input` now **requires** the `$max_length` argument. This ensures all text fields have explicit character limits tied to database schema.
|
||||
|
||||
### Before (Old Pattern)
|
||||
```html
|
||||
<Text_Input $name="email" $placeholder="user@example.com" />
|
||||
<Text_Input $name="notes" $type="textarea" $rows=5 />
|
||||
```
|
||||
|
||||
### After (New Pattern)
|
||||
```html
|
||||
<Text_Input $name="email" $max_length=User_Model.field_length('email') $placeholder="user@example.com" />
|
||||
<Text_Input $name="notes" $type="textarea" $max_length=-1 $rows=5 />
|
||||
```
|
||||
|
||||
## Why This Change
|
||||
|
||||
Database VARCHAR columns have length limits. When forms don't enforce these limits:
|
||||
- Users can enter text that gets truncated on save
|
||||
- Data loss occurs silently
|
||||
- No feedback to user about constraints
|
||||
|
||||
By requiring `$max_length`, we ensure:
|
||||
- Character limits are enforced in the UI
|
||||
- Limits come from a single source of truth (database schema)
|
||||
- Changes to database column sizes automatically propagate to forms
|
||||
|
||||
## The field_length() API
|
||||
|
||||
Models now expose a `field_length(column)` static method that returns the VARCHAR max length:
|
||||
|
||||
```javascript
|
||||
// Returns max length for varchar/char columns, null for others
|
||||
User_Model.field_length('email') // 255
|
||||
User_Model.field_length('username') // 50
|
||||
User_Model.field_length('id') // null (not a varchar)
|
||||
```
|
||||
|
||||
This is generated automatically from database schema metadata.
|
||||
|
||||
## $max_length Values
|
||||
|
||||
| Value | Behavior |
|
||||
|-------|----------|
|
||||
| Positive number | Sets HTML `maxlength` attribute to that value |
|
||||
| `-1` | Unlimited - no maxlength attribute applied |
|
||||
| `undefined` | **Error** - console.error with guidance |
|
||||
|
||||
## Migration Checklist
|
||||
|
||||
### 1. Find all Text_Input components
|
||||
|
||||
```bash
|
||||
grep -rn '<Text_Input' rsx/app --include="*.jqhtml" --include="*.blade.php"
|
||||
```
|
||||
|
||||
### 2. Add $max_length to each
|
||||
|
||||
For database-backed fields (recommended):
|
||||
```html
|
||||
<Text_Input $name="email" $max_length=User_Model.field_length('email') />
|
||||
```
|
||||
|
||||
For custom limits:
|
||||
```html
|
||||
<Text_Input $name="search" $max_length=100 />
|
||||
```
|
||||
|
||||
For truly unlimited fields (use sparingly):
|
||||
```html
|
||||
<Text_Input $name="notes" $type="textarea" $max_length=-1 />
|
||||
```
|
||||
|
||||
### 3. Verify no console errors
|
||||
|
||||
Load each form and check browser console. Any Text_Input missing `$max_length` will log:
|
||||
|
||||
```
|
||||
Text_Input with $name="fieldname" requires $max_length. Use $max_length=Model_Name.field_length('column_name') for database-driven limits, a numeric value for custom limits, or -1 for unlimited.
|
||||
```
|
||||
|
||||
## Subclasses Are Exempt
|
||||
|
||||
Components extending `Text_Input` (like `Phone_Text_Input`, `Currency_Input`) are NOT affected. The validation only triggers when `Text_Input` is used directly:
|
||||
|
||||
```javascript
|
||||
// In Text_Input.on_create():
|
||||
if (this.constructor === Text_Input && this.args.max_length === undefined) {
|
||||
console.error(...);
|
||||
}
|
||||
```
|
||||
|
||||
This allows specialized inputs to define their own intrinsic limits.
|
||||
|
||||
## Custom Text Input Components
|
||||
|
||||
If you have custom components extending `Text_Input`, they automatically bypass the $max_length requirement. If you want your custom component to also require $max_length, add the same check:
|
||||
|
||||
```javascript
|
||||
class My_Text_Input extends Text_Input {
|
||||
on_create() {
|
||||
super.on_create();
|
||||
|
||||
// Optionally enforce $max_length on this component too
|
||||
if (this.constructor === My_Text_Input && this.args.max_length === undefined) {
|
||||
console.error(`My_Text_Input with $name="${this.args.name}" requires $max_length.`);
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Renamed Argument
|
||||
|
||||
The old `$maxlength` argument has been renamed to `$max_length` (with underscore) for consistency with RSX naming conventions.
|
||||
|
||||
```html
|
||||
<!-- OLD (no longer works) -->
|
||||
<Text_Input $maxlength="255" />
|
||||
|
||||
<!-- NEW -->
|
||||
<Text_Input $max_length=255 />
|
||||
```
|
||||
|
||||
## Validation
|
||||
|
||||
After migration, verify no console errors by loading forms and checking browser console:
|
||||
|
||||
```bash
|
||||
# Find all forms in the app
|
||||
find rsx/app -name "*.jqhtml" -exec grep -l '<Text_Input' {} \;
|
||||
```
|
||||
|
||||
Load each form and confirm no $max_length errors in console.
|
||||
Reference in New Issue
Block a user