is a Phoenix form helper that utilizes Lea Verou's autocomplete / autosuggest / typeahead / inputsearch Awesomplete widget.

It comes with an AwesompleteUtil javascript library which adds the following features:

  • Dynamic remote data loading; based on what is typed-in it performs an ajax lookup.
  • Allow HTML markup in the shown items. Show value with description. Optionally search in the description text.
  • Show when there is an exact match.
  • Show when there isn't a match.
  • When there is an exact match show related data (supplied in the remote data) in other parts of the page.
  • Select the highlighted item when the tab key is used.

Examples

Github username search
Example demonstrates:
  • Dynamic data loading.
  • Show related data of selected item.

Enter a username:

Photo or avatar
avatar image
Side note: the Github API will not show suggestions anymore if the API rate limit is exceeded for your IP address.
The example above is made with the HEEx template below.

The Github REST API also returns avatar url's. When there is an exact match, the avatar url will be set in the IMG tag to show the avatar picture.

HEEx

<.simple_form
  for={@form}
  id="list-form"
  phx-target={@myself}
  phx-change="validate"
  phx-submit="save"
  >
        
  <.input field={@form[:name1]} type="autocomplete" phx-debounce="blur" />

  <img id="awe-avatar-img" 
    alt="avatar image" 
    width="100"
    height="100"
    />

  <.autocomplete
    forField={@form[:name1]}
    url="https://api.github.com/search/users?q=" 
    urlEnd=" in:login type:user&per_page=30"  
    minChars="3"
    maxItems="10"
    limit="30"
    value="login"
    filter="filterStartsWith"
    item="itemStartsWith"
    />

  <.copy_value_to_id
    field={@form[:name1]}
    dataField="avatar_url"
    target="#awe-avatar-img"
    />

</.simple_form>
In this first example the surrounding elements are shown in the HEEx template to clarify the context:
  • The input is placed in a simple_form and uses the @form assign.
  • The div with phx-update="ignore" is needed in LiveView, because Awesomplete manipulates the DOM.
The maxItems parameter indicates that maximum 10 suggestions must be shown in the dropdown.

The per_page parameter in the url tells the server to return maximum 30 suggestions per call.

The limit parameter informs the AwesompleteUtil component that normally 30 results can be expected. If there are less than 30 results, any additional characters in the search phrase won't need a new server call, because all the matching suggestions are already retrieved.

The client-side suggestions filter must be made consistent with the search functionality of the REST API. In this case, the server will only return usernames that start with the entered text, and the client-side filter should work in the same way.

The item parameter is configured to highlight only matching text at the beginning of the suggestions, to clearly indicate why these suggestions are shown.

show/hide runtime code

Runtime javascript

iTunes artist search
Example demonstrates:
  • Dynamic data loading.
  • Word search filter.
  • Word text highlighter.
Obviously we cannot load all artists known by iTunes in the browsers memory, so that's why we use here dynamic data loading. The search starts if 3 characters are entered.
Enter an artist name:
The standard supplied 'starts-with' and 'contains' filters don't exactly match with how iTunes searches. iTunes searches words, you can search for 'Michael Jackson' or 'Jackson Michael' and get the same results. Thats why a filter called 'filterWords' is used that filters on the words entered. To highlight the words in the list of suggestions a function called 'itemWords' is used.
A limit of -1 is specified to force it to requery if more characters are typed. The service search for whole words, so when a part of the word is entered and doesn't result in any hits, this doesn't mean that there wouldn't be more results if more characters are typed. For example: search for 'jackso' results in 1 hit, but 'jackson' gives a lot more results.

The iTunes service doesn't support wildcards but others (like Elasticsearch) do. Assuming that the user is completing the last word while typing, you could add a * wildcard at the end of the search url with the urlEnd: '*' configuration to search the last word starting with the typed letters.

HEEx

<.input field={@form[:artist1]} type="text" phx-debounce="blur" />

<.autocomplete
  forField={@form[:artist1]}          
  url="https://itunes.apple.com/search?entity=musicArtist&attribute=artistTerm&media=music&limit=15&country=gb&sort=artistTerm&term="
  limit="-1"
  debounce="300"
  filter="filterWords"
  item="itemWords"
  value="artistName"
  />

show/hide runtime code

runtime javascript

iTunes track search
Example demonstrates:
  • Dynamic data loading.
  • Show related data of selected item.
Enter a song title:
Artists
Album
album image
The creation of the Awesomplete object below is almost the same as in the 'iTunes artist search' example.

HEEx

<.input field={@form[:track1]} type="text" phx-debounce="blur" />

<.copy_value_to_id
  field={@form[:track1]}
  dataField="collectionName"
  target="#awe-track-album"
  />

<.copy_value_to_id
  field={@form[:track1]}
  dataField="artistName"
  target="#awe-track-artist"
  />

<.copy_value_to_id
  field={@form[:track1]}
  dataField="artworkUrl100"
  target="#awe-track-album-img"
  />

<.autocomplete
  forField={@form[:track1]}
  url="https://itunes.apple.com/search?entity=song&attribute=songTerm&media=music&limit=10&country=gb&sort=songTerm&term="
  limit="-1"
  debounce="300"
  maxItems="10"
  minChars="4"
  value="trackName"
  descr="collectionName"
  filter="filterWords"
  item="itemWords"
  />
Unfortunetly we cannot instruct to iTunes to sort on song title length. If you search for songs with only one or two words, it might be that the desired song is not in the first 10 shown results.

show/hide runtime code

runtime javascript

Combobox with countries
Example demonstrates:
  • Combobox.
  • Use remote service which returns everything; load all.
The combobox is useful when there are only a few items in the list, or to give the user some examples of expected input. Here we use maxItems:8, so when the combobox is opened, only the first 8 countries are shown. The shortest country names are shown first.

HEEx

<.input field={@form[:combocountry]} type="text" phx-debounce="blur" />

<span class="input-group-btn">
  <button class="dropdown-btn" id={"awe_btn_#{@form[:combocountry].id}"} type="button" tabindex="-1">
      <span class="caron">&#9660;</span>
  </button>
</span>

<.autocomplete
  forField={@form[:combocountry]}
  url="https://nico-amsterdam.github.io/awesomplete-util/json/countries.json"
  loadall="true"
  prepop="true"
  minChars="1"
  maxItems="8"
  value="name"
  combobox="true"
  />
The prepop:true makes sure that the countries are loaded during page load. The combobox:true will open/close the list when the button is pushed, assuming the id of the button is 'awe_btn_' + id of input-text. Instead of true/false you can also supply the id of the button with the combobox property.

show/hide runtime code

runtime javascript

Connect as user to host
Example demonstrates:
  • Type user name and get suggestions for hostnames; autocomplete after a certain character.
  • Show descriptions in the suggestion list.
  • Load suggestions from static json file.
Enter for example 'root@192.168.1.1'
Add a custom function to the custom awesomplete context in app.js:

Javascript

customAwesompleteContext.dataAfterAtSign = function (data, input) { 
  var before = (input.slice(0, input.indexOf('@')) + '@').trim(); 
  return {label: before + data.label, value: before + data.value}; 
}

HEEx

<.input field={@form[:host1]} type="text" phx-debounce="blur" />

<.autocomplete
  forField={@form[:host1]}
  url="/js/host.json"
  loadall="true"
  filter="filterStartsWith"
  value="value"
  descr="label"
  data="dataAfterAtSign"
  />

show/hide runtime code

runtime javascript

Country iso code
Example demonstrates:
  • Use remote service that doesn't return a list but a single entry to verify input.
  • Handle HTTP 404 status with custom ajax function.
  • Show related data if entry found.
  • Flatten the json structure in the response.
Enter for example 'usa'
The remote service returns a HTTP status 404/400 if the searched value is not found. The default ajax function only processes responses with HTTP OK status 200. The ajax404 function below replaces the onload function to set the empty list if a 404 is received.

Javascript

customAwesompleteContext.ajax404 = ajax404
This is actually not an autocomplete, but it does verify the input.

HEEx

<.input field={@form[:country_iso]} type="text" phx-debounce="blur" />

<span id="awe-country-name"></span>

<.autocomplete
  forField={@form[:country_iso]}
  url="https://restcountries.com/v3/alpha/"
  value="cca3"
  limit="1"
  minChars="3"
  maxItems="0"
  filter="filterStartsWith"
  ajax="ajax404"
  convertResponse="jsonFlatten"
  />

<.copy_value_to_id
  field={@form[:country_iso]}
  dataField="name.official"
  target="#awe-country-name"
  />
A limit:1 tells that not more than 1 result is expected, so the json service doesn't have to return an array.

The official name is in JSON structure like this { "name": {"official": "the country name"}}. The jsonFlatten function transforms this to {"name.official": "the country name"}, which makes is easier to pick it up in the copy_to_value_id function.

show/hide runtime code

runtime javascript

Colors
Example demonstrates:
  • Label with HTML markup.
  • Show related data of initial/autofill value; Prepopulation event during page load
Enter a color:
Define the color list in app.js and add it to the custom awesomplete context:

Javascript


customAwesompleteContext.listColor = [
  { label: "<b style='color:black'>black</b>",   value: 'black' },
  { label: "<b style='color:blue'>blue</b>",     value: 'blue'  },
  { label: "<b style='color:brown'>brown</b>",   value: 'brown' },
  { label: "<b style='color:green'>green</b>",   value: 'green' },
  { label: "<b style='color:orange'>orange</b>", value: 'orange'},
  { label: "<b style='color:pink'>pink</b>",     value: 'pink'  },
  { label: "<b style='color:purple'>purple</b>", value: 'purple'},
  { label: "<b style='color:red'>red</b>",       value: 'red'   },
  { label: "<b style='color:yellow'>yellow</b>", value: 'yellow'}
]
Input control:

HEEx

<.input field={@form[:color]} type="text" value="purple" phx-debounce="blur" />
<div><span id="awe-color-result"></span> &#x21B2;</div>

<.autocomplete
  forField={@form[:color]} 
  minChars="1" 
  prepop="true"
  filter="filterContains"
  list="listColor"
  />

<.copy_value_to_id
  field={@form[:color]}
  dataField="label"
  target="#awe-color-result"
  />
Notice the prepop property and the startCopy function call. The initial/autofilled value of input-control triggers the copy action, and the label is copied to the span-tag with id 'awe-color-result' during page load. The order of the HTML elements and the inline script is important here. The startCopy function must be called before the startAwesomplete function, because the latter can immediately fire the 'awesomplete-prepop' event which should be handled by the former. The startCopy inline script should preferable be placed after the HTML element which is the target. If you do, it only has to lookup the target element once, but if you don't do this it will lookup the target element with each event.

If you use remote data loading and combine prepop:true with loadall:true the remote call will happen during page load. If prepop:false, the loadall will be done lazely, e.g. if the user makes changes and the input size >= minChars.

show/hide runtime code

runtime javascript

Country capitals
Example demonstrates:
  • One remote call (load all) resulting in one response list shared by multiple controls.
  • Copy related data of selected item to other input field, in two directions!
Enter either country or capital, and look what happens in the other field.
Load the countries and capitals in app.js and add them to the custom awesomplete context:

Javascript


// use fetch function and json parser to retrieve countries
const countryResponse = await fetch("https://nico-amsterdam.github.io/awesomplete-util/json/countries.json")
customAwesompleteContext.listCountry = await countryResponse.json() 

// The filter below removes countries without capital.
customAwesompleteContext.listCapital = listCountry.filter(function(e) { return e.capital })

HEEx

<.input field={@form[:country]} type="text" phx-debounce="blur" />
          
<.input field={@form[:capital]} type="text" phx-debounce="blur" />

<.autocomplete
  forField={@form[:country]} 
  assign="true"
  minChars="1" 
  maxItems="7" 
  value="name" 
  list="listCountry"
  />

<.autocomplete
  forField={@form[:capital]} 
  assign="true"
  minChars="1" 
  maxItems="7" 
  value="capital" 
  list="listCapital"
  />

<.copy_value_to_field
  sourceField={@form[:country]}
  dataField="capital"
  targetField={@form[:capital]}
  />

<.copy_value_to_field
  sourceField={@form[:capital]}
  dataField="name"
  targetField={@form[:country]}
  />

show/hide runtime code

runtime javascript

Seafood toppings
Example demonstrates:
  • Data list in page source.
  • Select multiple items.
Enter for example 'Crayfish, Oysters'
Notice that you can enter multiple toppings, seperated by , or ;

HEEx

<.input field={@form[:seafood]} type="text" phx-debounce="blur" data-list="#seafood_options" />
<datalist id="seafood_options">
   <option>Anchovies</option>
   <option>Cajun Prawn</option>
   <option>Crayfish</option>
   <option>Lobster</option>
   <option>Oysters</option>
   <option>Prawns</option>
   <option>Salmon</option>
   <option>Shrimps</option>
   <option>Smoked Salmon</option>
   <option>Squid</option>
   <option>Tuna</option>
   <option>Whitebait</option>
</datalist>

<.autocomplete
  forField={@form[:seafood]}
  minChars="1"
  multiple=",;"
  />

show/hide runtime code

runtime javascript

The regular expressions are used to extract the last part of the input from the rest.
Email address
Example demonstrates:
  • In the shown suggestions the email address is shown below the name. The value contains both.
  • Multiple input values.
Enter an email address (they are all from starwars.com):
This example uses Star Wars names from a github repository. The names2contacts function below constructs email addresses and a value that contains both name and email address. In app.js this function is added to the custom awesomplete context.

Javascript

customAwesompleteContext.names2contacts = names2contacts
Below the Awesomplete object and input control created

HEEx

<.input field={@form[:multimail]} type="text" phx-debounce="blur" />

<.autocomplete
  forField={@form[:multimail]}
  url="https://raw.githubusercontent.com/kentcdodds/starwars-names/main/src/starwars-names.json"
  convertResponse="names2contacts"
  loadall="true"
  minChars="1"
  maxItems="7"
  multiple=";,"
  value="value"
  label="name"
  descr="email"
  item="itemMarkAll"
  />
In this example the 'value' field contains both name (label) and email address (descr). By default the description is not highlighted. The itemMarkAll function above highlights all matching text in the suggestion list, including the description. Try for example to search on '@starwars.com' to see what it highlights with or without this function.

show/hide runtime code

runtime javascript

Dismissible chip pills
Example demonstrates:
  • Data list in page source.
  • Dismissible chip pills, rendered via template.
Enter for example 'Oysters'
This example has a lot of custom code. The hidden input with id user_seafood2_all is used to store the complete list. The visible input with id user_seafood2 is used to add new chip pills.

HEEx

<div id="seafood2-chip-container" class="chip-container" aria-live="polite"></div>

<.input field={@form[:seafood2]} type="text" phx-debounce="blur" enterkeyhint="done"
  data-list="Anchovies, Cajun Prawn, Crayfish, Lobster, Oysters, Prawns, Salmon, Shrimps, Smoked Salmon, Squid, Tuna, Whitebait" />

<input type="hidden" id="user_seafood2_all" value="" />

<.autocomplete
  forField={@form[:seafood2]}
  minChars="1"
  />

Javascript

show/hide runtime code

runtime javascript

After every selection, the chip is added and the input field is cleared for the topping. The complete list is stored in an input element with type hidden.
Diacritics and ligatures
Example demonstrates:
  • Type base characters and get suggustions with diacritics and ligatures
  • Using a custom filter
This example allows you to search for words with diacritics by typing just the base characters. For example search for 'oss' and besides 'fossilien' you will get the suggestion of 'größe'.
  • o is base character of ö
  • The ligature ß can be searched with 'ss'
The functions below are used to convert diacrites and ligatures to base characters.

Javascript

The custom awesomplete context has a custom diacritics filter:

Javascript



// This filter converts diacritics and ligatures to base characters and compares the converted strings.
customAwesompleteContext.filterDiacrites = function(text, input) {
  return trimLowerDiacrites(text).indexOf(trimLowerDiacrites(input)) !== -1;
}
This example uses a fixed list of words. If you use a backend service to get suggestions, this backend service must apply the same search algorithm as this filter.
Input control:

HEEx

<.input field={@form[:diacritics]} type="text" phx-debounce="blur" 
    data-list="ænigma, Æthel, böse, fließen, fossilien, fröhlich
    , führen, füllen, gänse, götter, größe, hände, hölle, hügel
    , küssen, kräftig, lösen, löwe, mädchen, männer, mächtig
    , müde, möglichkeit, Œdipus, œuvre, Osnabrück, rätseln
    , rösten, rühren, säen, säubern, schön, schwül, STRAUß
    , süß, träumen, überlegen, übermäßig, vögel, wählen, wände
    , IJmuiden, zählen, accès, appétit, cliché, déjà vu, garçon
    , hôtel, île, accès, maître, pâté, où, quête, tête-à-tête
    , voilà" />
            
<.autocomplete
  forField={@form[:diacritics]}
  minChars="1"
  maxItems="10"
  filter="filterDiacrites"
  />

show/hide runtime code

Runtime javascript

Text with mentions
Example demonstrates:
  • Autocomplete after typing symbol (in this case: @)
  • Suggestions in the middle of the text, at the cursor position.
  • Autocomplete in a textarea
  • Custom input converter
  • Custom replace function
  • Custom search filter
  • Custom item highlighter
  • Option label used as description
  • Mixture of suggestions with and without extra description
Enter for example: 'Maybe @Neo can solve this. I wil notify @Frank about this.'

Note that you can change the names @Neo and @Frank afterwards with autocompletion.

This is an advanced example

HEEx

<.input field={@form[:mentions]} type="textarea" rows="2" phx-debounce="blur"
  data-list="#peoplelist" />

<datalist id="peoplelist">
  <option>Ada</option>
  <option>Beyonce</option>
  <option>Chris</option>
  <option label="HR">Danny</option>
  <option>Edward</option>
  <option label="CEO">Frank</option>
  <option>Gerard</option>
  <option>Hanna</option>
  <option>Iris</option>
  <option>John</option>
  <option label="Director">Kate</option>
  <option label="Marketing">Leo</option>
  <option label="Sales">Max</option>
  <option label="IT">Neo</option>
  <option>Olivia</option>
  <option label="Finance">Penny</option>
  <option>Quincey</option>
  <option>Ryan</option>
  <option>Sarah</option>
  <option>Tara</option>
  <option>Umar</option>
  <option label="PR">Veronica</option>
  <option>Wendy</option>
  <option>Xena</option>
  <option>Yasmine</option>
  <option>Zara</option>
</datalist>

<.autocomplete
  forField={@form[:mentions]}
  minChars="1"
  convertInput="convertTextWithMentions"
  data="dataOptionalLabel"
  filter="filterTextWithMentions"
  item="itemTextWithMentions"
  replace="replaceTextWithMentions"
  />

Javascript

customAwesompleteContext.convertTextWithMentions = convertTextWithMentions;
customAwesompleteContext.replaceTextWithMentions = replaceTextWithMentions;
customAwesompleteContext.filterTextWithMentions = filterTextWithMentions;
customAwesompleteContext.itemTextWithMentions = itemTextWithMentions;
customAwesompleteContext.dataOptionalLabel = dataOptionalLabel;

show/hide runtime code

runtime javascript

Links

Installation

Doc

License: MIT