Skip to content

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