Skip to content

Commit db2c2d4

Browse files
NPM version 1.16.0; numeric-sort class; plus bug fixes.
1 parent 9f59eef commit db2c2d4

File tree

9 files changed

+418
-251
lines changed

9 files changed

+418
-251
lines changed

README.md

+8-2
Original file line numberDiff line numberDiff line change
@@ -17,24 +17,30 @@
1717

1818
## Install instructions.
1919

20-
- <b>Option 1:</b> Install from npm:
20+
- <b>Option 1:</b> Install from npm:
21+
2122
```javascript
2223
npm install table-sort-js
2324
```
2425

2526
```javascript
2627
import tableSort from "table-sort-js/table-sort.js";
2728
```
29+
2830
Examples on using table-sort-js with frontend frameworks such as [React.js](https://leewannacott.github.io/table-sort-js/docs/react.html) and [Vue.js](https://leewannacott.github.io/table-sort-js/docs/vue.html)
2931

3032
- <b>Option 2</b>: Load as script from a Content Delivery Network (CDN):
33+
3134
```javascript
3235
<script src="https://cdn.jsdelivr.net/npm/table-sort-js@latest/table-sort.js"></script>
3336
```
37+
3438
Or Minified (smaller size, but harder to debug!):
39+
3540
```javascript
3641
<script src="https://cdn.jsdelivr.net/npm/table-sort-js@latest/table-sort.min.js"></script>
3742
```
43+
3844
Refer to the documenation for examples on how to use table-sort-js with [HTML](https://leewannacott.github.io/table-sort-js/docs/html5.html)
3945

4046
- <b>Option 3:</b> Download [table-sort.js](https://cdn.jsdelivr.net/npm/table-sort-js@latest/table-sort.js) (Select save as.), or download a [minified version](https://cdn.jsdelivr.net/npm/table-sort-js@latest/table-sort.min.js) (~5kB)
@@ -72,7 +78,7 @@ Then rename and add the following script before your HTML table:
7278

7379
| &lt;th&gt; Inferred Classes. | Description |
7480
| ---------------------------- | ----------------------------------------------------------------------------------------------------------------------------------- |
75-
| "numeric-sort" | Sorts numbers including decimals - Positive, Negative (in both minus and parenthesis representations) |
81+
| "numeric-sort" | Sorts numbers including decimals - Positive, Negative (in both minus and parenthesis representations) |
7682
| "dates-dmy-sort" | Sorts dates in dd/mm/yyyy format. e.g (18/10/1995). Can use "/" or "-" as separator. |
7783
| "dates-ymd-sort" | Sorts dates in [ISO 8601](https://en.wikipedia.org/wiki/ISO_8601) yyyy/mm/dd format. e.g (2021/10/28). Use "/" or "-" as separator. |
7884
| "file-size-sort" | Sorts file sizes(B->TiB) uses the binary prefix. (e.g 10 B, 100 KiB, 1 MiB); optional space between number and prefix. |
262 Bytes
Binary file not shown.

browser-extensions/chrome/table-sort.js

+114-76
Original file line numberDiff line numberDiff line change
@@ -65,11 +65,13 @@ function tableSortJs(testingTableSortJS = false, domDocumentWindow = document) {
6565
// Doesn't infer dates with delimiter "."; as could capture semantic version numbers.
6666
const dmyRegex = /^(\d\d?)[/-](\d\d?)[/-]((\d\d)?\d\d)/;
6767
const ymdRegex = /^(\d\d\d\d)[/-](\d\d?)[/-](\d\d?)/;
68+
const numericRegex = /^(?:\(\d+(?:\.\d+)?\)|-?\d+(?:\.\d+)?)$/;
6869
const inferableClasses = {
6970
runtime: { regexp: runtimeRegex, class: "runtime-sort", count: 0 },
7071
filesize: { regexp: fileSizeRegex, class: "file-size-sort", count: 0 },
7172
dmyDates: { regexp: dmyRegex, class: "dates-dmy-sort", count: 0 },
7273
ymdDates: { regexp: ymdRegex, class: "dates-ymd-sort", count: 0 },
74+
numericRegex: { regexp: numericRegex, class: "numeric-sort", count: 0 },
7375
};
7476
let classNameAdded = false;
7577
let regexNotFoundCount = 0;
@@ -105,28 +107,44 @@ function tableSortJs(testingTableSortJS = false, domDocumentWindow = document) {
105107
}
106108

107109
function makeTableSortable(sortableTable) {
108-
const tableBody = getTableBody(sortableTable);
109-
const tableHead = sortableTable.querySelector("thead");
110-
const tableHeadHeaders = tableHead.querySelectorAll("th");
111-
const tableRows = tableBody.querySelectorAll("tr");
110+
const table = {
111+
body: getTableBody(sortableTable),
112+
head: sortableTable.querySelector("thead"),
113+
};
114+
table.headers = table.head.querySelectorAll("th");
115+
table.rows = table.body.querySelectorAll("tr");
116+
117+
let columnIndexesClicked = [];
112118

113119
const isNoSortClassInference =
114120
sortableTable.classList.contains("no-class-infer");
115121

116-
for (let [columnIndex, th] of tableHeadHeaders.entries()) {
122+
for (let [columnIndex, th] of table.headers.entries()) {
117123
if (!th.classList.contains("disable-sort")) {
118124
th.style.cursor = "pointer";
119125
if (!isNoSortClassInference) {
120-
inferSortClasses(tableRows, columnIndex, th);
126+
inferSortClasses(table.rows, columnIndex, th);
121127
}
122-
makeEachColumnSortable(th, columnIndex, tableBody, sortableTable);
128+
makeEachColumnSortable(
129+
th,
130+
columnIndex,
131+
table,
132+
sortableTable,
133+
columnIndexesClicked
134+
);
123135
}
124136
}
125137
}
126138

127-
function makeEachColumnSortable(th, columnIndex, tableBody, sortableTable) {
139+
function makeEachColumnSortable(
140+
th,
141+
columnIndex,
142+
table,
143+
sortableTable,
144+
columnIndexesClicked
145+
) {
128146
const desc = th.classList.contains("order-by-desc");
129-
let tableArrows = sortableTable.classList.contains("table-arrows");
147+
const tableArrows = sortableTable.classList.contains("table-arrows");
130148
const [arrowUp, arrowDown] = [" ▲", " ▼"];
131149
const fillValue = "!X!Y!Z!";
132150

@@ -249,9 +267,7 @@ function tableSortJs(testingTableSortJS = false, domDocumentWindow = document) {
249267
}
250268
}
251269

252-
let [timesClickedColumn, columnIndexesClicked] = [0, []];
253-
254-
function rememberSort(timesClickedColumn, columnIndexesClicked) {
270+
function rememberSort() {
255271
// if user clicked different column from first column reset times clicked.
256272
columnIndexesClicked.push(columnIndex);
257273
if (timesClickedColumn === 1 && columnIndexesClicked.length > 1) {
@@ -260,14 +276,15 @@ function tableSortJs(testingTableSortJS = false, domDocumentWindow = document) {
260276
const secondLastColumnClicked =
261277
columnIndexesClicked[columnIndexesClicked.length - 2];
262278
if (lastColumnClicked !== secondLastColumnClicked) {
263-
timesClickedColumn = 0;
264279
columnIndexesClicked.shift();
280+
timesClickedColumn = 0;
265281
}
266282
}
283+
return timesClickedColumn;
267284
}
268285

269-
function getColSpanData(sortableTable, column) {
270-
sortableTable.querySelectorAll("th").forEach((th, index) => {
286+
function getColSpanData(headers, column) {
287+
headers.forEach((th, index) => {
271288
column.span[index] = th.colSpan;
272289
if (index === 0) column.spanSum[index] = th.colSpan;
273290
else column.spanSum[index] = column.spanSum[index - 1] + th.colSpan;
@@ -285,16 +302,7 @@ function tableSortJs(testingTableSortJS = false, domDocumentWindow = document) {
285302
}
286303

287304
function getTableData(tableProperties) {
288-
const {
289-
tableRows,
290-
column,
291-
isFileSize,
292-
isTimeSort,
293-
isSortDateDayMonthYear,
294-
isSortDateMonthDayYear,
295-
isSortDateYearMonthDay,
296-
isDataAttribute,
297-
} = tableProperties;
305+
const { tableRows, column, hasThClass, isSortDates } = tableProperties;
298306
for (let [i, tr] of tableRows.entries()) {
299307
let tdTextContent = getColumn(
300308
tr,
@@ -305,17 +313,17 @@ function tableSortJs(testingTableSortJS = false, domDocumentWindow = document) {
305313
tdTextContent = "";
306314
}
307315
if (tdTextContent.trim() !== "") {
308-
if (isFileSize) {
316+
if (hasThClass.fileSize) {
309317
fileSizeColumnTextAndRow[column.toBeSorted[i]] = tr.outerHTML;
310318
}
311319
// These classes already handle pushing to column and setting the tr html.
312320
if (
313-
!isFileSize &&
314-
!isDataAttribute &&
315-
!isTimeSort &&
316-
!isSortDateDayMonthYear &&
317-
!isSortDateYearMonthDay &&
318-
!isSortDateMonthDayYear
321+
!hasThClass.fileSize &&
322+
!hasThClass.dataSort &&
323+
!hasThClass.runtime &&
324+
!isSortDates.dayMonthYear &&
325+
!isSortDates.yearMonthDay &&
326+
!isSortDates.monthDayYear
319327
) {
320328
column.toBeSorted.push(`${tdTextContent}#${i}`);
321329
columnIndexAndTableRow[`${tdTextContent}#${i}`] = tr.outerHTML;
@@ -329,17 +337,48 @@ function tableSortJs(testingTableSortJS = false, domDocumentWindow = document) {
329337

330338
const isPunctSort = th.classList.contains("punct-sort");
331339
const isAlphaSort = th.classList.contains("alpha-sort");
340+
const isNumericSort = th.classList.contains("numeric-sort");
341+
342+
function parseNumberFromString(str) {
343+
let num;
344+
str = str.slice(0, str.indexOf("#"));
345+
if (str.match(/^\((\d+(?:\.\d+)?)\)$/)) {
346+
num = -1 * Number(str.slice(1, -1));
347+
} else {
348+
num = Number(str);
349+
}
350+
return num;
351+
}
352+
353+
function strLocaleCompare(str1, str2) {
354+
return str1.localeCompare(
355+
str2,
356+
navigator.languages[0] || navigator.language,
357+
{ numeric: !isAlphaSort, ignorePunctuation: !isPunctSort }
358+
);
359+
}
360+
361+
function handleNumbers(str1, str2) {
362+
let num1, num2;
363+
num1 = parseNumberFromString(str1);
364+
num2 = parseNumberFromString(str2);
365+
366+
if (!isNaN(num1) && !isNaN(num2)) {
367+
return num1 - num2;
368+
} else {
369+
return strLocaleCompare(str1, str2);
370+
}
371+
}
372+
332373
function sortAscending(a, b) {
333374
if (a.includes(`${fillValue}#`)) {
334375
return 1;
335376
} else if (b.includes(`${fillValue}#`)) {
336377
return -1;
378+
} else if (isNumericSort) {
379+
return handleNumbers(a, b);
337380
} else {
338-
return a.localeCompare(
339-
b,
340-
navigator.languages[0] || navigator.language,
341-
{ numeric: !isAlphaSort, ignorePunctuation: !isPunctSort }
342-
);
381+
return strLocaleCompare(a, b);
343382
}
344383
}
345384

@@ -391,9 +430,9 @@ function tableSortJs(testingTableSortJS = false, domDocumentWindow = document) {
391430
}
392431

393432
function updateTable(tableProperties) {
394-
const { tableRows, column, isFileSize } = tableProperties;
433+
const { tableRows, column, hasThClass } = tableProperties;
395434
for (let [i, tr] of tableRows.entries()) {
396-
if (isFileSize) {
435+
if (hasThClass.fileSize) {
397436
tr.innerHTML = fileSizeColumnTextAndRow[column.toBeSorted[i]];
398437
let fileSizeInBytesHTML = tr
399438
.querySelectorAll("td")
@@ -426,71 +465,70 @@ function tableSortJs(testingTableSortJS = false, domDocumentWindow = document) {
426465
}
427466
tr.querySelectorAll("td").item(columnIndex).innerHTML =
428467
fileSizeInBytesHTML;
429-
} else if (!isFileSize) {
468+
} else if (!hasThClass.fileSize) {
430469
tr.outerHTML = columnIndexAndTableRow[column.toBeSorted[i]];
431470
}
432471
}
433472
}
434473

474+
let timesClickedColumn = 0;
435475
th.addEventListener("click", function () {
436-
timesClickedColumn += 1;
437476
const column = {
438-
// column used for sorting; better name?
439477
toBeSorted: [],
440478
span: {},
441479
spanSum: {},
442480
};
443481

444-
const visibleTableRows = Array.prototype.filter.call(
445-
tableBody.querySelectorAll("tr"),
482+
table.visibleRows = Array.prototype.filter.call(
483+
table.body.querySelectorAll("tr"),
446484
(tr) => {
447485
return tr.style.display !== "none";
448486
}
449487
);
450488

451-
getColSpanData(sortableTable, column);
489+
getColSpanData(table.headers, column);
452490

453-
const isDataAttribute = th.classList.contains("data-sort");
454-
if (isDataAttribute) {
455-
sortDataAttributes(visibleTableRows, column);
491+
const isRememberSort = sortableTable.classList.contains("remember-sort");
492+
if (!isRememberSort) {
493+
timesClickedColumn = rememberSort();
456494
}
495+
timesClickedColumn += 1;
457496

458-
const isFileSize = th.classList.contains("file-size-sort");
459-
if (isFileSize) {
460-
sortFileSize(visibleTableRows, column);
461-
}
497+
const hasThClass = {
498+
dataSort: th.classList.contains("data-sort"),
499+
fileSize: th.classList.contains("file-size-sort"),
500+
runtime: th.classList.contains("runtime-sort"),
501+
};
462502

463-
const isTimeSort = th.classList.contains("runtime-sort");
464-
if (isTimeSort) {
465-
sortByRuntime(visibleTableRows, column);
503+
if (hasThClass.dataSort) {
504+
sortDataAttributes(table.visibleRows, column);
466505
}
467-
468-
const isSortDateDayMonthYear = th.classList.contains("dates-dmy-sort");
469-
const isSortDateMonthDayYear = th.classList.contains("dates-mdy-sort");
470-
const isSortDateYearMonthDay = th.classList.contains("dates-ymd-sort");
471-
// pick mdy first to override the inferred default class which is dmy.
472-
if (isSortDateMonthDayYear) {
473-
sortDates("mdy", visibleTableRows, column);
474-
} else if (isSortDateYearMonthDay) {
475-
sortDates("ymd", visibleTableRows, column);
476-
} else if (isSortDateDayMonthYear) {
477-
sortDates("dmy", visibleTableRows, column);
506+
if (hasThClass.fileSize) {
507+
sortFileSize(table.visibleRows, column);
508+
}
509+
if (hasThClass.runtime) {
510+
sortByRuntime(table.visibleRows, column);
478511
}
479512

480-
const isRememberSort = sortableTable.classList.contains("remember-sort");
481-
if (!isRememberSort) {
482-
rememberSort(timesClickedColumn, columnIndexesClicked);
513+
const isSortDates = {
514+
dayMonthYear: th.classList.contains("dates-dmy-sort"),
515+
monthDayYear: th.classList.contains("dates-mdy-sort"),
516+
yearMonthDay: th.classList.contains("dates-ymd-sort"),
517+
};
518+
// pick mdy first to override the inferred default class which is dmy.
519+
if (isSortDates.monthDayYear) {
520+
sortDates("mdy", table.visibleRows, column);
521+
} else if (isSortDates.yearMonthDay) {
522+
sortDates("ymd", table.visibleRows, column);
523+
} else if (isSortDates.dayMonthYear) {
524+
sortDates("dmy", table.visibleRows, column);
483525
}
484526

485527
const tableProperties = {
486-
tableRows: visibleTableRows,
528+
tableRows: table.visibleRows,
487529
column,
488-
isFileSize,
489-
isSortDateDayMonthYear,
490-
isSortDateMonthDayYear,
491-
isSortDateYearMonthDay,
492-
isDataAttribute,
493-
isTimeSort,
530+
hasThClass,
531+
isSortDates,
494532
};
495533
getTableData(tableProperties);
496534
updateTable(tableProperties);
262 Bytes
Binary file not shown.

0 commit comments

Comments
 (0)