Skip to content

Instantly share code, notes, and snippets.

@akingdom
Last active August 13, 2023 10:27
Show Gist options
  • Save akingdom/8edbc92dccbe686d340221e28fb95abb to your computer and use it in GitHub Desktop.
Save akingdom/8edbc92dccbe686d340221e28fb95abb to your computer and use it in GitHub Desktop.
Example of a basic editable HTML dropdown (SELECT menu), aimed at easy use on multiple SELECT elements.
<html>
<!--
This replaces the SELECT dropdown menu with an editable INPUT textbox,
when the last ('other') option is selected from the list.
This version allows adding this functionality to multiple SELECT elements.
(See the 'setupEditableSelectElement' method.)
Note that this may not be a good fit when supplying both display values and raw values,
as evidenced by the 'spanish' option, for example.
Future: It wouldn't be that hard to add in functionality to switch back to dropdown menu...
Lookup the edited value against the original SELECT OPTIONS list (see 'allOptions')
and if a match is found, then transform the element from INPUT back to SELECT.
Future: A better way to do this might be to just swap the tag of the SELECT element to INPUT
and check the tag in the onChange
By Andrew Kingdom (C) 2023 all rights reserved. MIT License.
-->
<head>
<style>
.selecteditable {
width: 100%;
height: 30px;
border: 1px solid black;
}
input.selecteditable {
background-color: cornsilk;
}
option {
padding: 10px;
}
</style>
</head>
<body>
<h1>Hello</h1>
Dear people,
<select name="languages" id="languages" class="selecteditable">
<option value="english" selected>English</option><!-- OK -->
<option value="spanish">Español</option><!-- OK -->
<option value="greek">Ελλενικα</option><!-- OK -->
<option>French</option><!-- should have a value - won't be found -->
<option>German</option><!-- should have a value - won't be found -->
<option value="other">Other</option>
</select>
This example was brought to you by someone you know.
</body>
<script type="text/javascript">
document.addEventListener('DOMContentLoaded', function () {
// document is ready to query/use
console.log('The DOM is ready!');
// Specify the SELECT element and the OPTION within it (if any), then prepare it for use.
var languagesSelect = document.getElementById('languages');
var defaultValue = 'greek'; // stored value or undefined if nothing is stored
setupEditableSelectElement(languagesSelect, defaultValue);
function setupEditableSelectElement(selectElement, defaultValue) {
// parameters:
// selectElement = the SELECT element to modify
// defaultValue = the value to select
// When undefined or '', the first SELECTED OPTION is used, or the first OPTION if none are SELECTED.
// The OPTION value "other" is a special case that will replace the entire SELECT element with an *editable* INPUT text box when selected,
// for example, <option value="other">Enter your own value</option>
//
selectElement.dataset.priorSelectedValue = defaultValue; // dynamic state -- this is the only one we care about ongoing.
selectElement.dataset.defaultValue = defaultValue; // static state
selectElement.dataset.firstOption = selectElement.querySelector('option'); // static state
selectElement.dataset.firstSelectedOption = selectElement.querySelector('option[selected]'); // static state
//selectElement.dataset.allOptions= selectElement.querySelectorAll('option').join(','); // static state -- this could be useful to track if we switch to *edit mode*.
// Set the initially selected option...
var validDefaultValue;
if (typeof defaultValue === 'undefined' || defaultValue === '') {
// defaultValue has no value
validDefaultValue = false;
} else {
// defaultValue has a value, check it exists
if (selectElement.querySelectorAll(`option[value="${selectElement.dataset.defaultValue}"]`).length != 0) {
// defaultValue has a value that exists
validDefaultValue = true;
} else {
// defaultValue has a value that does not exist
validDefaultValue = false;
}
}
// set the previous value for later use
if (validDefaultValue) {
selectElement.dataset.priorSelectedValue = selectElement.dataset.defaultValue;
} else {
// set a valid previous value, which will be the value to edit, since the specified one doesn't exist
if (typeof defaultValue !== 'undefined' && defaultValue !== '') {
selectElement.dataset.priorSelectedValue = defaultValue; // defaultValue specified but doesn't exist
} else if (selectElement.dataset.firstSelectedOption) {
selectElement.dataset.priorSelectedValue = selectElement.dataset.firstSelectedOption.value; // defaultValue not specified, use first selected item in HTML list
} else if (firstOption) {
selectElement.dataset.priorSelectedValue = selectElement.dataset.firstOption.value; // defaultValue not specified, use first item in HTML list
} else {
return; // no options -- don't continue setting this up
}
selectElement.dataset.defaultValue = 'other'; // trigger edit mode, since we don't have a match so it must be a custom value
}
console.log('initialSelectedValue: ' + selectElement.dataset.defaultValue);
// Show options when clicked/tapped.
selectElement.addEventListener('click', function () {
this.classList.add('open');
});
const handleChange = function (currentElement) {
if (currentElement.value === 'other') {
const newElement = document.createElement('input');
newElement.name = currentElement.name;
newElement.id = currentElement.id;
newElement.dataset.priorSelectedValue = selectElement.dataset.priorSelectedValue;
newElement.dataset.defaultValue = selectElement.dataset.defaultValue;
newElement.dataset.firstOption = selectElement.dataset.firstOption;
newElement.dataset.firstSelectedOption = selectElement.dataset.firstSelectedOption;
//newElement.dataset.allOptions = selectElement.dataset.allOptions;
newElement.value = currentElement.dataset.priorSelectedValue; // the value to edit -- it may be preferred to do... newElement.value = '';
newElement.classList = currentElement.classList;
newElement.addEventListener('DOMNodeInserted', function (event) {
if (event.target === newElement) {
console.log('The local DOM is updated!');
// after the element is added
newElement.focus();
newElement.select();
}
});
currentElement.parentNode.replaceChild(newElement, currentElement);
} else {
// remember the new selection as the value to edit
currentElement.dataset.priorSelectedValue = currentElement.options[currentElement.selectedIndex].value;
}
};
selectElement.addEventListener('change', (event) => handleChange(event.target));
var initialOption = selectElement.querySelectorAll(`option[value="${selectElement.dataset.defaultValue}"]`);
if (initialOption.length != 0) {
initialOption[0].selected = true;
console.log(`selected: "${selectElement.dataset.defaultValue}"`);
handleChange(selectElement);
console.log(`selected2: "${selectElement.dataset.defaultValue}"`);
}
}
});
</script>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment