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

# Custom Events

Con báo ngược lên cha bằng cách emit event. Cha lắng nghe bằng `v-on`/`@`. Đây là chiều còn lại của luồng dữ liệu một chiều (cha xuống con bằng props, con lên cha bằng events).

## Emit và lắng nghe

Con phát event bằng `this.$emit('event-name', payload)`.

```vue
<!-- Child: ToggleButton.vue -->
<template>
  <button @click="$emit('toggle')">Toggle</button>
</template>
```

```html
<!-- Parent -->
<toggle-button @toggle="onToggle"></toggle-button>
```

## Truyền dữ liệu kèm event

Tham số sau tên event là payload, cha nhận trong handler.

```vue
<template>
  <button @click="$emit('select', item.id)">Chọn</button>
</template>
```

```html
<item-row @select="onSelect"></item-row>
```

```js
methods: {
  onSelect(id) {
    console.log('selected', id)
  }
}
```

Cần nhiều giá trị thì emit một object.

```js
this.$emit('change', { field: 'name', value: 'Nhan' })
```

## Tên event phải kebab-case

Khác với props/component, tên event không tự chuyển đổi case. Vue khuyến nghị luôn dùng kebab-case cho tên event, vì HTML không phân biệt hoa thường.

```js
// emit
this.$emit('item-selected')
```

```html
<!-- lắng nghe đúng tên -->
<my-item @item-selected="..."></my-item>
```

## v-model trên component

`v-model` trên component là cú pháp rút gọn của: bind prop `value` cộng lắng nghe event `input`.

```html
<custom-input v-model="searchText"></custom-input>

<!-- tương đương -->
<custom-input
  :value="searchText"
  @input="searchText = $event">
</custom-input>
```

Component phải khai báo prop `value` và emit `input`:

```vue
<template>
  <input :value="value" @input="$emit('input', $event.target.value)">
</template>

<script>
export default {
  props: { value: String }
}
</script>
```

Đổi tên prop/event mặc định bằng option `model`:

```js
export default {
  model: {
    prop: 'checked',
    event: 'change'
  },
  props: { checked: Boolean }
}
```

## .sync modifier

`.sync` cho phép two-way binding một prop bất kỳ. Cha viết `:prop.sync="x"`, con emit `update:prop`.

```html
<modal-box :visible.sync="showModal"></modal-box>
```

```js
// trong con: đóng modal
this.$emit('update:visible', false)
```

## Giao tiếp ngoài quan hệ cha-con

`$emit`/`props` chỉ giải quyết cha-con trực tiếp. Component không liên quan trực tiếp thì dùng event bus đơn giản (Vue 2) hoặc state store. Với app nhỏ trong AEM thường không cần; nâng state lên component cha chung là đủ.

```js
// event bus tối giản (Vue 2.7)
const bus = new Vue()
bus.$emit('refresh')
bus.$on('refresh', handler)
```

## Tips cho AEM

Trong một AEM component mount Vue, child component emit event lên root để root xử lý gọi API hoặc cập nhật state chung.

```vue
<!-- root: SearchWidget.vue -->
<template>
  <div>
    <search-input @search="fetchResults" />
    <result-list :items="results" />
  </div>
</template>

<script>
import SearchInput from './SearchInput.vue'
import ResultList from './ResultList.vue'

export default {
  components: { SearchInput, ResultList },
  data() {
    return { results: [] }
  },
  methods: {
    async fetchResults(keyword) {
      const res = await fetch(`/bin/search?q=${encodeURIComponent(keyword)}`)
      this.results = await res.json()
    }
  }
}
</script>
```
