> For AI agents: the complete documentation index is available at https://nhanbe.id.vn/llms.txt, the full documentation bundle is available at https://nhanbe.id.vn/llms-full.txt.

# Form Input Binding

`v-model` tạo two-way binding giữa form input và state. Trong AEM, dùng cho các form phía client (search bar, filter, contact form) nằm trong component Vue mount lên DOM HTL render.

## Bản chất v-model

`v-model` là cú pháp rút gọn của việc bind `value` cộng lắng nghe event cập nhật. Vue tự chọn property và event phù hợp theo loại input:

| Input           | Property bind | Event lắng nghe |
| --------------- | ------------- | --------------- |
| text, textarea  | `value`       | `input`         |
| checkbox, radio | `checked`     | `change`        |
| select          | `value`       | `change`        |

## Text và textarea

```html
<input v-model="message" placeholder="edit me">
<p>Message: {{ message }}</p>

<textarea v-model="message"></textarea>
```

Nội suy bên trong textarea không hoạt động: `<textarea>{{ text }}</textarea>` sai. Phải dùng `v-model`.

## Checkbox

Một checkbox đơn bind giá trị boolean:

```html
<input type="checkbox" id="agree" v-model="checked">
<label for="agree">{{ checked }}</label>
```

Nhiều checkbox cùng bind vào một mảng:

```html
<input type="checkbox" value="Jack" v-model="checkedNames">
<input type="checkbox" value="John" v-model="checkedNames">
<input type="checkbox" value="Mike" v-model="checkedNames">
<span>Checked: {{ checkedNames }}</span>
```

```js
data() {
  return { checkedNames: [] }
}
```

## Radio

```html
<input type="radio" value="One" v-model="picked">
<input type="radio" value="Two" v-model="picked">
<span>Picked: {{ picked }}</span>
```

## Select

Single select. Nên có một option disabled với value rỗng làm placeholder, tránh lỗi trạng thái unselected trên iOS.

```html
<select v-model="selected">
  <option disabled value="">Chọn một mục</option>
  <option>A</option>
  <option>B</option>
  <option>C</option>
</select>
```

```js
data() {
  return { selected: '' }
}
```

Multiple select bind vào mảng:

```html
<select v-model="selected" multiple>
  <option>A</option>
  <option>B</option>
</select>
```

Option động render bằng `v-for`:

```html
<select v-model="selected">
  <option v-for="opt in options" :value="opt.value" :key="opt.value">
    {{ opt.text }}
  </option>
</select>
```

```js
data() {
  return {
    selected: 'A',
    options: [
      { text: 'One', value: 'A' },
      { text: 'Two', value: 'B' }
    ]
  }
}
```

## Value bindings

Mặc định v-model bind giá trị tĩnh (string với radio, boolean với checkbox). Cần bind giá trị động hoặc kiểu khác thì dùng các attribute sau.

Checkbox với giá trị tùy chỉnh:

```html
<input type="checkbox" v-model="toggle" true-value="yes" false-value="no">
```

Khi checked thì `toggle === 'yes'`, khi unchecked thì `toggle === 'no'`. Lưu ý `true-value`/`false-value` không ảnh hưởng attribute `value` thật, nên unchecked box không được gửi trong form submit. Cần đảm bảo gửi một trong hai giá trị thì dùng radio.

Radio bind giá trị động:

```html
<input type="radio" v-model="pick" :value="a">
```

Select option bind object:

```html
<select v-model="selected">
  <option :value="{ number: 123 }">123</option>
</select>
```

## Modifiers

| Modifier  | Tác dụng                                                                  |
| --------- | ------------------------------------------------------------------------- |
| `.lazy`   | Sync sau event `change` thay vì `input` (gõ xong, rời focus mới cập nhật) |
| `.number` | Tự ép kiểu sang Number; không parse được thì giữ nguyên giá trị gốc       |
| `.trim`   | Tự cắt khoảng trắng đầu/cuối                                              |

```html
<input v-model.lazy="msg">
<input v-model.number="age" type="number">
<input v-model.trim="msg">
```

Lưu ý `.number`: input HTML luôn trả về string kể cả khi `type="number"`, nên cần `.number` nếu muốn nhận kiểu Number trong state.

## Tips cho AEM

Endpoint hoặc cấu hình lấy từ Sling Model qua data attribute, state form quản lý bằng `v-model`.

```html
<!-- HTL mount point -->
<div id="contact" data-endpoint="${model.formEndpoint}"></div>
```

```js
new Vue({
  el: '#contact',
  data() {
    return {
      endpoint: this.$el.dataset.endpoint,
      form: { name: '', email: '', topic: '' }
    }
  },
  methods: {
    async submit() {
      await fetch(this.endpoint, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify(this.form)
      })
    }
  }
})
```

```html
<!-- template Vue -->
<form @submit.prevent="submit">
  <input v-model.trim="form.name" placeholder="Tên">
  <input v-model.trim="form.email" type="email" placeholder="Email">
  <select v-model="form.topic">
    <option disabled value="">Chọn chủ đề</option>
    <option>Hỗ trợ</option>
    <option>Góp ý</option>
  </select>
  <button type="submit">Gửi</button>
</form>
```

Gom các field vào một object `form` để gửi nguyên cụm và dễ reset. Bind state nguồn vào computed có setter nếu cần biến đổi giá trị trước khi lưu.
