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

더 알아보기 (Learn more)