@@ -10,6 +10,13 @@ import Util from './util'
10
10
11
11
const Dropdown = ( ( $ ) => {
12
12
13
+ /**
14
+ * Check for Popper dependency
15
+ * Popper - https://popper.js.org
16
+ */
17
+ if ( typeof Popper === 'undefined' ) {
18
+ throw new Error ( 'Bootstrap dropdown require Popper (https://popper.js.org)' )
19
+ }
13
20
14
21
/**
15
22
* ------------------------------------------------------------------------
@@ -55,6 +62,20 @@ const Dropdown = (($) => {
55
62
VISIBLE_ITEMS : '.dropdown-menu .dropdown-item:not(.disabled)'
56
63
}
57
64
65
+ const Default = {
66
+ animation : true ,
67
+ trigger : 'click' ,
68
+ placement : 'bottom' ,
69
+ offset : '0 0'
70
+ }
71
+
72
+ const DefaultType = {
73
+ animation : 'boolean' ,
74
+ trigger : 'string' ,
75
+ placement : 'string' ,
76
+ offset : 'string'
77
+ }
78
+
58
79
59
80
/**
60
81
* ------------------------------------------------------------------------
@@ -64,8 +85,11 @@ const Dropdown = (($) => {
64
85
65
86
class Dropdown {
66
87
67
- constructor ( element ) {
88
+ constructor ( element , config ) {
68
89
this . _element = element
90
+ this . _popper = null
91
+ this . _config = this . _getConfig ( config )
92
+ this . _menu = this . _getMenuElement ( )
69
93
70
94
this . _addEventListeners ( )
71
95
}
@@ -77,16 +101,30 @@ const Dropdown = (($) => {
77
101
return VERSION
78
102
}
79
103
104
+ static get Default ( ) {
105
+ return Default
106
+ }
107
+
108
+ static get DefaultType ( ) {
109
+ return DefaultType
110
+ }
111
+
80
112
81
113
// public
82
114
83
115
toggle ( ) {
84
- if ( this . disabled || $ ( this ) . hasClass ( ClassName . DISABLED ) ) {
116
+ let context = $ ( this ) . data ( DATA_KEY )
117
+ if ( ! context ) {
118
+ context = new Dropdown ( this )
119
+ $ ( this ) . data ( DATA_KEY , context )
120
+ }
121
+
122
+ if ( context . disabled || $ ( this ) . hasClass ( ClassName . DISABLED ) ) {
85
123
return false
86
124
}
87
125
88
126
const parent = Dropdown . _getParentFromElement ( this )
89
- const isActive = $ ( parent ) . hasClass ( ClassName . SHOW )
127
+ const isActive = $ ( context . _menu ) . hasClass ( ClassName . SHOW )
90
128
91
129
Dropdown . _clearMenus ( )
92
130
@@ -97,14 +135,21 @@ const Dropdown = (($) => {
97
135
const relatedTarget = {
98
136
relatedTarget : this
99
137
}
100
- const showEvent = $ . Event ( Event . SHOW , relatedTarget )
138
+ const showEvent = $ . Event ( Event . SHOW , relatedTarget )
101
139
102
140
$ ( parent ) . trigger ( showEvent )
103
141
104
142
if ( showEvent . isDefaultPrevented ( ) ) {
105
143
return false
106
144
}
107
145
146
+ this . _popper = new Popper ( this , context . _menu , {
147
+ placement : context . _config . placement ,
148
+ offsets : {
149
+ popper : context . _config . offset
150
+ }
151
+ } )
152
+
108
153
// if this is a touch-enabled device we add extra
109
154
// empty mouseover listeners to the body's immediate children;
110
155
// only needed because of broken event delegation on iOS
@@ -117,8 +162,10 @@ const Dropdown = (($) => {
117
162
this . focus ( )
118
163
this . setAttribute ( 'aria-expanded' , true )
119
164
120
- $ ( parent ) . toggleClass ( ClassName . SHOW )
121
- $ ( parent ) . trigger ( $ . Event ( Event . SHOWN , relatedTarget ) )
165
+ $ ( context . _menu ) . toggleClass ( ClassName . SHOW )
166
+ $ ( parent )
167
+ . toggleClass ( ClassName . SHOW )
168
+ . trigger ( $ . Event ( Event . SHOWN , relatedTarget ) )
122
169
123
170
return false
124
171
}
@@ -127,6 +174,10 @@ const Dropdown = (($) => {
127
174
$ . removeData ( this . _element , DATA_KEY )
128
175
$ ( this . _element ) . off ( EVENT_KEY )
129
176
this . _element = null
177
+ this . _menu = null
178
+ if ( this . _popper !== null ) {
179
+ this . _popper . destroy ( )
180
+ }
130
181
}
131
182
132
183
@@ -136,15 +187,40 @@ const Dropdown = (($) => {
136
187
$ ( this . _element ) . on ( Event . CLICK , this . toggle )
137
188
}
138
189
190
+ _getConfig ( config ) {
191
+ config = $ . extend (
192
+ { } ,
193
+ this . constructor . Default ,
194
+ $ ( this . _element ) . data ( ) ,
195
+ config
196
+ )
197
+
198
+ Util . typeCheckConfig (
199
+ NAME ,
200
+ config ,
201
+ this . constructor . DefaultType
202
+ )
203
+
204
+ return config
205
+ }
206
+
207
+ _getMenuElement ( ) {
208
+ if ( ! this . _menu ) {
209
+ let parent = Dropdown . _getParentFromElement ( this . _element )
210
+ this . _menu = $ ( parent ) . find ( Selector . MENU ) [ 0 ]
211
+ }
212
+ return this . _menu
213
+ }
139
214
140
215
// static
141
216
142
217
static _jQueryInterface ( config ) {
143
218
return this . each ( function ( ) {
144
219
let data = $ ( this ) . data ( DATA_KEY )
220
+ let _config = typeof config === 'object' ? config : null
145
221
146
222
if ( ! data ) {
147
- data = new Dropdown ( this )
223
+ data = new Dropdown ( this , _config )
148
224
$ ( this ) . data ( DATA_KEY , data )
149
225
}
150
226
@@ -164,13 +240,18 @@ const Dropdown = (($) => {
164
240
}
165
241
166
242
const toggles = $ . makeArray ( $ ( Selector . DATA_TOGGLE ) )
167
-
168
243
for ( let i = 0 ; i < toggles . length ; i ++ ) {
169
244
const parent = Dropdown . _getParentFromElement ( toggles [ i ] )
245
+ let context = $ ( toggles [ i ] ) . data ( DATA_KEY )
170
246
const relatedTarget = {
171
247
relatedTarget : toggles [ i ]
172
248
}
173
249
250
+ if ( ! context ) {
251
+ continue
252
+ }
253
+
254
+ let dropdownMenu = context . _menu
174
255
if ( ! $ ( parent ) . hasClass ( ClassName . SHOW ) ) {
175
256
continue
176
257
}
@@ -195,6 +276,7 @@ const Dropdown = (($) => {
195
276
196
277
toggles [ i ] . setAttribute ( 'aria-expanded' , 'false' )
197
278
279
+ $ ( dropdownMenu ) . removeClass ( ClassName . SHOW )
198
280
$ ( parent )
199
281
. removeClass ( ClassName . SHOW )
200
282
. trigger ( $ . Event ( Event . HIDDEN , relatedTarget ) )
0 commit comments