Skip to content

Commit

Permalink
Dropdown: close menu when focusing outside element (#21375)
Browse files Browse the repository at this point in the history
* Close dropdown menu when focusing an outside element
* Update unit test to new markup
  • Loading branch information
pvdlg authored and mdo committed Jan 2, 2017
1 parent 9fc54f8 commit bbb0d2b
Show file tree
Hide file tree
Showing 2 changed files with 65 additions and 4 deletions.
9 changes: 5 additions & 4 deletions js/src/dropdown.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ const Dropdown = (($) => {
SHOWN : `shown${EVENT_KEY}`,
CLICK : `click${EVENT_KEY}`,
CLICK_DATA_API : `click${EVENT_KEY}${DATA_API_KEY}`,
FOCUSIN_DATA_API : `focusin${EVENT_KEY}${DATA_API_KEY}`,
KEYDOWN_DATA_API : `keydown${EVENT_KEY}${DATA_API_KEY}`
}

Expand Down Expand Up @@ -180,9 +181,9 @@ const Dropdown = (($) => {
continue
}

if (event && event.type === 'click' &&
/input|textarea/i.test(event.target.tagName) &&
$.contains(parent, event.target)) {
if (event && (event.type === 'click' &&
/input|textarea/i.test(event.target.tagName) || event.type === 'focusin')
&& $.contains(parent, event.target)) {
continue
}

Expand Down Expand Up @@ -275,7 +276,7 @@ const Dropdown = (($) => {
.on(Event.KEYDOWN_DATA_API, Selector.DATA_TOGGLE, Dropdown._dataApiKeydownHandler)
.on(Event.KEYDOWN_DATA_API, Selector.ROLE_MENU, Dropdown._dataApiKeydownHandler)
.on(Event.KEYDOWN_DATA_API, Selector.ROLE_LISTBOX, Dropdown._dataApiKeydownHandler)
.on(Event.CLICK_DATA_API, Dropdown._clearMenus)
.on(`${Event.CLICK_DATA_API} ${Event.FOCUSIN_DATA_API}`, Dropdown._clearMenus)
.on(Event.CLICK_DATA_API, Selector.DATA_TOGGLE, Dropdown.prototype.toggle)
.on(Event.CLICK_DATA_API, Selector.FORM_CHILD, (e) => {
e.stopPropagation()
Expand Down
60 changes: 60 additions & 0 deletions js/tests/unit/dropdown.js
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,30 @@ $(function () {
assert.ok(!$dropdown.parent('.dropdown').hasClass('show'), '"show" class removed')
})

QUnit.test('should remove "show" class if body is focused', function (assert) {
assert.expect(2)
var dropdownHTML = '<div class="tabs">'
+ '<div class="dropdown">'
+ '<a href="#" class="dropdown-toggle" data-toggle="dropdown">Dropdown</a>'
+ '<div class="dropdown-menu">'
+ '<a class="dropdown-item" href="#">Secondary link</a>'
+ '<a class="dropdown-item" href="#">Something else here</a>'
+ '<div class="dropdown-divider"/>'
+ '<a class="dropdown-item" href="#">Another link</a>'
+ '</div>'
+ '</div>'
+ '</div>'
var $dropdown = $(dropdownHTML)
.appendTo('#qunit-fixture')
.find('[data-toggle="dropdown"]')
.bootstrapDropdown()
.trigger('click')

assert.ok($dropdown.parent('.dropdown').hasClass('show'), '"show" class added on click')
$(document.body).trigger('focusin')
assert.ok(!$dropdown.parent('.dropdown').hasClass('show'), '"show" class removed')
})

QUnit.test('should remove "show" class if body is clicked, with multiple dropdowns', function (assert) {
assert.expect(7)
var dropdownHTML = '<ul class="nav">'
Expand Down Expand Up @@ -229,6 +253,42 @@ $(function () {
assert.strictEqual($('#qunit-fixture .show').length, 0, '"show" class removed')
})

QUnit.test('should remove "show" class if body is focused, with multiple dropdowns', function (assert) {
assert.expect(7)
var dropdownHTML = '<div class="nav">'
+ '<div class="dropdown" id="testmenu">'
+ '<a class="dropdown-toggle" data-toggle="dropdown" href="#testmenu">Test menu <span class="caret"/></a>'
+ '<div class="dropdown-menu">'
+ '<a class="dropdown-item" href="#sub1">Submenu 1</a>'
+ '</div>'
+ '</div>'
+ '</div>'
+ '<div class="btn-group">'
+ '<button class="btn">Actions</button>'
+ '<button class="btn dropdown-toggle" data-toggle="dropdown"><span class="caret"/></button>'
+ '<div class="dropdown-menu">'
+ '<a class="dropdown-item" href="#">Action 1</a>'
+ '</div>'
+ '</div>'
var $dropdowns = $(dropdownHTML).appendTo('#qunit-fixture').find('[data-toggle="dropdown"]')
var $first = $dropdowns.first()
var $last = $dropdowns.last()

assert.strictEqual($dropdowns.length, 2, 'two dropdowns')

$first.trigger('click')
assert.strictEqual($first.parents('.show').length, 1, '"show" class added on click')
assert.strictEqual($('#qunit-fixture .show').length, 1, 'only one dropdown is show')
$(document.body).trigger('focusin')
assert.strictEqual($('#qunit-fixture .show').length, 0, '"show" class removed')

$last.trigger('click')
assert.strictEqual($last.parent('.show').length, 1, '"show" class added on click')
assert.strictEqual($('#qunit-fixture .show').length, 1, 'only one dropdown is show')
$(document.body).trigger('focusin')
assert.strictEqual($('#qunit-fixture .show').length, 0, '"show" class removed')
})

QUnit.test('should fire show and hide event', function (assert) {
assert.expect(2)
var dropdownHTML = '<ul class="tabs">'
Expand Down

0 comments on commit bbb0d2b

Please sign in to comment.