js 插件-實現過程

有一個需求,需要實現一個下拉選擇人員,選出的人員後需要在 3d 中選中,由於人員列表比較大,並且人員是實時刷新的,最開始用的下拉菜單存在以下問題.
1. 需要選中特定人員時,查找此人員住住很難找到,找到後可能剛好刷新,又找不到了
2. 有時需要全選或反選時,需像機器人一樣點很多次
所以考慮弄一個下拉麪板
界面設計如下:
這裏寫圖片描述

面板類設計如下:
屬性:
    面板寬高 width,height
    數據列表 data
    請求地址 url
    刷新頻率 interval
    面板是否已經關閉 closed
    是否支持多選 singleSelect
    是否顯示行號 rownum
    選中的元素 selected (由於 data 在不斷刷新,把選中的元素存在 data 的每個元素上是不合理的,所以加了個內部屬性來存取選中的元素)
事件:
private:
    上,下,左,右,回車,esc ,空格鍵監聽
public:
    面板找開之前 onBeforeOpen
    面板關閉之前 onBeforeClose
    元素選中後 onSelected
    面板打開後  onOpen
方法:
    private:
        打開面板 openPanel
        關閉面板 closePanel
        反選        retainSelect
        全選/取消 toggleSelect
        搜索        search
        數據結點點擊時 dataLiClick
        數據排序 sort
        加載數據 loadData
        渲染 render
        初始化方法  init
    public 
        獲取屬性 options
        刷新數據 refresh
        選中指定元素 select
        關閉面板 close
        打開面板 open

最終界面如下:
這裏寫圖片描述

是後上下代碼 :
css

ul{
    list-style-type: none;
    margin: 0 ;
    padding: 0;
}

.dropdown-panel{
    position: absolute;
    top: 50px;
    left: 100px;

    width: 400px;
    height: 300px;
    border:1px solid #7d262f;

    box-sizing:border-box;
    -moz-box-sizing:border-box; /* Firefox */
    -webkit-box-sizing:border-box; /* Safari */

    font-family: "Helvetica Neue", Helvetica, Microsoft Yahei, Hiragino Sans GB, WenQuanYi Micro Hei, sans-serif;
    font-size: 14px;
    line-height: 1.42857143;

    z-index: 4;
}

.dropdown-panel i.fa{
    width: 16px;
    height: 16px;

    cursor: pointer;
}

.dropdown-panel input[type=checkbox]{
    width: 20px;
    height: 20px;

    position: relative;
    top: 6px;

    cursor: pointer;
}

.toolbar {
    border-bottom: 1px dotted #777;
    padding: 2px 0;

    height: 35px;

}

.toolbar input[type=text]{
    line-height: normal;

    height: 25px;
    border: 1px solid #ccc;

    padding-left: 3px;
    outline:none;
    line-height:25px\9;
    _line-height:26px;
}

.toolbar .toolbar-item{
    float: left;
    line-height: 35px;
    position: relative;
    margin-right: 5px;
    padding: 0 3px;
}

.toolbar .toolbar-item .fa-refresh:HOVER{
    color: #3232d8;
}

.toolbar .toolbar-item label{
    padding: 0 3px;

    cursor: pointer;
}

.toolbar .toolbar-right{
    float: right;
    margin-right: 0;
}
.toolbar .toolbar-right i.fa-search{
    position: absolute;

    top: 10px;
    right: 6px;
    color: #999;
}

.list-data{
    height:260px;

    overflow-y:scroll;
    overflow-x:hidden;
}

.list-data li{
    line-height: 25px;

    cursor: pointer;
}

.list-data li a{
    text-decoration: none;
    color: #333;

    display: block;

    padding: 2px 10px;
}

.list-data li.selected{
    background-color: #999; 
}

.list-data li a.hover,
.list-data li a:HOVER{
    background-color: pink;
}

.list-data li.selected a.hover,
.list-data li.selected a:hover{
    background-color: #79F2F2 !important;
}


.list-data li a i{
    position:relative;
    display: inline-block;
    margin-left: 3px;
}

.list-data li  a .rownum{
    width:30px;
    display: inline-block;
    padding: 2px 0;
    text-align: center;
}

.list-data li  a .text{
    display: inline-block;
    padding: 2px 0 ;
}

js:

(function($,undefined){
    //擴展 trim 方法
  if (!String.prototype.trim) {
    String.prototype.trim = function() {
        return $.trim(this);
    }
  }

    /*
     * 初始化方法
     */
    function init($this,opts){
        var offset = $this.offset(),
                targetWidth = $this.outerWidth(true),
                targetHeight = $this.outerHeight(true),
                $parent = $this.parent(),
                $toolbar = $('<div class="toolbar clearfix"><div class="toolbar-item toolbar-right" eventId="search"><input type="text" placeholder="請輸入要搜索的人員"> <i class="fa fa-search"></i></div><div class="toolbar-item" eventId="selectAll"><input type="checkbox"><label>全選/取消</label></div><div class="toolbar-item" eventId="retainSelect"><label>反選</label></div></div>'),
                $dataUl = $('<ul class="list-data"></ul>'),
                $panel  = $('<div class="dropdown-panel"></div>').append($toolbar).append($dataUl).appendTo($parent);
        //設置位置信息
        $panel.css({
            top:(offset.top + targetHeight),
            left:(offset.left - (opts.width - targetWidth)), /*右對齊 */
            width:opts.width,
            height:opts.height
        });
//      //解決在 3d 被遮蓋的問題 放這裏不管用
        $panel.append('<iframe  src="about:blank" frameborder=0 style="height: 100%; width: 100%; position: absolute; left: 0px; z-index: -1; top: 0px; scrolling: no"></iframe>');
        //設置數據區大小
        $dataUl.css({
            height:(opts.height - $toolbar.outerHeight(true))
        });
        $panel.hide(); //加載後先隱藏
        return {
            panel:$panel,
            toolbar:$toolbar,
            dataUl:$dataUl
        }
    }

    /*
     * 排序,默認選中的會排在前面
     */
    function sort(jq){
        var opts = $.data(jq[0],'floatpanel').opts,
                data = opts.data;
        if(!data || data.length <= 1){
            return ;
        }
        var dataSelected = [],dataNotSelected = [],selectedStr = '$'+opts.selected.join('$')+'$';
        for(var i=0;i<data.length;i++){
            var row = data[i];
            if(selectedStr.indexOf(row.id) != -1){
                dataSelected.push(row);
            }else{
                dataNotSelected.push(row);
            }
        }
        opts.data = dataSelected.concat(dataNotSelected);
    }
    /*
     * 刷新視圖,此方法僅加載數據,賦值給全局數據
     */
    function loadData(jq){
        var opts = $.data(jq[0],'floatpanel').opts;
        if(opts.url){
            //優先數據請求
            $.log('通過地址請求數據,暫未實現');
            return ;
        }
    }
    /*
     * 渲染視圖
     * 可加入過濾的數據來渲染
     */
    function render(jq,filterData){
        var opts = $.data(jq[0],'floatpanel').opts,
                $dataUl = $.data(jq[0],'floatpanel').dataUl,
                data = opts.data;
        if(filterData){
            data = filterData;
        }

        $dataUl.empty();
        if(!data || data.length == 0){
            return ;
        }

        var uldata = [];
        for(var i=0;i<data.length;i++){
            var row = data[i];
            if(row['selected']){
                opts.selected.push(data[i].id);
            }

            uldata.push('<li id="'+row.id+'" >');
            uldata.push('<a href="#">');
            if(opts.rownum){
                uldata.push('<span class="rownum">'+(i+1)+'</span>');
            }
            uldata.push('<input type="checkbox" />');
            uldata.push('<i class="fa '+row.iconCls+'"></i>');
            uldata.push('<span class="text">'+row.text+'</span>');
            uldata.push('</a></li>');
        }
        $dataUl.append(uldata.join(''));
        //選中已經選中的元素
        for(var i =0 ;i<opts.selected.length;i++){
            $dataUl.find('li[id='+opts.selected[i]+']').addClass('selected');
            $dataUl.find('li[id='+opts.selected[i]+'] input[type=checkbox]').prop('checked',true);
        }
        //回調選中事件 
        opts.onSelected.call(this,opts.selected);
    }

    /*
     * 事件綁定
     */
    function bindEvents(jq){
        var state = $.data(jq[0],'floatpanel'),
                opts = state.opts,
                $toolbar = state.toolbar,
                $dataUl = state.dataUl,
                $panel = state.panel,
                events = [{selector:'.toolbar-item[eventId=selectAll]',types:['click'],handler:toggleSelect},
                          {selector:'.toolbar-item[eventId=retainSelect]',types:['click'],handler:retainSelect},
                          {selector:'.toolbar-item[eventId=search]',types:['blur','keyup'],handler:search},
                          {selector:'.toolbar-item[eventId=search] i',types:['click'],handler:search}]

//      for(var i=0;i<events.length;i++){
//          for(var j=0;j<events[i]['events'].length;j++){
//              $(events[i]['selector'],$toolbar).bind(events[i]['events'][j],function(e){
//                  alert(events[i].handler)
//                  events[i].handler.call(this,jq,e);
//              });
//          }
//      }
        while(events[0]){
        (function(cfg){
                for(var i = 0, l = cfg.types.length; i < l; i++) {
                    $(cfg.selector).bind(cfg.types[i], function(e) {
                        if($.isFunction(cfg.handler)) {
                            cfg.handler.call(this, jq,e);
                            e.stopPropagation();
                        }
                    });
                }
        })(events.shift());
    }


        //監聽 上,下,左,右,回車鍵,esc
//      $(document).on('keyup',$panel,panelOnKeyDown);
        //元素點擊時打開/關閉面板
        jq.bind('keydown',function(e){
            panelOnKeyDown.call(this,jq,e);
        }).bind('click',function(e){
            if(opts.closed){
                openPanel(jq, false);
                return ;
            }
            closePanel(jq, false);
        });
        //數據結點點擊事件
        $dataUl.on('click','li',function(e){
            dataLiClick.call(this,jq,e);
        });


    }
    /*
     * 監聽 上,下,左,右,回車鍵,esc 鍵
     */
    function panelOnKeyDown(jq,e){
        var state = $.data(jq[0],'floatpanel'),
                opts = state.opts,
                $dataUl = state.dataUl,
                $panel = state.panel,
                $hoverA = $dataUl.find('li>a.hover'),$hoverLi = $hoverA.parent();
        switch(e.keyCode){
        case 13: //enter
            search(jq,e);
            break;
        case 27: //esc 
            closePanel(jq,false);
            break;
        case 32: //space 
            toggleSelectLi($hoverLi,opts);
            break;
        case 37://arrow left
            //暫時未實現 TODO 
            break;
        case 38: //arrow up
            if($hoverLi.size() == 0){
                //一個都沒選中的時候,選中第一個
                $dataUl.find('li:first>a').addClass('hover');
                return ;
            }
            $hoverA.removeClass('hover');
            var $nowLi = $hoverLi.prev();
            $nowLi.find('a').addClass('hover');
            if($nowLi.size() > 0 && $nowLi.offset().top - $panel.offset().top < $nowLi.outerHeight(true) ){
                //scroll
                var preScrollTop = $dataUl.scrollTop();
                $dataUl.scrollTop(preScrollTop - $nowLi.outerHeight(true));
            }
            break;
        case 39://arrow right
            //暫時未實現 TODO 
            break;
        case 40: //arrow down
            if($hoverLi.size() == 0){
                //一個都沒選中的時候,選中第一個
                $dataUl.find('li:first>a').addClass('hover');
                return ;
            }
            $hoverA.removeClass('hover');
            var $nowLi = $hoverLi.next();
            $nowLi.find('a').addClass('hover');
            if($nowLi.size() > 0 && $nowLi.offset().top - $panel.offset().top > opts.height - $nowLi.outerHeight(true) ){
                //scroll
                //console.log('scroll')
                var preScrollTop = $dataUl.scrollTop();
                $dataUl.scrollTop(preScrollTop + $nowLi.outerHeight(true));
            }
            break;
        default:
            //其它鍵不做處理
        }
    }


    /*
     * 數據節點擊
     */
    function dataLiClick(jq,e){
        var $li = $(e.target).closest('li'),
                opts = $.data(jq[0], "floatpanel").opts;
        toggleSelectLi($li,opts);
    }

    /*
     * 取消,選中單個元素
     */
    function toggleSelectLi($li,opts){
        var $checkbox = $li.find('input[type=checkbox]'),
                status = $checkbox.prop('checked'),
                id = $li.attr('id');

        $checkbox.prop('checked',!!!status);
        if(!!!status){
            $li.addClass('selected');
            opts.selected.push(id);
        }else{
            $li.removeClass('selected');
            for(var i=0;i<opts.selected.length;i++){
                if(opts.selected[i] == id){
                    opts.selected.splice(i,1);
                    break;
                }
            }
        }
        opts.onSelected.call(this,opts.selected);
    }

    /*
     * 搜索人員
     */
    function search(jq,e){
        var state = $.data(jq[0], "floatpanel"),
                opts = state.opts,
                $toolbar = state.toolbar,
                $input = $toolbar.find('input[type=text]'),
                $dataUl = state.dataUl;

        if(e.keyCode == 38 || e.keyCode == 40 || (e.keyCode == 32 && e.ctrlKey) || e.keyCode == 27 || e.keyCode == 13){
            panelOnKeyDown.call(this,jq,e);
            return ;
        }

        if(!opts.data || opts.data.length == 0){
            return ;
        }
        var filterParam = $input.val().trim();
        var filterData = [];
        //當爲空時,查詢所有
        if(filterParam == ''){
            render(jq, opts.data);
            return ;
        }
        for(var i=0;i<opts.data.length;i++){
            var row = opts.data[i];
            if(row['text'].toUpperCase().indexOf(filterParam.toUpperCase()) != -1){
                filterData.push(row);
            }
        }
        render(jq, filterData);
    }

    /*
     * 全選/取消 
     */
    function toggleSelect(jq,e){
        var state = $.data(jq[0], "floatpanel"),
                opts = state.opts,
                $dataUl = state.dataUl,
                $selectFlag = $(e.target).closest('.toolbar-item').find('input[type=checkbox]');

        var selected = $selectFlag.prop('checked');
        $selectFlag.prop('checked',!!!selected);
        $dataUl.find('li input[type=checkbox]').prop('checked',!!!selected);
        //設置選中
        if(!opts.data || opts.data.length == 0){return ;}
        if(!!!selected){
            //防止重複選中
            var selectedStr = '$'+opts.selected.join('$')+'$';
            for(var i=0;i<opts.data.length;i++){
                if(selectedStr.indexOf(opts.data[i].id) == -1){
                    opts.selected.push(opts.data[i].id);
                }
            }
            opts.onSelected.call(this,opts.selected);
            $dataUl.find('li').addClass('selected');
            return ;
        }
        opts.selected = [];
        opts.onSelected.call(this,opts.selected);
        $dataUl.find('li').removeClass('selected');
    }
    /*
     * 反選
     */
    function retainSelect(jq,e){
        var state = $.data(jq[0], "floatpanel"),
                opts = state.opts,
                selected = opts.selected,
                data = opts.data,
                $dataUl = state.dataUl,
                newSelect = [];
        if(!data || data.length  == 0){
            return ;
        }
        var selectedStr = '$'+selected.join('$')+'$';
        for(var i=0;i<data.length;i++){
            if(selectedStr.indexOf('$'+data[i].id+'$') == -1){
                newSelect.push(data[i].id);
                $dataUl.find('li[id='+data[i].id+']').addClass('selected');
                $dataUl.find('li[id='+data[i].id+'] input[type=checkbox]').prop('checked',true);
                continue;
            }
            $dataUl.find('li[id='+data[i].id+']').removeClass('selected');
            $dataUl.find('li[id='+data[i].id+'] input[type=checkbox]').prop('checked',false);
        }
        opts.selected = newSelect;
        opts.onSelected.call(this,opts.selected);
    }
    /*
     * 關閉面板
     */
    function closePanel(jq,forceClose){
        var state = $.data(jq[0], "floatpanel"),
                opts = state.opts;
      if (forceClose != true && !opts.onBeforeClose.call(jq)) {
        return;
      }
      state.panel.hide();
      opts.closed = true;
    }
    /*
     * 打開面板
     */
    function openPanel(jq,forceOpen){
        var state = $.data(jq[0], "floatpanel"),
                opts = state.opts,
                $panel = state.panel;
         if (forceOpen != true && !opts.onBeforeOpen.call(jq)) {
       return;
         }
         sort(jq); //對選擇的數據進行排序
         render(jq);
         state.panel.show();
         opts.closed = false; //TODO 有可能刷新數據在排序之前,導致數據沒有排序
         //搜索聚焦
         state.toolbar.find('.toolbar-item>input[type=text]').focus();
    }

    $.fn.floatpanel = function(options,params){
        if(typeof options == 'string'){
            var method = $.fn.floatpanel.methods[options];
            if (method) {
                return method(this, params);
            }
            throw 'no such method ' + options;
            return;
        }
        options = options || {};

        return this.each(function(){
            var _this = $(this);
            var state = $.data(this, 'floatpanel');
            if (state) {
                $.extend(state.options, options);
            }else{
                state = $.extend({}, $.fn.floatpanel.defaults, options);
        state = $.data(this, "floatpanel", $.extend({opts: state}, init($(this), state)));
        $(this).addClass('dropdown-close');
        loadData($(this));
            render($(this));
        bindEvents($(this));
            }
        });
    }
    $.fn.floatpanel.defaults = {
            width:400,
            height:300,
            data:null,
            selected:[],
            url:undefined,
            interval:-1,
            rownum:true,
            singleSelect:false,
            pageSize:10,
            onBeforeClose:function(jq,param){return true}, //返回 false 阻止關閉
            onBeforeOpen:function(jq,param){return true},
            onSelected:function(selecteds){}, //在選擇後調用,返回選擇的數據 [id] 數組
            onOpen:function(jq,param){},
            closed:true
    }
    $.fn.floatpanel.methods={
            options:function(jq){
                return $.data(jq[0], "floatpanel").opts;
            },
            /**
             * 刷新數據,只會刷新第一個的數據
             */
            refresh:function(jq,_data){
                var opts = $.data(jq[0],'floatpanel').opts;
                //如果面板已經打開,則不需要刷新數據了
                if(opts.closed){
                    if(!_data || _data.length == 0){
                        return ;
                    }
                    if(opts.selected.length == 0){
                        opts.data = _data;
                    }else{
                        var selectedStr = opts.selected.join('$');
                        //已經選中的數據加上選擇標記
                        for(var i=0;i<_data.length;i++){
                            var row = _data[i];
                            if('$'+selectedStr+'$'.indexOf('$'+row['id']+'$') != -1){
                                row['selected'] = true;
                            }
                        }
                    }
                    //render(jq);
                }
            },
            select:function(jq,param){
                var opts = $.data(jq[0],'floatpanel').opts;
                opts.selected = param;
                render(jq);
            },
            close:function(jq,param){
                return jq.each(function(){
                    closePanel($(this),param);
                });
            },
            open:function(jq,param){
                return jq.each(function(){
                    openPanel($(this),param)
                })
            }

    }

})(window.jQuery)

使用說明 :
$(selector).floatpanel({});

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章