diff --git a/js/src/dropdown.js b/js/src/dropdown.js
index eb536dc7dc..a2b5561c27 100644
--- a/js/src/dropdown.js
+++ b/js/src/dropdown.js
@@ -10,6 +10,13 @@ import Util from './util'
const Dropdown = (($) => {
+ /**
+ * Check for Popper dependency
+ * Popper - https://popper.js.org
+ */
+ if (typeof Popper === 'undefined') {
+ throw new Error('Bootstrap dropdown require Popper (https://popper.js.org)')
+ }
/**
* ------------------------------------------------------------------------
@@ -55,6 +62,20 @@ const Dropdown = (($) => {
VISIBLE_ITEMS : '.dropdown-menu .dropdown-item:not(.disabled)'
}
+ const Default = {
+ animation : true,
+ trigger : 'click',
+ placement : 'bottom',
+ offset : '0 0'
+ }
+
+ const DefaultType = {
+ animation : 'boolean',
+ trigger : 'string',
+ placement : 'string',
+ offset : 'string'
+ }
+
/**
* ------------------------------------------------------------------------
@@ -64,8 +85,11 @@ const Dropdown = (($) => {
class Dropdown {
- constructor(element) {
+ constructor(element, config) {
this._element = element
+ this._popper = null
+ this._config = this._getConfig(config)
+ this._menu = this._getMenuElement()
this._addEventListeners()
}
@@ -77,16 +101,30 @@ const Dropdown = (($) => {
return VERSION
}
+ static get Default() {
+ return Default
+ }
+
+ static get DefaultType() {
+ return DefaultType
+ }
+
// public
toggle() {
- if (this.disabled || $(this).hasClass(ClassName.DISABLED)) {
+ let context = $(this).data(DATA_KEY)
+ if (!context) {
+ context = new Dropdown(this)
+ $(this).data(DATA_KEY, context)
+ }
+
+ if (context.disabled || $(this).hasClass(ClassName.DISABLED)) {
return false
}
const parent = Dropdown._getParentFromElement(this)
- const isActive = $(parent).hasClass(ClassName.SHOW)
+ const isActive = $(context._menu).hasClass(ClassName.SHOW)
Dropdown._clearMenus()
@@ -97,7 +135,7 @@ const Dropdown = (($) => {
const relatedTarget = {
relatedTarget : this
}
- const showEvent = $.Event(Event.SHOW, relatedTarget)
+ const showEvent = $.Event(Event.SHOW, relatedTarget)
$(parent).trigger(showEvent)
@@ -105,6 +143,13 @@ const Dropdown = (($) => {
return false
}
+ this._popper = new Popper(this, context._menu, {
+ placement : context._config.placement,
+ offsets : {
+ popper : context._config.offset
+ }
+ })
+
// if this is a touch-enabled device we add extra
// empty mouseover listeners to the body's immediate children;
// only needed because of broken event delegation on iOS
@@ -117,8 +162,10 @@ const Dropdown = (($) => {
this.focus()
this.setAttribute('aria-expanded', true)
- $(parent).toggleClass(ClassName.SHOW)
- $(parent).trigger($.Event(Event.SHOWN, relatedTarget))
+ $(context._menu).toggleClass(ClassName.SHOW)
+ $(parent)
+ .toggleClass(ClassName.SHOW)
+ .trigger($.Event(Event.SHOWN, relatedTarget))
return false
}
@@ -127,6 +174,10 @@ const Dropdown = (($) => {
$.removeData(this._element, DATA_KEY)
$(this._element).off(EVENT_KEY)
this._element = null
+ this._menu = null
+ if (this._popper !== null) {
+ this._popper.destroy()
+ }
}
@@ -136,15 +187,40 @@ const Dropdown = (($) => {
$(this._element).on(Event.CLICK, this.toggle)
}
+ _getConfig(config) {
+ config = $.extend(
+ {},
+ this.constructor.Default,
+ $(this._element).data(),
+ config
+ )
+
+ Util.typeCheckConfig(
+ NAME,
+ config,
+ this.constructor.DefaultType
+ )
+
+ return config
+ }
+
+ _getMenuElement() {
+ if (!this._menu) {
+ let parent = Dropdown._getParentFromElement(this._element)
+ this._menu = $(parent).find(Selector.MENU)[0]
+ }
+ return this._menu
+ }
// static
static _jQueryInterface(config) {
return this.each(function () {
let data = $(this).data(DATA_KEY)
+ let _config = typeof config === 'object' ? config : null
if (!data) {
- data = new Dropdown(this)
+ data = new Dropdown(this, _config)
$(this).data(DATA_KEY, data)
}
@@ -164,13 +240,18 @@ const Dropdown = (($) => {
}
const toggles = $.makeArray($(Selector.DATA_TOGGLE))
-
for (let i = 0; i < toggles.length; i++) {
const parent = Dropdown._getParentFromElement(toggles[i])
+ let context = $(toggles[i]).data(DATA_KEY)
const relatedTarget = {
relatedTarget : toggles[i]
}
+ if (!context) {
+ continue
+ }
+
+ let dropdownMenu = context._menu
if (!$(parent).hasClass(ClassName.SHOW)) {
continue
}
@@ -195,6 +276,7 @@ const Dropdown = (($) => {
toggles[i].setAttribute('aria-expanded', 'false')
+ $(dropdownMenu).removeClass(ClassName.SHOW)
$(parent)
.removeClass(ClassName.SHOW)
.trigger($.Event(Event.HIDDEN, relatedTarget))
diff --git a/js/src/tooltip.js b/js/src/tooltip.js
index c380f8675f..2c22a7ed41 100644
--- a/js/src/tooltip.js
+++ b/js/src/tooltip.js
@@ -14,7 +14,7 @@ const Tooltip = (($) => {
/**
* Check for Popper dependency
- * Tether - https://popper.js.org
+ * Popper - https://popper.js.org
*/
if (typeof Popper === 'undefined') {
throw new Error('Bootstrap tooltips require Popper (https://popper.js.org)')
diff --git a/js/tests/visual/dropdown.html b/js/tests/visual/dropdown.html
index cc8a6e709c..d96b367e99 100644
--- a/js/tests/visual/dropdown.html
+++ b/js/tests/visual/dropdown.html
@@ -61,6 +61,7 @@
+
diff --git a/scss/_dropdown.scss b/scss/_dropdown.scss
index 3c5a5c66ed..2c57f0209c 100644
--- a/scss/_dropdown.scss
+++ b/scss/_dropdown.scss
@@ -100,11 +100,6 @@
// Open state for the dropdown
.show {
- // Show the menu
- > .dropdown-menu {
- display: block;
- }
-
// Remove the outline when :focus is triggered
> a {
outline: 0;
@@ -125,6 +120,10 @@
left: 0;
}
+.dropdown-menu.show {
+ display: block;
+}
+
// Dropdown section headers
.dropdown-header {
display: block;
@@ -139,11 +138,4 @@
//
// Just add .dropup after the standard .dropdown class and you're set.
-.dropup {
- // Different positioning for bottom up menu
- .dropdown-menu {
- top: auto;
- bottom: 100%;
- margin-bottom: $dropdown-margin-top;
- }
-}
+.dropup {}