The plugin allows you to set search feeds so that when typing, a query occurs in them, and if there are results, they are displayed in a pop-up window. In the window using the keyboard, you can navigate up and down. You can select the desired item either with the mouse, or by pressing tab or Enter.
Search feeds can be either simple arrays of strings, simple functions that return arrays of strings, or go to the server and return a Promise.
Copyconst names = [ '@mary', '@jain', '@entany', '@isaak', '@ivan', '@fedya', '@yakov', '@jhon', '@lena', '@elvin' ]; Jodit.make('#editor', { autocomplete: { sources: [names] } });
A more complex case of going to the server is also possible:
CopyJodit.make('#editor', { autocomplete: { sources: [ // Async feed as AutoCompleteCallback<IAutoCompleteItem> async (query) => fetch('./mention.php?q=' + encodeURIComponent(query)).then( (resp) => resp.json() ) ] } });
The plugin can accept several sources of information for autocomplete:
Copyconst names = [ '@mary', '@jain', '@entany', '@isaak', '@ivan', '@fedya', '@yakov', '@jhon', '@lena', '@elvin' ]; Jodit.make('#editor', { autocomplete: { sources: [ ['wysiwyg', 'editor', 'rich', 'jodit'], // Frist feed as string[] [{ value: 'Scrooge McDuck' }, { value: 'Dewey Duck' }], // Second feed as IAutoCompleteItem[] // Sync feed as AutoCompleteCallback<IAutoCompleteItem> (query) => names .filter((value) => value.indexOf(query) === 0) .map((value) => ({ title: value, value })), // Async feed as AutoCompleteCallback<IAutoCompleteItem> async (query) => fetch('./mention.php?q=' + query).then((resp) => resp.json()) ] } });
AutoCompleteSource[]
These are all feeds available to the plugin. When entering text, it will poll them and display the result, if any.
Copytype AutoCompleteSource = | string[] // Simple array of strings | Promise<string[]> // Promise resolving to strings | IAutoCompleteItem[] // Array of items | Promise<IAutoCompleteItem[]> // Promise resolving to items | AutoCompleteCallback // Callback function | IAutoCompleteCustomFeed // Custom feed object interface IAutoCompleteItem<T = IDictionary> { value: string; // Required: the actual value to insert and reconciliation key title?: string; // Optional: display text in dropdown itemRenderer?: (item: T) => string | HTMLElement; insertValueRenderer?: (item: T) => string | HTMLElement; } // IMPORTANT: The 'value' field is used as a unique key for list reconciliation. // When the list updates, items with the same 'value' are reused, preserving selection. type AutoCompleteCallback = (query: string) => Promise<IAutoCompleteItem[]>; interface IAutoCompleteCustomFeed { feed: string[] | Promise<string[]> | IAutoCompleteItem[] | Promise<IAutoCompleteItem[]> | AutoCompleteCallback; itemRenderer?: (item: IAutoCompleteItem) => string | HTMLElement; insertValueRenderer?: (item: IAutoCompleteItem) => string | HTMLElement; }
The source can be:
IAutoCompleteItem
objectsfeed
field and optional renderersCopyJodit.make('#editor', { autocomplete: { sources: [ ['apple', 'banana', 'cherry', 'date', 'elderberry'] ] } });
Copyconst fetchFruits = async () => { const response = await fetch('/api/fruits'); return response.json(); // Returns ['apple', 'banana', ...] }; Jodit.make('#editor', { autocomplete: { sources: [ fetchFruits() // Promise<string[]> ] } });
Copyconst users = [ { value: '@john', title: 'John Doe' }, { value: '@jane', title: 'Jane Smith' }, { value: '@bob', title: 'Bob Johnson' } ]; Jodit.make('#editor', { autocomplete: { sources: [users] } });
Copyconst searchCallback = (query) => { const items = ['item1', 'item2', 'item3']; return items .filter(item => item.includes(query)) .map(item => ({ value: item, title: item.toUpperCase() })); }; Jodit.make('#editor', { autocomplete: { sources: [searchCallback] } });
Copyconst asyncSearchCallback = async (query) => { const response = await fetch(`/api/search?q=${query}`); const data = await response.json(); return data.map(item => ({ value: item.id, title: item.name })); }; Jodit.make('#editor', { autocomplete: { sources: [asyncSearchCallback] } });
Copyconst customFeed = { feed: ['@alice', '@bob', '@charlie'], itemRenderer: (item) => { const div = document.createElement('div'); div.className = 'user-item'; div.innerHTML = `<b>${item.value}</b>`; return div; }, insertValueRenderer: (item) => { return `<span class="mention">${item.value}</span> `; } }; Jodit.make('#editor', { autocomplete: { sources: [customFeed] } });
CopyJodit.make('#editor', { autocomplete: { sources: [ // Static list ['static1', 'static2'], // Dynamic callback (query) => { const emojis = ['😀', '😎', '🚀', '❤️']; return emojis .filter(e => query === ':') .map(e => ({ value: e })); }, // Async data fetch('/api/mentions').then(r => r.json()), // Custom feed with renderers { feed: async (query) => { const res = await fetch(`/api/users?q=${query}`); return res.json(); }, itemRenderer: (item) => `<img src="${item.avatar}"> ${item.name}`, insertValueRenderer: (item) => `@${item.username}` } ] } });
For example, let's make a mention of everyone's favorite characters from the Friends series.
Copyconst friends = [ { value: '@Rachel', userId: '1', name: 'Rachel Green', link: 'https://www.imdb.com/title/tt0108778/characters/nm0000098' }, { value: '@Chandler', userId: '2', name: 'Chandler Bing', link: 'https://www.imdb.com/title/tt0108778/characters/nm0001612' }, { value: '@Monica', userId: '3', name: 'Monica Geller', link: 'https://www.imdb.com/title/tt0108778/characters/nm0001073' }, { value: '@Ross', userId: '4', name: 'Dr. Ross Geller', link: 'https://www.imdb.com/title/tt0108778/characters/nm0001710' }, { value: '@Phoebe', userId: '5', name: 'Phoebe Buffay', link: 'https://www.imdb.com/title/tt0108778/characters/nm0001435' }, { value: '@Ursula', userId: '6', name: 'Ursula Buffay', link: 'https://www.imdb.com/title/tt0108778/characters/nm0001435' }, { value: '@Joey', userId: '7', name: 'Joey Tribbiani', link: 'https://www.imdb.com/title/tt0108778/characters/nm0001455' } ]; Jodit.make('#editor', { autocomplete: { sources: [ // Feed as IAutoCompleteCustomFeed<IDictionary> { feed: (query) => friends.filter( (value) => value.value.indexOf(query) === 0 || value.name.indexOf(query) !== -1 ), itemRenderer: (item) => `${item.value} (${item.name})`, insertValueRenderer: ({ value, userId, name, link }) => `<a class="mention" data-mention="${value}" data-user-id="${userId}" href="${link}">${name}</a>` } ] } });
Please note that we also set the way to display items in the list, as well as how it will be inserted into the editor when selected.
(item) => item.title ?? item.value
This function takes your list item as input and returns either a string or a ready-made HTML item to display in the list.
The function has the following signature:
Copyinterface itemRenderer { (item: T) => string | HTMLElement; }
In the example above, we set it right in the feed. However, you can define your own function for the entire plugin and all feeds.
CopyJodit.make('#editor', { autocomplete: { itemRenderer: (item) => `<span>${item.value} (${item.name})</span>`, sources: [ // feeds ... ] } });
(item) => item.value + ' '
The function formats the result to be inserted into the editor. You are free to transform the selected value as desired.
The function has a signature similar to itemRenderer
and can also return a ready-made HTMLElement
;
Copyinterface insertValueRenderer { (item: T) => string | HTMLElement; }
CopyJodit.make('#editor', { autocomplete: { insertValueRenderer: (item) => { const a = document.createElement('a'); a.href = item.link; a.innerText = item.name; return a; }, sources: [ // feeds ... ] } });
The maximum number of items that the autocomplete list will display.
CopyJodit.make('#editor', { autocomplete: { maxItems: 10, // Show only 10 items even if more matches found sources: [ // Large dataset ['item1', 'item2', 'item3' /* ... many more items ... */] ] } });
(query, value) => value.toLowerCase().indexOf(query.toLowerCase()) === 0
For cases where feed is specified as a regular array, this method allows you to describe the search for matches in it.
Copyconst names = [ '@mary', '@jain', '@entany', '@isaak', '@ivan', '@fedya', '@yakov', '@jhon', '@lena', '@elvin' ]; Jodit.make('#editor', { autocomplete: { isMatchedQuery: (q, value) => { value = value.substr(1); return value.toLocaleUpperCase().includes(q.toLocaleUpperCase()); // Full text search }, sources: [names] } });
Run query checked and return array of matched items.
Copyconst editor = Jodit.make('#editor'); const items = editor.events.fire('autocomplete', 'some query'); console.log(items); // all found results
Fired on item selection by keyboard or by mouse.
Copyconst editor = Jodit.make('#editor'); editor.events.on('select.autocomplete', (selectedItem) => { console.log(selectedItem); });
The main event for registering your own autocomplete sources.
Copyconst editor = Jodit.make('#editor'); await editor.waitForReady(); // Because plugin Autocomplete can be loaded asynchronously const languages = [ 'ActionScript', 'AppleScript', 'Asp', 'BASIC', 'Erlang', 'Fortran', 'Haskell', 'Java', 'JavaScript', 'Scala' ]; const sourceClb = (query) => languages .filter((value) => value.indexOf(query) === 0) .map((value) => ({ title: value, value })); editor.events.fire('registerAutocompleteSource', sourceClb); // To unregister the source editor.events.fire('unregisterAutocompleteSource', sourceClb);
The autocomplete plugin uses an efficient reconciliation algorithm to update the dropdown list while preserving user selection and minimizing DOM operations.
maxItems
: Limit the number of displayed items to improve rendering performanceExample with debouncing:
Copyconst debounce = (func, delay) => { let timeoutId; return (...args) => { clearTimeout(timeoutId); return new Promise((resolve) => { timeoutId = setTimeout(() => resolve(func(...args)), delay); }); }; }; Jodit.make('#editor', { autocomplete: { sources: [ debounce(async (query) => { const response = await fetch(`/api/search?q=${query}`); return response.json(); }, 300) ] } });