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
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>
- 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.
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.
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"
/>
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.
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">▼</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.
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"
/>
Javascript
customAwesompleteContext.ajax404 = ajax404
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.
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'}
]
HEEx
<.input field={@form[:color]} type="text" value="purple" phx-debounce="blur" />
<div><span id="awe-color-result"></span> ↲</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.
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]}
/>
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=",;"
/>
Javascript
customAwesompleteContext.names2contacts = names2contacts
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.
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
Javascript
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.
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"
/>
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;