JavaScript Table排序

序二(09/05/03)

近来还是那么忙,趁五一更新一下程序吧。
这个版本主要增加和改进了以下东西:
1,对字符串改用localeCompare来比较;
2,一次排序中能使用多个排序对象(用于值相等时再排序);
3,修正一些发现的问题;
4,改进程序结构,个人觉得是更灵活更方便了;
5,增加bool类型比较;
6,添加attribute/property的内容;
7,修正ie6/7的radio/checkbox状态恢复bug;
8,增加自定义取值函数。

序一(08/10/06)

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

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

效果预览

点击表头排序

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

基本步骤

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

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

this._rows = $$A.map(tBody.rows, function(o){ return o; });

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

this._rows.sort($$F.bind( this._compare, this, orders, 0 ));

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

var frag = document.createDocumentFragment();
$$A.forEach(this._rows, function(o){ frag.appendChild(o); });

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

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

this._tBody.appendChild(frag);

程序说明

【排序函数】

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

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

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

result = od.compare ? od.compare(value1, value2) :
    typeof value2 == "string" ? value1.localeCompare(value2) : (value1 - value2);

如果desc属性是true(降序排序),那么在result的基础上乘以-1就能得到相反的排序了:

(od.desc ? -1 : 1) * result

【获取比较值】

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

var td = tr.cells[order.index], att = order.property
    ,data = order.value ? order.value(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集合:

this._checked = $$A.filter(this._tBody.getElementsByTagName("input"), function(o){
    return (($$B.ie6 && o.type == "checkbox") || o.type == "radio") &&
        o.checked != o.defaultChecked;
});

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

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

但这样效率还是比较低,所以除了考虑浏览器:

_repair: $$B.ie6 || $$B.ie7,

还可以自定义的repair来决定是否需要修正:

var repair = this._repair && $$A.some(orders, function(o){ return o.repair; });

如果有更好的方法记得告诉我啊。

【排序对象】

为了程序的更灵活,加了一个排序对象的东西。
这个排序对象有以下属性:
属性  默认值//说明
index:  0,//td索引
property: "innerHTML",//获取数据的属性 
type:  "string",//比较的数据类型
desc:  true,//是否按降序
compare: null,//自定义排序函数
value:  null,//自定义取值函数
repair:  this._repair,//是否解决checkbox和radio状态恢复bug
onBegin: function(){},//排序前执行
onEnd:  function(){}//排序后执行

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

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

return $$.extend($$.extend({}, this.options), options || {});

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

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

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

return !result && od[++i] ? this._compare(orders, i, tr1, tr2) : (od.desc ? -1 : 1) * result;

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

使用方法

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

var to = new TableOrder("idTable");

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

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

odID = to.creat({ type: "int", desc: false })

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

to.sort(order, odID);

程序源码 

var TableOrder = function(table, options) {
    this._checked = [];//存放checkbox和radio集合
    
    var tBody = $$(table).tBodies[0];
    this._tBody = tBody;//tbody对象
    this._rows = $$A.map(tBody.rows, function(o){ return o; });//行集合
    
    this._setOptions(options);
}
TableOrder.prototype = {
  _repair: $$B.ie6 || $$B.ie7,//在ie6/7才需要修复bug
  //设置默认属性
  _setOptions: function(options) {
    this.options = {//默认值
        index:        0,//td索引
        property:    "innerHTML",//获取数据的属性 
        type:        "string",//比较的数据类型
        desc:        true,//是否按降序
        compare:    null,//自定义排序函数
        value:        null,//自定义取值函数
        repair:        this._repair,//是否解决checkbox和radio状态恢复bug
        onBegin:    function(){},//排序前执行
        onEnd:        function(){}//排序后执行
    };
    $$.extend(this.options, options || {});
  },
  //排序并显示
  sort: function() {
    //没有排序对象返回
    if(!arguments.length){ return false };
    var orders = Array.prototype.slice.call(arguments);
    //执行附加函数
    orders[0].onBegin();
    //排序
    this._rows.sort($$F.bind( this._compare, this, orders, 0 ));
    //获取集合
    var repair = this._repair && $$A.some(orders, function(o){ return o.repair; });
    repair && this._getChecked();
    //显示表格
    var frag = document.createDocumentFragment();
    $$A.forEach(this._rows, function(o){ frag.appendChild(o); });
    this._tBody.appendChild(frag);
    //恢复状态
    repair && this._setChecked();
    //执行附加函数
    orders[0].onEnd();
  },
  //比较函数
  _compare: function(orders, i, tr1, tr2) {
    var od = orders[i], value1 = this._value(od, tr1), value2 = this._value(od, tr2)
        ,result = od.compare ? od.compare(value1, value2) ://使用自定义排序函数
            typeof value2 == "string" ? value1.localeCompare(value2) : (value1 - value2);
    //如果result是0(值相同)同时有下一个排序对象的话继续比较否则根据desc修正结果并返回
    return !result && od[++i] ? this._compare(orders, i, tr1, tr2) : (od.desc ? -1 : 1) * result;
  },
  //获取比较值
  _value: function(order, tr) {
    var td = tr.cells[order.index], att = order.property
        ,data = order.value ? order.value(td) ://使用自定义取值函数
            att in td ? td[att] : td.getAttribute(att);
    //数据转换
    switch (order.type.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 = $$A.filter(this._tBody.getElementsByTagName("input"), function(o){
        return (($$B.ie6 && o.type == "checkbox") || o.type == "radio") &&
            o.checked != o.defaultChecked;
    });
  },
  //设置checkbox和radio集合的checked
  _setChecked: function() {
    $$A.forEach(this._checked, function(o){ o.checked = !o.defaultChecked; });
  }
}

下载完整实例

本文转自博客园cloudgamer的博客,原文链接:JavaScript Table排序,如需转载请自行联系原博主。

时间: 2024-09-16 06:39:44

JavaScript Table排序的相关文章

JavaScript Table排序 2.0 (更新)_javascript技巧

ID  名称 / 类型 上传时间 大小 C R 1 new.htm 2008/9/12 423.09 K 2 Scroller.js 2008/9/23 2.5 K 3 AlertBox.js 2008/9/23 3.48 K 4 1.xml 2008/10/4 11.13 K 5 4.xml 2008/10/4 351 b 6 news.htm 2008/10/4 13.74 K 7 function.js 2008/10/4 2.78 K 8 神秘园 - Nocturne.mp3 2008/

javascript table排序 这个更简单了,不用改变现在的表格结构_javascript技巧

Name Age Position Income Gender John 37 Managing director 90.000 Male Susan 34 Partner 90.000 Female David 29 Head of production 70.000 Male Laura 29 Head of marketing 70.000 Female Kate 18 Marketing 50.000 Female Mona 21 Marketing 53.000 Female Mike

javascript实现Table排序的方法_javascript技巧

本文实例讲述了javascript实现Table排序的方法.分享给大家供大家参考.具体实现方法如下: <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml"&

javascript实现Table排序的方法

 本文实例讲述了javascript实现Table排序的方法.分享给大家供大家参考.具体实现方法如下: ? 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 6

javascript 实现简单的table排序及table操作练习_javascript技巧

在这个列子中,练习了table的操作,主要有:tBodies.rows.cells,还有有关数组的排序方法:sort 先上代码: 复制代码 代码如下: <!DOCTYPE HTML> <html> <head> <meta charset="utf-8"> <title>table排序</title> </head> <body> <table id="tableTest&q

如何实现XML+XSL+javascript数据排序

javascript|xml|排序|数据 如何实现XML+XSL+javascript数据排序 数据排序模版<?xml version="1.0" encoding="gb2312" ?><xsl:stylesheet version="1.1" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:fo="http://www.w3.org/

用jquery.sortElements实现table排序

 实现table排序,网上有很多解决方案,很多都基于jQuery,最后我选择用sortElements,实现很简单 项目中要实现table排序的功能. 网上有很多解决方案,很多都基于jQuery. jquery.tablesorter,大小17KB,不过他的首页在ie10下兼容性有点问题. DataTables,大小75KB,功能强大,带分页,搜索等功能. 还有插件叫sortElements,很小巧,只有3KB,兼容性也不错,而且在Github上有818个星. 最后我选择用sortElement

javascript Table

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml"> <head>     <title>Javascript Table 

javascript时间排序算法实现活动秒杀倒计时效果_javascript技巧

制做一个活动页面 秒杀列表页 需要一个时间的算法排序 自己琢磨了半天想了各种算法也没搞出来,后来问了下一个后台的php同学 他写了个算法给我看了下 ,刚开始看的时候觉得这就是个纯算法,不能转化成页面的dom效果,可是再看了两遍发现可以, 于是我就改了改,实现了,先分享给大家. 页面需求是:从11点到20点 每隔一个小时一场秒杀 如果是当前时间就显示正在秒杀 之前的商品就往最后排 以此类推 类似最开始的11点顺序是 11,12,13,14,15,16,17,18,19,20(点): 到12点的顺序