comparison default/node_modules/tablesaw/src/tables.sortable.js @ 0:1d038bc9b3d2 default tip

Up:default
author Liny <dev@neowd.com>
date Sat, 31 May 2025 09:21:51 +0800
parents
children
comparison
equal deleted inserted replaced
-1:000000000000 0:1d038bc9b3d2
1 /*
2 * tablesaw: A set of plugins for responsive tables
3 * Sortable column headers
4 * Copyright (c) 2013 Filament Group, Inc.
5 * MIT License
6 */
7
8 (function() {
9 function getSortValue(cell) {
10 var text = [];
11 $(cell.childNodes).each(function() {
12 var $el = $(this);
13 if ($el.is("input, select")) {
14 text.push($el.val());
15 } else if ($el.is(".tablesaw-cell-label")) {
16 } else {
17 text.push(($el.text() || "").replace(/^\s+|\s+$/g, ""));
18 }
19 });
20
21 return text.join("");
22 }
23
24 var pluginName = "tablesaw-sortable",
25 initSelector = "table[data-" + pluginName + "]",
26 sortableSwitchSelector = "[data-" + pluginName + "-switch]",
27 attrs = {
28 sortCol: "data-tablesaw-sortable-col",
29 defaultCol: "data-tablesaw-sortable-default-col",
30 numericCol: "data-tablesaw-sortable-numeric",
31 subRow: "data-tablesaw-subrow",
32 ignoreRow: "data-tablesaw-ignorerow"
33 },
34 classes = {
35 head: pluginName + "-head",
36 ascend: pluginName + "-ascending",
37 descend: pluginName + "-descending",
38 switcher: pluginName + "-switch",
39 tableToolbar: "tablesaw-bar-section",
40 sortButton: pluginName + "-btn"
41 },
42 methods = {
43 _create: function(o) {
44 return $(this).each(function() {
45 var init = $(this).data(pluginName + "-init");
46 if (init) {
47 return false;
48 }
49 $(this)
50 .data(pluginName + "-init", true)
51 .trigger("beforecreate." + pluginName)
52 [pluginName]("_init", o)
53 .trigger("create." + pluginName);
54 });
55 },
56 _init: function() {
57 var el = $(this);
58 var tblsaw = el.data("tablesaw");
59 var heads;
60 var $switcher;
61
62 function addClassToHeads(h) {
63 $.each(h, function(i, v) {
64 $(v).addClass(classes.head);
65 });
66 }
67
68 function makeHeadsActionable(h, fn) {
69 $.each(h, function(i, col) {
70 var b = $("<button class='" + classes.sortButton + "'/>");
71 b.on("click", { col: col }, fn);
72 $(col)
73 .wrapInner(b)
74 .find("button")
75 .append("<span class='tablesaw-sortable-arrow'>");
76 });
77 }
78
79 function clearOthers(headcells) {
80 $.each(headcells, function(i, v) {
81 var col = $(v);
82 col.removeAttr(attrs.defaultCol);
83 col.removeClass(classes.ascend);
84 col.removeClass(classes.descend);
85 });
86 }
87
88 function headsOnAction(e) {
89 if ($(e.target).is("a[href]")) {
90 return;
91 }
92
93 e.stopPropagation();
94 var headCell = $(e.target).closest("[" + attrs.sortCol + "]"),
95 v = e.data.col,
96 newSortValue = heads.index(headCell[0]);
97
98 clearOthers(
99 headCell
100 .closest("thead")
101 .find("th")
102 .filter(function() {
103 return this !== headCell[0];
104 })
105 );
106 if (headCell.is("." + classes.descend) || !headCell.is("." + classes.ascend)) {
107 el[pluginName]("sortBy", v, true);
108 newSortValue += "_asc";
109 } else {
110 el[pluginName]("sortBy", v);
111 newSortValue += "_desc";
112 }
113 if ($switcher) {
114 $switcher
115 .find("select")
116 .val(newSortValue)
117 .trigger("refresh");
118 }
119
120 e.preventDefault();
121 }
122
123 function handleDefault(heads) {
124 $.each(heads, function(idx, el) {
125 var $el = $(el);
126 if ($el.is("[" + attrs.defaultCol + "]")) {
127 if (!$el.is("." + classes.descend)) {
128 $el.addClass(classes.ascend);
129 }
130 }
131 });
132 }
133
134 function addSwitcher(heads) {
135 $switcher = $("<div>")
136 .addClass(classes.switcher)
137 .addClass(classes.tableToolbar);
138
139 var html = ["<label>" + Tablesaw.i18n.sort + ":"];
140
141 // TODO next major version: remove .btn
142 html.push('<span class="btn tablesaw-btn"><select>');
143 heads.each(function(j) {
144 var $t = $(this);
145 var isDefaultCol = $t.is("[" + attrs.defaultCol + "]");
146 var isDescending = $t.is("." + classes.descend);
147
148 var hasNumericAttribute = $t.is("[" + attrs.numericCol + "]");
149 var numericCount = 0;
150 // Check only the first four rows to see if the column is numbers.
151 var numericCountMax = 5;
152
153 $(this.cells.slice(0, numericCountMax)).each(function() {
154 if (!isNaN(parseInt(getSortValue(this), 10))) {
155 numericCount++;
156 }
157 });
158 var isNumeric = numericCount === numericCountMax;
159 if (!hasNumericAttribute) {
160 $t.attr(attrs.numericCol, isNumeric ? "" : "false");
161 }
162
163 html.push(
164 "<option" +
165 (isDefaultCol && !isDescending ? " selected" : "") +
166 ' value="' +
167 j +
168 '_asc">' +
169 $t.text() +
170 " " +
171 (isNumeric ? "&#x2191;" : "(A-Z)") +
172 "</option>"
173 );
174 html.push(
175 "<option" +
176 (isDefaultCol && isDescending ? " selected" : "") +
177 ' value="' +
178 j +
179 '_desc">' +
180 $t.text() +
181 " " +
182 (isNumeric ? "&#x2193;" : "(Z-A)") +
183 "</option>"
184 );
185 });
186 html.push("</select></span></label>");
187
188 $switcher.html(html.join(""));
189
190 var $firstChild = tblsaw.$toolbar.children().eq(0);
191 if ($firstChild.length) {
192 $switcher.insertBefore($firstChild);
193 } else {
194 $switcher.appendTo(tblsaw.$toolbar);
195 }
196 $switcher.find(".tablesaw-btn").tablesawbtn();
197 $switcher.find("select").on("change", function() {
198 var val = $(this)
199 .val()
200 .split("_"),
201 head = heads.eq(val[0]);
202
203 clearOthers(head.siblings());
204 el[pluginName]("sortBy", head.get(0), val[1] === "asc");
205 });
206 }
207
208 el.addClass(pluginName);
209
210 heads = el
211 .children()
212 .filter("thead")
213 .find("th[" + attrs.sortCol + "]");
214
215 addClassToHeads(heads);
216 makeHeadsActionable(heads, headsOnAction);
217 handleDefault(heads);
218
219 if (el.is(sortableSwitchSelector)) {
220 addSwitcher(heads);
221 }
222 },
223 sortRows: function(rows, colNum, ascending, col, tbody) {
224 function convertCells(cellArr, belongingToTbody) {
225 var cells = [];
226 $.each(cellArr, function(i, cell) {
227 var row = cell.parentNode;
228 var $row = $(row);
229 // next row is a subrow
230 var subrows = [];
231 var $next = $row.next();
232 while ($next.is("[" + attrs.subRow + "]")) {
233 subrows.push($next[0]);
234 $next = $next.next();
235 }
236
237 var tbody = row.parentNode;
238
239 // current row is a subrow
240 if ($row.is("[" + attrs.subRow + "]")) {
241 } else if (tbody === belongingToTbody) {
242 cells.push({
243 element: cell,
244 cell: getSortValue(cell),
245 row: row,
246 subrows: subrows.length ? subrows : null,
247 ignored: $row.is("[" + attrs.ignoreRow + "]")
248 });
249 }
250 });
251 return cells;
252 }
253
254 function getSortFxn(ascending, forceNumeric) {
255 var fn,
256 regex = /[^\-\+\d\.]/g;
257 if (ascending) {
258 fn = function(a, b) {
259 if (a.ignored || b.ignored) {
260 return 0;
261 }
262 if (forceNumeric) {
263 return (
264 parseFloat(a.cell.replace(regex, "")) - parseFloat(b.cell.replace(regex, ""))
265 );
266 } else {
267 return a.cell.toLowerCase() > b.cell.toLowerCase() ? 1 : -1;
268 }
269 };
270 } else {
271 fn = function(a, b) {
272 if (a.ignored || b.ignored) {
273 return 0;
274 }
275 if (forceNumeric) {
276 return (
277 parseFloat(b.cell.replace(regex, "")) - parseFloat(a.cell.replace(regex, ""))
278 );
279 } else {
280 return a.cell.toLowerCase() < b.cell.toLowerCase() ? 1 : -1;
281 }
282 };
283 }
284 return fn;
285 }
286
287 function convertToRows(sorted) {
288 var newRows = [],
289 i,
290 l;
291 for (i = 0, l = sorted.length; i < l; i++) {
292 newRows.push(sorted[i].row);
293 if (sorted[i].subrows) {
294 newRows.push(sorted[i].subrows);
295 }
296 }
297 return newRows;
298 }
299
300 var fn;
301 var sorted;
302 var cells = convertCells(col.cells, tbody);
303
304 var customFn = $(col).data("tablesaw-sort");
305
306 fn =
307 (customFn && typeof customFn === "function" ? customFn(ascending) : false) ||
308 getSortFxn(
309 ascending,
310 $(col).is("[" + attrs.numericCol + "]") &&
311 !$(col).is("[" + attrs.numericCol + '="false"]')
312 );
313
314 sorted = cells.sort(fn);
315
316 rows = convertToRows(sorted);
317
318 return rows;
319 },
320 makeColDefault: function(col, a) {
321 var c = $(col);
322 c.attr(attrs.defaultCol, "true");
323 if (a) {
324 c.removeClass(classes.descend);
325 c.addClass(classes.ascend);
326 } else {
327 c.removeClass(classes.ascend);
328 c.addClass(classes.descend);
329 }
330 },
331 sortBy: function(col, ascending) {
332 var el = $(this);
333 var colNum;
334 var tbl = el.data("tablesaw");
335 tbl.$tbody.each(function() {
336 var tbody = this;
337 var $tbody = $(this);
338 var rows = tbl.getBodyRows(tbody);
339 var sortedRows;
340 var map = tbl.headerMapping[0];
341 var j, k;
342
343 // find the column number that we’re sorting
344 for (j = 0, k = map.length; j < k; j++) {
345 if (map[j] === col) {
346 colNum = j;
347 break;
348 }
349 }
350
351 sortedRows = el[pluginName]("sortRows", rows, colNum, ascending, col, tbody);
352
353 // replace Table rows
354 for (j = 0, k = sortedRows.length; j < k; j++) {
355 $tbody.append(sortedRows[j]);
356 }
357 });
358
359 el[pluginName]("makeColDefault", col, ascending);
360
361 el.trigger("tablesaw-sorted");
362 }
363 };
364
365 // Collection method.
366 $.fn[pluginName] = function(arrg) {
367 var args = Array.prototype.slice.call(arguments, 1),
368 returnVal;
369
370 // if it's a method
371 if (arrg && typeof arrg === "string") {
372 returnVal = $.fn[pluginName].prototype[arrg].apply(this[0], args);
373 return typeof returnVal !== "undefined" ? returnVal : $(this);
374 }
375 // check init
376 if (!$(this).data(pluginName + "-active")) {
377 $(this).data(pluginName + "-active", true);
378 $.fn[pluginName].prototype._create.call(this, arrg);
379 }
380 return $(this);
381 };
382 // add methods
383 $.extend($.fn[pluginName].prototype, methods);
384
385 $(document).on(Tablesaw.events.create, function(e, Tablesaw) {
386 if (Tablesaw.$table.is(initSelector)) {
387 Tablesaw.$table[pluginName]();
388 }
389 });
390
391 // TODO OOP this and add to Tablesaw object
392 })();