Autocomplete
Autocomplete (자동 완성)
출처: 문서
본문
Autocomplete
현재 입력을 바탕으로 추천 힌트를 받아요.
기본 사용법
Autocomplete 컴포넌트는 입력 제안을 제공해요.
fetch-suggestions 속성은 추천 입력을 반환하는 메서드예요. 이 예시에서 querySearch(queryString, cb)는 제안이 준비되면 cb(data)를 통해 Autocomplete에 제안을 반환해요.
import { onMounted, ref } from 'vue'
interface RestaurantItem {
value: string
link: string
}
const state1 = ref('')
const state2 = ref('')
const restaurants = ref([])
const querySearch = (queryString: string, cb: any) => {
const results = queryString
? restaurants.value.filter(createFilter(queryString))
: restaurants.value
// call callback function to return suggestions
cb(results)
}
const createFilter = (queryString: string) => {
return (restaurant: RestaurantItem) => {
return (
restaurant.value.toLowerCase().indexOf(queryString.toLowerCase()) === 0
)
}
}
const loadAll = () => {
return [
{ value: 'vue', link: 'https://github.com/vuejs/vue' },
{ value: 'element', link: 'https://github.com/ElemeFE/element' },
{ value: 'cooking', link: 'https://github.com/ElemeFE/cooking' },
{ value: 'mint-ui', link: 'https://github.com/ElemeFE/mint-ui' },
{ value: 'vuex', link: 'https://github.com/vuejs/vuex' },
{ value: 'vue-router', link: 'https://github.com/vuejs/vue-router' },
{ value: 'babel', link: 'https://github.com/babel/babel' },
]
}
const handleSelect = (item: Record) => {
console.log(item)
}
onMounted(() => {
restaurants.value = loadAll()
})
.demo-autocomplete {
display: flex;
flex-wrap: wrap;
gap: 2rem;
}
.demo-block {
display: flex;
flex-direction: column;
gap: 0.5rem;
}
.demo-title {
font-size: 0.875rem;
color: var(--el-text-color-secondary);
min-height: 2.5em;
display: flex;
align-items: center;
}
@media screen and (max-width: 768px) {
.demo-autocomplete {
gap: 1rem;
}
.demo-block {
width: 100%;
}
}
커스텀 템플릿
제안이 표시되는 방식을 커스터마이즈해요.
스코프 슬롯을 사용해서 제안 항목을 커스터마이즈해요. 스코프 안에서는 item 키를 통해 제안 객체에 접근할 수 있어요.
import { onMounted, ref } from 'vue'
import { Edit } from '@element-plus/icons-vue'
interface LinkItem {
value: string
link: string
}
const state = ref('')
const links = ref([])
const querySearch = (queryString: string, cb) => {
const results = queryString
? links.value.filter(createFilter(queryString))
: links.value
// call callback function to return suggestion objects
cb(results)
}
const createFilter = (queryString: string) => {
return (restaurant: LinkItem) => {
return (
restaurant.value.toLowerCase().indexOf(queryString.toLowerCase()) === 0
)
}
}
const loadAll = () => {
return [
{ value: 'vue', link: 'https://github.com/vuejs/vue' },
{ value: 'element', link: 'https://github.com/ElemeFE/element' },
{ value: 'cooking', link: 'https://github.com/ElemeFE/cooking' },
{ value: 'mint-ui', link: 'https://github.com/ElemeFE/mint-ui' },
{ value: 'vuex', link: 'https://github.com/vuejs/vuex' },
{ value: 'vue-router', link: 'https://github.com/vuejs/vue-router' },
{ value: 'babel', link: 'https://github.com/babel/babel' },
]
}
const handleSelect = (item: Record) => {
console.log(item)
}
const handleIconClick = (ev: Event) => {
console.log(ev)
}
onMounted(() => {
links.value = loadAll()
})
.my-autocomplete li {
line-height: normal;
padding: 7px;
}
.my-autocomplete li .name {
text-overflow: ellipsis;
overflow: hidden;
}
.my-autocomplete li .addr {
font-size: 12px;
color: #b4b4b4;
}
.my-autocomplete li .highlighted .addr {
color: #ddd;
}
원격 검색
서버 측에서 데이터를 검색해요.
커스텀 로딩 2.5.0
로딩 내용을 재정의해요.
import { onMounted, ref } from 'vue'
const state = ref('')
interface LinkItem {
value: string
link: string
}
const links = ref([])
const loadAll = () => {
return [
{ value: 'vue', link: 'https://github.com/vuejs/vue' },
{ value: 'element', link: 'https://github.com/ElemeFE/element' },
{ value: 'cooking', link: 'https://github.com/ElemeFE/cooking' },
{ value: 'mint-ui', link: 'https://github.com/ElemeFE/mint-ui' },
{ value: 'vuex', link: 'https://github.com/vuejs/vuex' },
{ value: 'vue-router', link: 'https://github.com/vuejs/vue-router' },
{ value: 'babel', link: 'https://github.com/babel/babel' },
]
}
let timeout: ReturnType
const querySearchAsync = (queryString: string, cb: (arg: any) => void) => {
const results = queryString
? links.value.filter(createFilter(queryString))
: links.value
clearTimeout(timeout)
timeout = setTimeout(() => {
cb(results)
}, 5000 * Math.random())
}
const createFilter = (queryString: string) => {
return (restaurant: LinkItem) => {
return (
restaurant.value.toLowerCase().indexOf(queryString.toLowerCase()) === 0
)
}
}
const handleSelect = (item: Record) => {
console.log(item)
}
onMounted(() => {
links.value = loadAll()
})
.demo-autocomplete {
display: flex;
flex-wrap: wrap;
gap: 2rem;
}
.demo-block {
display: flex;
flex-direction: column;
gap: 0.5rem;
}
.demo-title {
font-size: 0.875rem;
color: var(--el-text-color-secondary);
min-height: 2.5em;
display: flex;
align-items: center;
}
@media screen and (max-width: 768px) {
.demo-autocomplete {
gap: 1rem;
}
.demo-block {
width: 100%;
}
}
.circular {
display: inline;
height: 30px;
width: 30px;
animation: loading-rotate 2s linear infinite;
}
.path {
animation: loading-dash 1.5s ease-in-out infinite;
stroke-dasharray: 90, 150;
stroke-dashoffset: 0;
stroke-width: 2;
stroke: var(--el-color-primary);
stroke-linecap: round;
}
.loading-path .dot1 {
transform: translate(3.75px, 3.75px);
fill: var(--el-color-primary);
animation: custom-spin-move 1s infinite linear alternate;
opacity: 0.3;
}
.loading-path .dot2 {
transform: translate(calc(100% - 3.75px), 3.75px);
fill: var(--el-color-primary);
animation: custom-spin-move 1s infinite linear alternate;
opacity: 0.3;
animation-delay: 0.4s;
}
.loading-path .dot3 {
transform: translate(3.75px, calc(100% - 3.75px));
fill: var(--el-color-primary);
animation: custom-spin-move 1s infinite linear alternate;
opacity: 0.3;
animation-delay: 1.2s;
}
.loading-path .dot4 {
transform: translate(calc(100% - 3.75px), calc(100% - 3.75px));
fill: var(--el-color-primary);
animation: custom-spin-move 1s infinite linear alternate;
opacity: 0.3;
animation-delay: 0.8s;
}
@keyframes loading-rotate {
to {
transform: rotate(360deg);
}
}
@keyframes loading-dash {
0% {
stroke-dasharray: 1, 200;
stroke-dashoffset: 0;
}
50% {
stroke-dasharray: 90, 150;
stroke-dashoffset: -40px;
}
100% {
stroke-dasharray: 90, 150;
stroke-dashoffset: -120px;
}
}
@keyframes custom-spin-move {
to {
opacity: 1;
}
}
커스텀 헤더 & 푸터 2.10.6
슬롯을 사용해서 드롭다운의 헤더와 푸터를 모두 커스터마이즈할 수 있어요. 슬롯을 사용해서 내용을 커스터마이즈해요.
import { onMounted, ref } from 'vue'
const headerSlotState = ref('')
const footerSlotstate = ref('')
interface LinkItem {
value: string
link: string
}
const links = ref([])
const loadAll = () => {
return [
{ value: 'vue', link: 'https://github.com/vuejs/vue' },
{ value: 'element', link: 'https://github.com/ElemeFE/element' },
{ value: 'cooking', link: 'https://github.com/ElemeFE/cooking' },
{ value: 'mint-ui', link: 'https://github.com/ElemeFE/mint-ui' },
{ value: 'vuex', link: 'https://github.com/vuejs/vuex' },
{ value: 'vue-router', link: 'https://github.com/vuejs/vue-router' },
{ value: 'babel', link: 'https://github.com/babel/babel' },
]
}
let timeout: ReturnType
const querySearchAsync = (queryString: string, cb: (arg: any) => void) => {
const results = queryString
? links.value.filter(createFilter(queryString))
: links.value
clearTimeout(timeout)
timeout = setTimeout(() => {
cb(results)
}, 3000 * Math.random())
}
const createFilter = (queryString: string) => {
return (restaurant: LinkItem) => {
return (
restaurant.value.toLowerCase().indexOf(queryString.toLowerCase()) === 0
)
}
}
const handleSelect = (item: Record) => {
console.log(item)
}
onMounted(() => {
links.value = loadAll()
})
const footerAutocompleteRef = ref()
const handleClear = () => {
footerSlotstate.value = ''
footerAutocompleteRef.value.getData()
}
.autocomplete-custom-header-footer {
display: flex;
}
.autocomplete-custom-header-footer > div {
flex: 1;
text-align: center;
}
.autocomplete-custom-header-footer > div > :deep(.el-autocomplete) {
width: 50%;
}
.autocomplete-custom-header-footer > div:not(:last-child) {
border-right: 1px solid var(--el-border-color);
}
API
Attributes
| Name | Description | Type | Default |
|---|---|---|---|
| model-value / v-model | 바인딩 값 | string | — |
| placeholder | Autocomplete의 placeholder | string | — |
| clearable | 클리어 버튼 표시 여부 | boolean | false |
| disabled | Autocomplete 비활성화 여부 | boolean | false |
| value-key | 표시할 입력 제안 객체의 키 이름 | string | value |
| debounce | 입력 시 디바운스 지연 시간(밀리초) | number | 300 |
| placement | 팝업 메뉴의 위치 | enum | bottom-start |
| fetch-suggestions | 입력 제안을 가져오는 메서드. 제안이 준비되면 callback(data:[])을 호출해서 Autocomplete에 전달해요 | array / Function | — |
| trigger-on-focus | 입력 포커스 시 제안을 표시할지 여부 | boolean | true |
| select-when-unmatched | autocomplete 매칭이 없을 때 enter로 select 이벤트를 발생시킬지 여부 | boolean | false |
| name | 네이티브 input의 name과 동일 | string | — |
| aria-label a11y 2.7.2 | 네이티브 aria-label 속성 | string | — |
| hide-loading | 원격 검색에서 로딩 아이콘 숨김 여부 | boolean | false |
| popper-class | autocomplete 드롭다운의 커스텀 클래스 이름 | string / object | '' |
| popper-style 2.11.4 | autocomplete 드롭다운의 커스텀 스타일 | string / object | — |
| popper-options 2.14.0 | popper.js 파라미터 | object refer to popper.js doc | {} |
| show-arrow 2.14.0 | 드롭다운에 화살표가 있는지 여부 | boolean | true |
| teleported | select 드롭다운을 body로 텔레포트할지 여부 | boolean | true |
| append-to 2.9.9 | select 드롭다운이 붙는 대상 | CSSSelector / HTMLElement | — |
| highlight-first-item | 원격 검색 제안에서 첫 항목을 기본으로 강조할지 여부 | boolean | false |
| fit-input-width | 드롭다운의 너비를 입력과 같게 할지 여부 | boolean | false |
| popper-append-to-body deprecated | 드롭다운을 body에 추가할지 여부. 드롭다운 위치가 잘못되면 이 prop을 false로 설정해볼 수 있어요 | boolean | false |
| loop-navigation 2.11.4 | 키보드 탐색을 끝에서 시작으로 순환할지 여부 | boolean | true |
| input props | — | — | — |
Events
| Name | Description | Type |
|---|---|---|
| blur | Input이 블러될 때 실행돼요 | Function |
| focus | Input이 포커스될 때 실행돼요 | Function |
| input | Input 값이 변경될 때 실행돼요 | Function |
| clear | 클리어 버튼을 클릭해서 Input이 지워질 때 실행돼요 | Function |
| select | 제안이 클릭될 때 실행돼요 | Function |
| change | Input 값이 변경될 때 실행돼요 | Function |
Slots
| Name | Description | Type |
|---|---|---|
| default | 입력 제안의 커스텀 내용 | object |
| header 2.10.6 | 드롭다운 상단의 내용 | - |
| footer 2.10.6 | 드롭다운 하단의 내용 | - |
| prefix | Input의 접두사 내용 | - |
| suffix | Input의 접미사 내용 | - |
| prepend | Input 앞에 추가할 내용 | - |
| append | Input 뒤에 추가할 내용 | - |
| loading 2.5.0 | 로딩 내용 재정의 | - |
Exposes
| Name | Description | Type |
|---|---|---|
| activated | autocomplete 활성화 여부 | object |
| blur | input 요소 블러 | Function |
| close | 제안 목록 접기 | Function |
| focus | input 요소 포커스 | Function |
| handleSelect | 제안이 클릭될 때 실행돼요 | Function |
| handleKeyEnter | 키보드 enter 이벤트 처리 | Function |
| highlightedIndex | 현재 강조된 항목의 인덱스 | object |
| highlight | 제안에서 항목 강조 | Function |
| inputRef | el-input 컴포넌트 인스턴스 | object |
| loading | 원격 검색 로딩 표시기 | object |
| popperRef | el-tooltip 컴포넌트 인스턴스 | object |
| suggestions | 제안 가져오기 결과 | object |
| getData 2.8.4 | 제안 목록 로딩 | Function |