MyDropdown

原生js实现简洁的下拉菜单

אין להתקין סקריפט זה ישירות. זוהי ספריה עבור סקריפטים אחרים // @require https://update.greasyfork.org/scripts/463933/1177909/MyDropdown.js

You will need to install an extension such as Tampermonkey, Greasemonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Userscripts to install this script.

You will need to install an extension such as Tampermonkey to install this script.

You will need to install a user script manager extension to install this script.

(I already have a user script manager, let me install it!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(I already have a user style manager, let me install it!)

// ==UserScript==
// @name         MyDropdown
// @namespace    http://https://wish123.cnblogs.com/?MyDropdown
// @version      0.1.1.1
// @description  原生js实现简洁的下拉菜单
// @author       Wilson

//构造方法
function MyDropdown(options){
    //_count是某实例下的菜单个数
	this._count = 0;
	this._zIndex = 1000;
	this._config = options;
    window._MyDropdownPosIndex = (window._MyDropdownPosIndex||this._zIndex)+1;
    //实例总个数
    window._MyDropdownInsCount = (window._MyDropdownInsCount||0)+1;
    //当前第几个实例
    this.insCount = window._MyDropdownInsCount;
	if(this._config) {
	    this.config(options);
	    this.createStyle();
	}
}
MyDropdown.prototype.createStyle = function() {
  if(document.querySelector("#myDropdownStyle")) {
    return;
  }
  let = style = `
<style id="myDropdownStyle">
.my-dropdown-wrapper {
  display: none;
  position: absolute;
  background-color: #f1f1f1;
  min-width: 160px;
  overflow: auto;
  box-shadow: 0px 8px 16px 0px rgba(0,0,0,0.2);
  z-index: 1;
}
.my-dropdown-wrapper .my-dropdown-item:hover{
    background-color: #ddd;
}
.my-dropdown-wrapper .my-dropdown-item.selected {background-color: #ddd;}
.my-dropdown-wrapper a {
  color: black;
  padding: 12px 16px;
  text-decoration: none;
  display: block;
  padding-right: 6px;
  overflow: hidden;
  text-overflow: ellipsis;
}

.my-dropdown-wrapper a:hover {background-color: #ddd;}
.my-dropdown-wrapper a.selected {background-color: #ddd;}
.show {display: block;}
.my-dropdown-item-icon svg{width: 24px;height: 24px;float: left;}
.my-dropdown-wrapper .close {
  width: 24px;
  height: 24px;
  display: inline-block;
  line-height: 24px;
  text-align: center;
  font-size: 18px;
  float: right;
  position: relative;
  top: 10px;
  right: 6px;
  cursor: pointer;
}
.my-dropdown-wrapper .close:hover{
  opacity: 0.6;
}
</style>
  `
  document.body.insertAdjacentHTML("beforeend", style);
}
//返回对象类型
MyDropdown.prototype.type = function(obj) {
    return Object.prototype.toString
          .call(obj)
          .replace(/^\[object (.+)\]$/, '$1')
          .toLowerCase();
}
//设置用户配置
MyDropdown.prototype.config = function(options) {
    var _this = this;
    //设置用户配置
    _this._config = options || _this._config;
    if(_this._config.toggleEvent && _this._config.toggleEvent.toLowerCase() === 'mouseover') {
        _this._config.toggleEvent = 'mouseenter';
    }

    if(_this.type(_this._config.el) !== 'string') {
        //手动绑定
        //初始化事件按钮
        _this._config.byManual = true;
        const btnObj = _this._config.el.target || _this._config.el;
        let event = null;
        if(_this._config.el.target) {
            event = _this._config.el;
        }
        if(_this._config.event) {
            event = _this._config.event;
        }
        let id = btnObj.getAttribute("id");
        if(!id) {
            id = "myDropdownBtn" + _this.insCount;
            btnObj.setAttribute("id", id);
        }
        const count = btnObj.dataset.count;
        if(!count) btnObj.dataset.count = 1;
        btnObj.classList.add('my-btn'+_this.insCount+btnObj.dataset.count);
        if(!btnObj.classList.contains('my-btn-by-manual')) btnObj.classList.add('my-btn-by-manual');
        _this._config.el = "#" + id;
        _this.create(btnObj);
        _this.show(btnObj);
        if(event) event.stopPropagation();
    } else {
        //自动绑定
        //绑定事件源按钮点击事件
        document.querySelectorAll(_this._config.el||".my-dropdown-btn").forEach(function(item){
            item.addEventListener(_this._config.toggleEvent||'click', function(e){
                _this.create(this);
                _this.show(this);
                e.stopPropagation();
                return false;
            });
        });
    }
}
//创建菜单
MyDropdown.prototype.create = function(objBtn, options) {
    var _this = this;
    options = options || _this._config;
    if(options.el) {
        _this._config.el = options.el;
    }

    if(objBtn) {
        if (!objBtn.dataset.count) {
            objBtn.dataset.count = _this._count++;
            objBtn.classList.add('my-btn'+_this.insCount+objBtn.dataset.count);
        }
    }
    var count = objBtn.dataset.count || 0;

    //已存在则返回
    if(document.querySelector(`#myDropdownWrapper${_this.insCount}${count}`)) {
        return;
    }

    //点击空白,菜单消失(每次显示后,重新绑定一次)
    if(!window._MyDropdownEventListener) {
        window._MyDropdownEventListener = 1;
        document.addEventListener('click', (e) => {
           if (!e.target.matches(this._config.el||'.my-dropdown-btn')) {
             this.hide();
           }
       });
    }

    var mouseenterClass = "";
    if(_this._config.toggleEvent && _this._config.toggleEvent.toLowerCase() === 'mouseenter') {
        mouseenterClass = " mouseenter";
    }
    var byManualClass = "";
    if(objBtn.classList.contains("my-btn-by-manual")) {
        byManualClass = " my-dropdown-by-manual"
    }
    //生成菜单 这里count是某实例下的菜单个数,_this.insCount是实例个数
    var menu = `<div id="myDropdownWrapper${_this.insCount}${count}" class="my-dropdown-wrapper${mouseenterClass}${byManualClass}" style="overflow:auto;${options.maxWidth?'max-width:'+options.maxWidth+';':''}${options.maxHeight?'max-height:'+options.maxHeight+';':''}" data-count="${count}">`;
    if(options.items) {
        for(var i in options.items){
            var item = options.items[i];
            //处理icon选项
            var iconHtml = '';
            if(item.icon){
                if(_this.type(item.icon) === 'object'){
                    iconHtml = item.icon.html ? item.icon.html : '';
                } else {
                    iconHtml = item.icon ? item.icon : '';
                }
            }

            //处理op选项
            var opHtml = '';
            if(item.op){
                if(_this.type(item.op) === 'object'){
                    opHtml = item.op.html ? item.op.html : '';
                } else {
                    opHtml = item.op ? item.op : '';
                }
            }
            //生成菜单项
            menu += `<div class="my-dropdown-item${item.selected?' selected':''}" data-index="${i}" data-value="${item.value}"><span class="my-dropdown-item-op">${opHtml}</span><a href="##" class="${item.selected?'selected':''}" data-index="${i}" data-value="${item.value}"><span class="my-dropdown-item-icon">${iconHtml}</span>${item.name}</a></div>`;
        }
    }
    menu += `</div>`;
    //追加到body中
    document.body.insertAdjacentHTML("beforeend", menu);

    //记录hidden回调
    window._MyDropdownConfig = window._MyDropdownConfig || {};
    window._MyDropdownConfig[`myDropdownWrapper${_this.insCount}${count}`] = _this._config || {};

    //绑定菜单事件
    if(_this._config.toggleEvent && _this._config.toggleEvent.toLowerCase() === 'mouseenter') {
        //绑定菜单列表
        document.querySelector("#myDropdownWrapper" + _this.insCount + count).addEventListener("mouseleave", function(e){
            _this.hide(this);
        });
    }

    //绑定菜单item点击事件
    document.querySelectorAll("#myDropdownWrapper" + _this.insCount + count + " .my-dropdown-item").forEach(function(item){
        item.addEventListener("click", function(e){
            if(typeof this.dataset.index !== 'undefined' && options.items[this.dataset.index]) {
                options.items[this.dataset.index].fn(e);
            }
        });
    });

    //绑定菜单项icon按钮点击事件
    document.querySelectorAll("#myDropdownWrapper" + _this.insCount + count + " .my-dropdown-item-icon").forEach(function(item){
        //if(!item.innerHTML) return;
        item.addEventListener("click", function(e){
            if(typeof this.parentElement.dataset.index !== 'undefined' && options.items[this.parentElement.dataset.index] && 
            options.items[this.parentElement.dataset.index].icon && options.items[this.parentElement.dataset.index].icon.fn) {
                var _this_item = this;
                (function(e){
                    options.items[_this_item.parentElement.dataset.index].icon.fn(e);
                    e.stopPropagation();
                    return false;
                })(e);
            }
        });
    });

    //绑定菜单项操作按钮点击事件
    document.querySelectorAll("#myDropdownWrapper" + _this.insCount + count + " .my-dropdown-item-op").forEach(function(item){
        //if(!item.innerHTML) return;
        item.addEventListener("click", function(e){
            if(typeof this.nextElementSibling.dataset.index !== 'undefined' && options.items[this.nextElementSibling.dataset.index] && 
            options.items[this.nextElementSibling.dataset.index].op && options.items[this.nextElementSibling.dataset.index].op.fn) {
                var _this_item = this;
                (function(e){
                    options.items[_this_item.nextElementSibling.dataset.index].op.fn(e);
                    e.stopPropagation();
                    return false;
                })(e);
            }
        });
    });

    if(options.created) options.created(document.querySelector(`#myDropdownWrapper${_this.insCount}${count}`));
};
//菜单显示
MyDropdown.prototype.show = function(objBtn, callback) {
  var count = objBtn.dataset.count || 0;
  var myDropdownWrapper = document.getElementById("myDropdownWrapper"+this.insCount+count);
  if(objBtn){
      myDropdownWrapper.style.top = (objBtn.offsetTop + objBtn.offsetHeight) + "px";
      myDropdownWrapper.style.left = objBtn.offsetLeft + "px";
  }
  if (myDropdownWrapper.classList.contains('show')) {
      if(this._config.toggleEvent && this._config.toggleEvent.toLowerCase() === 'mouseenter'){
        return;
      }
      //隐藏菜单
      myDropdownWrapper.classList.remove('show');
      if(this._config.hidden) this._config.hidden();
  } else {
      if(this._config.toggleEvent && this._config.toggleEvent.toLowerCase() === 'mouseenter'){
        this.hide();
      }
      //显示菜单
      myDropdownWrapper.style.zIndex = window._MyDropdownPosIndex++;
      myDropdownWrapper.classList.add('show');
      callback = callback || this._config.shown;
      if(callback) callback(myDropdownWrapper);
  }
  //myDropdownWrapper.classList.toggle("show");
}
//菜单隐藏
MyDropdown.prototype.hide = function(objMenu, callback) {
    objMenu = typeof objMenu === 'string' ? document.querySelector(objMenu) : objMenu;
    if(objMenu) {
        //单个菜单隐藏
        var id = objMenu.getAttribute("id");
        var config = window._MyDropdownConfig[id] || {};
        if(config.byManual && objMenu.classList.contains("my-dropdown-by-manual")){
            let idNum = objMenu.getAttribute("id").match(/\d+/);
            idNum = idNum ? (idNum[0]||0) : 0;
            let myBtn = document.querySelector(".my-btn"+idNum);
            if(myBtn) myBtn.classList.remove("my-btn"+idNum);
            objMenu.remove();
            callback = callback || config.hidden;
            if(callback) callback(objMenu);
        } else {
            if (objMenu.classList.contains('show')) {
                objMenu.classList.remove('show');
                callback = callback || config.hidden;
                if(callback) callback(objMenu);
            }
        }
    } else {
        //所有菜单隐藏
        var dropdowns = document.querySelectorAll(".my-dropdown-wrapper");
        for (var i = 0; i < dropdowns.length; i++) {
            var openDropdown = dropdowns[i];
            var id = openDropdown.getAttribute("id");
            var config = window._MyDropdownConfig[id] || {};
            if(config.byManual && openDropdown.classList.contains("my-dropdown-by-manual")){
                let idNum = openDropdown.getAttribute("id").match(/\d+/);
                idNum = idNum ? (idNum[0]||0) : 0;
                let myBtn = document.querySelector(".my-btn"+idNum);
                if(myBtn) myBtn.classList.remove("my-btn"+idNum);
                openDropdown.remove();
                if(window._MyDropdownConfig[id] && window._MyDropdownConfig[id].hidden){
                    callback = callback || config.hidden;
                    if(callback) callback(openDropdown);
                }
                delete window._MyDropdownConfig[id];
            } else {
                if (openDropdown.classList.contains('show')) {
                    openDropdown.classList.remove('show');
                    if(window._MyDropdownConfig[id] && window._MyDropdownConfig[id].hidden){
                        callback = callback || config.hidden;
                        if(callback) callback(openDropdown);
                    }
                }
            }
        }
    }
}

//使用示例
// //测试1
// var clicked = function(e) {
//     console.log("clicked", e.target.dataset.value)
// }
// new MyDropdown({
//     el: ".my-dropdown-btn",
//     maxWidth: '200px',
//     maxHeight: '400px',
//     //支持click mouseenter dblclick等,默认click
//     toggleEvent: 'mouseenter',
//     items: [
//         {
//             name: 'Home',
//             value: 'home',
//             icon: '',
//             fn: clicked
//         },
//         {
//             name: 'About',
//             value: 'about',
//             icon: '',
//             selected: false,
//             fn: clicked
//         },
//         {
//             name: 'Contact',
//             value: 'contact',
//             icon: '',
//             fn: clicked,
//             //icon也支持对象传值,同样具有html和fn属性
//             op: {
//                 html: `<span class="close">&times;</span>`,
//                 fn: function(e) {
//                     console.log('op clicked');
//                 }
//             }
//         }
//     ],
//     created: function(menu) {
//         console.log('After created callback1');
//     },
//     shown: function(menu) {
//         console.log('After shown callback1');
//     },
//     hidden: function(menu) {
//         console.log('After hidden callback1');
//     }
// });

// //测试2
// var clicked2 = function(e) {
//     console.log("clicked2", e.target.dataset.value)
// }
// new MyDropdown({
//     el: ".my-dropdown-btn2",
//     items: [
//         {
//             name: 'Home2',
//             value: 'home2',
//             icon: '',
//             fn: clicked2
//         },
//         {
//             name: 'About2',
//             value: 'about2',
//             icon: '',
//             selected: false,
//             fn: clicked2
//         },
//         {
//             name: 'Contact2',
//             value: 'contact2',
//             icon: '',
//             fn: clicked2
//         }
//     ],
//     created: function(menu) {
//         console.log('After created callback2');
//     },
//     shown: function(menu) {
//         console.log('After shown callback2');
//     },
//     hidden: function(menu) {
//         console.log('After hidden callback2');
//     }
// });

// //测试3
// document.querySelector("#test3").addEventListener('click', function(e){
//     new MyDropdown({
//         el: this,
//         maxWidth: '200px',
//         maxHeight: '400px',
//         items: [
//             {
//                 name: 'Home3',
//                 value: 'home3',
//                 fn: (e) => {
//                     console.log("clicked", e.target.dataset.value)
//                 }
//             },
//             {
//                 name: 'About3',
//                 value: 'about3',
//                 selected: false,
//                 fn: (e) => {
//                     console.log("clicked", e.target.dataset.value)
//                 }
//             },
//             {
//                 name: 'Contact3',
//                 value: 'contact3',
//                 selected: false,
//                 fn: (e) => {
//                     console.log("clicked", e.target.dataset.value)
//                 },
//                 op: {
//                     html: `<span class="close">&times;</span>`,
//                     fn: (e) => {
//                         console.log('op clicked');
//                     }
//                 }
//             }
//         ],
//         created: function(menu) {
//             console.log('After created callback3');
//         },
//         shown: function(menu) {
//             console.log('After shown callback3');
//         },
//         hidden: function(menu) {
//             console.log('After hidden callback3');
//         }
//     });
//     e.stopPropagation();
//     return false;
// });