zoukankan      html  css  js  c++  java
  • 转:一步步教你实现表格排序(第一部分)

    实现表格排序,说仔细点,就是实现对表格的tbody的行进行排序,因为我们一般是把排序的侦听器绑定在thead的格子中。明确这一点,我们可以用以下方法取出我们所需要的变量

    /*得到要排序的表格对象*/
    var table = document.getElementById(id);
    /*得到要变动的部分*/
    var tbody = table.getElementsByTagName("tbody")[0];
    /*得到要排序的行的集合*/
    var rows = tbody.getElementsByTagName("tr");
    

    上面的tbody也可以以这样的方式取得

    /*得到要变动的部分*/
    var tbody = table.tBodies[0];
    

    个人喜好问题,挑自己顺眼的,反正javascript的写法很灵活。获得rows后,我们就可以对它实现排序,这有现成的排序方法sort()供我们调用,但sort()是数组的实例方法,而rows是一个集合,我们需要把它里面的元素逐个取出来放进一个新数组中进行排序。

    var index = [];
    //把要排序的行的引用放到index数组中。
    for (var i=0,l = rows.length; i < l; i++) {
        index[i] = rows[i];
    }
    

    然后我们就可以对数组进行排序,然后把排序后的数组元素放到文档碎片中,重新加入到tbody中去。

    讲了这么多理论,想必大家都闷了,这时我们需要一个直观的例子来帮助我们一个立体的认识。如果一直关注我的Blog的朋友,想必对我以下的类的写法不会陌生,我们综合上面的步骤把它们塞进TableSorter类的核心函数中便是!

     var Class = {
          create: function() {
            return function() {
              this.initialize.apply(this, arguments);
            }
          }
        }
        var extend = function(destination, source) {
          for (var property in source) {
            destination[property] = source[property];
          }
          return destination;
        }
        var TableSorter =  Class.create();//表格排序器
        TableSorter.prototype = {
          initialize:function(options){//★★入口函数兼构造函数★★
            this.setOptions(options);
            this.sortTable(this.options.table_id);
          },
          setOptions:function(options){
            this.options = { //这里集中设置默认属性
              table_id:null//必选项
            };
            extend(this.options, options || {});//这里是用来重写默认属性
          },
          ID:function(id){return document.getElementById(id) },//getElementById的快捷方式
          TN:function(){//getElementsByTagName的快捷方式
            if(arguments.length == 1){
              return document.getElementsByTagName(arguments[0])
            } else if(arguments.length == 2){
              return arguments[0].getElementsByTagName(arguments[1])
            }
          },
          sortTable:function(id){//★★核心函数,所有方法在这里集中调用★★
            var $ = this,
            table = $.ID(id),
            tbody = $.TN(table,"tbody")[0],
            rows = $.TN(tbody,"tr"),
            index = [];
            //把要排序的行的引用放到index数组中。
            for (var i=0,l = rows.length; i < l; i++) {
              index[i] = rows[i];
            }
            table.onclick = function(){//★★添加侦听器,直接绑定在table上★★
              var e = arguments[0] || window.event,
              th = e.srcElement ? e.srcElement : e.target,
              thn = th.nodeName.toLowerCase(),
              theadn = th.parentNode.parentNode.nodeName.toLowerCase();
              if(thn == 'th' && theadn == 'thead'){
                index.sort($.sortby);
                var fragment = document.createDocumentFragment();
                for (var i=0,l = index.length; i < l; i++) {
                  fragment.appendChild(index[i]);
                }
                tbody.appendChild(fragment);
              }
            };
          },      
          sortby : function(row1,row2){
            //row1,row2为调用sort方法的数组rows上的两个元素
            var value1 = row1.cells[0].firstChild.nodeValue;
            var value2 = row2.cells[0].firstChild.nodeValue;
            return value1.localeCompare(value2)
          }
        }
    

    继续讲解我们的类,想必主要疑问是集中在添加侦听器这个部分,为什么要这样写,可以看我另一篇博文《利用Delegator模式保护javascript程序的核心与提高执行性能》,为的是节约侦听器,提高性能。现在我们的表头就只一个th元素,只要监听这个就行了,如果多几个,我们也只需要一个!Event Delegation就神奇在这个地方。然后是排序,由于sort()在没有添加参数的情形下,功能特弱,只能对数字进行排序,因此我们得传入一个比较函数进去作参数。网上有的资数说这个比较函数有两参数,分别为比较数组的元素,这说法不太妥。其实只要这个函数返回的是0,-1或其他负数,1或其他正数,它不在乎有多少参数。现在我们这个表格只能排序一次,不能再次反向排序,因此接着下来的部分就是在这里进行扩展。

    现在我们一步步来,先解决多列排序的问题,目前只有对一列进行排序也太难看了。看一下我们的实现:

     sortby : function(row1,row2){
            //row1,row2为调用sort方法的数组rows上的两个元素
            var value1 = row1.cells[0].firstChild.nodeValue;
            var value2 = row2.cells[0].firstChild.nodeValue;
            return value1.localeCompare(value2)
     }
    

    我们想把它改写成以下样子,用于传入第三个参数

     sortby : function(row1,row2,i){
            //row1,row2为调用sort方法的数组rows上的两个元素
            //i为列的序数
            var value1 = row1.cells[i].firstChild.nodeValue;
            var value2 = row2.cells[i].firstChild.nodeValue;
            return value1.localeCompare(value2)
     }
    

    不过这样做,我们肯定会撞得头破血流

    我们也不要妄图绞尽脑汁想怎样传入row1与row2了,这是sort函数自动传入,只要它里面的括号不为空,它就智能地从它的调用者那里挖两个元素传入去!它的内部实现会随游览器的不同而不同。唯一肯定的是如果存在比较函数就肯定有两个东西被传入。许多人就在这里铩羽而归,有些人则干脆不用sort函数,自己实现一个排序函数,这样就可以为所欲为了。我几经思考,最后动用闭包解决了这个问题。既然那两个参数被sort()方法霸着,那就让它自己传入好了,我传我的,它传它,互不干扰。那怎样做到呢?!利用闭包我们可以构造一种特别的函数,外国人称之为currying函数。currying函数会在函数参数个数不足时,返回接受剩余参数的函数。说到这里,答案很明显了,剩余的参数由sort()来分配就行了。

    sortby : function (colIndex) {
        var _cellIndex = colIndex;
        return function (row1, row2) {
            var value1 = row1.cells[_cellIndex].firstChild.nodeValue;
            var value2 = row2.cells[_cellIndex].firstChild.nodeValue;
            return value1.localeCompare(value2);
        };
    }
    /***************相应调用的地方改为*******************/
    if(thn == 'th' && theadn == 'thead'){
        var colIndex = th.cellIndex;
        index.sort($.sortby(colIndex));
     /************************略***********************/
    }
     /************************略***********************/
    

    我们继续扩展sortby函数,让它能处理更多数据类型,基本上,我们是让它们转换上number与string两种类型

          sortby : function (colIndex) {
            var _cellIndex = colIndex;
            var format = function(s){
              if(/^\d+$/.test(s)){/*如果是正整数*/
                return parseInt(s,10)/*确保它的类型为number*/
              }else if(/^(-?\d+)(\.\d+)$/.test(s)){/*如果是浮点数*/
                return parseFloat(s, 10)/*确保它的类型为number*/
              }else if(/^(\d{4})-(\d{1,2})-(\d{1,2})$/.test(s)){/*如果是日期*/
                return Date.parse(s.replace(/\-/g, '/'));/*确保它的类型为number*/
              }else if(/\%$/.test(s)){/*如果是百分数*/
                return Number(s.replace("%", ""));/*确保它的类型为number*/
              } else {/*如果是字符串*/
                return s.toUpperCase()
              }
            }
            return function (row1, row2) {
              var value1 = format(row1.cells[_cellIndex].innerHTML);
              var value2 = format(row2.cells[_cellIndex].innerHTML);
              return typeof value1 == "string" ? value1.localeCompare(value2) : value1 - value2
            };
          }
    

    由于那两个参数霸着的缘故,所以我们到这里才能考虑radio与checkbox的问题,要得到他们的checked值,必须要有它们的对象,之前的format()函数只要字符串就很好工作了,因此format()对于对象是无可奈何,它最多能判定th里面是不是含有radio元素或checkbox元素(实质都是input元素)。

    /********如果是radio元素,我们就返回一个布尔对象**********/
    if(s.toLowerCase().search(/(type=)"?(radio)"?/)!=-1){
        return true
    } 
    

    我们可以根据这个布尔值,针对radio进行专门处理。我们可以用getElementsByTagName('input')[0]获得这个radio元素,然后取得其checked值,再转化为0或1,然后进行大小比较就是!

    if(typeof(value1) == "boolean" ){
        value1 = row1.cells[_cellIndex].getElementsByTagName('input')[0].checked;
        value1 = (value1 == true) ? 1 :0;
        value2 = row2.cells[_cellIndex].getElementsByTagName('input')[0].checked;
        value2 = (value2 == true) ? 1 :0;
        return value1 - value2;
    }
    

    checkbox的处理和上面一样,到此sortby()这个比较函数被扩展成这个样子:

          sortby : function (colIndex) {
            var _cellIndex = colIndex;
            var format = function(s){
              if(/^\d+$/.test(s)){/*如果是正整数*/
                return parseInt(s,10)/*确保它的类型为number*/
              }else if(/^(-?\d+)(\.\d+)$/.test(s)){/*如果是浮点数*/
                return parseFloat(s, 10)/*确保它的类型为number*/
              }else if(/^(\d{4})-(\d{1,2})-(\d{1,2})$/.test(s)){/*如果是日期*/
                return Date.parse(s.replace(/\-/g, '/'));/*确保它的类型为number*/
              }else if(/\%$/.test(s)){/*如果是百分数*/
                return Number(s.replace("%", ""));/*确保它的类型为number*/
              }else if(s.toLowerCase().search(/(type=)"?(radio)"?/)!=-1){
                return true;/*确保它的类型为boolean*/
              }else if(s.toLowerCase().search(/(type=)"?(checkbox)"?/)!=-1){
                return false;/*确保它的类型为boolean*/
              } else {/*如果是字符串*/
                return s.toUpperCase()
              };
            };
            return function (row1, row2) {
              var value1 = format(row1.cells[_cellIndex].innerHTML),
              value2 = format(row2.cells[_cellIndex].innerHTML),
              result = 0;
              switch(typeof value1){
                case "string":
                  result = value1.localeCompare(value2);
                  break;
                case "number" :
                  result = value1 - value2;
                  break;
                case "boolean" : /*处理radio与checkbox*/
                  value1 = row1.cells[_cellIndex].getElementsByTagName('input')[0].checked;
                  value1 = (value1 == true) ? 1 :0;
                  value2 = row2.cells[_cellIndex].getElementsByTagName('input')[0].checked;
                  value2 = (value2 == true) ? 1 :0;
                  result = value1 - value2;
                  break;
              }
              return result;
            };
          }
    

    这里IE6与IE7又找我们麻烦了,它们在排序后并不保存radio与checkbox的选中状态,这也没什么,在排序前我们把那些选中的input元素放进一个数组中,排序后再遍历数组把它们钩上便是!为此我们为sortby()函数添加上第二个参数,它是TableSorter这个类的实例的引用,通过它我们可以大大减轻sortby()函数的负担。

          checkedElements:[],
          sortby : function (colIndex,$) {
            var _cellIndex = colIndex;
            return function (row1, row2) {
              var value1 = $.format(row1.cells[_cellIndex].innerHTML),
              value2 = $.format(row2.cells[_cellIndex].innerHTML),
              result = 0;
              switch(typeof value1){
                case "string":
                  result = value1.localeCompare(value2);
                  break;
                case "number" :
                  result = value1 - value2;
                  break;
                case "boolean" : /*处理radio与checkbox*/
                  var input1 = $.TN(row1.cells[_cellIndex],'input')[0];;
                  value1 = input1.checked;
                  value1 = (value1 == true) ? 1 :0;
                  if(value1){
                    $.checkedElements.push(input1);
                  }
                  value2 = $.TN(row2.cells[_cellIndex],'input')[0].checked;
                  value2 = (value2 == true) ? 1 :0;
                  result = value1 - value2;
                  break;
              }
              return result;
            };
          },
          format : function(s){
           /************略***************/
          }
    

    然后,我们在排序完后重新为这些对象修正checked值:

         if(thn == 'th' && theadn == 'thead'){
                var colIndex = th.cellIndex;
                index.sort($.sortby(colIndex,$));
                var fragment = document.createDocumentFragment();
                for(var i=0,l = index.length; i < l; i++) {
                  fragment.appendChild(index[i]);
                }
                tbody.appendChild(fragment);
                for(var i=0 ,l = $.checkedElements.length;i< l; i++){
                  $.checkedElements[i].checked = true;
                }
          }
    

    眼尖的朋友们可以大叫——“你忘了给input2作判定,保存其checked状态。”放心,基本上要排序的行,sort()函数都会循环调用两次,它们都有机会为row1或row2。

    接着下来我们实现反向排序,原来我们的列一旦排序后就不能动了,我们要改变这种状况。由于在sortby()函数引入第二个参数,实现这功能简直易如反掌!我们为了TableSorter添加一个实例属性colsStatus,它是个数组,用于存储每一列的状态,如果没有反向排序我们设为1,反之为-1,这些1或-1是用来给sortby()最后的值相乘的。

    if(thn == 'th' && theadn == 'thead'){
        var colIndex = th.cellIndex;
        $.colsStatus[colIndex] = ($.colsStatus[colIndex] == null) ? 1 : $.colsStatus[colIndex] * -1;/★★★/
        index.sort($.sortby(colIndex,$));
        /**************略****************/
    }
    /**************略****************/
    checkedElements:[],
    colsStatus:[],/★★★★★★★/
    sortby : function (colIndex,$) {
        var _cellIndex = colIndex;
        return function (row1, row2) {
            /**************略****************/
            result *=  $.colsStatus[_cellIndex];/★★★★★★★/
            return result;
        };
    },
    /**************略****************/
    

    通过colsStatus,我们还可以做些东西,如在表头显示排序箭头。

    原帖地址:http://www.cnblogs.com/rubylouvre/archive/2009/08/13/1544365.html

  • 相关阅读:
    一、异常
    自控力_第三章
    Vocabulary Recitation 2020/05/05
    Vocabulary Recitation 2020/05/04
    Vocabulary Recitaion 2020/05/03
    Vocabulary Recitation 2020/05/01
    最大子序列和
    Vocabulary Recitation 2020/04/29
    自控力_第二章
    Vocabulary Recitation 2020/04/27
  • 原文地址:https://www.cnblogs.com/davinci/p/1652196.html
Copyright © 2011-2022 走看看