> 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.

# List Rendering

Lặp render danh sách bằng `v-for`. Trong AEM, list thường là mảng JSON từ Sling Model (vd danh sách card, link, item của multifield) được parse ra rồi render.

## v-for với mảng

Cú pháp `item in items`. Có thể lấy thêm index bằng `(item, index) in items`.

```html
<ul>
  <li v-for="(user, index) in users" :key="user.id">
    {{ index }} - {{ user.name }}
  </li>
</ul>
```

```js
data() {
  return {
    users: [
      { id: 1, name: 'An' },
      { id: 2, name: 'Binh' }
    ]
  }
}
```

## v-for với object

Lặp qua property của object: `(value, key, index)`.

```html
<ul>
  <li v-for="(value, key, index) in product" :key="key">
    {{ index }}. {{ key }}: {{ value }}
  </li>
</ul>
```

Thứ tự lặp theo thứ tự key của `Object.keys()`, không đảm bảo ổn định tuyệt đối giữa các engine. Cần thứ tự cố định thì dùng mảng.

## v-for với range

`n` chạy từ 1 đến số đó.

```html
<span v-for="n in 5" :key="n">{{ n }}</span>
```

## key là bắt buộc

Luôn gắn `:key` duy nhất và ổn định cho mỗi item. Vue dùng `key` để nhận diện và tái sử dụng đúng phần tử khi danh sách thay đổi.

```html
<!-- ĐÚNG: key ổn định theo id -->
<li v-for="item in items" :key="item.id">{{ item.name }}</li>
```

Không dùng `index` làm `key` khi danh sách có thể thêm/xóa/sắp xếp lại. Index thay đổi theo vị trí, làm Vue map sai phần tử, gây lỗi state (vd giá trị input dính nhầm dòng).

```html
<!-- SAI khi list thay đổi thứ tự: dùng index làm key -->
<li v-for="(item, index) in items" :key="index">{{ item.name }}</li>
```

Chỉ chấp nhận `index` làm key khi list tĩnh, không bao giờ đổi thứ tự.

## v-for với template

Render nhiều phần tử cho mỗi vòng lặp mà không thêm thẻ bọc thừa:

```html
<template v-for="item in items">
  <dt :key="`t-${item.id}`">{{ item.term }}</dt>
  <dd :key="`d-${item.id}`">{{ item.desc }}</dd>
</template>
```

Ở Vue 2, `key` đặt trên từng phần tử con bên trong `<template>`, không đặt trên `<template>`.

## v-for với component

Truyền dữ liệu vào component con qua props. Component không tự biết về vòng lặp, phải truyền tường minh.

```html
<card-item
  v-for="item in items"
  :key="item.id"
  :title="item.title"
  :image="item.image" />
```

## Array change detection

Vue 2 theo dõi được các mutation method của mảng, tự cập nhật view:

`push`, `pop`, `shift`, `unshift`, `splice`, `sort`, `reverse`.

```js
this.items.push({ id: 3, name: 'Cuong' }) // reactive, view cập nhật
```

Hai trường hợp Vue 2 KHÔNG phát hiện được:

```js
// 1. Gán phần tử theo index
this.items[0] = newValue // KHÔNG reactive

// 2. Đổi length trực tiếp
this.items.length = 0 // KHÔNG reactive
```

Cách xử lý:

```js
// Thay vì gán theo index
this.$set(this.items, 0, newValue)
// hoặc
this.items.splice(0, 1, newValue)

// Thay vì đổi length
this.items.splice(0) // xóa toàn bộ, reactive
```

Method không mutate (trả về mảng mới) như `filter`, `map`, `concat`, `slice` thì gán lại biến để cập nhật:

```js
this.items = this.items.filter(i => i.active)
```

## Tips cho AEM

Render list từ JSON do Sling Model cung cấp qua data attribute.

```html
<!-- HTL: serialize list ra JSON -->
<div id="cards" data-cards="${model.cardsJson @ context='attribute'}"></div>
```

```js
new Vue({
  el: '#cards',
  data() {
    return {
      cards: JSON.parse(this.$el.dataset.cards || '[]')
    }
  }
})
```

```html
<!-- template Vue -->
<div class="cards" v-if="cards.length">
  <div class="card" v-for="card in cards" :key="card.id">
    <h3>{{ card.title }}</h3>
    <p>{{ card.summary }}</p>
  </div>
</div>
<p v-else>Chưa có dữ liệu</p>
```

Lưu ý key: dùng id từ JCR (vd path hoặc một field định danh) làm `:key`, tránh dùng index để giữ đúng state khi author thêm/xóa item trong dialog.
