有一個需求,需要實現一個下拉選擇人員,選出的人員後需要在 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({});