I've created a mobile dropdown menu that toggles open and closed based on state. Once it's open, I would like the user to be able to close the dropdown by clicking anywhere outside the ul.
I'm setting the tabIndex attribute on the ul to 0, which gives the ul "focus". I've also added an onBlur event to the ul that triggers the state change (dropdownExpanded = false) that hides the ul.
<ul tabIndex="0" onBlur={this.hideDropdownMenu}> <li onClick={this.handlePageNavigation}>Page 1</li> <li onClick={this.handlePageNavigation}>Page 2</li> <li onClick={this.handlePageNavigation}>Page 3</li> </ul>
However, when I implement this fix, the onClick events that I have on each li element fail to fire.
I know something is going on with the event bubbling, but I am at a lose as to how to fix it. Can anyone help?
NOTE:
I know you can create a transparent div below the ul that spans the entire viewport and then just add an onClick even to that div that will change the state, but I read about this tabIndex/focus solution on Stack Overflow and I'd really like to get it working.
Here is a more complete view of the code (the dropdown is for users to select their home country, which updates the ui):
const mapStateToProps = (state) => { return { lang: state.lang } } const mapDispatchToProps = (dispatch) => { return { actions: bindActionCreators({ changeLang }, dispatch) }; } class Header extends Component { constructor() { super(); this.state = { langListExpanded: false } this.handleLangChange = this.handleLangChange.bind(this); this.toggleLangMenu = this.toggleLangMenu.bind(this); this.hideLangMenu = this.hideLangMenu.bind(this); } toggleLangMenu (){ this.setState({ langListExpanded: !this.state.langListExpanded }); } hideLangMenu (){ this.setState({ langListExpanded: false }); } handleLangChange(e) { let newLang = e.target.attributes['0'].value; let urlSegment = window.location.pathname.substr(7); // blast it to shared state this.props.actions.changeLang( newLang ); // update browser route to change locale, but stay where they are at browserHistory.push(`/${ newLang }/${ urlSegment }`); //close dropdown menu this.hideLangMenu(); } compileAvailableLocales() { let locales = availableLangs; let selectedLang = this.props.lang; let markup = _.map(locales, (loc) => { let readableName = language[ selectedLang ].navigation.locales[ loc ]; return ( <li key={ loc } value={ loc } onMouseDown={ this.handleLangChange }> { readableName } </li> ); }); return markup; } render() { let localeMarkup = this.compileAvailableLocales(); return ( <section className="header row expanded"> < Navigation /> <section className="locale_selection"> <button className="btn-locale" onClick={this.toggleLangMenu}> {this.props.lang} </button> <ul className={this.state.langListExpanded ? "mobile_open" : " "} value={ this.props.lang } tabIndex="0" onBlur={this.hideLangMenu}> > { localeMarkup } </ul> </section> </section> ) } }
In this situation, call event. preventDefault() on the onMouseDown event. onMouseDown will cause a blur event by default, and will not do so when preventDefault is called. Then, onClick will have a chance to be called.
You can add tabIndex={0} to outermost div in order to dismiss keyboard from input. If the element that has the onBlur effect and tabindex is created onClick of another element, it does not automatically gets focus when it appears. Thus, you may need to focus it using element. focus() after creating the element.
onBlur must be used instead of onchange, unless absolutely necessary and it causes no negative consequences for keyboard only or screen reader users #67.
The onblur event occurs when an object loses focus. The onblur event is most often used with form validation code (e.g. when the user leaves a form field). Tip: The onblur event is the opposite of the onfocus event.
Try using onMouseDown instead of onClick.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With