JavaScript插件化开发教程 (四)

  一,开篇分析

  Hi,还记得上一篇文章吗。主要讲述了一个“Tab”插件是如何组织代码以及实现的”,以及过程化设计与面向对象思想设计相结合的方式是

  如何设计一个插件的,两种方式各有利弊取长补短,本系列文章是以学习为导向的,具体场景大家自己定夺使用方式。在从这篇文章中,我们还是以那个“Tab”实例为主,

  继续扩展相关功能。嘿嘿嘿,废话少说,进入正题。直接上实际效果图:

JavaScript插件化开发教程 (四)

  大家看到了吧,增加了一个新的功能,如果我们在初始化时,我们的模块配置信息项目的条目数大于我们指定的,那么就会显示在“更多模块”

  操作项的隐藏列表中,我们的初始化参数配置也从新做了调整比如多了一个“displayMax”指定初始化时的条目数,还有一个项目属性,“status”

  在初始化时也去掉了不需要配置了,在程序中动态生成配置,增加了程序的灵活性,下面就具体分析一下吧。

  (二),实例分析

  (1),首先确定这个插件做什么事。下面看一下插件的调用方式,以及配置参数说明。如下代码:

  

复制代码 代码如下:

  {

  buttonText : "添加模块" ,

  result : [

  {

  text : "向导提示" ,

  url : "help.html" ,

  showClose : "0"

  } ,

  {

  text : "学生信息" ,

  url : "info.html" ,

  showClose : "1"

  } ,

  {

  text : "学生分类" ,

  url : "category.html" ,

  showClose : "1"

  } ,

  {

  text : "大熊君{{bb}}" ,

  url : "bb.html" ,

  showClose : "1"

  } ,

  {

  text : "Beta测试模块" ,

  url : "test.html" ,

  showClose : "1"

  } ,

  {

  text : "三胖子" ,

  url : "help.html" ,

  showClose : "1"

  } ,

  {

  text : "四秃子" ,

  url : "help.html" ,

  showClose : "1"

  }

  ] ,

  displayMax : 5 // 最多显示项目

  }

 

  “bigbear.ui.createTab”里面包含两个参数,第一个是dom节点对象,第二个是插件参数选项,"buttonText "代表“Tab“插件中,操作按钮的文字描述。

  ”result“是一个数组,里面包含的是选项卡项目的属性,包括文字描述,点击选项卡项目时做请求使用的url,”showClose“代表选项卡的选项是否显示关闭按钮。

  “status”在初始化时也去掉了不需要配置了,在程序中动态生成配置。可能会有关闭状态,分别表示为:1-默认显示,0-关闭状态,2-超过默认的条目数。

  (2),功能分步骤介绍

  1---,通过可选参数,初始化插件:

  

复制代码 代码如下:

  $(function(){

  bigbear.ui.createTab($("#tab"),{

  buttonText : "添加模块" ,

  result : [

  {

  text : "向导提示" ,

  url : "help.html" ,

  showClose : "0"

  } ,

  {

  text : "学生信息" ,

  url : "info.html" ,

  showClose : "1"

  } ,

  {

  text : "学生分类" ,

  url : "category.html" ,

  showClose : "1"

  } ,

  {

  text : "大熊君{{bb}}" ,

  url : "bb.html" ,

  showClose : "1"

  } ,

  {

  text : "Beta测试模块" ,

  url : "test.html" ,

  showClose : "1"

  } ,

  {

  text : "三胖子" ,

  url : "help.html" ,

  showClose : "1"

  } ,

  {

  text : "四秃子" ,

  url : "help.html" ,

  showClose : "1"

  }

  ] ,

  displayMax : 5 // 最多显示项目

  }) ;

  }) ;

  2---,渲染并且完成时间绑定以及相关的业务逻辑,比如初始化时条目数量验证。

  

复制代码 代码如下:

  tabProto.init = function(){

  if(this._isEmptyResult()){

  this._setContent("暂无任何模块!") ;

  }

  var that = this ;

  this.getElem().find(".title .adder")

  .text("+" + this.getOpts()["buttonText"])

  .on("click",function(){

  that.getElem().find(".console-panel").slideToggle(function(){

  that._renderConsolePanel("0") ;

  }) ;

  }) ;

  $.each(this.getOpts()["result"],function(i,item){

  if(that._isDisplayMax(i + 1)){

  that._saveOrUpdateStatus(item,"1") ;

  }

  else{

  that._saveOrUpdateStatus(item,"2") ;

  }

  that._render(item) ;

  }) ;

  if(!that._isDisplayMax(this.getOpts()["result"].length)){

  this.getElem().find(".title .more-mod").fadeIn(function(){

  $(this).find(".tag").on("click",function(){

  var root = $(this).next() ;

  root.empty() ;

  $.each(that._getItemListByStatus("2"),function(i,data){

  $("<div></div>").text(data["text"])

  .on("click",function(){

  if(that._getItemListByStatus("1").length < that.getOpts()["displayMax"]){

  that.getElem().find(".title .items div").eq(data["index"]).fadeIn(function(){

  that._saveOrUpdateStatus(data,"1") ;

  }) ;

  }

  else{

  alert("不能添加任何模块,目前已经是最大数量!") ;

  }

  })

  .appendTo(root) ;

  }) ;

  root.toggle() ;

  }) ;

  });

  }

  this.getElem().find(".title .items div")

  .eq(0)

  .trigger("click") ; // 假定是必须有一项,否则插件意义就不大了!

  } ;

  3---,选项卡切换以及数据内容渲染操作。

  

复制代码 代码如下:

  tabProto._setCurrent = function(index){

  var items = this.getElem().find(".title .items div").removeClass("active") ;

  items.eq(index).addClass("active") ;

  var contents = this.getElem().find(".content .c").hide() ;

  contents.eq(index).show() ;

  } ;

  

复制代码 代码如下:

  item.on("click",function(){

  that._setCurrent($(this).index()) ;

  that._getContent(data["url"]).done(function(result){

  that._setContent(result) ;

  })

  .fail(function(){

  throw new Error("Net Error !") ;

  });

  })

  

复制代码 代码如下:

  tabProto._setContent = function(html){

  this.getElem().find(".content").html(html) ;

  } ;

  tabProto._getContent = function(url){

  return $.ajax({

  url : url

  }) ;

  } ;

  4---,核心的辅助数据操作方法,不涉及dom。

  

复制代码 代码如下:

  /* update time 2015 1/26 15:36 */

  tabProto._isDisplayMax = function(size){

  var displayMax = this.getOpts()["displayMax"] || 5 ;

  return (size <= displayMax) ? true : false ;

  } ;

  tabProto._isEmptyResult = function(){

  if(!this.getOpts()["result"].length){

  return false ;

  }

  return true ;

  } ;

  tabProto._saveOrUpdateStatus = function(item,status){

  item["status"] = status ;

  } ;

  tabProto._getItemListByStatus = function(status){

  var list = [] ;

  var result = this.getOpts()["result"] ;

  $.each(result,function(i,item){

  if(status == item["status"]){

  list.push(item) ;

  }

  }) ;

  return list ;

  } ;

  tabProto._getStatusByIndex = function(index){

  var status = null ;

  var result = this.getOpts()["result"] ;

  $.each(result,function(i,item){

  if(index == item["index"]){

  status = item["status"] ;

  }

  }) ;

  return status ;

  } ;

  (三),完整代码以供学习,本代码已经过测试,包括目录结构以及相关的文件。

   1,html

  

复制代码 代码如下:

  <body>

  <div class="dxj-ui-hd">

  大熊君{{bb}} - DXJ UI ------ Tab

  </div>

  <div class="dxj-ui-bd">

  <div id="tab">

  <div class="title">

  <div class="adder">

  + 添加学生信息

  </div>

  <div class="items">

  <!--<div><span class="del">X</span>欢迎页</div>

  <div><span class="del">X</span>用户管理</div>

  <div><span class="del">X</span>Bigbear</div>-->

  </div>

  <div class="more-mod">

  <div class="tag">更多模块</div>

  <div class="mods">

  </div>

  </div>

  </div>

  <div class="console-panel">

  </div>

  <div class="content">

  <!--<div class="c">

  <div class="input-content"><span>姓名:</span><input type="text" /></div>

  <div class="input-content"><span>备注:</span><textarea></textarea></div>

  </div>    <div class="input-content"><input type="button" value="保存" /></div>

  -->

  </div>

  </div>

  </div>

  </body>

  2,css

  

复制代码 代码如下:

  .dxj-ui-hd {

  padding:0px ;

  margin : 0 auto;

  margin-top:30px;

  width:780px;

  height:60px;

  line-height: 60px;

  background: #3385ff;

  color:#fff;

  font-family: "微软雅黑" ;

  font-size: 28px;

  text-align: center;

  font-weight:bold;

  }

  .dxj-ui-bd {

  padding:0px ;

  margin : 0 auto;

  width:778px;

  padding-top : 30px ;

  padding-bottom : 30px ;

  overflow: hidden;

  border:1px solid #3385ff;

  }

  .dxj-ui-bd #tab {

  padding:0px ;

  margin : 0 auto;

  width:720px;

  overflow: hidden;

  position:relative;

  }

  .dxj-ui-bd #tab .title {

  width:720px;

  overflow: hidden;

  border-bottom:2px solid #3385ff;

  }

  .dxj-ui-bd #tab .title .adder {

  width:160px;

  height:32px;

  line-height: 32px;

  background: #DC143C;

  color:#fff;

  font-family: "微软雅黑" ;

  font-size: 14px;

  text-align: center;

  font-weight:bold;

  float : left;

  cursor:pointer;

  }

  .dxj-ui-bd #tab .title .more-mod {

  overflow:hidden;

  border:1px solid #DC143C;

  width:70px;

  position:absolute;

  right:0;

  margin-right:6px;

  display:none;

  }

  .dxj-ui-bd #tab .title .more-mod .tag{

  height:32px;

  line-height:32px;

  width:70px;

  background: #DC143C;

  color:#fff;

  font-family: arial ;

  font-size: 12px;

  text-align: center;

  cursor:pointer;

  }

  .dxj-ui-bd #tab .title .more-mod .mods {

  overflow:hidden;

  width:70px;

  display:none;

  }

  .dxj-ui-bd #tab .title .more-mod .mods div {

  height:24px;

  line-height:24px;

  width:62px;

  font-family: arial ;

  font-size: 12px;

  cursor:pointer;

  padding-left:10px;

  }

  .dxj-ui-bd #tab .title .items {

  height:32px;

  width:480px;

  overflow: hidden;

  float : left;

  }

  .dxj-ui-bd #tab .title .items div {

  padding:0px;

  margin-left:10px;

  width:84px;

  height:32px;

  line-height: 32px;

  background: #3385ff;

  color:#fff;

  font-family: arial ;

  font-size: 12px;

  text-align: center;

  position:relative;

  float : left;

  cursor:pointer;

  }

  .dxj-ui-bd #tab .title .items div span.del {

  width:16px;

  height:16px;

  line-height: 16px;

  display:block;

  background: #DC143C;

  position:absolute;

  right:0 ;

  top:0;

  cursor:pointer;

  }

  .dxj-ui-bd #tab .content {

  width:716px;

  padding-top:30px;

  overflow: hidden;

  border:2px solid #3385ff;

  border-top:0px;

  min-height:130px;

  text-align:center;

  }

  .dxj-ui-bd #tab .content table {

  margin : 0 auto ;

  }

  .dxj-ui-bd #tab .content div.c {

  padding-top : 20px ;

  padding-left:20px;

  background:#eee;

  height:140px;

  }

  .dxj-ui-bd #tab .content div.c .input-content {

  margin-top : 10px ;

  font-family: arial ;

  font-size: 12px;

  }

  .dxj-ui-bd #tab .console-panel {

  width:716px;

  padding-top:20px;

  padding-bottom:20px;

  overflow: hidden;

  border:2px solid #3385ff;

  border-top:0px;

  border-bottom:2px solid #3385ff;

  background:#fff;

  display:none;

  }

  .active {

  font-weight:bold ;

  }

  3,bigbear.js

JavaScript插件化开发教程 (四)

  

复制代码 代码如下:

  (function($){

  var win = window ;

  var bb = win.bigbear = win.bigbear || {

  ui : {}

  } ;

  var ui = bb.ui = {} ;

  var Tab = function(elem,opts){

  this.elem = elem ;

  this.opts = opts ;

  } ;

  var tabProto = Tab.prototype ;

  /* update time 2015 1/26 15:36 */

  tabProto._isDisplayMax = function(size){

  var displayMax = this.getOpts()["displayMax"] || 5 ;

  return (size <= displayMax) ? true : false ;

  } ;

  tabProto._isEmptyResult = function(){

  if(!this.getOpts()["result"].length){

  return false ;

  }

  return true ;

  } ;

  tabProto._saveOrUpdateStatus = function(item,status){

  item["status"] = status ;

  } ;

  tabProto._getItemListByStatus = function(status){

  var list = [] ;

  var result = this.getOpts()["result"] ;

  $.each(result,function(i,item){

  if(status == item["status"]){

  list.push(item) ;

  }

  }) ;

  return list ;

  } ;

  tabProto._getStatusByIndex = function(index){

  var status = null ;

  var result = this.getOpts()["result"] ;

  $.each(result,function(i,item){

  if(index == item["index"]){

  status = item["status"] ;

  }

  }) ;

  return status ;

  } ;

  tabProto._renderConsolePanel = function(status){

  var that = this ;

  var root = that.getElem().find(".console-panel") ;

  this._resetConsolePanel() ;

  $.each(that._getItemListByStatus(status),function(i,item){

  var elem = $("<div style='float:left';></div>").appendTo(root) ;

  $("<input type='radio' name='addmod' />")

  .data("item",item)

  .appendTo(elem) ;

  $("<span></span>").text(item["text"]).appendTo(elem) ;

  }) ;

  if(root.find("div").size()){

  $("<input type='button' value='添加模块' style='margin-left:20px'/>")

  .on("click",function(){

  var data = root.find("input[type=radio]:checked").data("item") ;

  if(that._getItemListByStatus("1").length < that.getOpts()["displayMax"]){

  that.getElem().find(".title .items div").eq(data["index"]).fadeIn(function(){

  that._saveOrUpdateStatus(data,"1") ;

  })

  .trigger("click") ;

  }

  else{

  that._saveOrUpdateStatus(data,"2") ;

  }

  that.getElem().find(".title .adder").trigger("click") ;

  })

  .appendTo(root) ;

  }

  else{

  root.text("暂无任何可添加的项目!") ;

  }

  } ;

  /* update time 2015 1/26 15:36 */

  tabProto._setCurrent = function(index){

  var items = this.getElem().find(".title .items div").removeClass("active") ;

  items.eq(index).addClass("active") ;

  var contents = this.getElem().find(".content .c").hide() ;

  contents.eq(index).show() ;

  } ;

  tabProto.getElem = function(){

  return this.elem ;

  } ;

  tabProto.getOpts = function(){

  return this.opts ;

  } ;

  tabProto._resetContent = function(){

  this.getElem().find(".content").html("") ;

  } ;

  tabProto._setContent = function(html){

  this.getElem().find(".content").html(html) ;

  } ;

  tabProto._getContent = function(url){

  return $.ajax({

  url : url

  }) ;

  } ;

  tabProto._deleteItem = function(elem){

  var that = this ;

  this.getElem().find(".title .items div")

  .eq(elem.index())

  .fadeOut(function(){

  that._resetContent() ;

  that._saveOrUpdateStatus(elem.data("item"),"0") ;

  that._triggerItem(elem.index() + 1) ;

  }) ;

  } ;

  tabProto._triggerItem = function(next){

  var nextStatus = this._getStatusByIndex(next) ;

  var items = this.getElem().find(".title .items div") ;

  next = items.eq(next) ;

  if(next.size() && "1" == nextStatus){ //后继dom节点存在

  next.trigger("click") ;

  }

  else{

  items.eq(0).trigger("click") ;

  }

  } ;

  tabProto._resetConsolePanel = function(){

  this.getElem().find(".console-panel").empty() ;

  } ;

  tabProto.init = function(){

  if(this._isEmptyResult()){

  this._setContent("暂无任何模块!") ;

  }

  var that = this ;

  this.getElem().find(".title .adder")

  .text("+" + this.getOpts()["buttonText"])

  .on("click",function(){

  that.getElem().find(".console-panel").slideToggle(function(){

  that._renderConsolePanel("0") ;

  }) ;

  }) ;

  $.each(this.getOpts()["result"],function(i,item){

  if(that._isDisplayMax(i + 1)){

  that._saveOrUpdateStatus(item,"1") ;

  }

  else{

  that._saveOrUpdateStatus(item,"2") ;

  }

  that._render(item) ;

  }) ;

  if(!that._isDisplayMax(this.getOpts()["result"].length)){

  this.getElem().find(".title .more-mod").fadeIn(function(){

  $(this).find(".tag").on("click",function(){

  var root = $(this).next() ;

  root.empty() ;

  $.each(that._getItemListByStatus("2"),function(i,data){

  $("<div></div>").text(data["text"])

  .on("click",function(){

  if(that._getItemListByStatus("1").length < that.getOpts()["displayMax"]){

  that.getElem().find(".title .items div").eq(data["index"]).fadeIn(function(){

  that._saveOrUpdateStatus(data,"1") ;

  }) ;

  }

  else{

  alert("不能添加任何模块,目前已经是最大数量!") ;

  }

  })

  .appendTo(root) ;

  }) ;

  root.toggle() ;

  }) ;

  });

  }

  this.getElem().find(".title .items div")

  .eq(0)

  .trigger("click") ; // 假定是必须有一项,否则插件意义就不大了!

  } ;

  tabProto._render = function(data){

  var that = this ;

  var item = $("<div></div>").text(data["text"]).appendTo(this.getElem().find(".title .items")) ;

  data["index"] = item.index() ;

  item.on("click",function(){

  that._setCurrent($(this).index()) ;

  that._getContent(data["url"]).done(function(result){

  that._setContent(result) ;

  })

  .fail(function(){

  throw new Error("Net Error !") ;

  });

  })

  .data("item",data) ;

  if("2" == data["status"]){

  item.hide() ;

  }

  if("1" == data["showClose"]){

  $("<span class='del'>X</span>")

  .on("click",function(){

  if(win.confirm("是否删除此项?")){

  that._deleteItem(item) ;

  return false ; // 阻止冒泡

  }

  })

  .appendTo(item) ;

  }

  } ;

  ui.createTab = function(elem,opts){

  var tab = new Tab(elem,opts) ;

  tab.init() ;

  return tab ;

  } ;

  })(jQuery) ;

  

  (四),最后总结

  (1),面向对象的思考方式合理分析功能需求。

  (2),以类的方式来组织我们的插件逻辑。

  (3),不断重构上面的实例,如何进行合理的重构那?不要设计过度,要游刃有余,推荐的方式是过程化设计与面向对象思想设计相结合。