• JavaScript Table排序


    序一(08/10/06)

    前一阵做了个网盘,用到了table的排序,趁热打铁做一个完整的table排序类出来。
    程序的实现的是在客户端对表格进行排序,有以下特点:
    1,自定义排序列、排序属性(例如innerHTML)、排序数据类型(包括int、float、date、string)、排序顺序(顺序和倒序);
    2,自定义排序函数;
    3,可同时设置多个排序列;

    网上也有很多其他的table排序函数,但有的是基于数组,有的不够灵活。本程序是在原有table结构上加入功能,套用一个流行词叫“无侵入”。


    效果预览

    ID  名称 / 类型 上传时间 大小
    5 4.xml 2008/10/4 108 b
    4 1.xml 2008/10/4 11.13 K
    12 超级无敌精彩的效果集合.txt 2009/2/2 351 b
    11 禁止文件预览功能.txt 2008/8/7 860 b
    8 神秘园 - Nocturne.mp3 2008/9/20 2.97 M
    7 function.js 2008/10/4 2.78 K
    3 AlertBox.js 2008/9/23 3.48 K
    2 Scroller.js 2008/9/23 2.5 K
    6 news.htm 2008/10/4 13.74 K
    1 new.htm 2008/9/12 423.09 K
    10 详细功略+剧情流程(二).doc 2009/2/2 160.5 K
    9 详细功略+剧情流程(一).doc 2009/2/2 62 K

    有中文的先排前面,再按时间倒序,ID倒序排序:



    基本步骤

    1,把需要排序的行放到tbody中(程序会直接取tbody的rows);

    2,把排序行放到一个数组中; 

    this.Rows = Map(this.tBody.rows, function(o){ return o; });


    3,按需求对数组进行排序(用数组的sort方法); 

    this.Rows.sort(Bind(thisthis.Compare, orders, 0));


    4,用一个文档碎片(document.createDocumentFragment())保存排好序的行;

    var oFragment = document.createDocumentFragment();
    forEach(
    this.Rows, function(o){ oFragment.appendChild(o); });


    ps:文档碎片并不是必须的,但建议使用,大量dom操作时使用文档碎片会更有效率。

    5,把文档碎片插入到tbody中。

    this.tBody.appendChild(oFragment);

    程序说明

    【排序函数】

    排序就不得不说数组中sort这个方法,手册是这样介绍的:返回一个元素已经进行了排序的 Array 对象。也就是对一个数组进行排序,很多跟排序相关的操作都用到这个方法。

    默认按照 ASCII 字符顺序进行升序排列,使用参数的话可以自定义排序方法,程序的Compare程序就是用来自定义排序的。
    一般来说排序函数会有两个默认参数分别是两个比较对象,程序中根据需要在调用Compare时Bind了两个参数,所以会有4个参数。
    要注意,排序函数必须返回下列值之一:
    负值,如果所传递的第一个参数比第二个参数小。
    零,如果两个参数相等。
    正值,如果第一个参数比第二个参数大。

    在取得比较值(后面说明)之后就进行值的比较。
    程序中如果是字符串,会用localeCompare获取比较结果,否则就直接相减得到比较结果:

    result = order.Compare ? order.Compare(value1, value2) ://使用自定义排序
        typeof value2 == "string" ? value1.localeCompare(value2) : (value1 - value2);


    如果Desc属性是true(倒序排序),那么在result的基础上乘以-1就能得到相反的排序了:(order.Down ? -1 : 1) * result


    【获取比较值】

    程序中是根据排序对象和GetValue方法从每个tr中获取比较值的。
    首先通过Index(td索引)和Attri(属性)获取对应的值。
    如果没有适合的属性放要比较的值的话,可以给td设一个自定义属性来放这个值(如例子中的_ext)。
    对于在html中设置的自定义属性,ie可以用[x]和getAttribute来取,而ff就只能用getAttribute来获取(后面会详细说明)。
    所以只需考虑ff的情况就行了,程序中用in来判断这个属性是否可以用[x]方式获取:

    var td = tr.cells[order.Index], att = order.Attri
        ,data 
    = order.GetValue ? order.GetValue(td) ://使用自定义取值函数
            att in td ? td[att] : td.getAttribute(att);


    如果in运算是true,那么可以用关键词方式取值,否则用getAttribute来取。
    取得值之后就进行比较值转换:


    switch (order.DataType.toLowerCase()) {
        
    case "int":
            
    return parseInt(data, 10|| 0;
        
    case "float":
            
    return parseFloat(data, 10|| 0;
        
    case "date":
            
    return Date.parse(data) || 0;
        
    case "bool":
            
    return data === true || String(data).toLowerCase() == "true" ? 1 : 0;
        
    case "string":
        
    default:
            
    return data.toString() || "";
    }


    这里会把日期用Date.parse转化成整数的形式方便比较,但要注意Date.parse的参数必须符合js的日期格式(参考这里)。
    对于bool值的比较,会判断是不是true或字符串的"true",其他都一律看成false。
    ps:如果觉得添加自定义属性不符合标准,可以考虑放在title之类的属性中。


    【attribute/property】

    在获取比较值的时候会用in来判断是否可以用[x]方式,其实是判断该属性是属于attribute还是property。
    那attribute和property到底是什么呢,有什么区别呢?这个或许很多人都没留意,或许认为是同一个东西。
    要明确attribute和property是不同的东西就要先知道它们分别是什么,这个很难说得清,举些例子就明白了。
    这里我们先以ff为标准,后面再说ie的区别。以div为例,查查网页制作完全手册,会找到它有以下属性:
    ALIGN      align
    CLASS      className
    ID            id
    TITLE       title
    ...            ...
    其中第一列就是attribute,第二列就是property。
    attribute是dom元素在文档中作为html标签拥有的属性,property就是dom元素在js中作为对象拥有的属性。
    例如在html中dom作为页面元素应该直接用class属性,对应在js中作为dom对象就必须用className属性。
    由于attribute是不分大小写的,这使得大部分的attribute和property看起来会一样,使人误以为同一个东西(当然ie的责任也很大)。
    还不相信的话可以用ff看看下面的例子:

    <div id="t" tt="1">test</div>
    <script>
    var o = document.getElementById('t');
    o[
    "tt"]="2";
    document.writeln(o.getAttribute(
    "tt"));
    document.writeln(o[
    "tt"]);
    </script>


    可以看出getAttribute和[x]方式得到了不同的答案。
    这里必须先说说getAttribute和[x]方式的区别,getAttribute和setAttribute是专门用来获取和设置attribute的,
    而[x]方式就是获取和设置property属性的,这个property跟我们一般操作的js对象的属性是一样的。
    或许有人会有疑问,像id,title不是都指向同一个属性吗,修改property对应attribute也会跟着修改。
    其实我们也可以自定义一个这样的属性,在ff测试下面的代码:


    <div id="t" tt="1">test</div>
    <script>
    var o = document.getElementById('t');
    o.__defineSetter__(
    "tt"function(x) { this.setAttribute("tt", x); });
    o.__defineGetter__(
    "tt"function() { return this.getAttribute("tt"); });
    o.tt
    ="2";
    document.writeln(o.getAttribute(
    "tt"));
    document.writeln(o[
    "tt"]);
    </script>


    这样就实现了“修改property对应attribute也会跟着修改”的属性了。
    从测试例子还可以看到attribute跟对应的property完全可以使用不一样的属性名,像class和className的效果。
    也能在Getter中对attribute的值进行处理再返回,就像href的property值是attribute的完整路径形式。
    而property可以没有对应的attribute,反过来也一样,像innerHTML这样的property就没有对应的attribute。
    ps:以上只是说明实现的原理,事实上并不需要这样来实现。

    既然知道attribute和property是不同的东西,那如何分辨一个属性是属于attribute还是property呢。
    我们可以用in来判断property,用hasAttribute判断attribute。
    但ie6/7没有hasAttribute,是不是只能用in来判断呢?对了一半,因为ie6/7根本就不需要hasAttribute。
    在ie6/7中,并没有很好地区分attribute和property。例如ie6/7运行下面代码:


    <div id="t" tt="1">test</div>
    <script>
    var o = document.getElementById('t');
    o[
    "tt"]="2";
    document.writeln(o.getAttribute(
    "tt"));
    document.writeln(o[
    "tt"]);
    o.setAttribute(
    "tt","3");
    document.writeln(o.getAttribute(
    "tt"));
    document.writeln(o[
    "tt"]);
    o[
    "rr"]="4";
    document.writeln(o.getAttribute(
    "rr"));
    document.writeln(o[
    "rr"]);
    document.writeln(o.getAttribute(
    "innerHTML"));
    document.writeln(o[
    "innerHTML"]);
    </script>


    可以看到,里面基本没有attribute和property之分,而ie8的结果除了getAttribute("innerHTML"),其他跟ie6/7一样。
    当然我觉得ie的制作者肯定知道attribute和property的区别,只是他们为了得到使用者想当然的结果,所以才这么弄。
    本来被这么忽悠也没什么不好,但后来我发现一个问题:

    <div id="t" class="a">test</div>
    <script>
    var o = document.getElementById('t');
    o.setAttribute(
    "class","b");
    alert(o.outerHTML);
    </script>


    这样修改的样式是无效的,按照ie的规矩要使用className,但问题是从outerHTML中居然看到div标签中有两个class属性。
    之前我一直都不知如何理解ie这个现象,不过这在ie8中已经得到了修正。
    在ie8中已经把attribute和property区分开了(详细看Attribute Differences in Internet Explorer 8)。
    例如getAttribute("innerHTML")返回的是null,说明innerHTML不再是 attribute;setAttribute("class",x)修改的是attribute,不再是给dom元素添加一个莫名其妙的class属 性;貌似getAttribute也没有了第二个参数(getAttribute的第二个参数可以看这里);还有name属性的混乱问题也正常了(参考这里)。
    不过ie8依然使用添加新属性会同时是attribute和property的模式,估计还是为了兼容之前的版本,可怜的ie8。

    ps:以上都以[x]为例子,而使用.运算符的效果跟[x]是一样的。
    ps2:由于对dom没有很深入的了解,这部分可能会有问题,欢迎各位指出。
    ps3:发现自己的dom知识太少,正准备找本dom的书看看。

    【radio/checkbox的checked状态bug】

    可以用ie6/7测试下面代码:


    <div id="c">
    <input type="checkbox" id="a" />
    <input name="b" type="radio" id="b" />
    </div>
    <input type="button" value="click" id="btn" />
    <script>
    var a = document.getElementById("a");
    var b = document.getElementById("b");
    var c = document.getElementById("c");
    document.getElementById(
    "btn").onclick = function(){
        c.appendChild(a);
        c.appendChild(b);
        
    var o1 = document.createElement("input");
        o1.type 
    = "checkbox"; o1.checked = true;
        c.appendChild(o1);
        
    var o2 = document.createElement("input");
        o2.type 
    = "radio"; o2.checked = true;
        c.appendChild(o2);
    }
    </script>


    先点选checkbox和radio,然后点击按钮,在ie6会发现checkbox和radio都恢复到没有点选的状态,ie7好点只是radio有问题。
    而且新插入的checkbox和radio虽然checked都设置成true,但显示出来还是没有选择的状态。
    这里其实都是一个问题,checkbox和radio在一些dom操作之后(例如这里的appendChild),checked会自动恢复成defaultChecked的状态。
    创建元素的问题可以参考这里
    程序中tr在排序后会用appendChild重新插入文档,结果就会导致上面的问题了,解决方法暂时想到三个:
    1,在appendChild之前修改defaultChecked。
    针对appendChild后会自动恢复成defaultChecked,那我们就在appendChild前把defaultChecked修改成当前的checked值。
    这个解决方法不错,只要appendChild之前扫一遍表单控件就行,但问题是这会影响到reset的结果,因为reset之后 checkbox/radio的checked就会恢复成defaultChecked的值,如果修改了defaultChecked那reset就失去 了效果。
    2,在appendChild之前保存checked的状态,并在appendChild之后恢复。
    要实现这个也有两个方法,一个是用一个数组或对象来保存checkbox/radio当前的checked值,在appendChild之后找出对应的值并设置。
    另一个是直接把checkbox/radio当前的checked值保存到该控件的一个自定义属性中,在appendChild之后再获取并设置。
    两个方法都要扫两次表单控件,后者比较方便。
    3,在appendChild之前找出checked跟defaultChecked不相等的控件,并在appendChild之后重新设置这些控件。
    这个方法比前一个稍好,只要在appendChild之前扫一遍控件,并筛选出需要修正的(checked跟defaultChecked不相等的),在appendChild之后设置checked为defaultChecked的相反值就行了。

    程序用的是第3个方法,在appendChild之前用GetChecked方法获取要修正的checkbox/radio集合:

      GetChecked: function() {
        
    this._checked = Filter(this.tBody.getElementsByTagName("input"), function(o){
            
    return ((isIE6 && o.type == "checkbox"|| o.type == "radio"&& o.checked != o.defaultChecked;
        });
      },


    在appendChild之后用SetChecked重新设置checked值:

      SetChecked: function() {
        forEach(
    this._checked, function(o){ o.checked = !o.defaultChecked; });
      }


    但这样效率还是比较低,如果有更好的方法记得告诉我啊。


    【排序对象】

    为了程序的更灵活,加了一个排序对象的东西。
    这个排序对象有以下属性:
    属性  默认值//说明
    Index:  0,//td索引
    Attri:  "innerHTML",//获取数据的属性
    DataType: "string",//比较的数据类型
    Desc:  true,//是否按降序
    Compare: null,//自定义排序函数
    GetValue: null,//自定义取值函数
    startSort: function(){},//排序前执行
    endSort: function(){}//排序后执

    可以看出这个排序对象就是用来保存该排序的规则和方式的,也就是用来告诉程序要怎么排序。
    采用这个模式是因为一个table通常同时需要多个不同的排序方式,使用排序对象就像玩拳王选人,哪个适合就用哪个。
    而程序在一次排序过程中还可以设置多个排序对象,一个被KO(比较值相等),另一个再上。
    用这个方式会更方便,重用性更好。

    程序中通过Creat程序来创建排序对象,其参数就是自定义的属性:

    Creat: function(options) {
        
    return Extend(Extend({}, this.options), options || {});
    }


    执行Sort程序就会进行排序,但必须一个或多个的排序对象为参数。
    在Sort程序中会先把排序对象参数转换成数组:

    var orders = Array.prototype.slice.call(arguments);


    然后传递到Compare程序中,当比较结果是0(即相等),同时有下一个排序对象,就会用下一个排序对象继续Compare:

    return !result && orders[++i] ? this.Compare(orders, i, o1, o2) : (order.Desc ? -1 : 1* result;


    这样的方式可以最大限度的利用已建立的排序对象。


    使用方法

    首先实例化一个主排序对象,参数是table的id:

    var to = new TableOrder("idTable");


    如果需要设置默认属性,一般建议在new的时候设置。

    接着用Creat方法添加一个排序对象,参数是要设置的属性对象(参考【排序对象】):

    odID = to.Creat({ DataType: "int", Desc: false })


    然后就可以用Sort方法配合排序对象为参数来排序了:

    to.Sort(order, odID);

    程序源码 



    var TableOrder = function(table, options) {
        
    this._checked = [];//checkbox和radio集合(用来解决ie6/7的状态恢复bug)
        
        
    this.tBody = $(table).tBodies[0];//tbody对象
        this.Rows = Map(this.tBody.rows, function(o){ return o; });//行集合
        
        
    this.SetOptions(options);
    }
    TableOrder.prototype 
    = {
      
    //设置默认属性
      SetOptions: function(options) {
        
    this.options = {//默认值
            Index:        0,//td索引
            Attri:        "innerHTML",//获取数据的属性
            DataType:    "string",//比较的数据类型
            Desc:        true,//是否按降序
            Compare:    null,//自定义排序函数
            GetValue:    null,//自定义取值函数
            startSort:    function(){},//排序前执行
            endSort:    function(){}//排序后执行
        };
        Extend(
    this.options, options || {});
      },
      
    //排序并显示
      Sort: function() {
        
    var orders = Array.prototype.slice.call(arguments);
        
    //没有排序对象返回
        if(!orders.length){ return false };
        
    //执行附加函数
        orders[0].startSort();
        
    //排序
        this.Rows.sort(Bind(thisthis.Compare, orders, 0));
        
    //获取集合
        isIE6or7 && this.GetChecked();
        
    //显示表格
        var oFragment = document.createDocumentFragment();
        forEach(
    this.Rows, function(o){ oFragment.appendChild(o); });
        
    this.tBody.appendChild(oFragment);
        
    //恢复状态
        isIE6or7 && this.SetChecked();
        
    //执行附加函数
        orders[0].endSort();
      },
      
    //比较函数
      Compare: function(orders, i, o1, o2) {
        
    var order = orders[i], value1 = this.GetValue(order, o1), value2 = this.GetValue(order, o2)
            ,result 
    = order.Compare ? order.Compare(value1, value2) ://使用自定义排序函数
                typeof value2 == "string" ? value1.localeCompare(value2) : (value1 - value2);
        
    //如果result是0(值相同)同时有下一个排序对象的话继续比较否则根据Desc修正结果并返回
        return !result && orders[++i] ? this.Compare(orders, i, o1, o2) : (order.Desc ? -1 : 1* result;
      },
      
    //获取比较值
      GetValue: function(order, tr) {
        
    var td = tr.cells[order.Index], att = order.Attri
            ,data 
    = order.GetValue ? order.GetValue(td) ://使用自定义取值函数
                att in td ? td[att] : td.getAttribute(att);
        
    //数据转换
        switch (order.DataType.toLowerCase()) {
            
    case "int":
                
    return parseInt(data, 10|| 0;
            
    case "float":
                
    return parseFloat(data, 10|| 0;
            
    case "date":
                
    return Date.parse(data) || 0;
            
    case "bool":
                
    return data === true || String(data).toLowerCase() == "true" ? 1 : 0;
            
    case "string":
            
    default:
                
    return data.toString() || "";
        }
      },
      
    //创建并返回一个排序对象
      Creat: function(options) {
        
    return Extend(Extend({}, this.options), options || {});
      },
      
    //获取要修正的checkbox和radio集合
      GetChecked: function() {
        
    this._checked = Filter(this.tBody.getElementsByTagName("input"), function(o){
            
    return ((isIE6 && o.type == "checkbox"|| o.type == "radio"&& o.checked != o.defaultChecked;
        });
      },
      
    //设置checkbox和radio集合的checked
      SetChecked: function() {
        forEach(
    this._checked, function(o){ o.checked = !o.defaultChecked; });
      }
    }

    完整源代码下载




    转载  http://www.cnblogs.com/cloudgamer/
  • 相关阅读:
    Linux之Permission denied没有权限
    soapUI的简单使用(webservice接口功能测试)
    jmeter学习(二),如何安装jmeter?
    loadrunner检查点设置失败,日志中SaveCount无法被正常统计出来
    loadrunner破解出现“license security violation,Operation is not allowed”的错误提示
    安装LoadRunner11报缺少vc2005_sp1_with_atl_fix_redist的错误
    IOS测试,打不开要测试的APP怎么办?设置信任
    Jmeter的好搭档Badboy的安装与简单使用
    映射网络驱动器会自动断开的解决方法
    oracle中如何修改用户名和密码
  • 原文地址:https://www.cnblogs.com/freeton/p/1578285.html
Copyright © 2020-2023  润新知