Odo Dropdown
Custom dropdown component that defaults to native select elements on touch devices.
Support
IE9+ with polyfills.
To support IE<=11, you will need to add classList and Element#closest polyfills.
Dependencies
Odo Base Component, Odo Helpers.
Basic Odo Dropdown
On non-touch devices, this component acts similarly to native select elements. All keyboard events that interact with native select elements may be used with the custom markup.
- When component is in closed state:
- Can tab over component to focus
- Can use
SPACE,ENTER,UP, orDOWNkeys to open the custom markup options
- When component is in open state:
- Can use
UPorDOWNkeys to highlight options - Can use
SPACEorENTERkeys to select a highlighted option - Can use
ESCorTABkeys to close the options, and return to closed state
- Can use
Markup
<div id="demo-1" class="odo-dropdown">
<select class="odo-dropdown__select">
<option value="0">Sunday</option>
<option value="1">Monday</option>
<option value="2">Tuesday</option>
<option value="3">Wednesday</option>
<option value="4">Thursday</option>
<option value="5">Friday</option>
<option value="6">Saturday</option>
</select>
<button type="button" class="odo-dropdown__button">
<div class="odo-dropdown__button-inner">
<span class="odo-dropdown__default">Sunday</span>
<span class="odo-dropdown__value"></span>
<svg class="odo-dropdown__button-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
<path d="M8,12c-0.3,0-0.5-0.1-0.7-0.3l-5-5c-0.4-0.4-0.4-1,0-1.4s1-0.4,1.4,0L8,9.6l4.3-4.3c0.4-0.4,1-0.4,1.4,0s0.4,1,0,1.4l-5,5 C8.5,11.9,8.3,12,8,12z"/>
</svg>
</div>
</button>
</div>
Setup
var dropdown = new OdoDropdown(document.getElementById('demo-1'));
Options Height
The options list has a static max-height applied to it. Feel free to change this, or subclass the dropdown and calculate the height based on the available viewport.
Public Event
The selected day value above is:
The CHANGE event is emitted when a user has selected a value in the dropdown. Use on to listen for the event. OdoDropdown is an event emitter, so you can also use once and off.
dropdown.on(OdoDropdown.EventType.CHANGE, (data) => {
console.log(data.value);
});
Public Properties
dropdown.value // get or set the value of the select.
dropdown.selectedText // get the selected option's text content.
dropdown.select // get the <select> element.
dropdown.button // get the <button> element.
dropdown.selectedIndex // get or set the value of the <select> by index.
dropdown.disabled // get or set the disabled state of the component.
Public Methods
dropdown.toggleOptionByValue(value, isDisabled)
dropdown.disableOptionByValue('bar')
dropdown.enableOptionByValue('today')
dropdown.getCustomOptions()
dropdown.getNativeOptions()
dropdown.dispose()
Options Placement
Additionally, you can modify the position of the drop down to display the custom options where you would like. In this example, the options are placed below the button.
This example also shows how you can use a <label> with the dropdown. You need the for attribute! You could also put the <label> inside the <button> if you wanted.
CSS
Add a style which moves the options container, then add the class to the main element.
.odo-dropdown--options-below .odo-dropdown__options {
transform: translate(0, 45px);
}
JavaScript
var dropdown = new OdoDropdown(document.getElementById('demo-2'));
Selected Option
It can be initialized with an option already selected via the selected attribute on an <option>.
Disabled Option(s)
It can be initialized with an option disabled via the disabled attribute on an <option>.
Custom Styling
Instead of importing the css file for the dropdown and overriding what you need, it is recommended to copy the styles to your own stylesheet and modify them there.
Accessibility with WAI-ARIA
This component manages aria-* attributes to provide screen readers with the appropriate information to control and choose options.
If your odo-dropdown__button is not a <button> element, you need to add the role="button" attribute to it.
Usage with React
Use the insertMarkup option and set it to false.
You will still need to use the lifecycle methods like componentDidMount to initialize the dropdown instance and componentWillUnmount to dispose of it.
Here's an example render method to give you an idea from a component called LabelledDropdown.
render() {
const selectedOption = this.props.options.find(option => option.selected) || {};
return (
<div className="labelled-dropdown">
<label htmlFor={this.props.id}>{this.props.label}</label>
<div className="odo-dropdown" ref={element => this.element = element}>
<div className={OdoDropdown.Classes.OPTIONS_CONTAINER} role="menu" aria-hidden="true">
{this.props.options.map((option) => {
const selected = option.selected ? ' ' + OdoDropdown.Classes.OPTION_SELECTED : '';
const disabled = option.disabled ? ' ' + OdoDropdown.Classes.OPTION_DISABLED : '';
const className = OdoDropdown.Classes.OPTION + selected + disabled;
return <div key={option.value} className={className} data-value={option.value} tabIndex="-1" role="menuitem">{option.label}</div>
})}
</div>
<select className="odo-dropdown__select" name={this.props.name} id={this.props.id} defaultValue={selectedOption.value}>
{this.props.options.map((option) => (
<option key={option.value} value={option.value}>{option.label}</option>
))}
</select>
<button type="button" className="odo-dropdown__button">
<div className="odo-dropdown__button-inner">
<span className="odo-dropdown__default">{selectedOption.label}</span>
<span className="odo-dropdown__value"></span>
<svg className="odo-dropdown__button-icon" viewBox="0 0 12.021 6.717">
<polygon points="6.01 6.717 0 0.707 0.707 0 6.01 5.303 11.313 0 12.021 0.707 6.01 6.717"/>
</svg>
</div>
</button>
</div>
</div>
);
}
The LabelledDropdown component could then be used like this:
<LabelledDropdown options={[{ selected: false, value: 'first_name', label: 'First Name' }, { selected: true, value: 'last_name', label: 'Last Name' }]}
name="sorter" id="sorter-dropdown" label="Sort" />