基于jQuery打造TabPanel

停了许久,终于有时间继续下一篇,这次我们要实现的控件时Tab控件,在实际的应用中也比较多,如大量信息查看,当网页多窗口框架等都会用到,现在网上基于jquery Tab控件,其实也蛮多了,我以前用过的idtabs,就比较简单实用,也是比较灵活,但是对于复杂情况就要编码多些,太简单了些。还有就是jquery UI的里面的tab控件(没用过,我对jquery ui不太感冒),另外就是近期有点小火的easyui 中的tab控件,最早在javaeye上面看到的,界面还算漂亮,因为之前没开源,所以一直没跟进(好像最近开源了,前几天下载了看看,编码风格有点像prototype,看不出jquery的影子,不知道为什么叫jquery easyui呵呵,因为没太深入去学习,也不好做其他评价)。说了那么多,我们还是回到主题,因为种种原因不得不想着自己开发一个吧。于是就有了这篇,先来看看效果吧。

下图是单网页多窗口框架的效果图

image

下载上图demo 

下图是文末提供调用示例的效果截图。

image

大家可以看到了还是使用ExtJs的效果。其实CSS基本上是直接copy它的。我觉得它那个就非常好看,当然实际使用的时候大家有能力完全可以自己样子

第一 我们还是从HTML开始吧

注:我先控件的思路始终是先确定HTML结构,其次是样式,最终才是js实现的事件方法等。

其实看图我们就可以基本确定,tab控件主要有两个部分的html 一个是头,用于放tab选项卡的;另外一个是体,是内容的容器。那么就是两个Div容器,讲tab控件分成了header和body两部分。

其中header部分因为包含多选项卡 所以很容易想到ul +li的配合。来看一下header中的实际html结构

image

通过通过其中li即是一个选项卡,第一个a是关闭按钮,第二个a才是实际内容 通过嵌套标签来实现 左右中的背景图片设置(这个做法比较多见的)。当然能够有个好的效果,还是要靠CSS支持。必须对CSS有一定的了解。

Body的结构则更简单就是div嵌套div就就结束了。

第二 CSS样式表

因为CSS是copy EXTJS的我也就不多介绍了。大家可以看代码下载里面的实际代码,如果有问题可以再沟通交流

第三:开始编写JS了

老规矩先来一段完整的JS代码,大概有500行左右的代码,其实我换行比较勤快,实际的代码量其实还是比较少。

; (function ($) {
    $.fn.tabpanel =function(option){
        var dfop ={
               items:[], //选项卡数据项 {id,text,classes,disabled,closeable,content,url,cuscall,onactive}
               500,
               height:400,
               scroll100,//如果存在滚动条,点击按钮次每次滚动的距离
               autoscroll:true //当选项卡宽度大于容器时自动添加滚动按钮
       };
       var headerheight=28;
       $.extend(dfop, option);      
       var me =$(this).addClass("x-tab-panel").width(dfop.width);
       innerwidth = dfop.width-2;
       //构建Tab的Html    
       var tcs= dfop.autoscroll?"x-tab-scrolling-top":"";      
       var header = $("<div class='x-tab-panel-header x-unselectable "+tcs+"' unselectable='on' style='"+innerwidth+"px;MozUserSelect:none;KhtmlUserSelect:none;'></div>");
       var stripwrap = $("<div class='x-tab-strip-wrap'/>");
       var scrollerright = $("<div class='x-tab-scroller-right x-unselectable' style='height: 24px; visibility: hidden; mozuserselect: none; khtmluserselect: none;' unselectable='on'/>");
       var scrollerleft = $("<div class='x-tab-scroller-left x-unselectable' style='height: 24px; visibility: hidden; mozuserselect: none; khtmluserselect: none;' unselectable='on'/>");
       var ulwrap = $("<ul class='x-tab-strip x-tab-strip-top'></ul>");
       var stripspacer = $("<div class='x-tab-strip-spacer'/>");
       var litemp =[];
       for(var i=0,l=dfop.items.length; i<l ;i++)
       {          
           var item =dfop.items[i];
           builditemlihtml(item,litemp);
       }
       litemp.push("<li class='x-tab-edge'/><div class='x-clear'></div>");
       
       ulwrap.html(litemp.join(""));
       litemp =null;
       stripwrap.append(ulwrap);
       if(dfop.autoscroll)
       {
         header.append(scrollerright).append(scrollerleft);
       }
       header.append(stripwrap).append(stripspacer);
       var bodyheight=dfop.height-headerheight;
       var bodywrap = $("<div class='x-tab-panel-bwrap'/>");
       var body = $("<div class='x-tab-panel-body x-tab-panel-body-top'/>").css({innerwidth,height:bodyheight});
       var bodytemp=[];
       for(var i=0,l=dfop.items.length; i<l ;i++){          
          var item =dfop.items[i];
          builditembodyhtml(item,bodytemp);       
       }
       body.html(bodytemp.join("")).appendTo(bodywrap);
       me.append(header).append(bodywrap);
       initevents();
       function builditemlihtml(item,parray)
       {           
           parray.push("<li id='tab_li_",item.id,"' class='",item.isactive?"x-tab-strip-active":"",item.disabled?"x-tab-strip-disabled":"",item.closeable?" x-tab-strip-closable":"",item.classes?" x-tab-with-icon":"","'>");
           parray.push("<a class='x-tab-strip-close' onclick='return false;'/>");
           parray.push("<a class='x-tab-right' onclick='return false;' href='#'>");
           parray.push("<em class='x-tab-left'><span class='x-tab-strip-inner'><span class='x-tab-strip-text ",item.classes||"","'>",item.text,"</span></span></em>");
           parray.push("</a></li>");    
       }
       function builditembodyhtml(item,parray)
       {  
          parray.push("<div class='x-panel x-panel-noborder",item.isactive?"":" x-hide-display","' id='tab_item_",item.id,"' style='",innerwidth,"px'>");
          parray.push("<div class='x-panel-bwrap'>");
          parray.push("<div class='x-panel-body x-panel-body-noheader x-panel-body-noborder'  id='tab_item_content_",item.id,"' style='position:relative;  ",innerwidth,"px; height:",bodyheight,"px; overflow: auto;'>");
          if(item.url){
            parray.push("<iframe name='tab_item_frame_",item.id,"' width='100%' height='100%'  id='tab_item_frame_",item.id,"' src='about:blank' frameBorder='0' />");
          }
          else if(item.cuscall){
            parray.push("<div class='loadingicon'/>");
          }
          else{
            parray.push(item.content);
          }
          parray.push("</div></div></div>");
       }
       function initevents()
       {
            //reset scoller
            resetscoller();
            scollerclick();
            ulwrap.find("li:not(.x-tab-edge)").each(function(e){
              inititemevents(this);
            });
       }
       function inititemevents(liitem)
       {
            liswaphover.call(liitem);
            liclick.call(liitem);
            closeitemclick.call(liitem);
       }
       function scollerclick()
       {
             if(dfop.autoscroll)
            {
                scrollerleft.click(function(e){scolling("left")});
                scrollerright.click(function(e){scolling("right")});
            }
       }
       function resetscoller()
       {
           if(dfop.autoscroll)
           {
               var edge = ulwrap.find("li.x-tab-edge");
               var eleft =edge.position().left;
               var sleft = stripwrap.attr("scrollLeft");              
               if( sleft+eleft>innerwidth )
               {
                    
                    header.addClass("x-tab-scrolling");
                    scrollerleft.css("visibility","visible");
                    scrollerright.css("visibility","visible");
                    if(sleft>0)
                    {
                       scrollerleft.removeClass("x-tab-scroller-left-disabled");
                    }
                    else{
                      scrollerleft.addClass("x-tab-scroller-left-disabled");
                    }
                    if(eleft>innerwidth)
                    {
                       scrollerright.removeClass("x-tab-scroller-right-disabled");
                    }
                    else{
                       scrollerright.addClass("x-tab-scroller-right-disabled");
                    }
                    dfop.showscrollnow =true;
                   
               }
               else
               {
                    header.removeClass("x-tab-scrolling");
                    stripwrap.animate({"scrollLeft":0},"fast");
                    scrollerleft.css("visibility","hidden");
                    scrollerright.css("visibility","hidden");
                    dfop.showscrollnow =false;
               }
           }
       }
       //
       function scolling(type,max)
       {
            //debugger;
            if(!dfop.autoscroll || !dfop.showscrollnow)
            {
                return;
            }
            //debugger;
            //var swidth = stripwrap.attr("scrollWidth");
            var sleft = stripwrap.attr("scrollLeft");
            var edge = ulwrap.find("li.x-tab-edge");
            var eleft = edge.position().left ;            
            if(type=="left"){
              if(scrollerleft.hasClass("x-tab-scroller-left-disabled"))
              {
                return;
              }           
              if(sleft-dfop.scrollwidth-20>0)
              {
                sleft -=dfop.scrollwidth;                
              }
              else{
                sleft =0;
                scrollerleft.addClass("x-tab-scroller-left-disabled");
              }
              if(scrollerright.hasClass("x-tab-scroller-right-disabled"))
               {
                  scrollerright.removeClass("x-tab-scroller-right-disabled");
               } 
              stripwrap.animate({"scrollLeft":sleft},"fast");
            }
            else{    
               if(scrollerright.hasClass("x-tab-scroller-right-disabled") && !max)
               {
                 return;
               }              
                //left + ;
               if(max || (eleft>innerwidth && eleft-dfop.scrollwidth-20<=innerwidth))
               {         
                 //debugger;
                 sleft = sleft+eleft-(innerwidth-38) ;
                 scrollerright.addClass("x-tab-scroller-right-disabled");
                 // sleft = eleft-innerwidth;
               }
               else
               {                 
                  sleft +=dfop.scrollwidth;                 
               }
               if(sleft>0)
               {
                  if(scrollerleft.hasClass("x-tab-scroller-left-disabled"))
                  {
                    scrollerleft.removeClass("x-tab-scroller-left-disabled");
                  }
               }              
              stripwrap.animate({"scrollLeft":sleft},"fast");
            }
       }
       function scollingToli(liitem)
       {
            var sleft = stripwrap.attr("scrollLeft");    
            var lleft = liitem.position().left;
            var lwidth = liitem.outerWidth(); 
            var edge = ulwrap.find("li.x-tab-edge");
            var eleft = edge.position().left ; 
            if(lleft<=0)
            {
                sleft +=(lleft-2) ;               
                if(sleft<0)
                {
                    sleft=0;
                    scrollerleft.addClass("x-tab-scroller-left-disabled");
                }   
                if(scrollerright.hasClass("x-tab-scroller-right-disabled"))
                {
                  scrollerright.removeClass("x-tab-scroller-right-disabled");
                }                    
                stripwrap.animate({"scrollLeft":sleft},"fast");
            }
            else{
                if(lleft+lwidth>innerwidth-40)
                {                     
                    sleft = sleft+lleft+lwidth+-innerwidth+40; // 40 =scrollerleft and scrollerrightwidth;
                    if(scrollerleft.hasClass("x-tab-scroller-left-disabled"))
                    {
                      scrollerleft.removeClass("x-tab-scroller-left-disabled");
                    } 
                    //滚到最后一个了,那么就要禁用right;
                    if(eleft-(lleft+lwidth+-innerwidth+40)<=innerwidth)
                    {
                        scrollerright.addClass("x-tab-scroller-right-disabled");
                    }
                    stripwrap.animate({"scrollLeft":sleft},"fast");

                }
            }
            liitem.click();           
            
       }
       function liswaphover()
       {
          $(this).hover(function(e){
              if(!$(this).hasClass("x-tab-strip-disabled"))
              {
                $(this).addClass("x-tab-strip-over");
              }
          },function(e){ 
              if(!$(this).hasClass("x-tab-strip-disabled"))
              {
                $(this).removeClass("x-tab-strip-over");
              }
          });
       }
       function closeitemclick()
       {
         if($(this).hasClass("x-tab-strip-closable"))
         {
            $(this).find("a.x-tab-strip-close").click(function(){
                deleteitembyliid($(this).parent().attr("id"));
            });
         }
       }
       function liclick()
       {
          $(this).click(function(e){
             var itemid = this.id.substr(7);
             var curr = getactiveitem();
             if( curr !=null && itemid == curr.id)
             {
                return;
             }
             var clickitem = getitembyid(itemid);
             if(clickitem && clickitem.disabled)
             {
                 return ;
             }
             if(curr)
             {             
                $("#tab_li_"+curr.id).removeClass("x-tab-strip-active");
                $("#tab_item_"+curr.id).addClass("x-hide-display");
                curr.isactive =false;
             }
             if(clickitem)
             {
                
                $(this).addClass("x-tab-strip-active");
                $("#tab_item_"+clickitem.id).removeClass("x-hide-display");
                if(clickitem.url)
                {
                    var cururl = $("#tab_item_frame_"+clickitem.id).attr("src");
                    if(cururl =="about:blank")
                    {
                        $("#tab_item_frame_"+clickitem.id).attr("src",clickitem.url);
                    }
                }
                else if(clickitem.cuscall && !clickitem.cuscalled)
                {
                   var panel = $("#tab_item_content_"+clickitem.id);
                   var ret = clickitem.cuscall(this,clickitem,panel);
                   clickitem.cuscalled =true;
                   if(ret) //如果存在返回值,且不为空
                   {
                       clickitem.content = ret;
                       panel.html(ret);
                   }
                }
                clickitem.isactive =true;
                if(clickitem.onactive)
                {
                   clickitem.onactive.call(this,clickitem);
                }
             }
          });
       }
       //获取当前活跃项
       function getactiveitem()
       {
            for(var i=0,j=dfop.items.length;i<j ;i++)
            {
                if(dfop.items[i].isactive)
                {
                    return dfop.items[i];
                    break;
                }
            }
            return null;
       }
       //根据ID获取Item数据
       function getitembyid(id)
       {
            for(var i=0,j=dfop.items.length;i<j ;i++)
            {
                if(dfop.items[i].id == id)
                {
                    return dfop.items[i];
                    break;
                }
            }
            return null;
       }
       function getIndexbyId(id)
       {
            for(var i=0,j=dfop.items.length;i<j ;i++)
            {
                if(dfop.items[i].id == id)
                {
                    return i;
                    break;
                }
            }
            return -1;
       }
       //添加项
       function addtabitem(item)
       {
          var chkitem =getitembyid(item.id);
          if(!chkitem){
            var isactive =item.isactive;
            item.isactive =false;
            var lastitem = dfop.items[dfop.items.length-1];
            dfop.items.push(item);
            var lastli = $("#tab_li_"+lastitem.id);
            var lastdiv = $("#tab_item_"+lastitem.id);
            var litemp =[];
            var bodytemp = [];
            builditemlihtml(item,litemp);
            builditembodyhtml(item,bodytemp);
            var liitem = $(litemp.join(""));
            var bodyitem= $(bodytemp.join(""));
            lastli.after(liitem);
            lastdiv.after(bodyitem);
            //事件
            var li = $("#tab_li_"+item.id);
            inititemevents(li);           
            if(isactive)
            {                
               li.click();
            }    
            resetscoller(); 
            scolling("right",true);
          }
          else{
            alert("指定的tab项已存在!");
          }
       }
       function openitemOrAdd(item,allowAdd)
       {
          var checkitem =  getitembyid(item.id);
          if(!checkitem && allowAdd )
          {
            addtabitem(item);
          }
          else{
             var li = $("#tab_li_"+item.id);
             scollingToli(li);
          }
       }
       //移除一个tab 项
       function deleteitembyliid(liid)
       {
          var id= liid.substr(7);
          $("#"+liid).remove();    
          $("#tab_item_"+id).remove();
          var index = getIndexbyId(id);
          if(index>=0)
          {
            var nextcur;
            if(index < dfop.items.length -1)
            {
             nextcur = dfop.items[index+1];
            }
            else if(index>0){             
                nextcur = dfop.items[index-1];
            }
            if(nextcur)
            {
                 $("#tab_li_"+nextcur.id).click();
            }
            dfop.items.splice(index,1);
            resetscoller();           
            scolling("right",true);
          }
       }
       function resize(width,height)
       {
        if(width ==dfop.width && height ==dfop.height)
        {
            return;
        }
         if(width){ dfop.width=width};
         if(height){ dfop.height =height;}
         innerwidth = width-2;
         bodyheight=dfop.height-headerheight;
         me.css("width",dfop.width);
         header.css("width",innerwidth);
         body.css({innerwidth,height:bodyheight});
         for(var i=0,j=dfop.items.length;i<j;i++)
         {
            var item =dfop.items[i];
            $("#tab_item_"+item.id).css({innerwidth});
            $("#tab_item_content_"+item.id).css({innerwidth,height:bodyheight});
         }
         resetscoller();
       }
       //设置选项卡项是否disabled
       function setdisabletabitem(itemId,disabled)
       {
             var chitem= getitembyid(itemId);
             if(!chitem || chitem.disabled ==disabled)
             {
                return;
             }
             if(disabled)
             {
                chitem.disabled =true;
                $("#tab_item_"+item.id).addClass("x-tab-strip-disabled");
             }
             else{
               chitem.disabled =false;
               $("#tab_item_"+item.id).removeClass("x-tab-strip-disabled");
             }
       }
       me[0].tab = {
        addtabitem:addtabitem,
        opentabitem:openitemOrAdd,
        resize:resize,
        setdisabletabitem:setdisabletabitem
       };
    };
    $.fn.addtabitem =function(item)
    {
        if(this[0].tab)
        {
           return this[0].tab.addtabitem(item);
        }
        return false;
    }
    $.fn.opentabitem =function(item,orAdd)
    {
        if(this[0].tab)
        {
           return this[0].tab.opentabitem(item,orAdd);
        }
        return false;
    }
    $.fn.resizetabpanel =function(w,h)
    {
        if(this[0].tab)
        {
           return this[0].tab.resize(w,h);
        }
        return false;
    }
    $.fn.setdisabletabitem =function(itemId,disabled)
    {
        if(this[0].tab)
        {
           return this[0].tab.setdisabletabitem(itemId,disabled);
        }
        return false;
    }
})(jQuery);

接着我们来一步一步来分析我的实现,开始还是编写jQuery控件的“模板”,关于为什么要这么写,请参考这篇的说明

; (function ($) {
    $.fn.tabpanel =function(option){
};
)(jQuery);

接着就是编写默认参数

var dfop ={
               items:[], //选项卡数据项 {id,text,classes,disabled,closeable,content,url,cuscall,onactive}
               500,
               height:400,
               scroll100,//如果存在滚动条,点击按钮次每次滚动的距离
               autoscroll:true //当选项卡宽度大于容器时自动添加滚动按钮
       };

默认参数还是比较简单,我已加上了注释,其中就是item数组的项麻烦些,不过我相信大家通过字面的意思就已经知道大半了,我还是描述一下吧:id 即标示,必须唯一、text显示的文本、classes 特定的样式,如效果中的主页,我加了个图标,就通过此属性实现、disabled 是否禁用、closeable 是否可关闭、

content 和url 和cuscall 三个只要设置其中之一即可,content就是实际的内容html、url标示内容为网页,自动往内容中添加iframe,cuscall则是自定义,即内容显示什么有cuscall执行的结果来决定,可通过此属性来实现异步content内容。

onactive是指当tab项被激活时触发的事件。 是一个接受item内容的函数,详见demo

参数设置完了,通过外部传递的参数来更新默认的参数:

$.extend(dfop, option);    

接着就是构建html的部分,这部分比较长,我就不重复贴代码了。

当我们把html构建完成之后,就要给html元素添加事件,包括 选项卡的点击事件,左移按钮,右移按钮的点击事件,选项卡的鼠标hover效果事件等。

 
 function initevents()
       {
            //reset scoller
            resetscoller(); //设置默认是否出现滚动掉
            scollerclick(); //滚动条的点击事件,如果存在的话
            ulwrap.find("li:not(.x-tab-edge)").each(function(e){
              inititemevents(this); //给每个选项卡 添加事件
            });
       }
       function inititemevents(liitem)
       {
            liswaphover.call(liitem); //选项卡的鼠标hover效果
            liclick.call(liitem); //选项卡的点击事件
            closeitemclick.call(liitem); // 点击关闭按钮的事件
       }

至于事件的实现,其实一个个来做,各个击破也就简单了。主要繁琐在控制滚动按钮的出现和禁用等的处理上,其他点击事件等都比较简单。

最后就是公开方法,和为了公开这些方法来编写一些内部方法,这个tabpanel自然还是比较简单易用,同时扩展性。大家可以根据实际的需求做些调整,当然现在的功能应该也满足大部分的要求了。

最后来看一下公开了哪些方法:

1:动态 新增tab项的方法,即通过js动态新增tab项,这里其实就是对items数据的维护,然后重新调用tabitem的输出html方法,最后单独为其设置事件。简单

2:选中或者新增。这也是通过js调用的方法,是对上一方法的扩展,即可通过js让某个tab项激活,如果该项不存在则通过参数来新增该选项卡

3:重新设置tabpanel的大小,即通过js重新设置tabpanel的大小,这个在窗口大小变化时调用,非常实用哦。

4:设置某项为禁用,通过js方法设置某项tabitem状态为禁用。

附上演示的地址:

http://jscs.cloudapp.net/ControlsSample/TabPanel

最后大家可以通过 代码 包括之前控件的实例,我已经提供了一个压缩包,但是我更推荐大家实用SVN获取最新代码。因为有的时候一些小的变动我就不发文告知了。

http://code.google.com/p/xjplugin/downloads/list

原文地址:https://www.cnblogs.com/xuanye/p/Xuanye_jQuery_TabPanel.html