diff --git a/js/src/dropdown.js b/js/src/dropdown.js
index a7a77cb3b6..e52c51e4db 100644
--- a/js/src/dropdown.js
+++ b/js/src/dropdown.js
@@ -194,6 +194,52 @@ class Dropdown {
.trigger($.Event(Event.SHOWN, relatedTarget))
}
+ show() {
+ if (this._element.disabled || $(this._element).hasClass(ClassName.DISABLED) || $(this._menu).hasClass(ClassName.SHOW)) {
+ return
+ }
+
+ const relatedTarget = {
+ relatedTarget: this._element
+ }
+ const showEvent = $.Event(Event.SHOW, relatedTarget)
+
+ const parent = Dropdown._getParentFromElement(this._element)
+ $(parent).trigger(showEvent)
+
+ if (showEvent.isDefaultPrevented()) {
+ return
+ }
+
+ $(this._menu).toggleClass(ClassName.SHOW)
+ $(parent)
+ .toggleClass(ClassName.SHOW)
+ .trigger($.Event(Event.SHOWN, relatedTarget))
+ }
+
+ hide() {
+ if (this._element.disabled || $(this._element).hasClass(ClassName.DISABLED) || !$(this._menu).hasClass(ClassName.SHOW)) {
+ return
+ }
+
+ const relatedTarget = {
+ relatedTarget: this._element
+ }
+ const hideEvent = $.Event(Event.HIDE, relatedTarget)
+
+ const parent = Dropdown._getParentFromElement(this._element)
+ $(parent).trigger(hideEvent)
+
+ if (hideEvent.isDefaultPrevented()) {
+ return
+ }
+
+ $(this._menu).toggleClass(ClassName.SHOW)
+ $(parent)
+ .toggleClass(ClassName.SHOW)
+ .trigger($.Event(Event.HIDDEN, relatedTarget))
+ }
+
dispose() {
$.removeData(this._element, DATA_KEY)
$(this._element).off(EVENT_KEY)
diff --git a/js/tests/unit/dropdown.js b/js/tests/unit/dropdown.js
index 40489c5f2c..fd9b7f9629 100644
--- a/js/tests/unit/dropdown.js
+++ b/js/tests/unit/dropdown.js
@@ -1097,4 +1097,268 @@ $(function () {
assert.ok(dropdown._menu === null)
assert.ok(dropdown._element === null)
})
+
+ QUnit.test('should show dropdown', function (assert) {
+ assert.expect(2)
+
+ var dropdownHTML =
+ '
'
+
+ var $dropdown = $(dropdownHTML)
+ .appendTo('#qunit-fixture')
+ .find('[data-toggle="dropdown"]')
+ .bootstrapDropdown()
+
+ var dropdown = $dropdown.data('bs.dropdown')
+ var done = assert.async()
+
+ $dropdown
+ .parent('.dropdown')
+ .on('show.bs.dropdown', function () {
+ assert.ok(true, 'show was fired')
+ })
+ .on('shown.bs.dropdown', function () {
+ assert.ok($dropdown.parent('.dropdown').hasClass('show'), 'dropdown menu is shown')
+ done()
+ })
+
+ dropdown.show()
+ })
+
+ QUnit.test('should hide dropdown', function (assert) {
+ assert.expect(2)
+
+ var dropdownHTML =
+ ''
+
+ var $dropdown = $(dropdownHTML)
+ .appendTo('#qunit-fixture')
+ .find('[data-toggle="dropdown"]')
+ .bootstrapDropdown()
+
+ var dropdown = $dropdown.data('bs.dropdown')
+ var done = assert.async()
+ $dropdown.trigger('click')
+
+ $dropdown
+ .parent('.dropdown')
+ .on('hide.bs.dropdown', function () {
+ assert.ok(true, 'hide was fired')
+ })
+ .on('hidden.bs.dropdown', function () {
+ assert.ok(!$dropdown.parent('.dropdown').hasClass('show'), 'dropdown menu is hidden')
+ done()
+ })
+
+ dropdown.hide()
+ })
+
+ QUnit.test('should not hide dropdown', function (assert) {
+ assert.expect(1)
+
+ var dropdownHTML =
+ ''
+
+ var $dropdown = $(dropdownHTML)
+ .appendTo('#qunit-fixture')
+ .find('[data-toggle="dropdown"]')
+ .bootstrapDropdown()
+
+ var dropdown = $dropdown.data('bs.dropdown')
+ $dropdown.trigger('click')
+ dropdown.show()
+
+ assert.ok($dropdown.parent('.dropdown').hasClass('show'), 'dropdown menu is still shown')
+ })
+
+ QUnit.test('should not show dropdown', function (assert) {
+ assert.expect(1)
+
+ var dropdownHTML =
+ ''
+
+ var $dropdown = $(dropdownHTML)
+ .appendTo('#qunit-fixture')
+ .find('[data-toggle="dropdown"]')
+ .bootstrapDropdown()
+
+ var dropdown = $dropdown.data('bs.dropdown')
+ dropdown.hide()
+ assert.ok(!$dropdown.parent('.dropdown').hasClass('show'), 'dropdown menu is still hidden')
+ })
+
+ QUnit.test('should show dropdown', function (assert) {
+ assert.expect(2)
+
+ var dropdownHTML =
+ ''
+
+ var $dropdown = $(dropdownHTML)
+ .appendTo('#qunit-fixture')
+ .find('[data-toggle="dropdown"]')
+ .bootstrapDropdown()
+
+ var dropdown = $dropdown.data('bs.dropdown')
+ var done = assert.async()
+
+ $dropdown
+ .parent('.dropdown')
+ .on('show.bs.dropdown', function () {
+ assert.ok(true, 'show was fired')
+ })
+ .on('shown.bs.dropdown', function () {
+ assert.ok($dropdown.parent('.dropdown').hasClass('show'), 'dropdown menu is shown')
+ done()
+ })
+
+ dropdown.show()
+ })
+
+ QUnit.test('should prevent default event on show method call', function (assert) {
+ assert.expect(1)
+
+ var dropdownHTML =
+ ''
+
+ var $dropdown = $(dropdownHTML)
+ .appendTo('#qunit-fixture')
+ .find('[data-toggle="dropdown"]')
+ .bootstrapDropdown()
+
+ var dropdown = $dropdown.data('bs.dropdown')
+ var done = assert.async()
+
+ $dropdown
+ .parent('.dropdown')
+ .on('show.bs.dropdown', function (event) {
+ event.preventDefault()
+ done()
+ })
+
+ dropdown.show()
+ assert.ok(!$dropdown.parent('.dropdown').hasClass('show'), 'dropdown menu is hidden')
+ })
+
+ QUnit.test('should prevent default event on hide method call', function (assert) {
+ assert.expect(1)
+
+ var dropdownHTML =
+ ''
+
+ var $dropdown = $(dropdownHTML)
+ .appendTo('#qunit-fixture')
+ .find('[data-toggle="dropdown"]')
+ .bootstrapDropdown()
+
+ var dropdown = $dropdown.data('bs.dropdown')
+ var done = assert.async()
+ $dropdown.trigger('click')
+
+ $dropdown
+ .parent('.dropdown')
+ .on('hide.bs.dropdown', function (event) {
+ event.preventDefault()
+ done()
+ })
+
+ dropdown.hide()
+ assert.ok($dropdown.parent('.dropdown').hasClass('show'), 'dropdown menu is shown')
+ })
+
+ QUnit.test('should not open dropdown via show method if target is disabled via attribute', function (assert) {
+ assert.expect(1)
+ var dropdownHTML =
+ '' +
+ ' Dropdown ' +
+ ' ' +
+ '
'
+ $(dropdownHTML).appendTo('#qunit-fixture')
+ var $dropdown = $('#qunit-fixture').find('[data-toggle="dropdown"]').bootstrapDropdown()
+ $dropdown.show()
+ assert.ok(!$dropdown.parent('.dropdown').hasClass('show'))
+ })
+
+ QUnit.test('should not open dropdown via show method if target is disabled via class', function (assert) {
+ assert.expect(1)
+ var dropdownHTML =
+ '' +
+ ' Dropdown ' +
+ ' ' +
+ '
'
+
+ $(dropdownHTML).appendTo('#qunit-fixture')
+ var $dropdown = $('#qunit-fixture').find('[data-toggle="dropdown"]').bootstrapDropdown()
+ $dropdown.show()
+ assert.ok(!$dropdown.parent('.dropdown').hasClass('show'))
+ })
+
+ QUnit.test('should not hide dropdown via hide method if target is disabled via attribute', function (assert) {
+ assert.expect(1)
+ var dropdownHTML =
+ '' +
+ ' Dropdown ' +
+ ' ' +
+ '
'
+ $(dropdownHTML).appendTo('#qunit-fixture')
+ var $dropdown = $('#qunit-fixture').find('[data-toggle="dropdown"]').bootstrapDropdown()
+ $dropdown.hide()
+ assert.ok($dropdown.parent('.dropdown').hasClass('show'))
+ })
+
+ QUnit.test('should not hide dropdown via hide method if target is disabled via class', function (assert) {
+ assert.expect(1)
+ var dropdownHTML =
+ '' +
+ ' Dropdown ' +
+ ' ' +
+ '
'
+
+ $(dropdownHTML).appendTo('#qunit-fixture')
+ var $dropdown = $('#qunit-fixture').find('[data-toggle="dropdown"]').bootstrapDropdown()
+ $dropdown.hide()
+ assert.ok($dropdown.parent('.dropdown').hasClass('show'))
+ })
})
diff --git a/site/docs/4.1/components/dropdowns.md b/site/docs/4.1/components/dropdowns.md
index c9daf55186..ab61390d4e 100644
--- a/site/docs/4.1/components/dropdowns.md
+++ b/site/docs/4.1/components/dropdowns.md
@@ -839,6 +839,8 @@ Note when `boundary` is set to any value other than `'scrollParent'`, the style
| Method | Description |
| --- | --- |
| `$().dropdown('toggle')` | Toggles the dropdown menu of a given navbar or tabbed navigation. |
+| `$().dropdown('show')` | Shows the dropdown menu of a given navbar or tabbed navigation. |
+| `$().dropdown('hide')` | Hides the dropdown menu of a given navbar or tabbed navigation. |
| `$().dropdown('update')` | Updates the position of an element's dropdown. |
| `$().dropdown('dispose')` | Destroys an element's dropdown. |