序二(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 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为例,查查网页制作完全手册,会找到它有以下属性:
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测试下面的代码:

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

下载完整实例

JavaScript Table排序相关推荐

  1. 算法笔记(JavaScript版)——排序

    算法笔记(JavaScript版)--排序 本文内容根据Rebert Sedgewick和Kevin Wayne的<算法(第四版)>整理,原代码为java语言,自己修改为JavaScrip ...

  2. javascript排序_鸡尾酒在JavaScript中排序

    javascript排序 Just want the code? Scroll all the way down for two versions of the code: 只需要代码? 一直向下滚动 ...

  3. html表格td向下排序,jquery实现的table排序功能示例

    本文实例讲述了jquery实现的table排序功能.分享给大家供大家参考,具体如下: Document div { width: 1024px; margin: 0 auto; /*div相对屏幕左右 ...

  4. bootstrap table排序php,BootStrap+Table排序分页序号

    这次给大家带来BootStrap+Table排序分页序号,BootStrap+Table排序分页序号的注意事项有哪些,下面就是实战案例,一起来看一下. 前言在使用bootstrap table的时候难 ...

  5. JavaScript的排序算法——快速排序

    排序算法(Sorting algorithm)是计算机科学最古老.最基本的课题之一.要想成为合格的程序员,就必须理解和掌握各种排序算法. 快速排序(Quicksort)是对冒泡排序的一种改进. 快速排 ...

  6. DWZ (JUI) 教程 table 排序

    DWZ (JUI) 教程 table 排序 最近有朋友问到dwz 排序的问题,我简单说一下.dwz排序是后台排序,不是前台的js排序,他的流程和搜索,分页是一样的,当你点击排序的按钮时,从新发送请求刷 ...

  7. JavaScript 名称排序示例

    JavaScript 名称排序示例 let Array = [{ name: "rtsp测试1" },{ name: "btsp测试5" },{ name: & ...

  8. layui 改写 table 排序,填加中文按照拼音排序

    因为layui table默认的排序不支持中文拼音,改写layui源码来完成我们的需求 注意在layui官网下载到的layui代码是压缩后的,这里需要下载layui的源码 layui源码地址: 码云地 ...

  9. antd table 排序如何使用_Antd Table列字符串排序+数字排序

    在中后端WEB app开发过程中经常遇到table列排序的问题,Antd table 提供了sorter的接口,我们可以根据各自项目特点拓展即可. 1.Number类型数据排序,官方提供了number ...

最新文章

  1. 特斯拉首次达成连续4季度盈利:车卖的少了,钱却挣得多了
  2. div+css+theme
  3. 《textanalytics》课程简单总结(1):两种word relations——Paradigmatic vs. Syntagmatic...
  4. Uva 1625 - Color Length(DP)
  5. 上海电力学院计算机技术,上海电力学院计算机与信息工程学院介绍
  6. 思必驰十年创业,(现在)是一家怎样的公司?
  7. 版本差异_终极版!三星Note20系列在真机再曝光,不同版本差异在这几点
  8. springboot + mybatis 学英语网、背单词网站
  9. 周志华-机器学习.pdf 学习心得 附整理材料
  10. AD-001 CR11220纽扣电池库文件
  11. word中如何单独修改某一页页眉
  12. matlab中fprintf整数,matlab中fprintf函数的用法
  13. 什么是实体-联系图(ER图)
  14. LabVIEW如何将脚本插入Quick Drop
  15. python实现下载网络视频资源
  16. linux连接交换机命令,Linux连接路由器交换机防火墙Console接口的5个实用命令
  17. 雷迪9000使用说明_雷迪司UPS监控软件使用说明中文
  18. 一步一步玩转树莓派~
  19. 毕业论文指之 “国内外研究现状”的撰写
  20. 是傻频日志呵阡啄凛仄

热门文章

  1. sql怎么实现取当前数据以及累计7天数据_年薪60万+大佬吐血整理字节跳动大数据面试真题...
  2. 邮件整体解决方案_面向未来的冻干机进出料解决方案:阿尔法(ALUS)系列自动进出料系统...
  3. iOS 11开发教程(四)iOS11模拟器介绍一
  4. Arduino可穿戴教程保存源文件与打开已经存在的源文件
  5. jforum oracle报错,JForum安装在Oracle数据库上
  6. python中self_一个例子带你入门Python装饰器
  7. python安装不了jieba_python安装jieba失败怎么办?_后端开发
  8. 脑电分析系列[MNE-Python-1]| MNE-Python详细安装与使用(更新)
  9. Xbox“天蝎计划”中国区负责人:“今年的E3展会将超乎你想像”
  10. 新冠轻症也会导致大脑退化,牛津大学最新研究登上Nature