zoukankan      html  css  js  c++  java
  • element dropdown源码

    dropdown.vue

    <script>
      import Clickoutside from 'element-ui/src/utils/clickoutside';
      import Emitter from 'element-ui/src/mixins/emitter';
      import Migrating from 'element-ui/src/mixins/migrating';
      import ElButton from 'element-ui/packages/button';
      import ElButtonGroup from 'element-ui/packages/button-group';
      import { generateId } from 'element-ui/src/utils/util';
    
      export default {
        name: 'ElDropdown',
    
        componentName: 'ElDropdown',
    
        mixins: [Emitter, Migrating],
    
        directives: { Clickoutside },
    
        components: {
          ElButton,
          ElButtonGroup
        },
    
        provide() {
          return {
            dropdown: this
          };
        },
    
        props: {
          // trigger    触发下拉的行为    string    hover, click    hover
          trigger: {
            type: String,
            default: 'hover'
          },
          // 菜单按钮类型,同 Button 组件(只在split-button为 true 的情况下有效)
          type: String,
          // 菜单尺寸,在split-button为 true 的情况下也对触发按钮生效
          size: {
            type: String,
            default: ''
          },
          // 下拉触发元素呈现为按钮组
          splitButton: Boolean,
          // hide-on-click    是否在点击菜单项后隐藏菜单
          hideOnClick: {
            type: Boolean,
            default: true
          },
          // placement    菜单弹出位置    string    top/top-start/top-end/bottom/bottom-start/bottom-end
          placement: {
            type: String,
            default: 'bottom-end'
          },
          visibleArrow: {
            default: true
          },
          // 展开下拉菜单的延时(仅在 trigger 为 hover 时有效)
          showTimeout: {
            type: Number,
            default: 250
          },
          // 收起下拉菜单的延时(仅在 trigger 为 hover 时有效)
          hideTimeout: {
            type: Number,
            default: 150
          },
          // Dropdown 组件的 tabindex    number    —    0
          tabindex: {
            type: Number,
            default: 0
          }
        },
    
        data() {
          return {
            timeout: null,
            visible: false,
            triggerElm: null,
            menuItems: null,
            menuItemsArray: null,
            dropdownElm: null,
            focusing: false,
            listId: `dropdown-menu-${generateId()}`
          };
        },
    
        computed: {
          dropdownSize() {
            return this.size || (this.$ELEMENT || {}).size;
          }
        },
    
        mounted() {
          // 接收下拉的li点击事件
          this.$on('menu-item-click', this.handleMenuItemClick);
        },
    
        watch: {
          visible(val) {
            this.broadcast('ElDropdownMenu', 'visible', val);
            this.$emit('visible-change', val);
          },
          focusing(val) {
            const selfDefine = this.$el.querySelector('.el-dropdown-selfdefine');
            if (selfDefine) { // 自定义
              if (val) {
                selfDefine.className += ' focusing';
              } else {
                selfDefine.className = selfDefine.className.replace('focusing', '');
              }
            }
          }
        },
    
        methods: {
          getMigratingConfig() {
            return {
              props: {
                'menu-align': 'menu-align is renamed to placement.'
              }
            };
          },
          // 展开下拉框框
          show() {
            if (this.triggerElm.disabled) return;
            clearTimeout(this.timeout);
            this.timeout = setTimeout(() => {
              this.visible = true;
              // 如果是点击直接触发,反之延迟传入的额数值触发
            }, this.trigger === 'click' ? 0 : this.showTimeout);
          },
          // 隐藏下拉框
          hide() {
            if (this.triggerElm.disabled) return;
            this.removeTabindex();
            if (this.tabindex >= 0) {
              this.resetTabindex(this.triggerElm);
            }
            clearTimeout(this.timeout);
            this.timeout = setTimeout(() => {
              this.visible = false;
            }, this.trigger === 'click' ? 0 : this.hideTimeout);
          },
          // 点击展开关闭
          handleClick() {
            if (this.triggerElm.disabled) return;
            if (this.visible) {
              this.hide();
            } else {
              this.show();
            }
          },
    
          handleTriggerKeyDown(ev) {
            const keyCode = ev.keyCode;
            if ([38, 40].indexOf(keyCode) > -1) { // up/down
              this.removeTabindex();
              this.resetTabindex(this.menuItems[0]);
              this.menuItems[0].focus();
              ev.preventDefault();
              ev.stopPropagation();
            } else if (keyCode === 13) { // space enter选中
              this.handleClick();
            } else if ([9, 27].indexOf(keyCode) > -1) { // tab || esc
              this.hide();
            }
          },
          handleItemKeyDown(ev) {
            const keyCode = ev.keyCode;
            const target = ev.target;
            const currentIndex = this.menuItemsArray.indexOf(target);
            const max = this.menuItemsArray.length - 1;
            let nextIndex;
            if ([38, 40].indexOf(keyCode) > -1) { // up/down
              if (keyCode === 38) { // up
                nextIndex = currentIndex !== 0 ? currentIndex - 1 : 0;
              } else { // down
                nextIndex = currentIndex < max ? currentIndex + 1 : max;
              }
              this.removeTabindex();
              this.resetTabindex(this.menuItems[nextIndex]);
              this.menuItems[nextIndex].focus();
              ev.preventDefault();
              ev.stopPropagation();
            } else if (keyCode === 13) { // enter选中
              this.triggerElmFocus();
              target.click();
              if (this.hideOnClick) { // click关闭
                this.visible = false;
              }
            } else if ([9, 27].indexOf(keyCode) > -1) { // tab // esc
              this.hide();
              this.triggerElmFocus();
            }
          },
          resetTabindex(ele) { // 下次tab时组件聚焦元素
            this.removeTabindex();
            ele.setAttribute('tabindex', '0'); // 下次期望的聚焦元素
          },
          removeTabindex() {
            this.triggerElm.setAttribute('tabindex', '-1');
            this.menuItemsArray.forEach((item) => {
              item.setAttribute('tabindex', '-1');
            });
          },
          initAria() {
            this.dropdownElm.setAttribute('id', this.listId);
            this.triggerElm.setAttribute('aria-haspopup', 'list');
            this.triggerElm.setAttribute('aria-controls', this.listId);
    
            if (!this.splitButton) { // 自定义
              this.triggerElm.setAttribute('role', 'button');
              this.triggerElm.setAttribute('tabindex', this.tabindex);
              this.triggerElm.setAttribute('class', (this.triggerElm.getAttribute('class') || '') + ' el-dropdown-selfdefine'); // 控制
            }
          },
          // 初始化事件
          initEvent() {
            let { trigger, show, hide, handleClick, splitButton, handleTriggerKeyDown, handleItemKeyDown } = this;
            this.triggerElm = splitButton
              ? this.$refs.trigger.$el
              : this.$slots.default[0].elm;
    
            let dropdownElm = this.dropdownElm;
    
            this.triggerElm.addEventListener('keydown', handleTriggerKeyDown); // triggerElm keydown
            dropdownElm.addEventListener('keydown', handleItemKeyDown, true); // item keydown
            // 控制自定义元素的样式
            if (!splitButton) {
              this.triggerElm.addEventListener('focus', () => {
                this.focusing = true;
              });
              this.triggerElm.addEventListener('blur', () => {
                this.focusing = false;
              });
              this.triggerElm.addEventListener('click', () => {
                this.focusing = false;
              });
            }
            if (trigger === 'hover') {
              this.triggerElm.addEventListener('mouseenter', show);
              this.triggerElm.addEventListener('mouseleave', hide);
              dropdownElm.addEventListener('mouseenter', show);
              dropdownElm.addEventListener('mouseleave', hide);
            } else if (trigger === 'click') {
              this.triggerElm.addEventListener('click', handleClick);
            }
          },
          // 点击菜单触发的回调
          handleMenuItemClick(command, instance) {
            // 如果设置了点击菜单后隐藏菜单
            if (this.hideOnClick) {
              // 隐藏下拉框
              this.visible = false;
            }
            // 否则触发command事件
            this.$emit('command', command, instance);
          },
          // 触发获取焦点事件
          triggerElmFocus() {
            this.triggerElm.focus && this.triggerElm.focus();
          },
          // 初始化dom
          initDomOperation() {
            this.dropdownElm = this.popperElm;
            // 获取所有tab键选中的元素
            this.menuItems = this.dropdownElm.querySelectorAll("[tabindex='-1']");
            this.menuItemsArray = [].slice.call(this.menuItems);
            // 初始化事件
            this.initEvent();
            this.initAria();
          }
        },
    
        render(h) {
          let { hide, splitButton, type, dropdownSize } = this;
    
          const handleMainButtonClick = (event) => {
            this.$emit('click', event);
            hide();
          };
    
          let triggerElm = !splitButton
            ? this.$slots.default
            : (<el-button-group>
              <el-button type={type} size={dropdownSize} nativeOn-click={handleMainButtonClick}>
                {this.$slots.default}
              </el-button>
              <el-button ref="trigger" type={type} size={dropdownSize} class="el-dropdown__caret-button">
                <i class="el-dropdown__icon el-icon-arrow-down"></i>
              </el-button>
            </el-button-group>);
    
          return (
            <div class="el-dropdown" v-clickoutside={hide}>
              {triggerElm}
              {this.$slots.dropdown}
            </div>
          );
        }
      };
    </script>

    dropdown-menu.vue

    <template>
      <transition name="el-zoom-in-top" @after-leave="doDestroy">
        <ul class="el-dropdown-menu el-popper" :class="[size && `el-dropdown-menu--${size}`]" v-show="showPopper">
          <slot></slot>
        </ul>
      </transition>
    </template>
    <script>
      import Popper from 'element-ui/src/utils/vue-popper';
    
      export default {
        name: 'ElDropdownMenu',
    
        componentName: 'ElDropdownMenu',
    
        mixins: [Popper],
    
        props: {
          visibleArrow: {
            type: Boolean,
            default: true
          },
          arrowOffset: {
            type: Number,
            default: 0
          }
        },
    
        data() {
          return {
            size: this.dropdown.dropdownSize
          };
        },
        // 注入父组件传进的组件
        inject: ['dropdown'],
    
        created() {
          this.$on('updatePopper', () => {
            if (this.showPopper) this.updatePopper();
          });
          // 监听visible事件
          this.$on('visible', val => {
            // 改变showPoper
            this.showPopper = val;
          });
        },
    
        mounted() {
          // 获取到popperElm元素
          this.dropdown.popperElm = this.popperElm = this.$el;
          this.referenceElm = this.dropdown.$el;
          // compatible with 2.6 new v-slot syntax
          // issue link https://github.com/ElemeFE/element/issues/14345
          // 在此初始化调用
          this.dropdown.initDomOperation();
        },
    
        watch: {
          'dropdown.placement': {
            immediate: true,
            handler(val) {
              this.currentPlacement = val;
            }
          }
        }
      };
    </script>

    dropdown-item.vue

    <template>
      <li
        class="el-dropdown-menu__item"
        :class="{
          'is-disabled': disabled,
          'el-dropdown-menu__item--divided': divided
        }"
        @click="handleClick"
        :aria-disabled="disabled"
        :tabindex="disabled ? null : -1"
      >
        <i :class="icon" v-if="icon"></i>
        <slot></slot>
      </li>
    </template>
    <script>
      import Emitter from 'element-ui/src/mixins/emitter';
    
      export default {
        name: 'ElDropdownItem',
    
        mixins: [Emitter],
    
        props: {
          command: {},
          disabled: Boolean,
          divided: Boolean,
          icon: String
        },
    
        methods: {
          handleClick(e) {
            // 触发showPopper的menu-item-click事件
            this.dispatch('ElDropdown', 'menu-item-click', [this.command, this]);
          }
        }
      };
    </script>
  • 相关阅读:
    【js】数据表格开启同比化显示,增加对比性
    Kotlin 使用协程编写高效的并发程序
    Kotlin泛型的高级特性
    kotlin基础
    Java接口和抽象类区别
    Java内存泄漏
    jmeter,注意:您可以通过定义属性resultcollector.action_if_file_exists来避免这个弹出框
    性能测试术语
    jenkins正常显示jmeter的html报告设置
    jmeter: beanshell后置处理程序,清空文件和保存json提取器提取的数据到文件中
  • 原文地址:https://www.cnblogs.com/wsk1576025821/p/10950880.html
Copyright © 2011-2022 走看看