JavaScript Table排序

本文介绍了一种在客户端实现的自定义Table排序类,支持多种数据类型和排序方式,包括自定义排序函数和多级排序。同时解决了IE6/7中表单控件状态丢失的问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >


======================================================
注:本文源代码点此下载
======================================================

序二(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

名称 / 类型

上传时间

大小

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

108 b

6

news.htm

2008/10/4

13.74 k

7

function.js

2008/10/4

2.78 k

8

神秘园 - nocturne.mp3

2008/9/20

2.97 m

9

详细功略+剧情流程(一).doc

2009/2/2

62 k

10

详细功略+剧情流程(二).doc

2009/2/2

160.5 k

11

禁止文件预览功能.txt

2008/8/7

860 b

12

超级无敌精彩的效果集合.txt

2009/2/2

351 b

有中文的先排前面,再按时间倒序,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来取。

取得值之后就进行比较值转换:

code

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为例,查查网页制作完全手册,会找到它有以下属性:

alignalign

classclassname

idid

titletitle

......

其中第一列就是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测试下面的代码:

code

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运行下面代码:

code

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测试下面代码:

code

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);

程序源码

code

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; });

}

}

下载完整实例


======================================================
在最后,我邀请大家参加新浪APP,就是新浪免费送大家的一个空间,支持PHP+MySql,免费二级域名,免费域名绑定 这个是我邀请的地址,您通过这个链接注册即为我的好友,并获赠云豆500个,价值5元哦!短网址是http://t.cn/SXOiLh我创建的小站每天访客已经达到2000+了,每天挂广告赚50+元哦,呵呵,饭钱不愁了,\(^o^)/
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值