Autocomplete Select Menu
This is a work in progress. (aka: it doesn't work yet)
Hacking
- The reason I went through this is I couldn't figure out how to get it so that if you hit enter it would pull the first item in the list and use it.
- Simply couldn't figure out how to get access to the first filtered item
Didn't work
- Was going to try just using a filter on the input and pulling the value based off that, but there's no guarantee that the filter used by the selection data-list would match
- https://stackoverflow.com/a/31831776/102401
- Tried setting up to add an innerHTML with a space or letter then remove it to blank, but that didn't work
- Tried: user-select: text; in the css, but that didn't work
HTML
<div id="awsselectmenu--wrapper">
<div
autocomplete="off"
spellcheck="false"
id="awsselectmenu--search-text"
contenteditable="true"
></div>
<div id="awsselectmenu--selections"></div>
<div id="awsselectmenu--current-selection">Active Selection: None</div>
</div>
<h4>Hacking</h4>
<ul>
<li>
The reason I went through this is I couldn't figure out how to get it so
that if you hit enter it would pull the first item in the list and use it.
</li>
<li>
Simply couldn't figure out how to get access to the first filtered item
</li>
</ul>
<h4>Didn't work</h4>
<ul>
<li>
Was going to try just using a filter on the input and pulling the value
based off that, but there's no guarantee that the filter used by the
selection data-list would match
</li>
<li>https://stackoverflow.com/a/31831776/102401</li>
<li>
Tried setting up to add an innerHTML with a space or letter then remove it
to blank, but that didn't work
</li>
<li>Tried: user-select: text; in the css, but that didn't work</li>
</ul>
CSS
#awsselectmenu--wrapper {
background-color: white;
/* height: 1.4rem; */
padding: 4px;
color: black;
}
#awsselectmenu--search-text {
/* user-select: text; */
user-select: text;
}
JavaScript
const options = [
{ key: 'roboto', value: 'Roboto' },
{ key: 'opensans', value: 'OpenSans' },
{ key: 'montserrat', value: 'Montserrat' },
{ key: 'lato', value: 'Lato' },
{ key: 'poppins', value: 'Poppins' },
{ key: 'sourcesanspro', value: 'Source Sans Pro' },
{ key: 'robotocondensed', value: 'Roboto Condensed' },
{ key: 'oswald', value: 'Oswald' },
{ key: 'robotomono', value: 'Roboto Mono' },
{ key: 'raleway', value: 'Raleway' },
{ key: 'inter', value: 'Inter' },
{ key: 'notosans', value: 'Noto Sans' },
{ key: 'ubuntu', value: 'Ubuntu' },
{ key: 'mukta', value: 'Mukta' },
{ key: 'robotoslab', value: 'Roboto Slab' },
{ key: 'nunito', value: 'Nunito' },
{ key: 'playfairdisplay', value: 'Playfair Display' },
{ key: 'ptsans', value: 'PT Sans' },
{ key: 'nunitosans', value: 'Nunito Sans' },
{ key: 'merriweather', value: 'Merriweather' },
{ key: 'rubik', value: 'Rubik' },
{ key: 'notosanskr', value: 'Noto Sans KR' },
{ key: 'worksans', value: 'Work Sans' },
{ key: 'lora', value: 'Lora' },
{ key: 'firasans', value: 'Fira Sans' },
]
const state = {
activeKey: null,
fullKeys: {},
}
const defocusInput = () => {
const actualElement = document.getElementById('awsselectmenu--search-text')
const fixElement = document.getElementById(
'awsselectmenu--current-selection'
)
actualElement.blur()
fixElement.setAttribute('contenteditable', 'true')
fixElement.focus()
fixElement.blur()
fixElement.setAttribute('contenteditable', 'false')
}
const handleInputFocus = (event) => {
console.log('focus')
/////////////////
// Trying to fix safari issue where blanking text
// removes the caret
//
// this didn't work for for getting the
// caret to focus properly
// jevent.target.innerHTML = '-'
// event.target.innerHTML = ''
//
//
// doing it explicitly by element didn't work
const input = document.getElementById('awsselectmenu--search-text')
input.innerText = 'a'
// input.innerText = ''
//
// const selectionsEl = document.getElementById('awsselectmenu--selections')
// while (selectionsEl.firstChild) {
// selectionsEl.removeChild(selectionsEl.firstChild)
// }
// for (let i = 0; i < 5; i++) {
// const newItem = document.createElement('button')
// const buttonId = `awsselectmenu--choice-id--${options[i].key}`
// newItem.id = buttonId
// newItem.innerHTML = options[i].value
// selectionsEl.appendChild(newItem)
// }
}
const handleKeyup = (event) => {
// TODO: Handle tab and escape
const pressedKey = event.key.toLowerCase()
if (pressedKey === 'enter') {
console.log('ENTER')
makeSelection()
} else {
state.currentSearch = document.getElementById(
'awsselectmenu--search-text'
).innerText
removeSelections()
const selectionsEl = document.getElementById(
'awsselectmenu--selections'
)
let counter = 0
for (i = 0; i < options.length; i++) {
const pattern = new RegExp(state.currentSearch, 'gi')
if (options[i].value.toLowerCase().match(pattern)) {
const newItem = document.createElement('button')
const buttonId = `awsselectmenu--choice-id--${options[i].key}`
newItem.id = buttonId
newItem.innerHTML = options[i].value
selectionsEl.appendChild(newItem)
counter += 1
}
if (counter === 5) {
break
}
}
console.log(state.currentSearch)
}
}
const makeSelection = () => {
// TODO: deal with if there isn't a valid option
console.log('Making selection')
for (i = 0; i < options.length; i++) {
const pattern = new RegExp(state.currentSearch, 'gi')
if (options[i].value.toLowerCase().match(pattern)) {
state.activeKey = options[i].key
break
}
}
setPlaceholder()
removeSelections()
defocusInput()
}
const prepKeys = () => {
options.forEach((option) => {
state.fullKeys[option.key] = option.value
})
}
const removeSelections = () => {
const selectionsEl = document.getElementById('awsselectmenu--selections')
while (selectionsEl.firstChild) {
selectionsEl.removeChild(selectionsEl.firstChild)
}
}
const setSelectionFromKey = (key) => {
state.activeKey = key
console.log(state.activeKey)
setPlaceholder()
defocusInput()
removeSelections()
}
const setPlaceholder = () => {
const inputField = document.getElementById('awsselectmenu--search-text')
if (state.activeKey) {
inputField.innerHTML = state.fullKeys[state.activeKey]
} else {
inputField.innerHTML = 'Select a font'
}
}
// This is here to deal with clicking away from
// the input field so it can be closed without
// haveing to set a timer to keep the buttons from
// closing for a bit before they disappear
const handlePageClick = (event) => {
const clickId = event.target.id
if (clickId) {
const idParts = clickId.split('--')
if (idParts[0] !== 'awsselectmenu') {
removeSelections()
} else {
if (idParts[1] === 'choice-id') {
setSelectionFromKey(idParts[2])
defocusInput()
}
}
} else {
removeSelections()
}
}
const kickoff = () => {
console.log(`Kickoff: ${new Date().getTime()}`)
prepKeys()
document
.getElementById('awsselectmenu--search-text')
.addEventListener('focus', handleInputFocus)
document
.getElementById('awsselectmenu--search-text')
.addEventListener('keyup', handleKeyup)
document.addEventListener('click', handlePageClick)
setPlaceholder()
}
document.addEventListener('DOMContentLoaded', kickoff)
Deatils
- GOAL: [ ] Accepts an array of key-value pairs to use as selections
- GOAL: [ ] Filter selections based on text input
- GOAL: [ ] Proper accessability
- GOAL: [ ] Show autocompete when typing
- GOAL: [ ] Show first option in text area if autocomplete doesn't match exactly
- GOAL: [ ] Clicking enter selects first item
- GOAL: [ ] Tabbing to secondary selections
- GOAL: [ ] Hitting enter on a secondary selection chooses it
- GOAL: [ ] Clicking a secondary button selects it
- GOAL: [ ] Show the last select item as a placeholder when the field isn't focused
- GOAL: [ ] Ability to select a placeholder on load
- GOAL: [ ] Scrollable list of selections
- GOAL: [ ] Multiple instances on the same page
Notes
- This is currently a very naive approach to the matching selections. If the string in the input field matches anywhere in the item list it gets included.
TODO
- Dig through this for multiline suppression: https://stackoverflow.com/questions/6831482/contenteditable-single-line-input
References
- Create a custom autocomplete list for an input field
- html5 datalist to select only predefined options
- How to create an autocomplete input with only HTML
- HTMLElement: input event
- Detect the Enter key in a text input field
- HTMLElement.contentEditable
- contenteditable single-line input
- contenteditable change events
- When To Use Input Autocomplete In HTML (And When List Or Datalist)
- How to achieve autocomplete feature over HTML drop-down or select element?
- HTMLElement.blur() does not remove focus in Safari when used on contenteditable elements?
- contenteditable
- Why blur and focus doesn't work on Safari?
- HTMLElement.enterKeyHint
- How can I make a browser display all datalist options when a default value is set?
- HTML5 Datalist: What You Need To Know
- [HTML][JS] Input + datalist with validation instead of selects
- HTML attribute: required
- <input>': The Input (Form Input) element
- <datalist>: The HTML Data List element
- How can i validate the input from a html5 Datalist?
- Validate 'data-value' in a html 'datalist'
- Using data attributes
- Find selected item in Datalist in HTML
- data-*
- Javascript: Match datalist options based on input
- HTML - Living Standard - datalist element
- Content categories
- Using data attributes
- Constraint validation
- How to Create Autocomplete Dropdowns with the Datalist Element
- Auto select first option in datalist while typing
- How to auto select the first item in datalist (html 5)?
- Lightweight Autocomplete Controls with the HTML5 Datalist
- Datalist element in HTML 5
- Find selected item in Datalist in HTML
- HTMLFormElement: formdata event
- Selection
- Selection API
- Document.activeElement
- HTMLElement.blur() does not remove focus in Safari when used on contenteditable elements?
- Caret positioning is incorrect in an empty contenteditable element with ::before pseudo
- Why is the caret invisible in a contenteditable with position:relative?
- contenteditable not working in safari but works in chrome
- setCursor doesn't work well with inputStyle: 'contenteditable', #6167
- WebKit contentEditable focus bug workaround.html
- HTMLInputElement.setSelectionRange()
- HTMLElement.contentEditable
- Global attributes
- The HTML datalist tag for filter search
- Avoid filtering of datalist items in an input element
- HTML: Select multiple as dropdown
- <select>: The HTML Select element
- Accessible Rich Internet Applications (WAI-ARIA) 1.3
- HTMLElement: input event
- <optgroup>: The Option Group element
- HTMLElement: change event
- m- Autocomplete