Dropdown(下拉框)组件
一、简介
- 此组件可以不指定
data-target
属性,不指定的话,必须按.dropdown-toggle
按钮和dropdown-menu
列表放在同一个父元素下 - 注意,如果想要指定
data-target
属性,那么不是指向.drop-menu
元素,而是指向包含.dropdown-toggle
按钮和dropdown-menu
列表的父元素.dropdown
二、样式
隐藏与显示也是通过父类的 show 类,来控制子元素的 display 属性,在
none
和block
之间切换.show { // Show the menu > .dropdown-menu { display: block; } // Remove the outline when :focus is triggered > a { outline: 0; } }
- 发现.show类是可以让a标签的outline为0的,但是是show的直接子元素a才有此属性,而下面提到a标签必须被放在li标签里才可以被上下键选来选去,这样一来上下键选的时候a标签依旧有outline
按钮里的倒三角,只要指定了
dropdown-toggle
就可以有了,因为此类指定了after伪类,使用css3画了个三角:.dropdown-toggle { &::after { display: inline-block; width: 0; height: 0; margin-left: $caret-width; vertical-align: middle; content: ""; border-top: $caret-width solid; border-right: $caret-width solid transparent; border-left: $caret-width solid transparent; } }
- 上面提及的是下拉框向下的时候,bootstrap允许下拉框朝上,成为上拉框,只要为父元素指定的是
dropup
类而不是dropdown
类就行 - 可以在选项之间使用分隔符,只要在需要的地方添加一个带
.dropdown-divider
类的div元素就行 - 下拉菜单默认是靠左的,如果需要让菜单靠右,那么可以在
.dropdiwn-menu
元素上添加.dropdown-menu-right
类,与此同时,为.dropdown
元素(当然,父元素不一定要有这个类,.dropdown的作用只是声明position:relative
),手动添加额外css属性:display:inline-block
三、脚本(可选)
此组件比较简单,类似Alert组件一样简单。
主要就是dropbox的弹出与收回。
以下是为此(类)组件绑定的事件:
$(document)
// 为 $('[data-toggle="dropdown"], [role="menu"], [role="listbox"]') 元素绑定键盘的 keydown 事件
.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)
// 点击document其他地方会导致收回下拉菜单
.on(`${Event.CLICK_DATA_API} ${Event.FOCUSIN_DATA_API}`, Dropdown._clearMenus)
// 点击按钮触发 toggle 事件
.on(Event.CLICK_DATA_API, Selector.DATA_TOGGLE, Dropdown.prototype.toggle)
// 未知原因(怕是要真正用到才理解了)
.on(Event.CLICK_DATA_API, Selector.FORM_CHILD, (e) => {
e.stopPropagation()
})
接下来就针对每个事件的回调进行简单分析:
下拉菜单收回
function _clearMenus
// 将所有下拉菜单收回 static _clearMenus(event) { const toggles = $.makeArray($(Selector.DATA_TOGGLE)) for (let i = 0; i < toggles.length; i++) { const parent = ... // 获取data-target元素或是父元素 // 只对展开的dropdown操作 if (!$(parent).hasClass(ClassName.SHOW)) { continue } // 如果是点击 .dropdown 内的特定元素,则不再操作 // 触发hide钩子 // 移除parent的class:.show // 触发hidden钩子 } }
根据代码,可以知道为什么
data-target
属性,不是指向.drop-menu
元素,而是指向包含.dropdown-toggle
按钮和dropdown-menu
列表的父元素.dropdown
有几种情况不收回下拉框:- 本身就没展开
- 点击
.dropdown
元素中的input
或是textarea
元素不收回下拉框 - 其他元素若只触发
focusin
事件也不收回(一般是键盘触发)。鼠标的click事件往往先触发focusin事件,之后继续触发click事件,所以依旧导致下拉框收回 - 用户在hide钩子中调用
preventDefault
函数
下拉框的弹出与收回
function toggle
此函数是绑定在事件钩子上的,所以this多是指按钮元素toggle() { // 不管弹没弹开,先收起所有存在的展开的dropdown Dropdown._clearMenus() // 如果 .dropdown 元素有 show 类,那么不再继续 // 下面就是弹开按钮菜单的过程 // if ('ontouchstart' in document.documentElement && !$(parent).closest(Selector.NAVBAR_NAV).length) { // if mobile we use a backdrop because click events don't delegate const dropdown = document.createElement('div') dropdown.className = ClassName.BACKDROP $(dropdown).insertBefore(this) $(dropdown).on('click', Dropdown._clearMenus) } // 触发show钩子 // 触发shown钩子 }
关于上述代码中,在移动端会添加阴影层,我这里没有模拟。。。不过这与Modal的阴影层类似,所以也可以想象出打开后的情形
键盘控制事件
function
- 可以使用tab键切换到dropdown按钮后,可以通过空格键(_dataApiKeydownHandler)或是ENTER键(toggle)打开(经过测试,发现button类型被focus后,按下ENTER键,会触发click事件!)
- 方向键上或者下也可以打开,并且可以进行选项选择(这个需要场景触发,代码分析完会提及)
- ESC键则可以收回下拉框
代码分析如下:
static _dataApiKeydownHandler(event) { // REGEXP_KEYDOWN 为TAB, ARROW_UP,ARROW_DOWN,ESC键的code if (!REGEXP_KEYDOWN.test(event.which) || /input|textarea/i.test(event.target.tagName)) { return } // 键盘控制toggle if (!isActive && event.which !== ESCAPE_KEYCODE || isActive && event.which === ESCAPE_KEYCODE) { // 打开状态下按ESC,除了执行toggle,还focus按钮 if (event.which === ESCAPE_KEYCODE) { const toggle = $(parent).find(Selector.DATA_TOGGLE)[0] $(toggle).trigger('focus') } $(this).trigger('click') return } // 键盘控制选项 const items = $(parent).find(Selector.VISIBLE_ITEMS).get() let index = items.indexOf(event.target) if (event.which === ARROW_UP_KEYCODE && index > 0) { // up index-- } if (event.which === ARROW_DOWN_KEYCODE && index < items.length - 1) { // down index++ } if (index < 0) { index = 0 } items[index].focus() }
上述代码获取下拉菜单选项的时候,获取的方式为:
$(parent).find([role="menu"] li:not(.disabled) a, [role="listbox"] li:not(.disabled) a).get()
,可以发现只有当我们指定 role 属性的时候,这些子元素才会被发现!且需要每个a标签被li所包含,那么li自然是被放在ul标签中的,下面是可用的示例:<div class="dropdown " id="dropdown-example" > <button class="btn btn-default dropdown-toggle" type="button" data-toggle="dropdown" data-target="#dropdown-example"> DropDown Toggle </button> <ul class="dropdown-menu" role='menu' > <li><a href="#" class="dropdown-item">Action</a></li> <li><a href="#" class="dropdown-item">Action Second</a></li> <li><a href="#" class="dropdown-item">Action Third</a></li> </ul> </div>