FreeGridv1.15.1한국어

Combo editor (autocomplete)

A text box with a list that filters as you type: pick with the arrows, allow free text or only listed values, and search a server with an async options function.

Live demoCombo editor

Filter as you type, listed values only, and a server search.

Code for this demo
import { createGrid, type ColumnDef, type Row } from '@freegridjs/core';
import { makeRows } from '../data';
import { button, separator, text, tr, type DemoContext } from '../ui';

const CITIES = ['Seoul', 'Busan', 'Incheon', 'Daegu', 'Daejeon', 'Gwangju', 'Ulsan', 'Suwon', 'Tokyo', 'Osaka', 'Paris', 'Berlin', 'Madrid', 'London', 'New York'];
const KO: Record<string, string> = { Seoul: '서울', Busan: '부산', Incheon: '인천', Daegu: '대구', Daejeon: '대전', Gwangju: '광주', Ulsan: '울산', Suwon: '수원', Tokyo: '도쿄', Osaka: '오사카', Paris: '파리', Berlin: '베를린', Madrid: '마드리드', London: '런던', 'New York': '뉴욕' };
export default function mount(ctx: DemoContext) {
  const h = (en: string, ko: string) => tr(ctx.lang, { en, ko });
  const options = CITIES.map(c => ({ value: c, label: ctx.lang === 'ko' ? KO[c] : c }));
  // a fake server: answers after 300 ms with the matching cities
  const search = (_row: Row, q: string) => new Promise<typeof options>(res => setTimeout(() => res(options.filter(o => o.label.toLowerCase().includes(q.toLowerCase()))), 300));
  const data = makeRows(200).map((r, i) => ({ ...r, city: CITIES[i % CITIES.length], hub: CITIES[(i * 3) % CITIES.length], remote: CITIES[(i * 7) % CITIES.length] }));
  const grid = createGrid(ctx.host, {
    columns: [
      { field: 'name', header: h('Name', '이름'), width: 150 },
      { field: 'city', header: h('City (free text)', '도시(자유 입력)'), width: 150, editable: true, editor: { type: 'combo', options } },
      { field: 'hub', header: h('Hub (listed only)', '거점(목록만)'), width: 150, editable: true, editor: { type: 'combo', options, free: false } },
      { field: 'remote', header: h('Server search', '서버 검색'), width: 150, editable: true, editor: { type: 'combo', options: search, free: false } },
    ],
    data, locale: ctx.lang,
  });
  grid.on('cellValueChanged', e => ctx.log(`${e.colId} = ${String(e.newValue)}`));
  grid.on('validationFailed', e => ctx.log(`refused: ${String(e.value ?? '')} — ${e.message}`));
  text(ctx, h('Type to filter (ㅅㅇ works in Korean), ↓ ↑ to pick, Enter to save. The hub column refuses cities that are not listed.',
    '입력하면 걸러지고(초성 ㅅㅇ도 됨) ↓ ↑로 고르고 Enter로 저장합니다. 거점 컬럼은 목록에 없는 도시를 받지 않습니다.'));
  return grid;
}

editor: { type: 'combo' } is a text box with a list under it that filters as you type. It suits long lists (cities, products, customers) where a plain select is hard to scroll. Matching ignores case and also understands Korean initial consonants (ㅅㅇ finds 서울).

Basic use

const cities = [{ value: 'SEL', label: 'Seoul' }, { value: 'BSN', label: 'Busan' }, { value: 'ICN', label: 'Incheon' }];
createGrid(el, {
  columns: [
    // free text allowed (the default): a city not in the list is kept as typed
    { field: 'city', editable: true, editor: { type: 'combo', options: cities } },
    // only listed values: anything else is refused with a message
    { field: 'hub', editable: true, editor: { type: 'combo', options: cities, free: false } },
  ],
  data,
});

Keyboard and mouse

KeyDoes
TypeFilters the list (label or value contains the text)
↓ / ↑Move the highlight; the box shows the highlighted option
EnterSave the highlighted option, or the typed text (free) — with free: false the first match is highlighted, so Enter after a few letters picks it
Click an itemSave it at once
EscClose without saving

Options

NameTypeDescription
optionsSelectOption[] | (row, query) => SelectOption[] | Promise<SelectOption[]>The choices: values, or { value, label, disabled }. A function receives the row and the typed text; it is called again as people type (150 ms after the last key) and may return a Promise. While a Promise is pending, the list says Loading….
freebooleanAccept text that is not in the list (default true). With false, only listed, enabled values are saved; pasting other text is skipped.

Searching a server

{ field: 'customerId', header: 'Customer', editable: true,
  editor: {
    type: 'combo',
    free: false,
    // called with what is typed; answers that arrive late for an older query are dropped
    options: async (row, query) => {
      const res = await fetch('/api/customers?q=' + encodeURIComponent(query));
      const list = await res.json();
      return list.map(c => ({ value: c.id, label: c.name }));
    },
  },
  // show the name for the stored id (the cell keeps the id)
  valueGetter: row => row.customerName ?? row.customerId,
}

With a function, the grid cannot know every option in advance: the cell shows the stored value (use valueGetter or renderer to show a name), and with free: false pasted text is refused because it cannot be checked.

What the cell shows and stores

  • With a fixed list, the cell shows the option label and stores its value (SEL is stored, Seoul is shown).
  • Pasting accepts a label or a value, in any case.
  • A combo with free: false and a fixed list gets a list filter (set filter) by label; with free text it gets a text filter. See Filters on editor columns.

Questions about this page

How do I make an autocomplete cell that searches the server?

Give the combo an options function: editor: { type: 'combo', options: (row, q) => fetch('/api?q=' + q).then(r => r.json()) }. It is called as people type (debounced) and may return a Promise.