Mercurial > nebulaweb3
diff default/node_modules/tablesaw/dist/tablesaw.jquery.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 diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/default/node_modules/tablesaw/dist/tablesaw.jquery.js Sat May 31 09:21:51 2025 +0800 @@ -0,0 +1,2073 @@ +/*! Tablesaw - v3.0.8 - 2018-01-25 +* https://github.com/filamentgroup/tablesaw +* Copyright (c) 2018 Filament Group; Licensed MIT */ +(function (root, factory) { + if (typeof define === 'function' && define.amd) { + define(["jquery"], function (jQuery) { + return (root.Tablesaw = factory(jQuery, root)); + }); + } else if (typeof exports === 'object') { + if( "document" in root ) { + module.exports = factory(require('jquery'), root); + } else { + // special jQuery case for CommonJS (pass in a window) + module.exports = factory(require('jquery')(root), root); + } + } else { + root.Tablesaw = factory(jQuery, root); + } +}(typeof window !== "undefined" ? window : this, function ($, window) { + "use strict"; + + var document = window.document; + +var domContentLoadedTriggered = false; +document.addEventListener("DOMContentLoaded", function() { + domContentLoadedTriggered = true; +}); + +var Tablesaw = { + i18n: { + modeStack: "Stack", + modeSwipe: "Swipe", + modeToggle: "Toggle", + modeSwitchColumnsAbbreviated: "Cols", + modeSwitchColumns: "Columns", + columnToggleButton: "Columns", + columnToggleError: "No eligible columns.", + sort: "Sort", + swipePreviousColumn: "Previous column", + swipeNextColumn: "Next column" + }, + // cut the mustard + mustard: + "head" in document && // IE9+, Firefox 4+, Safari 5.1+, Mobile Safari 4.1+, Opera 11.5+, Android 2.3+ + (!window.blackberry || window.WebKitPoint) && // only WebKit Blackberry (OS 6+) + !window.operamini, + $: $, + _init: function(element) { + Tablesaw.$(element || document).trigger("enhance.tablesaw"); + }, + init: function(element) { + if (!domContentLoadedTriggered) { + if ("addEventListener" in document) { + // Use raw DOMContentLoaded instead of shoestring (may have issues in Android 2.3, exhibited by stack table) + document.addEventListener("DOMContentLoaded", function() { + Tablesaw._init(element); + }); + } + } else { + Tablesaw._init(element); + } + } +}; + +$(document).on("enhance.tablesaw", function() { + // Extend i18n config, if one exists. + if (typeof TablesawConfig !== "undefined" && TablesawConfig.i18n) { + Tablesaw.i18n = $.extend(Tablesaw.i18n, TablesawConfig.i18n || {}); + } + + Tablesaw.i18n.modes = [ + Tablesaw.i18n.modeStack, + Tablesaw.i18n.modeSwipe, + Tablesaw.i18n.modeToggle + ]; +}); + +if (Tablesaw.mustard) { + $(document.documentElement).addClass("tablesaw-enhanced"); +} + +(function() { + var pluginName = "tablesaw"; + var classes = { + toolbar: "tablesaw-bar" + }; + var events = { + create: "tablesawcreate", + destroy: "tablesawdestroy", + refresh: "tablesawrefresh", + resize: "tablesawresize" + }; + var defaultMode = "stack"; + var initSelector = "table"; + var initFilterSelector = "[data-tablesaw],[data-tablesaw-mode],[data-tablesaw-sortable]"; + var defaultConfig = {}; + + Tablesaw.events = events; + + var Table = function(element) { + if (!element) { + throw new Error("Tablesaw requires an element."); + } + + this.table = element; + this.$table = $(element); + + // only one <thead> and <tfoot> are allowed, per the specification + this.$thead = this.$table + .children() + .filter("thead") + .eq(0); + + // multiple <tbody> are allowed, per the specification + this.$tbody = this.$table.children().filter("tbody"); + + this.mode = this.$table.attr("data-tablesaw-mode") || defaultMode; + + this.$toolbar = null; + + this.attributes = { + subrow: "data-tablesaw-subrow", + ignorerow: "data-tablesaw-ignorerow" + }; + + this.init(); + }; + + Table.prototype.init = function() { + if (!this.$thead.length) { + throw new Error("tablesaw: a <thead> is required, but none was found."); + } + + if (!this.$thead.find("th").length) { + throw new Error("tablesaw: no header cells found. Are you using <th> inside of <thead>?"); + } + + // assign an id if there is none + if (!this.$table.attr("id")) { + this.$table.attr("id", pluginName + "-" + Math.round(Math.random() * 10000)); + } + + this.createToolbar(); + + this._initCells(); + + this.$table.data(pluginName, this); + + this.$table.trigger(events.create, [this]); + }; + + Table.prototype.getConfig = function(pluginSpecificConfig) { + // shoestring extend doesn’t support arbitrary args + var configs = $.extend(defaultConfig, pluginSpecificConfig || {}); + return $.extend(configs, typeof TablesawConfig !== "undefined" ? TablesawConfig : {}); + }; + + Table.prototype._getPrimaryHeaderRow = function() { + return this._getHeaderRows().eq(0); + }; + + Table.prototype._getHeaderRows = function() { + return this.$thead + .children() + .filter("tr") + .filter(function() { + return !$(this).is("[data-tablesaw-ignorerow]"); + }); + }; + + Table.prototype._getRowIndex = function($row) { + return $row.prevAll().length; + }; + + Table.prototype._getHeaderRowIndeces = function() { + var self = this; + var indeces = []; + this._getHeaderRows().each(function() { + indeces.push(self._getRowIndex($(this))); + }); + return indeces; + }; + + Table.prototype._getPrimaryHeaderCells = function($row) { + return ($row || this._getPrimaryHeaderRow()).find("th"); + }; + + Table.prototype._$getCells = function(th) { + var self = this; + return $(th) + .add(th.cells) + .filter(function() { + var $t = $(this); + var $row = $t.parent(); + var hasColspan = $t.is("[colspan]"); + // no subrows or ignored rows (keep cells in ignored rows that do not have a colspan) + return ( + !$row.is("[" + self.attributes.subrow + "]") && + (!$row.is("[" + self.attributes.ignorerow + "]") || !hasColspan) + ); + }); + }; + + Table.prototype._getVisibleColspan = function() { + var colspan = 0; + this._getPrimaryHeaderCells().each(function() { + var $t = $(this); + if ($t.css("display") !== "none") { + colspan += parseInt($t.attr("colspan"), 10) || 1; + } + }); + return colspan; + }; + + Table.prototype.getColspanForCell = function($cell) { + var visibleColspan = this._getVisibleColspan(); + var visibleSiblingColumns = 0; + if ($cell.closest("tr").data("tablesaw-rowspanned")) { + visibleSiblingColumns++; + } + + $cell.siblings().each(function() { + var $t = $(this); + var colColspan = parseInt($t.attr("colspan"), 10) || 1; + + if ($t.css("display") !== "none") { + visibleSiblingColumns += colColspan; + } + }); + // console.log( $cell[ 0 ], visibleColspan, visibleSiblingColumns ); + + return visibleColspan - visibleSiblingColumns; + }; + + Table.prototype.isCellInColumn = function(header, cell) { + return $(header) + .add(header.cells) + .filter(function() { + return this === cell; + }).length; + }; + + Table.prototype.updateColspanCells = function(cls, header, userAction) { + var self = this; + var primaryHeaderRow = self._getPrimaryHeaderRow(); + + // find persistent column rowspans + this.$table.find("[rowspan][data-tablesaw-priority]").each(function() { + var $t = $(this); + if ($t.attr("data-tablesaw-priority") !== "persist") { + return; + } + + var $row = $t.closest("tr"); + var rowspan = parseInt($t.attr("rowspan"), 10); + if (rowspan > 1) { + $row = $row.next(); + + $row.data("tablesaw-rowspanned", true); + + rowspan--; + } + }); + + this.$table + .find("[colspan],[data-tablesaw-maxcolspan]") + .filter(function() { + // is not in primary header row + return $(this).closest("tr")[0] !== primaryHeaderRow[0]; + }) + .each(function() { + var $cell = $(this); + + if (userAction === undefined || self.isCellInColumn(header, this)) { + } else { + // if is not a user action AND the cell is not in the updating column, kill it + return; + } + + var colspan = self.getColspanForCell($cell); + + if (cls && userAction !== undefined) { + // console.log( colspan === 0 ? "addClass" : "removeClass", $cell ); + $cell[colspan === 0 ? "addClass" : "removeClass"](cls); + } + + // cache original colspan + var maxColspan = parseInt($cell.attr("data-tablesaw-maxcolspan"), 10); + if (!maxColspan) { + $cell.attr("data-tablesaw-maxcolspan", $cell.attr("colspan")); + } else if (colspan > maxColspan) { + colspan = maxColspan; + } + + // console.log( this, "setting colspan to ", colspan ); + $cell.attr("colspan", colspan); + }); + }; + + Table.prototype._findPrimaryHeadersForCell = function(cell) { + var $headerRow = this._getPrimaryHeaderRow(); + var $headers = this._getPrimaryHeaderCells($headerRow); + var headerRowIndex = this._getRowIndex($headerRow); + var results = []; + + for (var rowNumber = 0; rowNumber < this.headerMapping.length; rowNumber++) { + if (rowNumber === headerRowIndex) { + continue; + } + for (var colNumber = 0; colNumber < this.headerMapping[rowNumber].length; colNumber++) { + if (this.headerMapping[rowNumber][colNumber] === cell) { + results.push($headers[colNumber]); + } + } + } + return results; + }; + + // used by init cells + Table.prototype.getRows = function() { + var self = this; + return this.$table.find("tr").filter(function() { + return $(this) + .closest("table") + .is(self.$table); + }); + }; + + // used by sortable + Table.prototype.getBodyRows = function(tbody) { + return (tbody ? $(tbody) : this.$tbody).children().filter("tr"); + }; + + Table.prototype.getHeaderCellIndex = function(cell) { + var lookup = this.headerMapping[0]; + for (var colIndex = 0; colIndex < lookup.length; colIndex++) { + if (lookup[colIndex] === cell) { + return colIndex; + } + } + + return -1; + }; + + Table.prototype._initCells = function() { + // re-establish original colspans + this.$table.find("[data-tablesaw-maxcolspan]").each(function() { + var $t = $(this); + $t.attr("colspan", $t.attr("data-tablesaw-maxcolspan")); + }); + + var $rows = this.getRows(); + var columnLookup = []; + + $rows.each(function(rowNumber) { + columnLookup[rowNumber] = []; + }); + + $rows.each(function(rowNumber) { + var coltally = 0; + var $t = $(this); + var children = $t.children(); + + children.each(function() { + var colspan = parseInt( + this.getAttribute("data-tablesaw-maxcolspan") || this.getAttribute("colspan"), + 10 + ); + var rowspan = parseInt(this.getAttribute("rowspan"), 10); + + // set in a previous rowspan + while (columnLookup[rowNumber][coltally]) { + coltally++; + } + + columnLookup[rowNumber][coltally] = this; + + // TODO? both colspan and rowspan + if (colspan) { + for (var k = 0; k < colspan - 1; k++) { + coltally++; + columnLookup[rowNumber][coltally] = this; + } + } + if (rowspan) { + for (var j = 1; j < rowspan; j++) { + columnLookup[rowNumber + j][coltally] = this; + } + } + + coltally++; + }); + }); + + var headerRowIndeces = this._getHeaderRowIndeces(); + for (var colNumber = 0; colNumber < columnLookup[0].length; colNumber++) { + for (var headerIndex = 0, k = headerRowIndeces.length; headerIndex < k; headerIndex++) { + var headerCol = columnLookup[headerRowIndeces[headerIndex]][colNumber]; + + var rowNumber = headerRowIndeces[headerIndex]; + var rowCell; + + if (!headerCol.cells) { + headerCol.cells = []; + } + + while (rowNumber < columnLookup.length) { + rowCell = columnLookup[rowNumber][colNumber]; + + if (headerCol !== rowCell) { + headerCol.cells.push(rowCell); + } + + rowNumber++; + } + } + } + + this.headerMapping = columnLookup; + }; + + Table.prototype.refresh = function() { + this._initCells(); + + this.$table.trigger(events.refresh, [this]); + }; + + Table.prototype._getToolbarAnchor = function() { + var $parent = this.$table.parent(); + if ($parent.is(".tablesaw-overflow")) { + return $parent; + } + return this.$table; + }; + + Table.prototype._getToolbar = function($anchor) { + if (!$anchor) { + $anchor = this._getToolbarAnchor(); + } + return $anchor.prev().filter("." + classes.toolbar); + }; + + Table.prototype.createToolbar = function() { + // Insert the toolbar + // TODO move this into a separate component + var $anchor = this._getToolbarAnchor(); + var $toolbar = this._getToolbar($anchor); + if (!$toolbar.length) { + $toolbar = $("<div>") + .addClass(classes.toolbar) + .insertBefore($anchor); + } + this.$toolbar = $toolbar; + + if (this.mode) { + this.$toolbar.addClass("tablesaw-mode-" + this.mode); + } + }; + + Table.prototype.destroy = function() { + // Don’t remove the toolbar, just erase the classes on it. + // Some of the table features are not yet destroy-friendly. + this._getToolbar().each(function() { + this.className = this.className.replace(/\btablesaw-mode\-\w*\b/gi, ""); + }); + + var tableId = this.$table.attr("id"); + $(document).off("." + tableId); + $(window).off("." + tableId); + + // other plugins + this.$table.trigger(events.destroy, [this]); + + this.$table.removeData(pluginName); + }; + + // Collection method. + $.fn[pluginName] = function() { + return this.each(function() { + var $t = $(this); + + if ($t.data(pluginName)) { + return; + } + + new Table(this); + }); + }; + + var $doc = $(document); + $doc.on("enhance.tablesaw", function(e) { + // Cut the mustard + if (Tablesaw.mustard) { + $(e.target) + .find(initSelector) + .filter(initFilterSelector) + [pluginName](); + } + }); + + // Avoid a resize during scroll: + // Some Mobile devices trigger a resize during scroll (sometimes when + // doing elastic stretch at the end of the document or from the + // location bar hide) + var isScrolling = false; + var scrollTimeout; + $doc.on("scroll.tablesaw", function() { + isScrolling = true; + + window.clearTimeout(scrollTimeout); + scrollTimeout = window.setTimeout(function() { + isScrolling = false; + }, 300); // must be greater than the resize timeout below + }); + + var resizeTimeout; + $(window).on("resize", function() { + if (!isScrolling) { + window.clearTimeout(resizeTimeout); + resizeTimeout = window.setTimeout(function() { + $doc.trigger(events.resize); + }, 150); // must be less than the scrolling timeout above. + } + }); + + Tablesaw.Table = Table; +})(); + +(function() { + var classes = { + stackTable: "tablesaw-stack", + cellLabels: "tablesaw-cell-label", + cellContentLabels: "tablesaw-cell-content" + }; + + var data = { + key: "tablesaw-stack" + }; + + var attrs = { + labelless: "data-tablesaw-no-labels", + hideempty: "data-tablesaw-hide-empty" + }; + + var Stack = function(element, tablesaw) { + this.tablesaw = tablesaw; + this.$table = $(element); + + this.labelless = this.$table.is("[" + attrs.labelless + "]"); + this.hideempty = this.$table.is("[" + attrs.hideempty + "]"); + + this.$table.data(data.key, this); + }; + + Stack.prototype.init = function() { + this.$table.addClass(classes.stackTable); + + if (this.labelless) { + return; + } + + var self = this; + + this.$table + .find("th, td") + .filter(function() { + return !$(this).closest("thead").length; + }) + .filter(function() { + return ( + !$(this) + .closest("tr") + .is("[" + attrs.labelless + "]") && + (!self.hideempty || !!$(this).html()) + ); + }) + .each(function() { + var $newHeader = $(document.createElement("b")).addClass(classes.cellLabels); + var $cell = $(this); + + $(self.tablesaw._findPrimaryHeadersForCell(this)).each(function(index) { + var $header = $(this.cloneNode(true)); + // TODO decouple from sortable better + // Changed from .text() in https://github.com/filamentgroup/tablesaw/commit/b9c12a8f893ec192830ec3ba2d75f062642f935b + // to preserve structural html in headers, like <a> + var $sortableButton = $header.find(".tablesaw-sortable-btn"); + $header.find(".tablesaw-sortable-arrow").remove(); + + // TODO decouple from checkall better + var $checkall = $header.find("[data-tablesaw-checkall]"); + $checkall.closest("label").remove(); + if ($checkall.length) { + $newHeader = $([]); + return; + } + + if (index > 0) { + $newHeader.append(document.createTextNode(", ")); + } + $newHeader.append( + $sortableButton.length ? $sortableButton[0].childNodes : $header[0].childNodes + ); + }); + + if ($newHeader.length && !$cell.find("." + classes.cellContentLabels).length) { + $cell.wrapInner("<span class='" + classes.cellContentLabels + "'></span>"); + } + + // Update if already exists. + var $label = $cell.find("." + classes.cellLabels); + if (!$label.length) { + $cell.prepend($newHeader); + } else { + // only if changed + $label.replaceWith($newHeader); + } + }); + }; + + Stack.prototype.destroy = function() { + this.$table.removeClass(classes.stackTable); + this.$table.find("." + classes.cellLabels).remove(); + this.$table.find("." + classes.cellContentLabels).each(function() { + $(this).replaceWith(this.childNodes); + }); + }; + + // on tablecreate, init + $(document) + .on(Tablesaw.events.create, function(e, tablesaw) { + if (tablesaw.mode === "stack") { + var table = new Stack(tablesaw.table, tablesaw); + table.init(); + } + }) + .on(Tablesaw.events.refresh, function(e, tablesaw) { + if (tablesaw.mode === "stack") { + $(tablesaw.table) + .data(data.key) + .init(); + } + }) + .on(Tablesaw.events.destroy, function(e, tablesaw) { + if (tablesaw.mode === "stack") { + $(tablesaw.table) + .data(data.key) + .destroy(); + } + }); + + Tablesaw.Stack = Stack; +})(); + +(function() { + var pluginName = "tablesawbtn", + methods = { + _create: function() { + return $(this).each(function() { + $(this) + .trigger("beforecreate." + pluginName) + [pluginName]("_init") + .trigger("create." + pluginName); + }); + }, + _init: function() { + var oEl = $(this), + sel = this.getElementsByTagName("select")[0]; + + if (sel) { + // TODO next major version: remove .btn-select + $(this) + .addClass("btn-select tablesaw-btn-select") + [pluginName]("_select", sel); + } + return oEl; + }, + _select: function(sel) { + var update = function(oEl, sel) { + var opts = $(sel).find("option"); + var label = document.createElement("span"); + var el; + var children; + var found = false; + + label.setAttribute("aria-hidden", "true"); + label.innerHTML = " "; + + opts.each(function() { + var opt = this; + if (opt.selected) { + label.innerHTML = opt.text; + } + }); + + children = oEl.childNodes; + if (opts.length > 0) { + for (var i = 0, l = children.length; i < l; i++) { + el = children[i]; + + if (el && el.nodeName.toUpperCase() === "SPAN") { + oEl.replaceChild(label, el); + found = true; + } + } + + if (!found) { + oEl.insertBefore(label, oEl.firstChild); + } + } + }; + + update(this, sel); + // todo should this be tablesawrefresh? + $(this).on("change refresh", function() { + update(this, sel); + }); + } + }; + + // Collection method. + $.fn[pluginName] = function(arrg, a, b, c) { + return this.each(function() { + // if it's a method + if (arrg && typeof arrg === "string") { + return $.fn[pluginName].prototype[arrg].call(this, a, b, c); + } + + // don't re-init + if ($(this).data(pluginName + "active")) { + return $(this); + } + + $(this).data(pluginName + "active", true); + + $.fn[pluginName].prototype._create.call(this); + }); + }; + + // add methods + $.extend($.fn[pluginName].prototype, methods); + + // TODO OOP this and add to Tablesaw object +})(); + +(function() { + var data = { + key: "tablesaw-coltoggle" + }; + + var ColumnToggle = function(element) { + this.$table = $(element); + + if (!this.$table.length) { + return; + } + + this.tablesaw = this.$table.data("tablesaw"); + + this.attributes = { + btnTarget: "data-tablesaw-columntoggle-btn-target", + set: "data-tablesaw-columntoggle-set" + }; + + this.classes = { + columnToggleTable: "tablesaw-columntoggle", + columnBtnContain: "tablesaw-columntoggle-btnwrap tablesaw-advance", + columnBtn: "tablesaw-columntoggle-btn tablesaw-nav-btn down", + popup: "tablesaw-columntoggle-popup", + priorityPrefix: "tablesaw-priority-" + }; + + this.set = []; + this.$headers = this.tablesaw._getPrimaryHeaderCells(); + + this.$table.data(data.key, this); + }; + + // Column Toggle Sets (one column chooser can control multiple tables) + ColumnToggle.prototype.initSet = function() { + var set = this.$table.attr(this.attributes.set); + if (set) { + // Should not include the current table + var table = this.$table[0]; + this.set = $("table[" + this.attributes.set + "='" + set + "']") + .filter(function() { + return this !== table; + }) + .get(); + } + }; + + ColumnToggle.prototype.init = function() { + if (!this.$table.length) { + return; + } + + var tableId, + id, + $menuButton, + $popup, + $menu, + $btnContain, + self = this; + + var cfg = this.tablesaw.getConfig({ + getColumnToggleLabelTemplate: function(text) { + return "<label><input type='checkbox' checked>" + text + "</label>"; + } + }); + + this.$table.addClass(this.classes.columnToggleTable); + + tableId = this.$table.attr("id"); + id = tableId + "-popup"; + $btnContain = $("<div class='" + this.classes.columnBtnContain + "'></div>"); + // TODO next major version: remove .btn + $menuButton = $( + "<a href='#" + + id + + "' class='btn tablesaw-btn btn-micro " + + this.classes.columnBtn + + "' data-popup-link>" + + "<span>" + + Tablesaw.i18n.columnToggleButton + + "</span></a>" + ); + $popup = $("<div class='" + this.classes.popup + "' id='" + id + "'></div>"); + $menu = $("<div class='btn-group'></div>"); + + this.$popup = $popup; + + var hasNonPersistentHeaders = false; + this.$headers.each(function() { + var $this = $(this), + priority = $this.attr("data-tablesaw-priority"), + $cells = self.tablesaw._$getCells(this); + + if (priority && priority !== "persist") { + $cells.addClass(self.classes.priorityPrefix + priority); + + $(cfg.getColumnToggleLabelTemplate($this.text())) + .appendTo($menu) + .find('input[type="checkbox"]') + .data("tablesaw-header", this); + + hasNonPersistentHeaders = true; + } + }); + + if (!hasNonPersistentHeaders) { + $menu.append("<label>" + Tablesaw.i18n.columnToggleError + "</label>"); + } + + $menu.appendTo($popup); + + function onToggleCheckboxChange(checkbox) { + var checked = checkbox.checked; + + var header = self.getHeaderFromCheckbox(checkbox); + var $cells = self.tablesaw._$getCells(header); + + $cells[!checked ? "addClass" : "removeClass"]("tablesaw-toggle-cellhidden"); + $cells[checked ? "addClass" : "removeClass"]("tablesaw-toggle-cellvisible"); + + self.updateColspanCells(header, checked); + + self.$table.trigger("tablesawcolumns"); + } + + // bind change event listeners to inputs - TODO: move to a private method? + $menu.find('input[type="checkbox"]').on("change", function(e) { + onToggleCheckboxChange(e.target); + + if (self.set.length) { + var index; + $(self.$popup) + .find("input[type='checkbox']") + .each(function(j) { + if (this === e.target) { + index = j; + return false; + } + }); + + $(self.set).each(function() { + var checkbox = $(this) + .data(data.key) + .$popup.find("input[type='checkbox']") + .get(index); + if (checkbox) { + checkbox.checked = e.target.checked; + onToggleCheckboxChange(checkbox); + } + }); + } + }); + + $menuButton.appendTo($btnContain); + + // Use a different target than the toolbar + var $btnTarget = $(this.$table.attr(this.attributes.btnTarget)); + $btnContain.appendTo($btnTarget.length ? $btnTarget : this.tablesaw.$toolbar); + + function closePopup(event) { + // Click came from inside the popup, ignore. + if (event && $(event.target).closest("." + self.classes.popup).length) { + return; + } + + $(document).off("click." + tableId); + $menuButton.removeClass("up").addClass("down"); + $btnContain.removeClass("visible"); + } + + var closeTimeout; + function openPopup() { + $btnContain.addClass("visible"); + $menuButton.removeClass("down").addClass("up"); + + $(document).off("click." + tableId, closePopup); + + window.clearTimeout(closeTimeout); + closeTimeout = window.setTimeout(function() { + $(document).on("click." + tableId, closePopup); + }, 15); + } + + $menuButton.on("click.tablesaw", function(event) { + event.preventDefault(); + + if (!$btnContain.is(".visible")) { + openPopup(); + } else { + closePopup(); + } + }); + + $popup.appendTo($btnContain); + + this.$menu = $menu; + + // Fix for iOS not rendering shadows correctly when using `-webkit-overflow-scrolling` + var $overflow = this.$table.closest(".tablesaw-overflow"); + if ($overflow.css("-webkit-overflow-scrolling")) { + var timeout; + $overflow.on("scroll", function() { + var $div = $(this); + window.clearTimeout(timeout); + timeout = window.setTimeout(function() { + $div.css("-webkit-overflow-scrolling", "auto"); + window.setTimeout(function() { + $div.css("-webkit-overflow-scrolling", "touch"); + }, 0); + }, 100); + }); + } + + $(window).on(Tablesaw.events.resize + "." + tableId, function() { + self.refreshToggle(); + }); + + this.initSet(); + this.refreshToggle(); + }; + + ColumnToggle.prototype.getHeaderFromCheckbox = function(checkbox) { + return $(checkbox).data("tablesaw-header"); + }; + + ColumnToggle.prototype.refreshToggle = function() { + var self = this; + var invisibleColumns = 0; + this.$menu.find("input").each(function() { + var header = self.getHeaderFromCheckbox(this); + this.checked = + self.tablesaw + ._$getCells(header) + .eq(0) + .css("display") === "table-cell"; + }); + + this.updateColspanCells(); + }; + + ColumnToggle.prototype.updateColspanCells = function(header, userAction) { + this.tablesaw.updateColspanCells("tablesaw-toggle-cellhidden", header, userAction); + }; + + ColumnToggle.prototype.destroy = function() { + this.$table.removeClass(this.classes.columnToggleTable); + this.$table.find("th, td").each(function() { + var $cell = $(this); + $cell.removeClass("tablesaw-toggle-cellhidden").removeClass("tablesaw-toggle-cellvisible"); + + this.className = this.className.replace(/\bui\-table\-priority\-\d\b/g, ""); + }); + }; + + // on tablecreate, init + $(document).on(Tablesaw.events.create, function(e, tablesaw) { + if (tablesaw.mode === "columntoggle") { + var table = new ColumnToggle(tablesaw.table); + table.init(); + } + }); + + $(document).on(Tablesaw.events.destroy, function(e, tablesaw) { + if (tablesaw.mode === "columntoggle") { + $(tablesaw.table) + .data(data.key) + .destroy(); + } + }); + + $(document).on(Tablesaw.events.refresh, function(e, tablesaw) { + if (tablesaw.mode === "columntoggle") { + $(tablesaw.table) + .data(data.key) + .refreshPriority(); + } + }); + + Tablesaw.ColumnToggle = ColumnToggle; +})(); + +(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 ? "↑" : "(A-Z)") + + "</option>" + ); + html.push( + "<option" + + (isDefaultCol && isDescending ? " selected" : "") + + ' value="' + + j + + '_desc">' + + $t.text() + + " " + + (isNumeric ? "↓" : "(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 +})(); + +(function() { + var classes = { + hideBtn: "disabled", + persistWidths: "tablesaw-fix-persist", + hiddenCol: "tablesaw-swipe-cellhidden", + persistCol: "tablesaw-swipe-cellpersist", + allColumnsVisible: "tablesaw-all-cols-visible" + }; + var attrs = { + disableTouchEvents: "data-tablesaw-no-touch", + ignorerow: "data-tablesaw-ignorerow", + subrow: "data-tablesaw-subrow" + }; + + function createSwipeTable(tbl, $table) { + var tblsaw = $table.data("tablesaw"); + + var $btns = $("<div class='tablesaw-advance'></div>"); + // TODO next major version: remove .btn + var $prevBtn = $( + "<a href='#' class='btn tablesaw-nav-btn tablesaw-btn btn-micro left'>" + + Tablesaw.i18n.swipePreviousColumn + + "</a>" + ).appendTo($btns); + // TODO next major version: remove .btn + var $nextBtn = $( + "<a href='#' class='btn tablesaw-nav-btn tablesaw-btn btn-micro right'>" + + Tablesaw.i18n.swipeNextColumn + + "</a>" + ).appendTo($btns); + + var $headerCells = tbl._getPrimaryHeaderCells(); + var $headerCellsNoPersist = $headerCells.not('[data-tablesaw-priority="persist"]'); + var headerWidths = []; + var $head = $(document.head || "head"); + var tableId = $table.attr("id"); + + if (!$headerCells.length) { + throw new Error("tablesaw swipe: no header cells found."); + } + + $table.addClass("tablesaw-swipe"); + + function initMinHeaderWidths() { + $table.css({ + width: "1px" + }); + + // remove any hidden columns + $table.find("." + classes.hiddenCol).removeClass(classes.hiddenCol); + + headerWidths = []; + // Calculate initial widths + $headerCells.each(function() { + headerWidths.push(this.offsetWidth); + }); + + // reset props + $table.css({ + width: "" + }); + } + + initMinHeaderWidths(); + + $btns.appendTo(tblsaw.$toolbar); + + if (!tableId) { + tableId = "tableswipe-" + Math.round(Math.random() * 10000); + $table.attr("id", tableId); + } + + function showColumn(headerCell) { + tblsaw._$getCells(headerCell).removeClass(classes.hiddenCol); + } + + function hideColumn(headerCell) { + tblsaw._$getCells(headerCell).addClass(classes.hiddenCol); + } + + function persistColumn(headerCell) { + tblsaw._$getCells(headerCell).addClass(classes.persistCol); + } + + function isPersistent(headerCell) { + return $(headerCell).is('[data-tablesaw-priority="persist"]'); + } + + function unmaintainWidths() { + $table.removeClass(classes.persistWidths); + $("#" + tableId + "-persist").remove(); + } + + function maintainWidths() { + var prefix = "#" + tableId + ".tablesaw-swipe ", + styles = [], + tableWidth = $table.width(), + hash = [], + newHash; + + // save persistent column widths (as long as they take up less than 75% of table width) + $headerCells.each(function(index) { + var width; + if (isPersistent(this)) { + width = this.offsetWidth; + + if (width < tableWidth * 0.75) { + hash.push(index + "-" + width); + styles.push( + prefix + + " ." + + classes.persistCol + + ":nth-child(" + + (index + 1) + + ") { width: " + + width + + "px; }" + ); + } + } + }); + newHash = hash.join("_"); + + if (styles.length) { + $table.addClass(classes.persistWidths); + var $style = $("#" + tableId + "-persist"); + // If style element not yet added OR if the widths have changed + if (!$style.length || $style.data("tablesaw-hash") !== newHash) { + // Remove existing + $style.remove(); + + $("<style>" + styles.join("\n") + "</style>") + .attr("id", tableId + "-persist") + .data("tablesaw-hash", newHash) + .appendTo($head); + } + } + } + + function getNext() { + var next = [], + checkFound; + + $headerCellsNoPersist.each(function(i) { + var $t = $(this), + isHidden = $t.css("display") === "none" || $t.is("." + classes.hiddenCol); + + if (!isHidden && !checkFound) { + checkFound = true; + next[0] = i; + } else if (isHidden && checkFound) { + next[1] = i; + + return false; + } + }); + + return next; + } + + function getPrev() { + var next = getNext(); + return [next[1] - 1, next[0] - 1]; + } + + function nextpair(fwd) { + return fwd ? getNext() : getPrev(); + } + + function canAdvance(pair) { + return pair[1] > -1 && pair[1] < $headerCellsNoPersist.length; + } + + function matchesMedia() { + var matchMedia = $table.attr("data-tablesaw-swipe-media"); + return !matchMedia || ("matchMedia" in window && window.matchMedia(matchMedia).matches); + } + + function fakeBreakpoints() { + if (!matchesMedia()) { + return; + } + + var containerWidth = $table.parent().width(), + persist = [], + sum = 0, + sums = [], + visibleNonPersistantCount = $headerCells.length; + + $headerCells.each(function(index) { + var $t = $(this), + isPersist = $t.is('[data-tablesaw-priority="persist"]'); + + persist.push(isPersist); + sum += headerWidths[index]; + sums.push(sum); + + // is persistent or is hidden + if (isPersist || sum > containerWidth) { + visibleNonPersistantCount--; + } + }); + + // We need at least one column to swipe. + var needsNonPersistentColumn = visibleNonPersistantCount === 0; + + $headerCells.each(function(index) { + if (sums[index] > containerWidth) { + hideColumn(this); + } + }); + + $headerCells.each(function(index) { + if (persist[index]) { + // for visual box-shadow + persistColumn(this); + return; + } + + if (sums[index] <= containerWidth || needsNonPersistentColumn) { + needsNonPersistentColumn = false; + showColumn(this); + tblsaw.updateColspanCells(classes.hiddenCol, this, true); + } + }); + + unmaintainWidths(); + + $table.trigger("tablesawcolumns"); + } + + function advance(fwd) { + var pair = nextpair(fwd); + if (canAdvance(pair)) { + if (isNaN(pair[0])) { + if (fwd) { + pair[0] = 0; + } else { + pair[0] = $headerCellsNoPersist.length - 1; + } + } + + // TODO just blindly hiding the previous column and showing the next column can result in + // column content overflow + maintainWidths(); + hideColumn($headerCellsNoPersist.get(pair[0])); + tblsaw.updateColspanCells(classes.hiddenCol, $headerCellsNoPersist.get(pair[0]), false); + + showColumn($headerCellsNoPersist.get(pair[1])); + tblsaw.updateColspanCells(classes.hiddenCol, $headerCellsNoPersist.get(pair[1]), true); + + $table.trigger("tablesawcolumns"); + } + } + + $prevBtn.add($nextBtn).on("click", function(e) { + advance(!!$(e.target).closest($nextBtn).length); + e.preventDefault(); + }); + + function getCoord(event, key) { + return (event.touches || event.originalEvent.touches)[0][key]; + } + + if (!$table.is("[" + attrs.disableTouchEvents + "]")) { + $table.on("touchstart.swipetoggle", function(e) { + var originX = getCoord(e, "pageX"); + var originY = getCoord(e, "pageY"); + var x; + var y; + var scrollTop = window.pageYOffset; + + $(window).off(Tablesaw.events.resize, fakeBreakpoints); + + $(this) + .on("touchmove.swipetoggle", function(e) { + x = getCoord(e, "pageX"); + y = getCoord(e, "pageY"); + }) + .on("touchend.swipetoggle", function() { + var cfg = tbl.getConfig({ + swipeHorizontalThreshold: 30, + swipeVerticalThreshold: 30 + }); + + // This config code is a little awkward because shoestring doesn’t support deep $.extend + // Trying to work around when devs only override one of (not both) horizontalThreshold or + // verticalThreshold in their TablesawConfig. + // @TODO major version bump: remove cfg.swipe, move to just use the swipePrefix keys + var verticalThreshold = cfg.swipe + ? cfg.swipe.verticalThreshold + : cfg.swipeVerticalThreshold; + var horizontalThreshold = cfg.swipe + ? cfg.swipe.horizontalThreshold + : cfg.swipeHorizontalThreshold; + + var isPageScrolled = Math.abs(window.pageYOffset - scrollTop) >= verticalThreshold; + var isVerticalSwipe = Math.abs(y - originY) >= verticalThreshold; + + if (!isVerticalSwipe && !isPageScrolled) { + if (x - originX < -1 * horizontalThreshold) { + advance(true); + } + if (x - originX > horizontalThreshold) { + advance(false); + } + } + + window.setTimeout(function() { + $(window).on(Tablesaw.events.resize, fakeBreakpoints); + }, 300); + + $(this).off("touchmove.swipetoggle touchend.swipetoggle"); + }); + }); + } + + $table + .on("tablesawcolumns.swipetoggle", function() { + var canGoPrev = canAdvance(getPrev()); + var canGoNext = canAdvance(getNext()); + $prevBtn[canGoPrev ? "removeClass" : "addClass"](classes.hideBtn); + $nextBtn[canGoNext ? "removeClass" : "addClass"](classes.hideBtn); + + tblsaw.$toolbar[!canGoPrev && !canGoNext ? "addClass" : "removeClass"]( + classes.allColumnsVisible + ); + }) + .on("tablesawnext.swipetoggle", function() { + advance(true); + }) + .on("tablesawprev.swipetoggle", function() { + advance(false); + }) + .on(Tablesaw.events.destroy + ".swipetoggle", function() { + var $t = $(this); + + $t.removeClass("tablesaw-swipe"); + tblsaw.$toolbar.find(".tablesaw-advance").remove(); + $(window).off(Tablesaw.events.resize, fakeBreakpoints); + + $t.off(".swipetoggle"); + }) + .on(Tablesaw.events.refresh, function() { + unmaintainWidths(); + initMinHeaderWidths(); + fakeBreakpoints(); + }); + + fakeBreakpoints(); + $(window).on(Tablesaw.events.resize, fakeBreakpoints); + } + + // on tablecreate, init + $(document).on(Tablesaw.events.create, function(e, tablesaw) { + if (tablesaw.mode === "swipe") { + createSwipeTable(tablesaw, tablesaw.$table); + } + }); + + // TODO OOP this and add to Tablesaw object +})(); + +(function() { + var MiniMap = { + attr: { + init: "data-tablesaw-minimap" + }, + show: function(table) { + var mq = table.getAttribute(MiniMap.attr.init); + + if (mq === "") { + // value-less but exists + return true; + } else if (mq && "matchMedia" in window) { + // has a mq value + return window.matchMedia(mq).matches; + } + + return false; + } + }; + + function createMiniMap($table) { + var tblsaw = $table.data("tablesaw"); + var $btns = $('<div class="tablesaw-advance minimap">'); + var $dotNav = $('<ul class="tablesaw-advance-dots">').appendTo($btns); + var hideDot = "tablesaw-advance-dots-hide"; + var $headerCells = $table.data("tablesaw")._getPrimaryHeaderCells(); + + // populate dots + $headerCells.each(function() { + $dotNav.append("<li><i></i></li>"); + }); + + $btns.appendTo(tblsaw.$toolbar); + + function showHideNav() { + if (!MiniMap.show($table[0])) { + $btns.css("display", "none"); + return; + } + $btns.css("display", "block"); + + // show/hide dots + var dots = $dotNav.find("li").removeClass(hideDot); + $table.find("thead th").each(function(i) { + if ($(this).css("display") === "none") { + dots.eq(i).addClass(hideDot); + } + }); + } + + // run on init and resize + showHideNav(); + $(window).on(Tablesaw.events.resize, showHideNav); + + $table + .on("tablesawcolumns.minimap", function() { + showHideNav(); + }) + .on(Tablesaw.events.destroy + ".minimap", function() { + var $t = $(this); + + tblsaw.$toolbar.find(".tablesaw-advance").remove(); + $(window).off(Tablesaw.events.resize, showHideNav); + + $t.off(".minimap"); + }); + } + + // on tablecreate, init + $(document).on(Tablesaw.events.create, function(e, tablesaw) { + if ( + (tablesaw.mode === "swipe" || tablesaw.mode === "columntoggle") && + tablesaw.$table.is("[ " + MiniMap.attr.init + "]") + ) { + createMiniMap(tablesaw.$table); + } + }); + + // TODO OOP this better + Tablesaw.MiniMap = MiniMap; +})(); + +(function() { + var S = { + selectors: { + init: "table[data-tablesaw-mode-switch]" + }, + attributes: { + excludeMode: "data-tablesaw-mode-exclude" + }, + classes: { + main: "tablesaw-modeswitch", + toolbar: "tablesaw-bar-section" + }, + modes: ["stack", "swipe", "columntoggle"], + init: function(table) { + var $table = $(table); + var tblsaw = $table.data("tablesaw"); + var ignoreMode = $table.attr(S.attributes.excludeMode); + var $toolbar = tblsaw.$toolbar; + var $switcher = $("<div>").addClass(S.classes.main + " " + S.classes.toolbar); + + var html = [ + '<label><span class="abbreviated">' + + Tablesaw.i18n.modeSwitchColumnsAbbreviated + + '</span><span class="longform">' + + Tablesaw.i18n.modeSwitchColumns + + "</span>:" + ], + dataMode = $table.attr("data-tablesaw-mode"), + isSelected; + + // TODO next major version: remove .btn + html.push('<span class="btn tablesaw-btn"><select>'); + for (var j = 0, k = S.modes.length; j < k; j++) { + if (ignoreMode && ignoreMode.toLowerCase() === S.modes[j]) { + continue; + } + + isSelected = dataMode === S.modes[j]; + + html.push( + "<option" + + (isSelected ? " selected" : "") + + ' value="' + + S.modes[j] + + '">' + + Tablesaw.i18n.modes[j] + + "</option>" + ); + } + html.push("</select></span></label>"); + + $switcher.html(html.join("")); + + var $otherToolbarItems = $toolbar.find(".tablesaw-advance").eq(0); + if ($otherToolbarItems.length) { + $switcher.insertBefore($otherToolbarItems); + } else { + $switcher.appendTo($toolbar); + } + + $switcher.find(".tablesaw-btn").tablesawbtn(); + $switcher.find("select").on("change", function(event) { + return S.onModeChange.call(table, event, $(this).val()); + }); + }, + onModeChange: function(event, val) { + var $table = $(this); + var tblsaw = $table.data("tablesaw"); + var $switcher = tblsaw.$toolbar.find("." + S.classes.main); + + $switcher.remove(); + tblsaw.destroy(); + + $table.attr("data-tablesaw-mode", val); + $table.tablesaw(); + } + }; + + $(document).on(Tablesaw.events.create, function(e, Tablesaw) { + if (Tablesaw.$table.is(S.selectors.init)) { + S.init(Tablesaw.table); + } + }); + + // TODO OOP this and add to Tablesaw object +})(); + +(function() { + var pluginName = "tablesawCheckAll"; + + function CheckAll(tablesaw) { + this.tablesaw = tablesaw; + this.$table = tablesaw.$table; + + this.attr = "data-tablesaw-checkall"; + this.checkAllSelector = "[" + this.attr + "]"; + this.forceCheckedSelector = "[" + this.attr + "-checked]"; + this.forceUncheckedSelector = "[" + this.attr + "-unchecked]"; + this.checkboxSelector = 'input[type="checkbox"]'; + + this.$triggers = null; + this.$checkboxes = null; + + if (this.$table.data(pluginName)) { + return; + } + this.$table.data(pluginName, this); + this.init(); + } + + CheckAll.prototype._filterCells = function($checkboxes) { + return $checkboxes + .filter(function() { + return !$(this) + .closest("tr") + .is("[data-tablesaw-subrow],[data-tablesaw-ignorerow]"); + }) + .find(this.checkboxSelector) + .not(this.checkAllSelector); + }; + + // With buttons you can use a scoping selector like: data-tablesaw-checkall="#my-scoped-id input[type='checkbox']" + CheckAll.prototype.getCheckboxesForButton = function(button) { + return this._filterCells($($(button).attr(this.attr))); + }; + + CheckAll.prototype.getCheckboxesForCheckbox = function(checkbox) { + return this._filterCells($($(checkbox).closest("th")[0].cells)); + }; + + CheckAll.prototype.init = function() { + var self = this; + this.$table.find(this.checkAllSelector).each(function() { + var $trigger = $(this); + if ($trigger.is(self.checkboxSelector)) { + self.addCheckboxEvents(this); + } else { + self.addButtonEvents(this); + } + }); + }; + + CheckAll.prototype.addButtonEvents = function(trigger) { + var self = this; + + // Update body checkboxes when header checkbox is changed + $(trigger).on("click", function(event) { + event.preventDefault(); + + var $checkboxes = self.getCheckboxesForButton(this); + + var allChecked = true; + $checkboxes.each(function() { + if (!this.checked) { + allChecked = false; + } + }); + + var setChecked; + if ($(this).is(self.forceCheckedSelector)) { + setChecked = true; + } else if ($(this).is(self.forceUncheckedSelector)) { + setChecked = false; + } else { + setChecked = allChecked ? false : true; + } + + $checkboxes.each(function() { + this.checked = setChecked; + + $(this).trigger("change." + pluginName); + }); + }); + }; + + CheckAll.prototype.addCheckboxEvents = function(trigger) { + var self = this; + + // Update body checkboxes when header checkbox is changed + $(trigger).on("change", function() { + var setChecked = this.checked; + + self.getCheckboxesForCheckbox(this).each(function() { + this.checked = setChecked; + }); + }); + + var $checkboxes = self.getCheckboxesForCheckbox(trigger); + + // Update header checkbox when body checkboxes are changed + $checkboxes.on("change." + pluginName, function() { + var checkedCount = 0; + $checkboxes.each(function() { + if (this.checked) { + checkedCount++; + } + }); + + var allSelected = checkedCount === $checkboxes.length; + + trigger.checked = allSelected; + + // only indeterminate if some are selected (not all and not none) + trigger.indeterminate = checkedCount !== 0 && !allSelected; + }); + }; + + // on tablecreate, init + $(document).on(Tablesaw.events.create, function(e, tablesaw) { + new CheckAll(tablesaw); + }); + + Tablesaw.CheckAll = CheckAll; +})(); + + return Tablesaw; +}));