当前位置: 首页 > 图文教程 > 网络编程 > Javascript > JavaScript Table排序 2.0 (更新)

Javascript
JS getMonth()日期函数的值域是0-11
jQuery 处理网页内容的实现代码
jQuery 树形结构的选择器
jQuery 处理表单元素的代码
JQuery 动画卷页 返回顶部 动画特效(兼容Chrome)
JavaScript 10件让人费解的事情
类似GMAIL的Ajax信息反馈显示
两个比较有用的Javascript工具函数代码
JavaScript Timer实现代码
JavaScript 学习技巧
JavaScript 题型问答有答案参考
js删除select中重复项的实现代码
javascript中的链式调用
JavaScript DOM学习第一章 W3C DOM简介
JavaScript DOM 学习第二章 编辑文本
JavaScript DOM 学习第三章 内容表格
JavaScript DOM学习第四章 getElementByTagNames
JavaScript DOM 学习第五章 表单简介
JavaScript DOM学习第六章 表单实例
JavaScript DOM 学习第七章 表单的扩展

Javascript 中的 JavaScript Table排序 2.0 (更新)


出处:互联网   整理: 软晨网(RuanChen.com)   发布: 2009-09-12   浏览: 138 ::
收藏到网摘: n/a

JavaScript,Table,排序,order,attribute,property,sort,Compare 近来还是那么忙,趁五一更新一下程序吧。
这个版本主要增加和改进了以下东西:
1,对字符串改用localeCompare来比较;
2,一次排序中能使用多个排序对象(用于值相等时再排序);
3,修正一些发现的问题;
4,改进程序结构,个人觉得是更灵活更方便了;
5,增加bool类型比较;
6,添加attribute/property的内容;
7,修正ie6/7的radio/checkbox状态恢复bug;
8,增加自定义取值函数。
点击运行可以看到效果:
[Ctrl+A 全选 提示:你可先修改部分代码,再按运行]

打包代码
基本步骤
1,把需要排序的行放到tbody中(程序会直接取tbody的rows);
2,把排序行放到一个数组中;
this.Rows = Map(this.tBody.rows, function(o){ return o; });
3,按需求对数组进行排序(用数组的sort方法);
this.Rows.sort(Bind(this, this.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.getElementsByTagName("td")[order.Index]
, at = order.Attri, data = at in td ? td[at] : td.getAttribute(at);
如果in运算是true,那么可以用关键词方式取值,否则用getAttribute来取。
取得值之后就进行比较值转换,这里会把日期用Date.parse转化成整数的形式方便比较:
switch (order.DataType.toLowerCase()) {
case "int":
return parseInt(data) || 0;
case "float":
return parseFloat(data) || 0;
case "date":
return Date.parse(data) || 0;
case "string":
default:
return data.toString() || "";
}
要注意Date.parse的参数必须符合js的日期格式(参考这里)。
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的第二个参数可以看这里)。
不过ie8依然使用添加新属性会同时是attribute和property的模式,估计还是为了兼容之前的版本。
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,//自定义排序函数
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);