jQuery EasyUI框架中的Datagrid数据表格组件结构详解


Posted in Javascript onJune 09, 2016

基础DOM结构
什么叫“完整的基础DOM结构”,这里“基础”的意思是指这个结构不依赖具体数据,不依赖Datagrid的view属性,只要存在Datagrid实例就会存在这样的基础DOM结构;而“完整”的意思是指在冻结列,冻结行,标题,footer,分页这些功能块都存在时候的DOM结构。

要搞清楚Datagrid的工作原理,这个DOM结构必须要烂熟于胸的,我们直接来看这个“基础完整DOM结构”是什么样子的:

<!-- datagrid的最外层容器,可以使用$(target).datagrid('getPanel')或者$.data(target,'datagrid').panel得到这个DOM对象,这个DOM上其实承载了panel组件-->   
<div class="panel datagrid">   
  <!-- datagrid的标题区域容器,对应于panel组件的header部分,可以使用$(target).datagrid('getPanel').panel('header')得到这个DOM对象-->   
  <div class="panel-header">   
    <div class="panel-title"></div>   
    <div class="panel-tool"></div>   
  </div>   
  <!-- datagrid的主体区域容器,对应于panel组件的body部分,可以使用$(target).datagrid('getPanel').panel('body')得到这个DOM对象-->   
  <div class="datagrid-wrap panel-body"> 
    <!--工具栏--> 
    <div class="datagrid-toolbar"></div>   
    <!-- datagrid视图部分的容器,这是datagrid组件DOM结构的核心,其基础视图结构跟datagrid的view属性无任何关系。-->   
    <!-- 对应dc.view -->   
    <div class="datagrid-view">   
      <!-- div.datagrid-view1负责展示冻结列部分(包含行号或者frozenColumns)的数据-->   
      <!-- 对应dc.view1 -->   
      <div class="datagrid-view1">   
        <!--列标题部分-->   
        <div class="datagrid-header">   
          <!-- 对应dc.header1 -->   
          <div class="datagrid-header-inner">   
            <!--样式里有htable关键字,h代表header的意思-->   
            <table class="datagrid-htable">   
              <tbody>   
                <tr class="datagrid-header-row"></tr>   
              </tbody>   
            </table>   
          </div>   
        </div>   
        <!--列数据部分-->   
        <div class="datagrid-body">   
          <!-- 对应dc.body1 -->   
          <div class="datagrid-body-inner">   
            <!--frozenRows部分(有数据才会有这个table,故不属于基础DOM结构),固定行是1.3.2版本之后才加的功能,注意datagrid-btable-frozen关键样式,btable代码body table的意思-->   
            <table class="datagrid-btable datagrid-btable-frozen"></table>   
            <!--普通rows部分(有数据才会有这个table,故不属于基础DOM结构)-->   
            <table class="datagird-btable"></table>   
          </div>   
        </div>   
        <!--footer部分-->   
        <div class="datagrid-footer">   
          <!-- 对应dc.footer1 -->   
          <div class="datagrid-footer-inner">   
            <!--ftable代表footer table的意思-->   
            <table class="datagrid-ftable"></table>   
          </div>   
        </div>   
      </div>   
      <!-- div.datagrid-view2负责展示非冻结列部分的数据,大家注意到冻结列和普通列视图是分开的,也就是说冻结列和普通列是在不同表格中展示的,这样会产生一个问题,那就是两个表格行高之间的同步问题。-->   
      <!-- 对应dc.view2 -->   
      <div class="datagrid-view2">   
        <!--列标题部分-->   
        <div class="datagrid-header">   
          <!-- 对应dc.header2 -->   
          <div class="datagrid-header-inner">   
            <table class="datagrid-htable">   
              <tbody>   
                <tr class="datagrid-header-row"></tr>   
              </tbody>   
            </table>   
          </div>   
        </div>   
        <!--列数据部分,注意这里并无datagrid-body-inner这个子元素,而冻结列对应的body却是有的,这个是细微区别-->   
        <!-- 对应dc.body2 -->   
        <div class="datagrid-body">   
          <!--frozenRows部分有数据才会有这个table,故不属于基础DOM结构,固定行是1.3.2版本之后才加的功能,-->   
          <table class="datagrid-btable datagrid-btable-frozen"></table>   
          <table class="datagrid-btable"></table>   
        </div>   
        <!--footer部分-->   
        <div class="datagrid-footer">   
          <!-- 对应dc.footer2 -->   
          <div class="datagrid-footer-inner">   
            <table class="datagrid-ftable"></table>   
          </div>   
        </div>   
      </div>   
    </div>   
    <!--分页部分-->   
    <div class="datagrid-pager pagination"></div>   
  </div>   
</div>

对于这个DOM结构,我在html代码里面已经做了简单说明,这里提一下绑定于Datagrid宿主table上的对象的dc属性,这个dc属性存储了对DOM结构里不同部分的引用,获取dc属性的方法:

$.data(target,'datagrid').dc;

而dc属性跟DOM的对应关系,我也在html中做了详细注释,请大家自行查看,这些都是我们深入认识Datagrid组件的基础。

默认视图分析
上面对Datagrid组件的骨架做了很详细的描述。有了骨架还并不完整,还得有血有肉有衣服穿才行。强大的Datagrid组件允许我们自己定义如何在基础骨架上长出健壮诱人的身体,我们只要定义Datagrid的视图就可以实现。

在大多数情况下,我们并无特别要求,Datagrid给我们提供了默认的视图,默认视图被使用在90%以上的场景,所以对默认视图的分析显得非常有必要。注意视图里面定义了哪些接口,哪些方法,如果要自己写视图的话,最好把这些接口和方法都写齐全。

var view = {  
  /** 
   * 填充表格主体数据(生成数据部分的各行tr) 
   * @param {DOM object} target  datagrid宿主table对应的DOM对象 
   * @param {DOM object} container 数据主体容器。包含两个可能的值,即: 
   * 1.frozen部分body1,对应的DOM对象为:div.datagrid-view>div.datagrid-view1>div.datagrid-body>div.datagrid-body-inner 
   * 2.常规部分body2,对应的DOM对象为:div.datagrid-view>div.datagrid-view2>div.datagrid-body 
   * @param {boolean} frozen  是否是冻结列 
   * @return {undefined}      未返回值 
   */ 
  render: function(target, container, frozen) {  
    var data = $.data(target, "datagrid");  
    var opts = data.options;  
    var rows = data.data.rows;  
    var fields = $(target).datagrid("getColumnFields", frozen);  
    if(frozen) {  
      //如果grid不显示rownumbers并且也没有frozenColumns的话,直接退出。  
      if(!(opts.rownumbers || (opts.frozenColumns && opts.frozenColumns.length))) {  
        return;  
      }  
    }  
    //定义表格字符串,注意这里使用了数组的join方式代替了传统的"+"运算符,在大多浏览器中,这样效率会更高些。  
    var html = ["<table class=\"datagrid-btable\" cellspacing=\"0\" cellpadding=\"0\" border=\"0\"><tbody>"];  
    for(var i = 0; i < rows.length; i++) {  
      //striped属性,用于设置grid数据是否隔行变色,当然了实现原理很简单。  
      var cls = (i % 2 && opts.striped) ? "class=\"datagrid-row datagrid-row-alt\"" : "class=\"datagrid-row\"";  
      /** 
       * 表格的rowStyler属性用于处理数据行的css样式,当然了这个样式仅仅是作用于tr标签上。 
       * 这地方使用call了方法来设置上下文,如果rowStyler函数内部使用了this的话,则this指向datagrid的宿主table对应的DOM对象。 
       */ 
      var style = opts.rowStyler ? opts.rowStyler.call(target, i, rows[i]) : "";  
      var styler = style ? "style=\"" + style + "\"" : "";  
      /** 
       * rowId:行的唯一标示,对应于tr的id属性,其由以下几部分组成: 
       * 1.字符窜常量:"datagrid-row-r"; 
       * 2.全局索引index:该索引值从1开始递增,同一个datagrid组件实例拥有唯一值,如果同一页面内有多个datagrid实例,那么其值从1递增分配给每个datagrid实例; 
       * 3.冻结列标识frozen:该标识用于标示是否是冻结列(包含行号和用户指定的frozenColumns),"1"代表冻结列,"2"代表非冻结列; 
       * 4.行数索引:该值才是真正代表“第几行”的意思,该值从0开始递增 
       * 如页面内第一个datagrid实例的非冻结列第10行数据的rowId为"datagrid-row-r1-2-9" 
       */ 
      var rowId = data.rowIdPrefix + "-" + (frozen ? 1 : 2) + "-" + i;  
      html.push("<tr id=\"" + rowId + "\" datagrid-row-index=\"" + i + "\" " + cls + " " + styler + ">");  
      /** 
       * 调用renderRow方法,生成行数据(行内的各列数据)。 
       * 这里的this就是opts.view,之所以用call方法,只是为了传参进去。这里我们使用this.renderRow(target,fields,frozen,i,rows[i])来调用renderRow方法应该也是可以的。 
       */ 
      html.push(this.renderRow.call(this, target, fields, frozen, i, rows[i]));  
      html.push("</tr>");  
    }  
    html.push("</tbody></table>");  
    //用join方法完成字符创拼接后直接innerHTML到容器内。  
    $(container).html(html.join(""));  
  },  
  /** 
   * [renderFooter  description] 
   * @param {DOM object} target  datagrid宿主table对应的DOM对象 
   * @param {DOM object} container 可能为dc.footer1或者dc.footer2 
   * @param {boolean} frozen  是否为frozen区 
   * @return {undefined}      未返回值 
   */ 
  renderFooter: function(target, container, frozen) {  
    var opts = $.data(target, "datagrid").options;  
    //获取footer数据  
    var rows = $.data(target, "datagrid").footer || [];  
    var columnsFields = $(target).datagrid("getColumnFields", frozen);  
    //生成footer区的table  
    var footerTable = ["<table class=\"datagrid-ftable\" cellspacing=\"0\" cellpadding=\"0\" border=\"0\"><tbody>"];  
    for(var i = 0; i < rows.length; i++) {  
      footerTable.push("<tr class=\"datagrid-row\" datagrid-row-index=\"" + i + "\">");  
      footerTable.push(this.renderRow.call(this, target, columnsFields, frozen, i, rows[i]));  
      footerTable.push("</tr>");  
    }  
    footerTable.push("</tbody></table>");  
    $(container).html(footerTable.join(""));  
  },  
  /** 
   * 生成某一行数据 
   * @param {DOM object} target  datagrid宿主table对应的DOM对象 
   * @param {array} fields  datagrid的字段列表 
   * @param {boolean} frozen  是否为冻结列 
   * @param {number} rowIndex 行索引(从0开始) 
   * @param {json object} rowData 某一行的数据 
   * @return {string}     单元格的拼接字符串 
   */ 
  renderRow: function(target, fields, frozen, rowIndex, rowData) {  
    var opts = $.data(target, "datagrid").options;  
    //用于拼接字符串的数组  
    var cc = [];  
    if(frozen && opts.rownumbers) {  
      //rowIndex从0开始,而行号显示的时候是从1开始,所以这里要加1.  
      var rowNumber = rowIndex + 1;  
      //如果分页的话,根据页码和每页记录数重新设置行号  
      if(opts.pagination) {  
        rowNumber += (opts.pageNumber - 1) * opts.pageSize;  
      }  
      /** 
       * 先拼接行号列 
       * 注意DOM特征,用zenCoding可表达为"td.datagrid-td-rownumber>div.datagrid-cell-rownumber" 
       */ 
      cc.push("<td class=\"datagrid-td-rownumber\"><div class=\"datagrid-cell-rownumber\">" + rowNumber + "</div></td>");  
    }  
    for(var i = 0; i < fields.length; i++) {  
      var field = fields[i];  
      var col = $(target).datagrid("getColumnOption", field);  
      if(col) {  
        var value = rowData[field];  
        //获取用户定义的单元格样式,入参包括:单元格值,当前行数据,当前行索引(从0开始)  
        var style = col.styler ? (col.styler(value, rowData, rowIndex) || "") : "";  
        //如果是隐藏列直接设置display为none,否则设置为用户想要的样式  
        var styler = col.hidden ? "style=\"display:none;" + style + "\"" : (style ? "style=\"" + style + "\"" : "");  
        cc.push("<td field=\"" + field + "\" " + styler + ">");  
        //如果当前列是datagrid组件保留的ck列时,则忽略掉用户定义的样式,即styler属性对datagrid自带的ck列是不起作用的。  
        if(col.checkbox) {  
          var styler = "";  
        } else {  
          var styler = "";  
          //设置文字对齐属性  
          if(col.align) {  
            styler += "text-align:" + col.align + ";";  
          }  
          //设置文字超出td宽时是否自动换行(设置为自动换行的话会撑高单元格)  
          if(!opts.nowrap) {  
            styler += "white-space:normal;height:auto;";  
          } else {  
            /** 
             * 并不是nowrap属性为true单元格就肯定不会被撑高,这还得看autoRowHeight属性的脸色 
             * 当autoRowHeight属性为true的时候单元格的高度是根据单元格内容而定的,这种情况主要是用于表格里展示图片等媒体。 
             */ 
            if(opts.autoRowHeight) {  
              styler += "height:auto;";  
            }  
          }  
        }  
        //这个地方要特别注意,前面所拼接的styler属性并不是作用于td标签上,而是作用于td下的div标签上。  
        cc.push("<div style=\"" + styler + "\" ");  
        //如果是ck列,增加"datagrid-cell-check"样式类  
        if(col.checkbox) {  
          cc.push("class=\"datagrid-cell-check ");  
        }  
        //如果是普通列,增加"datagrid-cell-check"样式类  
        else {  
          cc.push("class=\"datagrid-cell " + col.cellClass);  
        }  
        cc.push("\">");  
        /** 
         * ck列光设置class是不够的,当突然还得append一个input进去才是真正的checkbox。此处未设置input的id,只设置了name属性。 
         * 我们注意到formatter属性对datagird自带的ck列同样不起作用。 
         */ 
        if(col.checkbox) {  
          cc.push("<input type=\"checkbox\" name=\"" + field + "\" value=\"" + (value != undefined ? value : "") + "\"/>");  
        }  
        //普通列  
        else {  
          /** 
           * 如果单元格有formatter,则将formatter后生成的DOM放到td>div里面 
           * 换句话说,td>div就是如来佛祖的五指山,而formatter只是孙猴子而已,猴子再怎么变化翻跟头,始终在佛祖手里。 
           */ 
          if(col.formatter) {  
            cc.push(col.formatter(value, rowData, rowIndex));  
          }  
          //操,这是最简单的简况了,将值直接放到td>div里面。  
          else {  
            cc.push(value);  
          }  
        }  
        cc.push("</div>");  
        cc.push("</td>");  
      }  
    }  
    //返回单元格字符串,注意这个函数内部并未把字符串放到文档流中。  
    return cc.join("");  
  },  
  /** 
   * 刷新行数据,只有一个行索引(从0开始),调用的updateRow方法,这里直接跳过。 
   * @param {DOM object} target  datagrid实例的宿主table对应的DOM对象 
   * @param {number} rowIndex 行索引(从0开始) 
   * @return {undefined}     未返回数据 
   */ 
  refreshRow: function(target, rowIndex) {  
    this.updateRow.call(this, target, rowIndex, {});  
  },  
  /** 
   * 刷新行数据,该接口方法肩负着同步行高,重新计算和布局grid面板等重任 
   * @param {DOM object} target  datagrid实例的宿主table对应的DOM对象 
   * @param {number} rowIndex 行索引(从0开始) 
   * @param {json object} 行数据 
   * @return {undefined}     未返回数据 
   */ 
  updateRow: function(target, rowIndex, row) {  
    var opts = $.data(target, "datagrid").options;  
    var rows = $(target).datagrid("getRows");  
    $.extend(rows[rowIndex], row);  
    var style = opts.rowStyler ? opts.rowStyler.call(target, rowIndex, rows[rowIndex]) : "";  
 
    function updateTableRow(frozen) {  
      var fields = $(target).datagrid("getColumnFields", frozen);  
      //这个地方查找grid的数据主体表格(可能包含冻结列对应的主体表格和普通列对应的主体表格)  
      //getTr这个函数,我在博客上介绍过,请参考:http://www.easyui.info/archives/396.html  
      var tr = opts.finder.getTr(target, rowIndex, "body", (frozen ? 1 : 2));  
      var checked = tr.find("div.datagrid-cell-check input[type=checkbox]").is(":checked");  
      //这里调用了renderRow方法来重新获取当前行的html字符串  
      tr.html(this.renderRow.call(this, target, fields, frozen, rowIndex, rows[rowIndex]));  
      tr.attr("style", style || "");  
      //更新的时候保留checkbox状态(包含两层信息:一是有ck列;二是ck列被之前就被选中)  
      if(checked) {  
        tr.find("div.datagrid-cell-check input[type=checkbox]")._propAttr("checked", true);  
      }  
    };  
    //更新冻结列对应的行  
    updateTableRow.call(this, true);  
    //更新普通列对应的行  
    updateTableRow.call(this, false);  
    //重新布局表格面板  
    $(target).datagrid("fixRowHeight", rowIndex);  
  },  
  insertRow: function(target, rowIndex, row) {  
    var state = $.data(target, "datagrid");  
    //options  
    var opts = state.options;  
    //document of datagrid  
    var dc = state.dc;  
    var data = state.data;  
    //兼容无效的rowIndex,默认设置为在最后一行追加  
    if(rowIndex == undefined || rowIndex == null) {  
      rowIndex = data.rows.length;  
    }  
    //为啥不跟上面的条件并到一起,真是蛋疼  
    if(rowIndex > data.rows.length) {  
      rowIndex = data.rows.length;  
    }  
    /** 
     * 下移rows 
     * @param {boolean} frozen 是否为frozen部分 
     * @return {undefined}    无返回值 
     */ 
    function moveDownRows(frozen) {  
      //1:冻结列部分;2:普通列部分  
      var whichBody = frozen ? 1 : 2;  
      for(var i = data.rows.length - 1; i >= rowIndex; i--) {  
        var tr = opts.finder.getTr(target, i, "body", whichBody);  
        //注意这地方设置了tr的"datagrid-row-index"和"id"属性  
        tr.attr("datagrid-row-index", i + 1);  
        tr.attr("id", state.rowIdPrefix + "-" + whichBody + "-" + (i + 1));  
        //计算行号  
        if(frozen && opts.rownumbers) {  
          //因rowIndex从0开始,以及须插入位置以下的tr要统一下移,所以新行号为i+2  
          var rownumber = i + 2;  
          //有分页的话,行号还要加上分页数据  
          if(opts.pagination) {  
            rownumber += (opts.pageNumber - 1) * opts.pageSize;  
          }  
          tr.find("div.datagrid-cell-rownumber").html(rownumber);  
        }  
      }  
    };  
    /** 
     * 插入了,要插两个地方的哦(如果你是男人,你可以淫荡地笑一下) 
     * @param {boolean} frozen 是否是frozen部分 
     * @return {undefined}    未返回值 
     */ 
    function doInsert(frozen) {  
      var whichBody = frozen ? 1 : 2;  
      //这行代码,不知道是干嘛的,怕插入得太快而早早缴械,所以才故意拖延时间的么?  
      var columnFields = $(target).datagrid("getColumnFields", frozen);  
      //构造新插入行的id属性  
      var trId = state.rowIdPrefix + "-" + whichBody + "-" + rowIndex;  
      var tr = "<tr id=\"" + trId + "\" class=\"datagrid-row\" datagrid-row-index=\"" + rowIndex + "\"></tr>";  
      if(rowIndex >= data.rows.length) {  
        //如果已经有记录,则插入tr即可  
        if(data.rows.length) {  
          //嗯哼,getTr的这个用法不多哦,未传入行索引,第三个参数为"last",随便的意淫一下就知道是获取最后一行了  
          //然后再在最后一行后插入一行,注意了,这里用的后入式  
          opts.finder.getTr(target, "", "last", whichBody).after(tr);  
        }  
        //如果表格尚无记录,则要生成表格,同时插入tr  
        else {  
          var cc = frozen ? dc.body1 : dc.body2;  
          cc.html("<table cellspacing=\"0\" cellpadding=\"0\" border=\"0\"><tbody>" + tr + "</tbody></table>");  
        }  
      }  
      //在rowIndex + 1前准确无误地插入,注意了,这里是前入式。  
      else {  
        opts.finder.getTr(target, rowIndex + 1, "body", whichBody).before(tr);  
      }  
    };  
    //下移frozen部分  
    moveDownRows.call(this, true);  
    //下移普通列部分  
    moveDownRows.call(this, false);  
    //插入frozen区  
    doInsert.call(this, true);  
    //插入普通区  
    doInsert.call(this, false);  
    //总数加1  
    data.total += 1;  
    //维护data.rows数组,这地方是插入一个数组元素了  
    data.rows.splice(rowIndex, 0, row);  
    //刷新,其中包含了重新布局grid面板等复杂得一笔的操作  
    //插入本是件很简单愉快的事情,可是你得为其后果负上沉重的代价  
    this.refreshRow.call(this, target, rowIndex);  
  },  
  /** 
   * 删除行接口 
   * @param {DOM object} target datagrid实例的宿主table对应的DOM对象 
   * @param {number} rowIndex 行索引 
   * @return {undefined}    未返回值 
   */ 
  deleteRow: function(target, rowIndex) {  
    var state = $.data(target, "datagrid");  
    var opts = state.options;  
    var data = state.data;  
 
    function moveUpRows(frozen) {  
      var whichBody = frozen ? 1 : 2;  
      for(var i = rowIndex + 1; i < data.rows.length; i++) {  
        var tr = opts.finder.getTr(target, i, "body", whichBody);  
        //"datagrid-row-index"和"id"属性减一  
        tr.attr("datagrid-row-index", i - 1);  
        tr.attr("id", state.rowIdPrefix + "-" + whichBody + "-" + (i - 1));  
        if(frozen && opts.rownumbers) {  
          var rownumber = i;  
          if(opts.pagination) {  
            rownumber += (opts.pageNumber - 1) * opts.pageSize;  
          }  
          tr.find("div.datagrid-cell-rownumber").html(rownumber);  
        }  
      }  
    };  
    //移除行  
    opts.finder.getTr(target, rowIndex).remove();  
    //上移frozen区  
    moveUpRows.call(this, true);  
    //上移普通区  
    moveUpRows.call(this, false);  
    //记录数减一  
    data.total -= 1;  
    //维护data.rows数据  
    data.rows.splice(rowIndex, 1);  
  },  
  /** 
   * 默认的onBeforeRender事件 为空 
   * @param {DOM object} target datagrid实例的宿主table对应的DOM对象 
   * @param {array} rows 要插入的数据 
   * @return {undefined}    默认未返回值 
   */ 
  onBeforeRender: function(target, rows) {},  
  /** 
   * 默认的onAfterRender 隐藏footer里的行号和check 
   * @param {DOM object} target datagrid实例的宿主table对应的DOM对象 
   * @return {undefined}    未返回值 
   */ 
  onAfterRender: function(target) {  
    var opts = $.data(target, "datagrid").options;  
    if(opts.showFooter) {  
      var footer = $(target).datagrid("getPanel").find("div.datagrid-footer");  
      footer.find("div.datagrid-cell-rownumber,div.datagrid-cell-check").css("visibility", "hidden");  
    }  
  }  
};
Javascript 相关文章推荐
新浪的图片新闻效果
Jan 13 Javascript
dojo学习第一天 Tab选项卡 实现
Aug 28 Javascript
JavaScript模块化开发之SeaJS
Dec 13 Javascript
jQuery+PHP实现微信转盘抽奖功能的方法
May 25 Javascript
AngularJS基础 ng-submit 指令简单示例
Aug 03 Javascript
ES6新特性八:async函数用法实例详解
Apr 21 Javascript
完美解决UI-Grid表格元素中多个空格显示为一个空格的问题
Apr 25 Javascript
JS判断非空至少输入两个字符的简单实现方法
Jun 23 Javascript
JavaScript原生实现观察者模式的示例
Dec 15 Javascript
JS设计模式之观察者模式实现实时改变页面中金额数的方法
Feb 05 Javascript
Layui实现带查询条件的分页
Jul 27 Javascript
JSON stringify方法原理及实例解析
Oct 23 Javascript
Active控件问题小结(附解决办法)
Jun 09 #Javascript
JS 通过系统时间限定动态添加 select option的实例代码
Jun 09 #Javascript
JavaScript的instanceof运算符学习教程
Jun 08 #Javascript
JavaScript中instanceof运算符的使用示例
Jun 08 #Javascript
实例讲解JavaScript中instanceof运算符的用法
Jun 08 #Javascript
js获取对象、数组的实际长度,元素实际个数的实现代码
Jun 08 #Javascript
JS &amp; JQuery 动态添加 select option
Jun 08 #Javascript
You might like
高亮度显示php源代码
2006/10/09 PHP
dedecms模版制作使用方法
2007/04/03 PHP
PHP之认识(二)关于Traits的用法详解
2019/04/11 PHP
js getElementsByTagName的简写方式
2010/06/27 Javascript
jQuery UI AutoComplete 自动完成使用小记
2010/08/21 Javascript
JavaScript访问样式表代码
2010/10/15 Javascript
javascript 45种缓动效果 非常酷
2011/06/28 Javascript
Mac/Windows下如何安装Node.js
2013/11/22 Javascript
Jquery EasyUI中弹出确认对话框以及加载效果示例代码
2014/02/13 Javascript
Bootstrap安装环境配置教程分享
2016/05/27 Javascript
jquery仿ps颜色拾取功能
2017/03/08 Javascript
js实现简单的二级联动效果
2017/03/09 Javascript
EasyUI中的dataGrid的行内编辑
2017/06/22 Javascript
vue通过watch对input做字数限定的方法
2017/07/13 Javascript
Vue 中的compile操作方法
2018/02/26 Javascript
nodejs高大上的部署方式(PM2)
2018/09/11 NodeJs
JS实现点击li标签弹出对应的索引功能【案例】
2019/02/18 Javascript
微信小程序实现搜索历史功能
2020/03/26 Javascript
django js 实现表格动态标序号的实例代码
2019/07/12 Javascript
jQuery实现简单弹幕效果
2019/11/28 jQuery
基于JavaScript实现轮播图效果
2021/01/02 Javascript
[01:28:43]2014 DOTA2华西杯精英邀请赛5 24 DK VS CIS
2014/05/25 DOTA
Python fileinput模块使用实例
2015/05/28 Python
Python队列的定义与使用方法示例
2017/06/24 Python
Python 输入一个数字判断成绩分数等级的方法
2018/11/15 Python
Pandas之ReIndex重新索引的实现
2019/06/25 Python
Python字符串格式化输出代码实例
2019/11/22 Python
Pytorch之Tensor和Numpy之间的转换的实现方法
2020/09/03 Python
柏林通行证:Berlin Pass
2018/04/11 全球购物
ParcelABC西班牙:包裹运送和快递服务
2019/12/24 全球购物
生日庆典策划方案
2014/06/02 职场文书
2014教师教育实践活动对照检查材料思想汇报
2014/09/21 职场文书
2015年个人现实表现材料
2014/12/10 职场文书
初任公务员培训心得体会
2016/01/08 职场文书
Django rest framework如何自定义用户表
2021/06/09 Python
js基于div丝滑实现贝塞尔曲线
2022/09/23 Javascript