12.2 移动和插入元素

在随后的例子中,我们会构建一种灵活的排序机制,能够实现按列排序。为此,需要使用jQuery的DOM操作方法向表格中插入一些新元素,并将已有的一些元素移动到其他地方。下面我们就从最简单的地方着手——为表头加链接。

12.2.1 为已有的文本添加链接

我们先为表头中的文本加上链接,以便触发根据各自列排序的操作。第5章曾介绍过jQuery的.wrapInner()方法,这个方法会把一个新元素(在这里就是<a>元素)放到匹配元素的内部,同时包含匹配元素的子元素,因此我们可以使用个方法,参见代码清单12-1。

代码清单12-1

  1. $(document).ready(function() {
  2. var $table1 = $('#t-1');
  3. var $headers = $table1.find('thead th').slice(1);
  4. $headers
  5. .wrapInner('<a href="#"></a>')
  6. .addClass('sort');
  7. });

首先(使用.slice()方法)跳过每个表格的第一个<th>元素,因为这个表头中不包含任何文本,同时也没有必要为封面图片加标签或排序。我们已经给其他表头(<th>)元素添加了sort类,以便通过CSS来区分不可以用来排序的表头。现在表头行的外观如图12-2所示。

12.2 移动和插入元素 - 图1

图 12-2

这里也是渐进增强的对立面——优雅降级的一个例子。与前面讨论的Ajax方案不同,现在这个例子如果没有JavaScript是不能发挥作用的;我们假设了服务器端没有脚本可用。因为必须有JavaScript才能实现排序,所以我们只通过代码来给这些链接添加sort类;这意味着,只有在浏览器支持JavaScript的情况下,界面中才会显示可以排序的提示。而且,由于给文本添加了链接,并没有仅仅通过添加视觉样式来表明可以单击表头,所以那些使用键盘的用户还可以(通过按Tab键)在这些表头间切换。最终,即使不能实现排序,页面也将退化为可以使用。

12.2.2 简单的JavaScript数组排序

要进行这种排序,可以利用JavaScript内置的.sort()方法。这个方法会对数组元素进行就地排序,可以接受一个比较函数作为参数。这个比较函数比较数组中的两个元素,根据哪个元素应该在排序后的数组中排在前面返回正值或负值。

比如,以下面这个简单的数值数组为例:

  1. var arr = [52, 97, 3, 62, 10, 63, 64, 1, 9, 3, 4];

调用arr.sort()可以对这个数组进行排序。排序之后得到的数组如下:

  1. [1, 10, 3, 3, 4, 52, 62, 63, 64, 9, 97]

我们注意到,这个方法在默认情况下是按照字母表顺序排序的。实际上,对于数值数组还是按照数值大小排序才有意义。为此,可以给.sort()方法传入一个比较函数:

  1. arr.sort(function(a,b) {
  2. if (a < b) {
  3. return -1;
  4. }
  5. if (a > b) {
  6. return 1;
  7. }
  8. return 0;
  9. });

在排序后的数组中,如果a应该排在前头,这个函数返回-1;如果b应该排在前头,这个函数返回1;如果两个元素谁排在前头都可以,则返回0。设置好了排序规则之后,.sort()方法就可以给出适当的排序了:

  1. [1, 3, 3, 4, 9, 10, 52, 62, 63, 64, 97]

稍后我们会对表格行应用.sort()方法。

12.2.3 对DOM元素排序

下面我们再来看一看如何对表格的Title(书名)列进行排序。请注意,虽然前面给这个列和其他表头列添加了sort类,但这个列本身的HTML代码中还有一个sort-alpha类。其他表头单元格中也根据排序方式不同添加了相似的类名。不过,这里先只关注Title表头单元格,这一列需要按照字母顺序排序,参见代码清单12-2。

代码清单12-2

  1. $headers.on('click', function(event) {
  2. event.preventDefault();
  3. var column = $(this).index();
  4. var rows = $table1.find('tbody > tr').get();
  5. rows.sort(function(a, b) {
  6. var keyA = $(a).children('td').eq(column).text();
  7. keyA = $.trim(keyA).toUpperCase();
  8. var keyB = $(b).children('td').eq(column).text();
  9. keyB = $.trim(keyB).toUpperCase();
  10. if (keyA < keyB) return -1;
  11. if (keyA > keyB) return 1;
  12. return 0;
  13. });
  14. $.each(rows, function(index, row) {
  15. $table1.children('tbody').append(row);
  16. });
  17. });

在找到被单击的表头单元格的索引之后,取得了包含所有数据行的数组。这也是使用.get()方法将jQuery对象转换为DOM节点数组的一个绝好的例子。之所以要这样做,是因为jQuery对象本身虽然与数组类似,但它却没有.pop().shift()等原生的数组方法。

 jQuery内部确实定义了一些与原生数组方法类似的方法。例如,.sort().push().splice()都是jQuery对象的方法。不过,这些方法都是内部使用的,并没有在文档中公开出来。我们不能依赖这些方法在自己的代码中也可以像使用原生方法那样得到预期的结果。总之,不能在jQuery对象上调用它们。

在取得DOM节点数组之后,就可以对它们排序了。不过,我们得为此写一个适当的比较函数。因为要根据相关单元格中的文本内容来对表格行进行排序,所以比较函数要比较的就是这些文本内容。通过调用.index()方法返回的列索引,我们知道应该查找哪个单元格。而使用jQuery的$.trim()方法删掉文本内容前后的空格,之后再将它们转换成全部大写,是因为JavaScript对字符串的比较区分大小写,而我们想做到不区分大小写。为了减少多余的计算,我们把转换好的字符串保存在变量中,比较它们,然后就像我们前面排序数组时一样返回正值或负值。

对DOM节点的排序完成了,但节点调用.sort()并没有改变DOM。要把排序结果反映在表格中,需要调用DOM操作方法移动原来的表格行。接下来的代码遍历排序后的每一行,每次重新插入一行。因为.append()方法不会复制节点,所以移动它们不会产生多余的副本。好了,现在表格行的排序完成了,效果如图12-3所示。

12.2 移动和插入元素 - 图2

图 12-3