view 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
line wrap: on
line source

/*
* tablesaw: A set of plugins for responsive tables
* Sortable column headers
* Copyright (c) 2013 Filament Group, Inc.
* MIT License
*/

(function() {
	function getSortValue(cell) {
		var text = [];
		$(cell.childNodes).each(function() {
			var $el = $(this);
			if ($el.is("input, select")) {
				text.push($el.val());
			} else if ($el.is(".tablesaw-cell-label")) {
			} else {
				text.push(($el.text() || "").replace(/^\s+|\s+$/g, ""));
			}
		});

		return text.join("");
	}

	var pluginName = "tablesaw-sortable",
		initSelector = "table[data-" + pluginName + "]",
		sortableSwitchSelector = "[data-" + pluginName + "-switch]",
		attrs = {
			sortCol: "data-tablesaw-sortable-col",
			defaultCol: "data-tablesaw-sortable-default-col",
			numericCol: "data-tablesaw-sortable-numeric",
			subRow: "data-tablesaw-subrow",
			ignoreRow: "data-tablesaw-ignorerow"
		},
		classes = {
			head: pluginName + "-head",
			ascend: pluginName + "-ascending",
			descend: pluginName + "-descending",
			switcher: pluginName + "-switch",
			tableToolbar: "tablesaw-bar-section",
			sortButton: pluginName + "-btn"
		},
		methods = {
			_create: function(o) {
				return $(this).each(function() {
					var init = $(this).data(pluginName + "-init");
					if (init) {
						return false;
					}
					$(this)
						.data(pluginName + "-init", true)
						.trigger("beforecreate." + pluginName)
						[pluginName]("_init", o)
						.trigger("create." + pluginName);
				});
			},
			_init: function() {
				var el = $(this);
				var tblsaw = el.data("tablesaw");
				var heads;
				var $switcher;

				function addClassToHeads(h) {
					$.each(h, function(i, v) {
						$(v).addClass(classes.head);
					});
				}

				function makeHeadsActionable(h, fn) {
					$.each(h, function(i, col) {
						var b = $("<button class='" + classes.sortButton + "'/>");
						b.on("click", { col: col }, fn);
						$(col)
							.wrapInner(b)
							.find("button")
							.append("<span class='tablesaw-sortable-arrow'>");
					});
				}

				function clearOthers(headcells) {
					$.each(headcells, function(i, v) {
						var col = $(v);
						col.removeAttr(attrs.defaultCol);
						col.removeClass(classes.ascend);
						col.removeClass(classes.descend);
					});
				}

				function headsOnAction(e) {
					if ($(e.target).is("a[href]")) {
						return;
					}

					e.stopPropagation();
					var headCell = $(e.target).closest("[" + attrs.sortCol + "]"),
						v = e.data.col,
						newSortValue = heads.index(headCell[0]);

					clearOthers(
						headCell
							.closest("thead")
							.find("th")
							.filter(function() {
								return this !== headCell[0];
							})
					);
					if (headCell.is("." + classes.descend) || !headCell.is("." + classes.ascend)) {
						el[pluginName]("sortBy", v, true);
						newSortValue += "_asc";
					} else {
						el[pluginName]("sortBy", v);
						newSortValue += "_desc";
					}
					if ($switcher) {
						$switcher
							.find("select")
							.val(newSortValue)
							.trigger("refresh");
					}

					e.preventDefault();
				}

				function handleDefault(heads) {
					$.each(heads, function(idx, el) {
						var $el = $(el);
						if ($el.is("[" + attrs.defaultCol + "]")) {
							if (!$el.is("." + classes.descend)) {
								$el.addClass(classes.ascend);
							}
						}
					});
				}

				function addSwitcher(heads) {
					$switcher = $("<div>")
						.addClass(classes.switcher)
						.addClass(classes.tableToolbar);

					var html = ["<label>" + Tablesaw.i18n.sort + ":"];

					// TODO next major version: remove .btn
					html.push('<span class="btn tablesaw-btn"><select>');
					heads.each(function(j) {
						var $t = $(this);
						var isDefaultCol = $t.is("[" + attrs.defaultCol + "]");
						var isDescending = $t.is("." + classes.descend);

						var hasNumericAttribute = $t.is("[" + attrs.numericCol + "]");
						var numericCount = 0;
						// Check only the first four rows to see if the column is numbers.
						var numericCountMax = 5;

						$(this.cells.slice(0, numericCountMax)).each(function() {
							if (!isNaN(parseInt(getSortValue(this), 10))) {
								numericCount++;
							}
						});
						var isNumeric = numericCount === numericCountMax;
						if (!hasNumericAttribute) {
							$t.attr(attrs.numericCol, isNumeric ? "" : "false");
						}

						html.push(
							"<option" +
								(isDefaultCol && !isDescending ? " selected" : "") +
								' value="' +
								j +
								'_asc">' +
								$t.text() +
								" " +
								(isNumeric ? "&#x2191;" : "(A-Z)") +
								"</option>"
						);
						html.push(
							"<option" +
								(isDefaultCol && isDescending ? " selected" : "") +
								' value="' +
								j +
								'_desc">' +
								$t.text() +
								" " +
								(isNumeric ? "&#x2193;" : "(Z-A)") +
								"</option>"
						);
					});
					html.push("</select></span></label>");

					$switcher.html(html.join(""));

					var $firstChild = tblsaw.$toolbar.children().eq(0);
					if ($firstChild.length) {
						$switcher.insertBefore($firstChild);
					} else {
						$switcher.appendTo(tblsaw.$toolbar);
					}
					$switcher.find(".tablesaw-btn").tablesawbtn();
					$switcher.find("select").on("change", function() {
						var val = $(this)
								.val()
								.split("_"),
							head = heads.eq(val[0]);

						clearOthers(head.siblings());
						el[pluginName]("sortBy", head.get(0), val[1] === "asc");
					});
				}

				el.addClass(pluginName);

				heads = el
					.children()
					.filter("thead")
					.find("th[" + attrs.sortCol + "]");

				addClassToHeads(heads);
				makeHeadsActionable(heads, headsOnAction);
				handleDefault(heads);

				if (el.is(sortableSwitchSelector)) {
					addSwitcher(heads);
				}
			},
			sortRows: function(rows, colNum, ascending, col, tbody) {
				function convertCells(cellArr, belongingToTbody) {
					var cells = [];
					$.each(cellArr, function(i, cell) {
						var row = cell.parentNode;
						var $row = $(row);
						// next row is a subrow
						var subrows = [];
						var $next = $row.next();
						while ($next.is("[" + attrs.subRow + "]")) {
							subrows.push($next[0]);
							$next = $next.next();
						}

						var tbody = row.parentNode;

						// current row is a subrow
						if ($row.is("[" + attrs.subRow + "]")) {
						} else if (tbody === belongingToTbody) {
							cells.push({
								element: cell,
								cell: getSortValue(cell),
								row: row,
								subrows: subrows.length ? subrows : null,
								ignored: $row.is("[" + attrs.ignoreRow + "]")
							});
						}
					});
					return cells;
				}

				function getSortFxn(ascending, forceNumeric) {
					var fn,
						regex = /[^\-\+\d\.]/g;
					if (ascending) {
						fn = function(a, b) {
							if (a.ignored || b.ignored) {
								return 0;
							}
							if (forceNumeric) {
								return (
									parseFloat(a.cell.replace(regex, "")) - parseFloat(b.cell.replace(regex, ""))
								);
							} else {
								return a.cell.toLowerCase() > b.cell.toLowerCase() ? 1 : -1;
							}
						};
					} else {
						fn = function(a, b) {
							if (a.ignored || b.ignored) {
								return 0;
							}
							if (forceNumeric) {
								return (
									parseFloat(b.cell.replace(regex, "")) - parseFloat(a.cell.replace(regex, ""))
								);
							} else {
								return a.cell.toLowerCase() < b.cell.toLowerCase() ? 1 : -1;
							}
						};
					}
					return fn;
				}

				function convertToRows(sorted) {
					var newRows = [],
						i,
						l;
					for (i = 0, l = sorted.length; i < l; i++) {
						newRows.push(sorted[i].row);
						if (sorted[i].subrows) {
							newRows.push(sorted[i].subrows);
						}
					}
					return newRows;
				}

				var fn;
				var sorted;
				var cells = convertCells(col.cells, tbody);

				var customFn = $(col).data("tablesaw-sort");

				fn =
					(customFn && typeof customFn === "function" ? customFn(ascending) : false) ||
					getSortFxn(
						ascending,
						$(col).is("[" + attrs.numericCol + "]") &&
							!$(col).is("[" + attrs.numericCol + '="false"]')
					);

				sorted = cells.sort(fn);

				rows = convertToRows(sorted);

				return rows;
			},
			makeColDefault: function(col, a) {
				var c = $(col);
				c.attr(attrs.defaultCol, "true");
				if (a) {
					c.removeClass(classes.descend);
					c.addClass(classes.ascend);
				} else {
					c.removeClass(classes.ascend);
					c.addClass(classes.descend);
				}
			},
			sortBy: function(col, ascending) {
				var el = $(this);
				var colNum;
				var tbl = el.data("tablesaw");
				tbl.$tbody.each(function() {
					var tbody = this;
					var $tbody = $(this);
					var rows = tbl.getBodyRows(tbody);
					var sortedRows;
					var map = tbl.headerMapping[0];
					var j, k;

					// find the column number that we’re sorting
					for (j = 0, k = map.length; j < k; j++) {
						if (map[j] === col) {
							colNum = j;
							break;
						}
					}

					sortedRows = el[pluginName]("sortRows", rows, colNum, ascending, col, tbody);

					// replace Table rows
					for (j = 0, k = sortedRows.length; j < k; j++) {
						$tbody.append(sortedRows[j]);
					}
				});

				el[pluginName]("makeColDefault", col, ascending);

				el.trigger("tablesaw-sorted");
			}
		};

	// Collection method.
	$.fn[pluginName] = function(arrg) {
		var args = Array.prototype.slice.call(arguments, 1),
			returnVal;

		// if it's a method
		if (arrg && typeof arrg === "string") {
			returnVal = $.fn[pluginName].prototype[arrg].apply(this[0], args);
			return typeof returnVal !== "undefined" ? returnVal : $(this);
		}
		// check init
		if (!$(this).data(pluginName + "-active")) {
			$(this).data(pluginName + "-active", true);
			$.fn[pluginName].prototype._create.call(this, arrg);
		}
		return $(this);
	};
	// add methods
	$.extend($.fn[pluginName].prototype, methods);

	$(document).on(Tablesaw.events.create, function(e, Tablesaw) {
		if (Tablesaw.$table.is(initSelector)) {
			Tablesaw.$table[pluginName]();
		}
	});

	// TODO OOP this and add to Tablesaw object
})();