flexdatalist

Autocomplete / tag input. Zero dependencies. Pure ES6 class.

Remote & static data · multiple tags · grouping · keyboard nav · localStorage cache · chainable API
Adapters for Vue, React & Svelte — or use standalone with a <script> tag

Value stored:

Quick Start

No jQuery. No build step. Drop two files and go.

1. Include files

<!-- CSS -->
<link rel="stylesheet"
      href="/path/to/jquery.flexdatalist.css">

<!-- JS (no jQuery needed) -->
<script src="/path/to/flexdatalist.js"></script>

2. HTML — auto-init

<input type="text"
       class="flexdatalist"
       data-data="/api/items.json"
       data-search-in="name"
       data-min-length="1"
       name="item">

Any input.flexdatalist is initialised automatically on DOMContentLoaded.

3. JavaScript — programmatic init

// init() is async — resolves after initial data loading is done
const [fd] = await Flexdatalist.init('#city', {
    url:            '/api/cities',
    searchIn:       ['name', 'zip'],
    valueProperty:  'id',
    textProperty:   '{name}, {zip}',
    minLength:      2,
});

// Chainable API
fd.setValue('42')
  .on('select:flexdatalist', e => console.log(e.detail));

// Get the stored value at any time
console.log(fd.getValue());

// When constructing directly, use .ready instead
const fd2 = new Flexdatalist(document.querySelector('#city'), opts);
await fd2.ready; // resolves once initial data is loaded
Auto-init: All input.flexdatalist elements are initialised automatically unless they carry the class autodiscover-disabled. Options are read from data-* attributes (camelCase → kebab-case).
CSS compatibility: The new standalone class uses the exact same CSS class names as the original jQuery plugin, so jquery.flexdatalist.css works without modification.

Data Sources

Three ways to provide data.

Static Array

Pass a JS array via the data option.

Flexdatalist.init('#el', {
  data: [
    {id: 1, label: 'Apple'},
    {id: 2, label: 'Banana'},
  ]
});

Static JSON File

Point data to a URL — loaded once and cached.

<input
  class="flexdatalist"
  data-data="items.json"
  data-search-in="label"
  name="item">

Remote URL recommended for large sets

Use url — queried on every keystroke.

<input
  class="flexdatalist"
  data-url="/api/search"
  data-search-in="name"
  name="item">

Server receives: keyword, contain, selected, load, relatives.

Server response format: Return a plain array [{...}] or wrap it:
{
  "results": [ { "id": 1, "name": "Paris" }, ... ],
  "options": { "url": "/api/next-endpoint" }  // optional — override options from server
}

Options

Set via JS constructor object or data-* attributes (camelCase → kebab-case). Options can also be changed at any time via fd.setOption(name, value).

Option Type Default Description
Data
data Array|string [] Static data array or URL string to a JSON file (loaded once, cached).
url string|null null Remote URL queried on every keystroke. Server-side filtering recommended for large datasets.
params Object|Function {} Extra query-string / body parameters sent with every remote request. May be a function (keyword) => Object.
resultsProperty string 'results' Key in the remote JSON response that holds the results array.
keywordParamName string 'keyword' Query-string parameter name that carries the typed keyword.
searchContainParamName string 'contain' Query-string parameter name that carries the searchContain flag.
requestType string 'get' HTTP method for remote requests: 'get' or 'post'.
requestContentType string 'x-www-form-urlencoded' Content-type for POST bodies: 'x-www-form-urlencoded' or 'json'.
requestHeaders new Object|null null Extra HTTP headers merged into every request. Useful for Authorization tokens.
Search
searchIn string[] ['label'] Properties searched when filtering results locally.
minLength number 3 Minimum characters before triggering search. Set 0 to show all results on focus.
searchContain boolean false Match keyword anywhere in the string (not just at the start).
searchEqual new boolean false Require an exact full-string match.
searchByWord boolean false Split keyword on spaces and match all words independently.
searchDisabled boolean false Skip local filtering entirely (rely entirely on server-side search).
searchDelay number 300 Debounce delay in ms before running a search after each keystroke.
normalizeString Function|null null Custom string normalizer called before comparison. Signature: (string) => string. Useful for accent-insensitive search (e.g. with latinize).
redoSearchOnFocus boolean true Re-trigger the search when the alias input re-gains focus.
Display
textProperty string|null null Property (or {placeholder} pattern) shown in the alias input after selection. Defaults to first entry of searchIn.
valueProperty string|string[]|null null Property stored as the actual submitted value. Use '*' to store the entire matched object as JSON.
visibleProperties string[] [] Properties rendered inside each result <li>. Supports {placeholder} patterns. Defaults to searchIn.
iconProperty string 'thumb' Property containing an image URL rendered as <img> in results.
groupBy string|false false Group results by a property value (e.g. 'continent').
maxShownResults number 100 Maximum number of results rendered in the dropdown (0 = unlimited).
noResultsText string 'No results found for "{keyword}"' Message shown when no results match. Set to empty string to suppress.
resultsLoader string|null null URL of a loading spinner image shown while fetching remote results.
focusFirstResult boolean false Automatically activate (highlight) the first result item.
Values
multiple boolean|null null Allow multiple tags. When null, inferred from the multiple HTML attribute.
selectionRequired boolean false Require the user to select a result from the dropdown (disables free-text entry).
limitOfValues number 0 Maximum number of tags in multiple mode (0 = unlimited).
valuesSeparator string ',' Separator used when serialising multiple values into a string.
allowDuplicateValues boolean false Allow adding the same value more than once.
removeOnBackspace boolean true Pressing Backspace on an empty alias marks then removes the last tag.
toggleSelected boolean false Clicking a selected tag in multiple mode toggles its disabled state.
collapseAfterN new number|false 50 Collapse multiple-value tags after this many items. Set false to disable collapsing.
collapsedValuesText new string '{count} More' Label for the collapse toggle. {count} is replaced with the hidden count.
showAddNewItem new boolean false Show an "Add new item" option at the bottom when no results match.
addNewItemText new string 'No results found for "{keyword}". Click to add it.' Text of the "Add new item" option. {keyword} is replaced.
Relatives
relatives string|NodeList|null null CSS selector or NodeList of inputs whose values are sent as relatives[name] with every request.
chainedRelatives boolean false Disable this input until all relatives have a value.
State & Cache
disabled boolean|null null Start disabled. When null, inferred from the disabled HTML attribute.
cache boolean true Cache remote results in localStorage.
cacheLifetime number 60 Cache lifetime in seconds.
debug boolean true Log warnings to the browser console.

API

All instance methods are chainable (return this) unless they return a value.

Static Methods

Signature Returns Description
Flexdatalist.init(selector, options) Promise<Flexdatalist[]> Initialise on one or many elements. Accepts a CSS selector string, HTMLElement, or NodeList. Resolves after all instances have finished loading their initial data.
Flexdatalist.getInstance(el) Flexdatalist|null Return the instance attached to a given element, or null.
instance.ready Promise<Flexdatalist> Promise that resolves with the instance once initial data loading is complete. Useful when constructing instances directly with new Flexdatalist(el, opts).

Options

Signature Returns Description
fd.getOption(name) any Return the current value of an option.
fd.setOption(name, value) this Update an option at runtime. Chainable.

Value

Signature Returns Description
fd.getValue() string|Object|Array Return the stored value as a string, parsed object, or array (multiple mode).
fd.getText(format?) string|string[] Return the user-facing display text shown in the alias input. Single mode always returns a string. Multiple mode: 'array' (default) returns a string[]; 'string' joins with the configured valuesSeparator; any other string is used as a custom join separator (e.g. ' | ').
fd.setValue(val) this Replace the current value. Triggers a data load to resolve display text.
fd.addValue(val) this Add a value in multiple mode.
fd.removeValue(val) this Remove a value in multiple mode.
fd.toggleValue(val) this Add the value if not present, remove if already present.
fd.clear() this Clear all values and reset the alias input.

State

Signature Returns Description
fd.disable() this Disable the input.
fd.enable() this Enable the input.
fd.readonly(state?) boolean|this Called with no argument: returns current readonly state. Called with boolean: sets readonly state (chainable).
fd.isDisabled() boolean Return true if currently disabled.
fd.isReadonly() boolean Return true if currently readonly.

Search & Results

Signature Returns Description
fd.search(keyword) this Programmatically trigger a search with the given keyword.
fd.closeResults() this Close the results dropdown.

Event Binding

Signature Returns Description
fd.on(eventName, handler) this Attach an event listener on the underlying <input> element. Chainable.
fd.off(eventName, handler) this Remove a previously attached event listener. Chainable.

Lifecycle

Signature Returns Description
fd.destroy(clear = false) void Remove all DOM additions, unbind all listeners, and unregister the instance. Pass true to also clear the original element's value.

Chaining Example

const [fd] = await Flexdatalist.init('#tags', {
    data:           myArray,
    multiple:       true,
    selectionRequired: true,
    limitOfValues:  5,
});

fd.setValue('1,2,3')           // pre-select three values
  .setOption('limitOfValues', 10) // change an option at runtime
  .on('change:flexdatalist', e => {
      console.log('changed', e.detail); // { value, text }
  })
  .on('select:flexdatalist', e => {
      console.log('selected item', e.detail); // the full item object
  });

// Later…
fd.addValue('4').disable();

// Or via native addEventListener
document.querySelector('#tags')
  .addEventListener('select:flexdatalist', e => console.log(e.detail));

Events

All events are dispatched as CustomEvent on the original <input> element. The payload is available at e.detail. Listen with fd.on(eventName, handler) or the native el.addEventListener(eventName, handler).

Event Name e.detail Description
change:flexdatalist {value, text} Fired every time the stored value changes. value is the submitted value, text is the displayed text.
select:flexdatalist Selected item object Fired when the user picks an option from the dropdown.
before:flexdatalist.data undefined Fired before loading data (static or remote).
after:flexdatalist.data Data array Fired after data is loaded.
before:flexdatalist.search {keywords, data} Fired before local search filtering begins.
after:flexdatalist.search {keywords, data, results} Fired after local search filtering, before rendering results.
show:flexdatalist.results Results array Fired before results dropdown is shown.
shown:flexdatalist.results Results array Fired after results dropdown is rendered.
item:flexdatalist.results Item object Fired for each rendered result item.
empty:flexdatalist.results null Fired when a search yields no results.
before:flexdatalist.toggle {value, text, action} Fired before a multiple value is toggled. action is "add" or "remove".
after:flexdatalist.toggle {value, text, action} Fired after a multiple value is toggled.
before:flexdatalist.remove Value string being removed Fired before a tag/value is removed.
after:flexdatalist.remove {value, text} Fired after a tag/value has been removed. Reflects the new stored state.
before:flexdatalist.remove.all Current value array Fired before all values are cleared (multiple mode).
after:flexdatalist.remove.all [] Fired after all values have been cleared.
addnew:flexdatalist Keyword string Fired when the user clicks "Add new item". e.detail is the typed keyword.
clear:flexdatalist null Fired when the value is fully cleared.
init:flexdatalist Options object Fired after initial data loading completes (resolves instance.ready).

Event Listener Example

const el = document.querySelector('#city');

// Using the fd instance (chainable)
const [fd] = Flexdatalist.init(el, { url: '/api/cities' });
fd.on('select:flexdatalist', e => {
    console.log('Selected:', e.detail);
    // e.detail → { id: 42, name: 'Paris', country: 'FR' }
});

// Or using native DOM event (dispatched on the <input> element)
el.addEventListener('change:flexdatalist', e => {
    console.log('Value changed:', e.detail.value, '→', e.detail.text);
});

Keyboard Shortcuts

Available while the alias input is focused.

↑ ↓ Navigate results
Enter Select highlighted result / add free-text value
Escape Close results dropdown
Tab Select highlighted result and move focus
, Add current text as a tag (multiple mode)
Backspace Mark / remove last tag when alias is empty

Framework Adapters

Use Flexdatalist as a native component in Vue, React, or Svelte. Each adapter is a thin wrapper (~5 kB) — the core does all the heavy lifting.

Framework Package Install
Vue 3flexdatalist-vuenpm install flexdatalist flexdatalist-vue
React 18/19flexdatalist-reactnpm install flexdatalist flexdatalist-react
Svelte 4/5flexdatalist-sveltenpm install flexdatalist flexdatalist-svelte

Vue 3

<script setup>
import { ref } from 'vue';
import { Flexdatalist } from 'flexdatalist-vue';
import 'flexdatalist/css';

const city = ref('');
</script>

<template>
  <Flexdatalist
    v-model="city"
    url="/api/cities"
    :min-length="2"
    @select="(item) => console.log(item)"
  />
</template>

React

import { Flexdatalist } from 'flexdatalist-react';
import 'flexdatalist/css';

function App() {
  return (
    <Flexdatalist
      url="/api/cities"
      minLength={2}
      onSelect={(item) =>
        console.log(item)
      }
    />
  );
}

Svelte

<script>
  import { Flexdatalist }
    from 'flexdatalist-svelte';
  import 'flexdatalist/css';

  let city = '';
</script>

<Flexdatalist
  bind:value={city}
  url="/api/cities"
  minLength={2}
  on:select={(e) =>
    console.log(e.detail)
  }
/>

All options are available as component props in every adapter.

All events are re-emitted as framework-native events (@select, onSelect, on:select).

Instance methods are exposed via refs — getValue(), setValue(), search(), clear(), etc.

Standalone use still works — just use <script src="flexdatalist.js"> with no framework.

Migrating from jQuery Plugin

Key differences when upgrading from the original jquery.flexdatalist.js.

Before (jQuery)

$('#el').flexdatalist({ url: '/api' });

$('#el').flexdatalist('value');
$('#el').flexdatalist('value', '42');
$('#el').flexdatalist('add', 'val');
$('#el').flexdatalist('remove', 'val');
$('#el').flexdatalist('disabled', true);
$('#el').flexdatalist('destroy');

$('#el').on('change:flexdatalist',
    function(evt, set, opts) {
        console.log(set.value, set.text);
    });

After (ES6 Class)

const [fd] = await Flexdatalist.init('#el', { url: '/api' });

fd.getValue();
fd.getText();                    // ['Paris', 'London']
fd.getText('string');            // 'Paris,London'
fd.getText(' | ');               // 'Paris | London'
fd.setValue('42');
fd.addValue('val');
fd.removeValue('val');
fd.disable();
fd.destroy();

fd.on('change:flexdatalist', e => {
    console.log(e.detail.value, e.detail.text);
});
// or: el.addEventListener('change:flexdatalist', e => ...)

CSS: No changes — same class names. Keep using jquery.flexdatalist.css.

HTML data-* attributes: Unchanged — all options work the same way.

Events: Same event names. Payload moved from jQuery extra args to e.detail (CustomEvent).

Alias input: No longer given a name attribute — never double-submitted.