Appearance
x:model Directive
The x:model directive provides two-way data binding between form elements and widget properties.
Basic Usage
Syntax
html
<input x:model="propertyName">Supported Input Types
Text Input
typescript
@Widget({
selector: '.text-input',
template: `
<div>
<input x:model="name" type="text" placeholder="Name">
<input x:model="email" type="email" placeholder="Email">
<input x:model="phone" type="tel" placeholder="Phone">
<textarea x:model="message" placeholder="Message"></textarea>
</div>
`
})
export class TextInputWidget {
@xproperty()
name: string = '';
@xproperty()
email: string = '';
@xproperty()
phone: string = '';
@xproperty()
message: string = '';
}Number Input
typescript
@Widget({
selector: '.number-input',
template: `
<div>
<input x:model="age" type="number" min="0" max="120">
<input x:model="price" type="number" step="0.01">
<input x:model="quantity" type="range" min="1" max="10">
<p>Age: <span x:text="age"></span></p>
<p>Price: <span x:text="price"></span></p>
<p>Quantity: <span x:text="quantity"></span></p>
</div>
`
})
export class NumberInputWidget {
@xproperty()
age: number = 0;
@xproperty()
price: number = 0;
@xproperty()
quantity: number = 1;
}Checkbox
typescript
@Widget({
selector: '.checkbox-input',
template: `
<div>
<label>
<input x:model="isAccepted" type="checkbox">
I accept the terms
</label>
<label>
<input x:model="newsletter" type="checkbox">
I want to receive newsletter
</label>
<p x:if="isAccepted">Terms accepted</p>
<p x:if="newsletter">Newsletter subscription active</p>
</div>
`
})
export class CheckboxInputWidget {
@xproperty()
isAccepted: boolean = false;
@xproperty()
newsletter: boolean = false;
}Select
typescript
@Widget({
selector: '.select-input',
template: `
<div>
<select x:model="selectedCountry">
<option value="">Select</option>
<option value="tr">Turkey</option>
<option value="us">USA</option>
<option value="uk">United Kingdom</option>
</select>
<p>Selected country: <span x:text="selectedCountry"></span></p>
</div>
`
})
export class SelectInputWidget {
@xproperty()
selectedCountry: string = '';
}Radio
typescript
@Widget({
selector: '.radio-input',
template: `
<div>
<label>
<input x:model="gender" type="radio" value="male">
Male
</label>
<label>
<input x:model="gender" type="radio" value="female">
Female
</label>
<p>Gender: <span x:text="gender"></span></p>
</div>
`
})
export class RadioInputWidget {
@xproperty()
gender: string = '';
}Nested Properties
typescript
@Widget({
selector: '.nested-model',
template: `
<div>
<input x:model="user.name" type="text" placeholder="Name">
<input x:model="user.email" type="email" placeholder="Email">
<input x:model="user.age" type="number" placeholder="Age">
<div>
<p>Name: <span x:text="user.name"></span></p>
<p>Email: <span x:text="user.email"></span></p>
<p>Age: <span x:text="user.age"></span></p>
</div>
</div>
`
})
export class NestedModelWidget {
@xproperty()
user = {
name: '',
email: '',
age: 0
};
}Model Config
Special configuration can be added to the x:model directive:
html
<input x:model="username" x:model:config='{"debounce": 500, "trim": true}'>Config Options
typescript
interface ModelConfig {
debounce?: number; // Delay in milliseconds
trim?: boolean; // Trim whitespace
lazy?: boolean; // Update on change event (instead of input)
number?: boolean; // Convert value to number
}Debounce Usage
typescript
@Widget({
selector: '.debounce-model',
template: `
<div>
<!-- Update with 500ms delay -->
<input
x:model="searchQuery"
x:model:config='{"debounce": 500}'
placeholder="Search...">
<p>Search: <span x:text="searchQuery"></span></p>
<template x:for="result in searchResults">
<div x:text="result"></div>
</template>
</div>
`
})
export class DebounceModelWidget {
@xproperty()
searchQuery: string = '';
@xproperty()
searchResults: string[] = [];
@xcomputed({ dependencies: ['searchQuery'] })
get searchResults(): string[] {
// API call simulation
return this.searchQuery
? ['Result 1', 'Result 2', 'Result 3']
: [];
}
}Trim Usage
typescript
@Widget({
selector: '.trim-model',
template: `
<div>
<!-- Automatically trim whitespace -->
<input
x:model="username"
x:model:config='{"trim": true}'
placeholder="Username">
<p>Value: "<span x:text="username"></span>"</p>
<p>Length: <span x:text="username.length"></span></p>
</div>
`
})
export class TrimModelWidget {
@xproperty()
username: string = '';
}Lazy Update
typescript
@Widget({
selector: '.lazy-model',
template: `
<div>
<!-- Update on change event (blur/enter) -->
<input
x:model="comment"
x:model:config='{"lazy": true}'
placeholder="Write a comment...">
<p>Saved: <span x:text="comment"></span></p>
</div>
`
})
export class LazyModelWidget {
@xproperty()
comment: string = '';
}Number Conversion
typescript
@Widget({
selector: '.number-model',
template: `
<div>
<!-- Automatically convert string to number -->
<input
x:model="price"
x:model:config='{"number": true}'
type="text"
placeholder="Price">
<p>Price: <span x:text="price"></span></p>
<p>Type: <span x:text="typeof price"></span></p>
<p>VAT Included: <span x:text="price * 1.18"></span></p>
</div>
`
})
export class NumberModelWidget {
@xproperty()
price: number = 0;
}Combined Usage
typescript
@Widget({
selector: '.combined-config',
template: `
<div>
<!-- Multiple configs -->
<input
x:model="amount"
x:model:config='{"debounce": 300, "number": true, "trim": true}'
placeholder="Amount">
<p>Amount: <span x:text="amount"></span> TL</p>
<p>VAT: <span x:text="amount * 0.18"></span> TL</p>
<p>Total: <span x:text="amount * 1.18"></span> TL</p>
</div>
`
})
export class CombinedConfigWidget {
@xproperty()
amount: number = 0;
}Search with Debounce
typescript
@Widget({
selector: '.search-config',
template: `
<div>
<input
x:model="searchTerm"
x:model:config='{"debounce": 500, "trim": true}'
placeholder="Search product...">
<p x:if="isSearching">Searching...</p>
<template x:for="product in filteredProducts">
<div class="product">
<span x:text="product.name"></span>
<span x:text="product.price"></span>
</div>
</template>
</div>
`
})
export class SearchConfigWidget {
@xproperty()
searchTerm: string = '';
@xproperty()
isSearching: boolean = false;
@xproperty()
products = [
{ name: 'Laptop', price: 15000 },
{ name: 'Mouse', price: 250 },
{ name: 'Keyboard', price: 450 }
];
@xcomputed({ dependencies: ['searchTerm', 'products'] })
get filteredProducts() {
if (!this.searchTerm) {
return this.products;
}
const term = this.searchTerm.toLowerCase();
return this.products.filter(p =>
p.name.toLowerCase().includes(term)
);
}
}Summary
- Two-way binding: Input changes update property, property changes update input
- All input types: text, number, checkbox, radio, select, textarea
- Nested properties: Access like
user.email - Config support: debounce, trim, lazy, number conversion
- Real-time: Instant validation and filtering
- Form management: Complex form structures
- Array binding: Dynamic list management