用jQuery打造TabPanel效果代码

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

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

用jQuery打造TabPanel效果代码

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

用jQuery打造TabPanel效果代码

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

  第一 我们还是从HTML开始吧

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

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

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

用jQuery打造TabPanel效果代码

  通过通过其中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}

  width:500,

  height:400,

  scrollwidth:100,//如果存在滚动条,点击按钮次每次滚动的距离

  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='width:"+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({width: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='width:",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; width:",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({width:innerwidth,height:bodyheight});

  for(var i=0,j=dfop.items.length;i<j;i++)

  {

  var item =dfop.items[i];

  $("#tab_item_"+item.id).css({width:innerwidth});

  $("#tab_item_content_"+item.id).css({width: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}

  width:500,

  height:400,

  scrollwidth:100,//如果存在滚动条,点击按钮次每次滚动的距离

  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状态为禁用。

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

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

  http://xiazai.glzy8.com/201005/yuanma/xjPlugin_addtabpanel.rar