diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000..dc731ee Binary files /dev/null and b/.DS_Store differ diff --git a/bower.json b/bower.json index b77cfb6..50dc509 100644 --- a/bower.json +++ b/bower.json @@ -23,5 +23,10 @@ "iron-demo-helpers": "PolymerElements/iron-demo-helpers#^1.0.0", "web-component-tester": "^4.0.0", "webcomponentsjs": "webcomponents/webcomponentsjs#^0.7.0" + }, + "resolutions": { + "iron-ajax": "^2.1.3", + "paper-button": "^2.1.0", + "webcomponentsjs": "^v1.0.19" } } diff --git a/editable-table.html b/editable-table.html index 59c73b6..a0ccea1 100644 --- a/editable-table.html +++ b/editable-table.html @@ -5,6 +5,7 @@ + + + + + + + + +``` + +Keep in mind that the priorities are not exclusive—multiple columns can reuse the same priority value. + + + +## Swipe Mode + +* [Swipe Demo](http://filamentgroup.github.io/tablesaw/demo/swipe.html) + +Allows the user to use the swipe gesture (or use the left and right buttons) to navigate the columns. + +![](docs/swipe-minimap.gif) + +```html + +``` + + +| Options | Description | +| --- | --- | +| Persist a Column | Columns also respect the `data-tablesaw-priority="persist"` attribute: `` | +| Add a Mini-Map | The little dots that appear next to the column navigation buttons. Use the `data-tablesaw-minimap` attribute: `
` | +| All columns visible class | Tablesaw also exposes a `tablesaw-all-cols-visible` class that is toggled on when all of the table columns are visible (and off when not). You can use this in CSS to hide the minimap or navigation buttons if needed. | +| Disable swipe touch events | Use the `
` attribute to opt-out of swiping left or right to navigate columns. Users will need to use the provided buttons instead. | + +
+Advanced Option: Configure Swipe Thresholds + +Add a `TablesawConfig` object to your page in a ` +``` + +* [Configure Swipe Threshold Demo](http://filamentgroup.github.io/tablesaw/demo/swipe-config.html) + +
+ +## Mini Map + +Use `data-tablesaw-minimap` to add a series of small dots to show which columns are currently visible and which are hidden. Only available on `swipe` and `columntoggle` tables. Examples available above. + +## Mode Switcher + +* [Mode Switcher Demo](http://filamentgroup.github.io/tablesaw/demo/modeswitch.html) + +![](docs/mode-switch.gif) + +```html +
+ + +
+ + +
+``` + +## Sortable + +* [Sortable Demo](http://filamentgroup.github.io/tablesaw/demo/sort.html) + +The “sortable” option allows the user to sort the table data by clicking on the table headers. Since all the columns may not be visible on smaller breakpoints (or not there at all if using the “stack” table mode), relying solely on the column headers to choose the table sort isn’t practical. To address this, there is an optional `data-tablesaw-sortable-switch` attribute on the table that adds a select menu auto-populated with the names of each column in the table with options for choosing ascending or descending sort direction. Data options on table headers can be used to control which columns are sortable (`data-tablesaw-sortable-col`) and the default sort order (`data-tablesaw-sortable-default-col`). + +```html +
+ + + + + + + + + + + + ... +``` + +Use `data-tablesaw-sortable-switch` to add a select form element to manually choose the sort order. + +```html +
RankMovie TitleYearRatingReviews
+``` + +![](docs/sortable.png) + +
+Advanced Option: Custom Sort Functions + +Tablesaw provides two methods of sorting built-in: string and numeric. To use numeric sort, use the `data-tablesaw-sortable-numeric` class as shown in the above sorting markup example. Otherwise, tablesaw uses a case insensitive string sort. + +All other types of sorting must use a Custom Sort function on the individual columns ([working example](http://filamentgroup.github.io/tablesaw/demo/sort-custom.html)). In the contrived example below, we want to sort full dates (e.g. `12/02/2014`) just on the year. + +``` +// Add a data function to the table header cell +$( "th#custom-sort" ).data( "tablesaw-sort", function( ascending ) { + // return a function + return function( a, b ) { + // Ignore rows with data-tablesaw-ignorerow (leave them where they were) + if( a.ignored || b.ignored ) { + return 0; + } + + // use a.cell and b.cell for cell values + var dateA = a.cell.split( "/" ), + dateB = b.cell.split( "/" ), + yearA = parseInt( dateA[ 2 ], 10 ), + yearB = parseInt( dateB[ 2 ], 10 ); + + if( ascending ) { + return yearA >= yearB ? 1 : -1; + } else { // descending + return yearA < yearB ? 1 : -1; + } + }; +}); +``` + +
+ +## Kitchen ~~Table~~ Sink + +* [Kitchen Sink Demo](http://filamentgroup.github.io/tablesaw/demo/kitchensink.html) + +All of the above options combined into a single table. + +## Check All + +_Added in 3.0.1._ Add the `data-tablesaw-checkall` to a checkbox in a `thead` cell to enable that checkbox to toggle the other checkboxes in the same column. + +* [Check All Demo](http://filamentgroup.github.io/tablesaw/demo/checkall.html) + +## Internationalization i18n + +_Added in 3.0.2._ Use the `TablesawConfig` global on your page to override internationalization strings. It doesn’t matter if it’s declared before or after the Tablesaw JavaScript library. + +```js + +``` + +## Getting Started + +Available through npm: + +``` +npm install tablesaw +``` + +### The Full Tablesaw + +
+Tablesaw (no dependencies) + +```html + + + +``` + +
+ +
+or Tablesaw (jQuery Plugin) + +```html + + + + + +``` + +
+ +Don’t forget to add your table markup! For a stack table, this is how it’d look: + +```html +
+``` + +The demos above include full markup examples for all of the Tablesaw types. + +#### Manual initialization of Tablesaw Components + +If you want to initialize your Tablesaw tables manually, don’t include ` + +``` + + + +
+or just Stack-only Tablesaw (jQuery Plugin) + +```html + + + + + +``` + +
+ +And then: + +```html +
+``` + +### Using Stack-Only Tablesaw SCSS Mixin + +To easily customize the breakpoint at which the stack table switches, use the SCSS mixin. First, include the `tablesaw.stackonly.scss` file instead of `tablesaw.stackonly.css` in your SASS. Then, use a parent selector on your table. + +```html +
+
+``` + +Include the mixin like so: + +```scss +.my-parent-selector { + @include tablesaw-stack( 50em ); +} +``` + +The argument to `tablesaw-stack` is the breakpoint at which the table will switch from columns to stacked. + +### Default Styles + +_Starting with Tablesaw 3.0, the “Bare”, or stripped down style version of Tablesaw has been made the default._ + +Some of the more intrusive default styles have instead moved to opt-in classes you can add to the `
` element: + +* `tablesaw-row-border`: Adds a bottom border to each table row. +* `tablesaw-row-zebra`: Adds a light background color to every other table row. +* `tablesaw-swipe-shadow`: Adds the light shadow to the right of persistant columns to make them stand out a little more. + +## Limitations + +* Simple `colspan` and `rowspan` are supported, in part thanks to a [lovely PR](https://github.com/filamentgroup/tablesaw/pull/225) from @jgibson. + +| | Stack | Column Toggle | Swipe | Sortable | +| --- | --- | --- | --- | --- | +| `rowspan` | _Not yet supported_ ([#247](https://github.com/filamentgroup/tablesaw/issues/247)) | Supported | Supported | _Not yet supported_ ([#268](https://github.com/filamentgroup/tablesaw/issues/268)) | +| `colspan` | Supported | Supported | Supported | Supported | + +## [Tests](http://filamentgroup.github.io/tablesaw/test-qunit/tablesaw.html) + +## Browser Support + +All major browsers (evergreens are not listed, but supported). Notably this project cuts the mustard for A-grade support with: + +* Internet Explorer 9+ +* Android Browser 2.3+ +* Blackberry OS 6+ + +Other legacy browsers and Opera Mini receive unenhanced table markup. + +## Bundler Compatibility + +* Added in `v3.0.6`: [tested to work in Webpack](./demo/webpack/). + +## Building the Project Locally + +Run `npm install` to install dependencies and then `grunt` to build the project files into the `dist` folder. + +## Release Names + +* [3.0.3: Cucumbertree](https://github.com/filamentgroup/tablesaw/releases/tag/v3.0.3) +* [3.0.2: Bald Cypress](https://github.com/filamentgroup/tablesaw/releases/tag/v3.0.2) +* [3.0.1: Cypress](https://github.com/filamentgroup/tablesaw/releases/tag/v3.0.1) +* [3.0.0: Rosewood](https://github.com/filamentgroup/tablesaw/releases/tag/v3.0.0) +* [2.0.1: Mountain Hemlock](https://github.com/filamentgroup/tablesaw/releases/tag/v2.0.1) +* [2.0.0: Hemlock](https://github.com/filamentgroup/tablesaw/releases/tag/v2.0.0) +* [1.0.5: Hickory](https://github.com/filamentgroup/tablesaw/releases/tag/v1.0.5) +* [1.0.4: Ironwood](https://github.com/filamentgroup/tablesaw/releases/tag/v1.0.4) +* [1.0.3: Red Mahogany](https://github.com/filamentgroup/tablesaw/releases/tag/v1.0.3) + +_Previous versions didn’t have names._ diff --git a/js/tablesaw/dist/.DS_Store b/js/tablesaw/dist/.DS_Store new file mode 100644 index 0000000..5008ddf Binary files /dev/null and b/js/tablesaw/dist/.DS_Store differ diff --git a/js/tablesaw/dist/tablesaw-init.js b/js/tablesaw/dist/tablesaw-init.js new file mode 100755 index 0000000..a4beea1 --- /dev/null +++ b/js/tablesaw/dist/tablesaw-init.js @@ -0,0 +1,18 @@ +/*! Tablesaw - v3.0.9 - 2018-02-14 +* https://github.com/filamentgroup/tablesaw +* Copyright (c) 2018 Filament Group; Licensed MIT */ +(function(win) { + "use strict"; + + // DOM-ready auto-init of plugins. + // Many plugins bind to an "enhance" event to init themselves on dom ready, or when new markup is inserted into the DOM + // Use raw DOMContentLoaded instead of shoestring (may have issues in Android 2.3, exhibited by stack table) + if (!("Tablesaw" in win)) { + throw new Error("Tablesaw library not found."); + } + if (!("init" in Tablesaw)) { + throw new Error("Your tablesaw-init.js is newer than the core Tablesaw version."); + } + + Tablesaw.init(); +})(typeof window !== "undefined" ? window : this); diff --git a/js/tablesaw/dist/tablesaw.css b/js/tablesaw/dist/tablesaw.css new file mode 100755 index 0000000..8706447 --- /dev/null +++ b/js/tablesaw/dist/tablesaw.css @@ -0,0 +1,752 @@ +/*! Tablesaw - v3.0.9 - 2018-02-14 +* https://github.com/filamentgroup/tablesaw +* Copyright (c) 2018 Filament Group; Licensed MIT */ + +.tablesaw { + width: 100%; + max-width: 100%; + empty-cells: show; + border-collapse: collapse; + border: 0; + padding: 0; +} + +.tablesaw * { + box-sizing: border-box; +} + +.tablesaw-btn { + border: 1px solid #ccc; + border-radius: .25em; + background: none; + box-shadow: 0 1px 0 rgba(255,255,255,1); + color: #4a4a4a; + cursor: pointer; + display: inline-block; + margin: 0; + padding: .5em .85em .4em .85em; + position: relative; + text-align: center; + text-decoration: none; + text-transform: capitalize; + -webkit-appearance: none; + -moz-appearance: none; + appearance: none; +} + +a.tablesaw-btn { + color: #1c95d4; +} + +.tablesaw-btn:hover { + text-decoration: none; +} + +/* Default radio/checkbox styling horizonal controlgroups. */ + +.tablesaw-btn:active { + background-color: #ddd; +} + +@supports (box-shadow: none ) { + .tablesaw-btn:focus { + background-color: #fff; + outline: none; + } + + .tablesaw-btn:focus { + box-shadow: 0 0 .35em #4faeef !important; + } +} + +.tablesaw-btn-select select { + background: none; + border: none; + display: inline-block; + position: absolute; + left: 0; + top: 0; + margin: 0; + width: 100%; + height: 100%; + z-index: 2; + min-height: 1em; + opacity: 0; + color: transparent; +} + +.tablesaw-btn select option { + background: #fff; + color: #000; +} + +.tablesaw-btn { + display: inline-block; + width: auto; + height: auto; + position: relative; + top: 0; +} + +.tablesaw-btn.btn-small { + font-size: 1.0625em; + line-height: 19px; + padding: .3em 1em .3em 1em; +} + +.tablesaw-btn.btn-micro { + font-size: .8125em; + padding: .4em .7em .25em .7em; +} + +.tablesaw-btn-select { + padding-right: 1.5em; + text-align: left; + display: inline-block; + color: #4d4d4d; + padding-right: 2.5em; + min-width: 7.25em; +} + +.tablesaw-btn-select:after { + content: " "; + position: absolute; + background: none; + background-repeat: no-repeat; + background-position: .25em .45em; + content: "\25bc"; + font-size: .55em; + padding-top: 1.2em; + padding-left: 1em; + left: auto; + right: 0; + margin: 0; + top: 0; + bottom: 0; + width: 1.8em; +} + +.tablesaw-btn-select.btn-small:after, +.tablesaw-btn-select.btn-micro:after { + width: 1.2em; + font-size: .5em; + padding-top: 1em; + padding-right: .5em; + line-height: 1.65; + background: none; + box-shadow: none; + border-left-width: 0; +} + +/* Column navigation buttons for swipe and columntoggle tables */ + +.tablesaw-advance .tablesaw-btn { + -webkit-appearance: none; + -moz-appearance: none; + box-sizing: border-box; + text-shadow: 0 1px 0 #fff; + border-radius: .25em; +} + +.tablesaw-advance .tablesaw-btn.btn-micro { + font-size: .8125em; + padding: .3em .7em .25em .7em; +} + +.tablesaw-advance a.tablesaw-nav-btn:first-child { + margin-left: 0; +} + +.tablesaw-advance a.tablesaw-nav-btn:last-child { + margin-right: 0; +} + +.tablesaw-advance a.tablesaw-nav-btn { + display: inline-block; + overflow: hidden; + width: 1.8em; + height: 1.8em; + background-position: 50% 50%; + margin-left: .25em; + margin-right: .25em; + position: relative; + text-indent: -9999px; +} + +.tablesaw-advance a.tablesaw-nav-btn.left:before, +.tablesaw-advance a.tablesaw-nav-btn.right:before, +.tablesaw-advance a.tablesaw-nav-btn.down:before, +.tablesaw-advance a.tablesaw-nav-btn.up:before { + content: "\0020"; + overflow: hidden; + width: 0; + height: 0; + position: absolute; +} + +.tablesaw-advance a.tablesaw-nav-btn.down:before { + left: .5em; + top: .65em; + border-left: 5px solid transparent; + border-right: 5px solid transparent; + border-top: 5px solid #808080; +} + +.tablesaw-advance a.tablesaw-nav-btn.up:before { + left: .5em; + top: .65em; + border-left: 5px solid transparent; + border-right: 5px solid transparent; + border-bottom: 5px solid #808080; +} + +.tablesaw-advance a.tablesaw-nav-btn.left:before, +.tablesaw-advance a.tablesaw-nav-btn.right:before { + top: .45em; + border-top: 5px solid transparent; + border-bottom: 5px solid transparent; +} + +.tablesaw-advance a.tablesaw-nav-btn.left:before { + left: .6em; + border-right: 5px solid #808080; +} + +.tablesaw-advance a.tablesaw-nav-btn.right:before { + left: .7em; + border-left: 5px solid #808080; +} + +.tablesaw-advance a.tablesaw-nav-btn.disabled { + opacity: .25; + cursor: default; + pointer-events: none; +} + +/* Table Toolbar */ + +.tablesaw-bar { + clear: both; +} + +.tablesaw-bar * { + box-sizing: border-box; +} + +.tablesaw-bar-section { + float: left; +} + +.tablesaw-bar-section label { + font-size: .875em; + padding: .5em 0; + clear: both; + display: block; + color: #888; + margin-right: .5em; + text-transform: uppercase; +} + +.tablesaw-btn, +.tablesaw-enhanced .tablesaw-btn { + margin-top: .5em; + margin-bottom: .5em; +} + +.tablesaw-btn-select, +.tablesaw-enhanced .tablesaw-btn-select { + margin-bottom: 0; +} + +/* TODO */ + +.tablesaw-bar .tablesaw-bar-section .tablesaw-btn { + margin-left: .4em; + margin-top: 0; + text-transform: uppercase; + border: none; + box-shadow: none; + background: transparent; + font-size: 1em; + padding-left: .3em; +} + +.tablesaw-bar .tablesaw-bar-section .btn-select { + min-width: 0; +} + +.tablesaw-bar .tablesaw-bar-section .btn-select:after { + padding-top: .9em; +} + +.tablesaw-bar .tablesaw-bar-section select { + color: #888; + text-transform: none; + background: transparent; +} + +.tablesaw-bar-section ~ table { + clear: both; +} + +.tablesaw-bar-section .abbreviated { + display: inline; +} + +.tablesaw-bar-section .longform { + display: none; +} + +@media (min-width: 24em) { + .tablesaw-bar-section .abbreviated { + display: none; + } + + .tablesaw-bar-section .longform { + display: inline; + } +} + +.tablesaw th, +.tablesaw td { + padding: .5em .7em; + text-align: left; + vertical-align: middle; +} + +.tablesaw-sortable-btn { + /* same as cell padding above */ + padding: .5em .7em; +} + +.tablesaw thead th { + text-align: left; +} + +/* Table rows have a gray bottom stroke by default */ + +.tablesaw-row-border tr { + border-bottom: 1px solid #dfdfdf; +} + +/* Zebra striping */ + +.tablesaw-row-zebra tr:nth-child(2n) { + background-color: #f8f8f8; +} + +.tablesaw caption { + text-align: left; + margin: .59375em 0; +} + +.tablesaw-swipe .tablesaw-swipe-cellpersist { + border-right: 2px solid #e4e1de; +} + +.tablesaw-swipe-shadow .tablesaw-swipe-cellpersist { + border-right-width: 1px; +} + +.tablesaw-swipe-shadow .tablesaw-swipe-cellpersist { + box-shadow: 3px 0 4px -1px #e4e1de; +} + +.tablesaw-stack td .tablesaw-cell-label, +.tablesaw-stack th .tablesaw-cell-label { + display: none; +} + +/* Mobile first styles: Begin with the stacked presentation at narrow widths */ + +/* Support note IE9+: @media only all */ + +@media only all { + /* Show the table cells as a block level element */ + + .tablesaw-stack { + clear: both; + } + + .tablesaw-stack td, + .tablesaw-stack th { + text-align: left; + display: block; + } + + .tablesaw-stack tr { + clear: both; + display: table-row; + } + + /* Make the label elements a percentage width */ + + .tablesaw-stack td .tablesaw-cell-label, + .tablesaw-stack th .tablesaw-cell-label { + display: inline-block; + padding: 0 .6em 0 0; + width: 30%; + } + + /* For grouped headers, have a different style to visually separate the levels by classing the first label in each col group */ + + .tablesaw-stack th .tablesaw-cell-label-top, + .tablesaw-stack td .tablesaw-cell-label-top { + display: block; + padding: .4em 0; + margin: .4em 0; + } + + .tablesaw-cell-label { + display: block; + } + + /* Avoid double strokes when stacked */ + + .tablesaw-stack tbody th.group { + margin-top: -1px; + } + + /* Avoid double strokes when stacked */ + + .tablesaw-stack th.group b.tablesaw-cell-label { + display: none !important; + } +} + +@media (max-width: 39.9375em) { + /* Table rows have a gray bottom stroke by default */ + + .tablesaw-stack tbody tr { + display: block; + width: 100%; + border-bottom: 1px solid #dfdfdf; + } + + .tablesaw-stack thead td, + .tablesaw-stack thead th { + display: none; + } + + .tablesaw-stack tbody td, + .tablesaw-stack tbody th { + display: block; + float: left; + clear: left; + width: 100%; + } + + .tablesaw-cell-label { + vertical-align: top; + } + + .tablesaw-cell-content { + display: inline-block; + max-width: 67%; + } + + .tablesaw-stack .tablesaw-stack-block .tablesaw-cell-label, + .tablesaw-stack .tablesaw-stack-block .tablesaw-cell-content { + display: block; + width: 100%; + max-width: 100%; + padding: 0; + } + + .tablesaw-stack td:empty, + .tablesaw-stack th:empty { + display: none; + } +} + +/* Media query to show as a standard table at 560px (35em x 16px) or wider */ + +@media (min-width: 40em) { + .tablesaw-stack tr { + display: table-row; + } + + /* Show the table header rows */ + + .tablesaw-stack td, + .tablesaw-stack th, + .tablesaw-stack thead td, + .tablesaw-stack thead th { + display: table-cell; + margin: 0; + } + + /* Hide the labels in each cell */ + + .tablesaw-stack td .tablesaw-cell-label, + .tablesaw-stack th .tablesaw-cell-label { + display: none !important; + } +} + +.tablesaw-fix-persist { + table-layout: fixed; +} + +@media only all { + /* Unchecked manually: Always hide */ + + .tablesaw-swipe th.tablesaw-swipe-cellhidden, + .tablesaw-swipe td.tablesaw-swipe-cellhidden { + display: none; + } +} + +.tablesaw-overflow { + position: relative; + width: 100%; + overflow-x: auto; + -webkit-overflow-scrolling: touch; + /* More in skin.css */ +} + +.tablesaw-overflow > .tablesaw { + margin-top: 2px; + /* sortable focus rings are clipped */ +} + +/* Used for a11y text on button: "Columns" */ + +.tablesaw-columntoggle-btn span { + text-indent: -9999px; + display: inline-block; +} + +.tablesaw-columntoggle-btnwrap { + position: relative; + /* for dialog positioning */ +} + +.tablesaw-columntoggle-btnwrap .dialog-content { + padding: .5em; +} + +.tablesaw-columntoggle tbody td { + line-height: 1.5; +} + +/* Remove top/bottom margins around the fieldcontain on check list */ + +.tablesaw-columntoggle-popup { + display: none; +} + +.tablesaw-columntoggle-btnwrap.visible .tablesaw-columntoggle-popup { + display: block; + position: absolute; + top: 2em; + right: 0; + background-color: #fff; + padding: .5em .8em; + border: 1px solid #ccc; + box-shadow: 0 1px 2px #ccc; + border-radius: .2em; + z-index: 1; +} + +.tablesaw-columntoggle-popup fieldset { + margin: 0; +} + +/* Hide all prioritized columns by default */ + +@media only all { + .tablesaw-columntoggle th.tablesaw-priority-6, + .tablesaw-columntoggle td.tablesaw-priority-6, + .tablesaw-columntoggle th.tablesaw-priority-5, + .tablesaw-columntoggle td.tablesaw-priority-5, + .tablesaw-columntoggle th.tablesaw-priority-4, + .tablesaw-columntoggle td.tablesaw-priority-4, + .tablesaw-columntoggle th.tablesaw-priority-3, + .tablesaw-columntoggle td.tablesaw-priority-3, + .tablesaw-columntoggle th.tablesaw-priority-2, + .tablesaw-columntoggle td.tablesaw-priority-2, + .tablesaw-columntoggle th.tablesaw-priority-1, + .tablesaw-columntoggle td.tablesaw-priority-1, + .tablesaw-columntoggle th.tablesaw-priority-0, + .tablesaw-columntoggle td.tablesaw-priority-0 { + display: none; + } +} + +.tablesaw-columntoggle-btnwrap .dialog-content { + top: 0 !important; + right: 1em; + left: auto !important; + width: 12em; + max-width: 18em; + margin: -.5em auto 0; +} + +.tablesaw-columntoggle-btnwrap .dialog-content:focus { + outline-style: none; +} + +/* Preset breakpoints if "" class added to table */ + +/* Show priority 1 at 320px (20em x 16px) */ + +@media (min-width: 20em) { + .tablesaw-columntoggle th.tablesaw-priority-1, + .tablesaw-columntoggle td.tablesaw-priority-1 { + display: table-cell; + } +} + +/* Show priority 2 at 480px (30em x 16px) */ + +@media (min-width: 30em) { + .tablesaw-columntoggle th.tablesaw-priority-2, + .tablesaw-columntoggle td.tablesaw-priority-2 { + display: table-cell; + } +} + +/* Show priority 3 at 640px (40em x 16px) */ + +@media (min-width: 40em) { + .tablesaw-columntoggle th.tablesaw-priority-3, + .tablesaw-columntoggle td.tablesaw-priority-3 { + display: table-cell; + } + + .tablesaw-columntoggle tbody td { + line-height: 2; + } +} + +/* Show priority 4 at 800px (50em x 16px) */ + +@media (min-width: 50em) { + .tablesaw-columntoggle th.tablesaw-priority-4, + .tablesaw-columntoggle td.tablesaw-priority-4 { + display: table-cell; + } +} + +/* Show priority 5 at 960px (60em x 16px) */ + +@media (min-width: 60em) { + .tablesaw-columntoggle th.tablesaw-priority-5, + .tablesaw-columntoggle td.tablesaw-priority-5 { + display: table-cell; + } +} + +/* Show priority 6 at 1,120px (70em x 16px) */ + +@media (min-width: 70em) { + .tablesaw-columntoggle th.tablesaw-priority-6, + .tablesaw-columntoggle td.tablesaw-priority-6 { + display: table-cell; + } +} + +@media only all { + /* Unchecked manually: Always hide */ + + .tablesaw-columntoggle th.tablesaw-toggle-cellhidden, + .tablesaw-columntoggle td.tablesaw-toggle-cellhidden { + display: none; + } + + /* Checked manually: Always show */ + + .tablesaw-columntoggle th.tablesaw-toggle-cellvisible, + .tablesaw-columntoggle td.tablesaw-toggle-cellvisible { + display: table-cell; + } +} + +.tablesaw-columntoggle-popup .btn-group > label { + display: block; + padding: .2em 0; + white-space: nowrap; + cursor: default; +} + +.tablesaw-columntoggle-popup .btn-group > label input { + margin-right: .8em; +} + +.tablesaw-sortable-head { + position: relative; + vertical-align: top; +} + +/* Override */ + +.tablesaw .tablesaw-sortable-head { + padding: 0; +} + +.tablesaw-sortable-btn { + min-width: 100%; + color: inherit; + background: transparent; + border: 0; + text-align: inherit; + font: inherit; + text-transform: inherit; +} + +.tablesaw-sortable-arrow:after { + display: inline-block; + width: 10px; + height: 14px; + content: " "; + margin-left: .3125em; +} + +.tablesaw-sortable-ascending .tablesaw-sortable-arrow:after, +.tablesaw-sortable-descending .tablesaw-sortable-arrow:after { + content: "\0020"; +} + +.tablesaw-sortable-ascending .tablesaw-sortable-arrow:after { + content: "\2191"; +} + +.tablesaw-sortable-descending .tablesaw-sortable-arrow:after { + content: "\2193"; +} + +.tablesaw-advance { + float: right; +} + +.tablesaw-advance.minimap { + margin-right: .4em; +} + +.tablesaw-advance-dots { + float: left; + margin: 0; + padding: 0; + list-style: none; +} + +.tablesaw-advance-dots li { + display: table-cell; + margin: 0; + padding: .4em .2em; +} + +.tablesaw-advance-dots li i { + width: .25em; + height: .25em; + background: #555; + border-radius: 100%; + display: inline-block; +} + +.tablesaw-advance-dots-hide { + opacity: .25; + cursor: default; + pointer-events: none; +} \ No newline at end of file diff --git a/js/tablesaw/dist/tablesaw.js b/js/tablesaw/dist/tablesaw.js new file mode 100755 index 0000000..6d56b9a --- /dev/null +++ b/js/tablesaw/dist/tablesaw.js @@ -0,0 +1,3777 @@ +/*! Tablesaw - v3.0.9 - 2018-02-14 +* https://github.com/filamentgroup/tablesaw +* Copyright (c) 2018 Filament Group; Licensed MIT */ +/*! Shoestring - v2.0.0 - 2017-02-14 +* http://github.com/filamentgroup/shoestring/ +* Copyright (c) 2017 Scott Jehl, Filament Group, Inc; Licensed MIT & GPLv2 */ +(function( factory ) { + if( typeof define === 'function' && define.amd ) { + // AMD. Register as an anonymous module. + define( [ 'shoestring' ], factory ); + } else if (typeof module === 'object' && module.exports) { + // Node/CommonJS + module.exports = factory(); + } else { + // Browser globals + factory(); + } +}(function () { + var win = typeof window !== "undefined" ? window : this; + var doc = win.document; + + + /** + * The shoestring object constructor. + * + * @param {string,object} prim The selector to find or element to wrap. + * @param {object} sec The context in which to match the `prim` selector. + * @returns shoestring + * @this window + */ + function shoestring( prim, sec ){ + var pType = typeof( prim ), + ret = [], + sel; + + // return an empty shoestring object + if( !prim ){ + return new Shoestring( ret ); + } + + // ready calls + if( prim.call ){ + return shoestring.ready( prim ); + } + + // handle re-wrapping shoestring objects + if( prim.constructor === Shoestring && !sec ){ + return prim; + } + + // if string starting with <, make html + if( pType === "string" && prim.indexOf( "<" ) === 0 ){ + var dfrag = doc.createElement( "div" ); + + dfrag.innerHTML = prim; + + // TODO depends on children (circular) + return shoestring( dfrag ).children().each(function(){ + dfrag.removeChild( this ); + }); + } + + // if string, it's a selector, use qsa + if( pType === "string" ){ + if( sec ){ + return shoestring( sec ).find( prim ); + } + + sel = doc.querySelectorAll( prim ); + + return new Shoestring( sel, prim ); + } + + // array like objects or node lists + if( Object.prototype.toString.call( pType ) === '[object Array]' || + (win.NodeList && prim instanceof win.NodeList) ){ + + return new Shoestring( prim, prim ); + } + + // if it's an array, use all the elements + if( prim.constructor === Array ){ + return new Shoestring( prim, prim ); + } + + // otherwise assume it's an object the we want at an index + return new Shoestring( [prim], prim ); + } + + var Shoestring = function( ret, prim ) { + this.length = 0; + this.selector = prim; + shoestring.merge(this, ret); + }; + + // TODO only required for tests + Shoestring.prototype.reverse = [].reverse; + + // For adding element set methods + shoestring.fn = Shoestring.prototype; + + shoestring.Shoestring = Shoestring; + + // For extending objects + // TODO move to separate module when we use prototypes + shoestring.extend = function( first, second ){ + for( var i in second ){ + if( second.hasOwnProperty( i ) ){ + first[ i ] = second[ i ]; + } + } + + return first; + }; + + // taken directly from jQuery + shoestring.merge = function( first, second ) { + var len, j, i; + + len = +second.length, + j = 0, + i = first.length; + + for ( ; j < len; j++ ) { + first[ i++ ] = second[ j ]; + } + + first.length = i; + + return first; + }; + + // expose + win.shoestring = shoestring; + + + + /** + * Iterates over `shoestring` collections. + * + * @param {function} callback The callback to be invoked on each element and index + * @return shoestring + * @this shoestring + */ + shoestring.fn.each = function( callback ){ + return shoestring.each( this, callback ); + }; + + shoestring.each = function( collection, callback ) { + var val; + for( var i = 0, il = collection.length; i < il; i++ ){ + val = callback.call( collection[i], i, collection[i] ); + if( val === false ){ + break; + } + } + + return collection; + }; + + + + /** + * Check for array membership. + * + * @param {object} needle The thing to find. + * @param {object} haystack The thing to find the needle in. + * @return {boolean} + * @this window + */ + shoestring.inArray = function( needle, haystack ){ + var isin = -1; + for( var i = 0, il = haystack.length; i < il; i++ ){ + if( haystack.hasOwnProperty( i ) && haystack[ i ] === needle ){ + isin = i; + } + } + return isin; + }; + + + + /** + * Bind callbacks to be run when the DOM is "ready". + * + * @param {function} fn The callback to be run + * @return shoestring + * @this shoestring + */ + shoestring.ready = function( fn ){ + if( ready && fn ){ + fn.call( doc ); + } + else if( fn ){ + readyQueue.push( fn ); + } + else { + runReady(); + } + + return [doc]; + }; + + // TODO necessary? + shoestring.fn.ready = function( fn ){ + shoestring.ready( fn ); + return this; + }; + + // Empty and exec the ready queue + var ready = false, + readyQueue = [], + runReady = function(){ + if( !ready ){ + while( readyQueue.length ){ + readyQueue.shift().call( doc ); + } + ready = true; + } + }; + + // If DOM is already ready at exec time, depends on the browser. + // From: https://github.com/mobify/mobifyjs/blob/526841be5509e28fc949038021799e4223479f8d/src/capture.js#L128 + if (doc.attachEvent ? doc.readyState === "complete" : doc.readyState !== "loading") { + runReady(); + } else { + doc.addEventListener( "DOMContentLoaded", runReady, false ); + doc.addEventListener( "readystatechange", runReady, false ); + win.addEventListener( "load", runReady, false ); + } + + + + /** + * Checks the current set of elements against the selector, if one matches return `true`. + * + * @param {string} selector The selector to check. + * @return {boolean} + * @this {shoestring} + */ + shoestring.fn.is = function( selector ){ + var ret = false, self = this, parents, check; + + // assume a dom element + if( typeof selector !== "string" ){ + // array-like, ie shoestring objects or element arrays + if( selector.length && selector[0] ){ + check = selector; + } else { + check = [selector]; + } + + return _checkElements(this, check); + } + + parents = this.parent(); + + if( !parents.length ){ + parents = shoestring( doc ); + } + + parents.each(function( i, e ) { + var children; + + children = e.querySelectorAll( selector ); + + ret = _checkElements( self, children ); + }); + + return ret; + }; + + function _checkElements(needles, haystack){ + var ret = false; + + needles.each(function() { + var j = 0; + + while( j < haystack.length ){ + if( this === haystack[j] ){ + ret = true; + } + + j++; + } + }); + + return ret; + } + + + + /** + * Get data attached to the first element or set data values on all elements in the current set. + * + * @param {string} name The data attribute name. + * @param {any} value The value assigned to the data attribute. + * @return {any|shoestring} + * @this shoestring + */ + shoestring.fn.data = function( name, value ){ + if( name !== undefined ){ + if( value !== undefined ){ + return this.each(function(){ + if( !this.shoestringData ){ + this.shoestringData = {}; + } + + this.shoestringData[ name ] = value; + }); + } + else { + if( this[ 0 ] ) { + if( this[ 0 ].shoestringData ) { + return this[ 0 ].shoestringData[ name ]; + } + } + } + } + else { + return this[ 0 ] ? this[ 0 ].shoestringData || {} : undefined; + } + }; + + + /** + * Remove data associated with `name` or all the data, for each element in the current set. + * + * @param {string} name The data attribute name. + * @return shoestring + * @this shoestring + */ + shoestring.fn.removeData = function( name ){ + return this.each(function(){ + if( name !== undefined && this.shoestringData ){ + this.shoestringData[ name ] = undefined; + delete this.shoestringData[ name ]; + } else { + this[ 0 ].shoestringData = {}; + } + }); + }; + + + + /** + * An alias for the `shoestring` constructor. + */ + win.$ = shoestring; + + + + /** + * Add a class to each DOM element in the set of elements. + * + * @param {string} className The name of the class to be added. + * @return shoestring + * @this shoestring + */ + shoestring.fn.addClass = function( className ){ + var classes = className.replace(/^\s+|\s+$/g, '').split( " " ); + + return this.each(function(){ + for( var i = 0, il = classes.length; i < il; i++ ){ + if( this.className !== undefined && + (this.className === "" || + !this.className.match( new RegExp( "(^|\\s)" + classes[ i ] + "($|\\s)"))) ){ + this.className += " " + classes[ i ]; + } + } + }); + }; + + + + /** + * Add elements matching the selector to the current set. + * + * @param {string} selector The selector for the elements to add from the DOM + * @return shoestring + * @this shoestring + */ + shoestring.fn.add = function( selector ){ + var ret = []; + this.each(function(){ + ret.push( this ); + }); + + shoestring( selector ).each(function(){ + ret.push( this ); + }); + + return shoestring( ret ); + }; + + + + /** + * Insert an element or HTML string as the last child of each element in the set. + * + * @param {string|HTMLElement} fragment The HTML or HTMLElement to insert. + * @return shoestring + * @this shoestring + */ + shoestring.fn.append = function( fragment ){ + if( typeof( fragment ) === "string" || fragment.nodeType !== undefined ){ + fragment = shoestring( fragment ); + } + + return this.each(function( i ){ + for( var j = 0, jl = fragment.length; j < jl; j++ ){ + this.appendChild( i > 0 ? fragment[ j ].cloneNode( true ) : fragment[ j ] ); + } + }); + }; + + + + /** + * Insert the current set as the last child of the elements matching the selector. + * + * @param {string} selector The selector after which to append the current set. + * @return shoestring + * @this shoestring + */ + shoestring.fn.appendTo = function( selector ){ + return this.each(function(){ + shoestring( selector ).append( this ); + }); + }; + + + + /** + * Get the value of the first element of the set or set the value of all the elements in the set. + * + * @param {string} name The attribute name. + * @param {string} value The new value for the attribute. + * @return {shoestring|string|undefined} + * @this {shoestring} + */ + shoestring.fn.attr = function( name, value ){ + var nameStr = typeof( name ) === "string"; + + if( value !== undefined || !nameStr ){ + return this.each(function(){ + if( nameStr ){ + this.setAttribute( name, value ); + } else { + for( var i in name ){ + if( name.hasOwnProperty( i ) ){ + this.setAttribute( i, name[ i ] ); + } + } + } + }); + } else { + return this[ 0 ] ? this[ 0 ].getAttribute( name ) : undefined; + } + }; + + + + /** + * Insert an element or HTML string before each element in the current set. + * + * @param {string|HTMLElement} fragment The HTML or HTMLElement to insert. + * @return shoestring + * @this shoestring + */ + shoestring.fn.before = function( fragment ){ + if( typeof( fragment ) === "string" || fragment.nodeType !== undefined ){ + fragment = shoestring( fragment ); + } + + return this.each(function( i ){ + for( var j = 0, jl = fragment.length; j < jl; j++ ){ + this.parentNode.insertBefore( i > 0 ? fragment[ j ].cloneNode( true ) : fragment[ j ], this ); + } + }); + }; + + + + /** + * Get the children of the current collection. + * @return shoestring + * @this shoestring + */ + shoestring.fn.children = function(){ + var ret = [], + childs, + j; + this.each(function(){ + childs = this.children; + j = -1; + + while( j++ < childs.length-1 ){ + if( shoestring.inArray( childs[ j ], ret ) === -1 ){ + ret.push( childs[ j ] ); + } + } + }); + return shoestring(ret); + }; + + + + /** + * Find an element matching the selector in the set of the current element and its parents. + * + * @param {string} selector The selector used to identify the target element. + * @return shoestring + * @this shoestring + */ + shoestring.fn.closest = function( selector ){ + var ret = []; + + if( !selector ){ + return shoestring( ret ); + } + + this.each(function(){ + var element, $self = shoestring( element = this ); + + if( $self.is(selector) ){ + ret.push( this ); + return; + } + + while( element.parentElement ) { + if( shoestring(element.parentElement).is(selector) ){ + ret.push( element.parentElement ); + break; + } + + element = element.parentElement; + } + }); + + return shoestring( ret ); + }; + + + + shoestring.cssExceptions = { + 'float': [ 'cssFloat' ] + }; + + + + (function() { + var cssExceptions = shoestring.cssExceptions; + + // IE8 uses marginRight instead of margin-right + function convertPropertyName( str ) { + return str.replace( /\-([A-Za-z])/g, function ( match, character ) { + return character.toUpperCase(); + }); + } + + function _getStyle( element, property ) { + return win.getComputedStyle( element, null ).getPropertyValue( property ); + } + + var vendorPrefixes = [ '', '-webkit-', '-ms-', '-moz-', '-o-', '-khtml-' ]; + + /** + * Private function for getting the computed style of an element. + * + * **NOTE** Please use the [css](../css.js.html) method instead. + * + * @method _getStyle + * @param {HTMLElement} element The element we want the style property for. + * @param {string} property The css property we want the style for. + */ + shoestring._getStyle = function( element, property ) { + var convert, value, j, k; + + if( cssExceptions[ property ] ) { + for( j = 0, k = cssExceptions[ property ].length; j < k; j++ ) { + value = _getStyle( element, cssExceptions[ property ][ j ] ); + + if( value ) { + return value; + } + } + } + + for( j = 0, k = vendorPrefixes.length; j < k; j++ ) { + convert = convertPropertyName( vendorPrefixes[ j ] + property ); + + // VendorprefixKeyName || key-name + value = _getStyle( element, convert ); + + if( convert !== property ) { + value = value || _getStyle( element, property ); + } + + if( vendorPrefixes[ j ] ) { + // -vendorprefix-key-name + value = value || _getStyle( element, vendorPrefixes[ j ] + property ); + } + + if( value ) { + return value; + } + } + + return undefined; + }; + })(); + + + + (function() { + var cssExceptions = shoestring.cssExceptions; + + // IE8 uses marginRight instead of margin-right + function convertPropertyName( str ) { + return str.replace( /\-([A-Za-z])/g, function ( match, character ) { + return character.toUpperCase(); + }); + } + + /** + * Private function for setting the style of an element. + * + * **NOTE** Please use the [css](../css.js.html) method instead. + * + * @method _setStyle + * @param {HTMLElement} element The element we want to style. + * @param {string} property The property being used to style the element. + * @param {string} value The css value for the style property. + */ + shoestring._setStyle = function( element, property, value ) { + var convertedProperty = convertPropertyName(property); + + element.style[ property ] = value; + + if( convertedProperty !== property ) { + element.style[ convertedProperty ] = value; + } + + if( cssExceptions[ property ] ) { + for( var j = 0, k = cssExceptions[ property ].length; j -1 ){ + ret.push( this ); + } + } + }); + + return shoestring( ret ); + }; + + + + /** + * Find descendant elements of the current collection. + * + * @param {string} selector The selector used to find the children + * @return shoestring + * @this shoestring + */ + shoestring.fn.find = function( selector ){ + var ret = [], + finds; + this.each(function(){ + finds = this.querySelectorAll( selector ); + + for( var i = 0, il = finds.length; i < il; i++ ){ + ret = ret.concat( finds[i] ); + } + }); + return shoestring( ret ); + }; + + + + /** + * Returns the first element of the set wrapped in a new `shoestring` object. + * + * @return shoestring + * @this shoestring + */ + shoestring.fn.first = function(){ + return this.eq( 0 ); + }; + + + + /** + * Returns the raw DOM node at the passed index. + * + * @param {integer} index The index of the element to wrap and return. + * @return {HTMLElement|undefined|array} + * @this shoestring + */ + shoestring.fn.get = function( index ){ + + // return an array of elements if index is undefined + if( index === undefined ){ + var elements = []; + + for( var i = 0; i < this.length; i++ ){ + elements.push( this[ i ] ); + } + + return elements; + } else { + return this[ index ]; + } + }; + + + + var set = function( html ){ + if( typeof html === "string" || typeof html === "number" ){ + return this.each(function(){ + this.innerHTML = "" + html; + }); + } else { + var h = ""; + if( typeof html.length !== "undefined" ){ + for( var i = 0, l = html.length; i < l; i++ ){ + h += html[i].outerHTML; + } + } else { + h = html.outerHTML; + } + return this.each(function(){ + this.innerHTML = h; + }); + } + }; + /** + * Gets or sets the `innerHTML` from all the elements in the set. + * + * @param {string|undefined} html The html to assign + * @return {string|shoestring} + * @this shoestring + */ + shoestring.fn.html = function( html ){ + if( typeof html !== "undefined" ){ + return set.call( this, html ); + } else { // get + var pile = ""; + + this.each(function(){ + pile += this.innerHTML; + }); + + return pile; + } + }; + + + + (function() { + function _getIndex( set, test ) { + var i, result, element; + + for( i = result = 0; i < set.length; i++ ) { + element = set.item ? set.item(i) : set[i]; + + if( test(element) ){ + return result; + } + + // ignore text nodes, etc + // NOTE may need to be more permissive + if( element.nodeType === 1 ){ + result++; + } + } + + return -1; + } + + /** + * Find the index in the current set for the passed selector. + * Without a selector it returns the index of the first node within the array of its siblings. + * + * @param {string|undefined} selector The selector used to search for the index. + * @return {integer} + * @this {shoestring} + */ + shoestring.fn.index = function( selector ){ + var self, children; + + self = this; + + // no arg? check the children, otherwise check each element that matches + if( selector === undefined ){ + children = ( ( this[ 0 ] && this[0].parentNode ) || doc.documentElement).childNodes; + + // check if the element matches the first of the set + return _getIndex(children, function( element ) { + return self[0] === element; + }); + } else { + + // check if the element matches the first selected node from the parent + return _getIndex(self, function( element ) { + return element === (shoestring( selector, element.parentNode )[ 0 ]); + }); + } + }; + })(); + + + + /** + * Insert the current set before the elements matching the selector. + * + * @param {string} selector The selector before which to insert the current set. + * @return shoestring + * @this shoestring + */ + shoestring.fn.insertBefore = function( selector ){ + return this.each(function(){ + shoestring( selector ).before( this ); + }); + }; + + + + /** + * Returns the last element of the set wrapped in a new `shoestring` object. + * + * @return shoestring + * @this shoestring + */ + shoestring.fn.last = function(){ + return this.eq( this.length - 1 ); + }; + + + + /** + * Returns a `shoestring` object with the set of siblings of each element in the original set. + * + * @return shoestring + * @this shoestring + */ + shoestring.fn.next = function(){ + + var result = []; + + // TODO need to implement map + this.each(function() { + var children, item, found; + + // get the child nodes for this member of the set + children = shoestring( this.parentNode )[0].childNodes; + + for( var i = 0; i < children.length; i++ ){ + item = children.item( i ); + + // found the item we needed (found) which means current item value is + // the next node in the list, as long as it's viable grab it + // NOTE may need to be more permissive + if( found && item.nodeType === 1 ){ + result.push( item ); + break; + } + + // find the current item and mark it as found + if( item === this ){ + found = true; + } + } + }); + + return shoestring( result ); + }; + + + + /** + * Removes elements from the current set. + * + * @param {string} selector The selector to use when removing the elements. + * @return shoestring + * @this shoestring + */ + shoestring.fn.not = function( selector ){ + var ret = []; + + this.each(function(){ + var found = shoestring( selector, this.parentNode ); + + if( shoestring.inArray(this, found) === -1 ){ + ret.push( this ); + } + }); + + return shoestring( ret ); + }; + + + + /** + * Returns the set of first parents for each element in the current set. + * + * @return shoestring + * @this shoestring + */ + shoestring.fn.parent = function(){ + var ret = [], + parent; + + this.each(function(){ + // no parent node, assume top level + // jQuery parent: return the document object for or the parent node if it exists + parent = (this === doc.documentElement ? doc : this.parentNode); + + // if there is a parent and it's not a document fragment + if( parent && parent.nodeType !== 11 ){ + ret.push( parent ); + } + }); + + return shoestring(ret); + }; + + + + /** + * Add an HTML string or element before the children of each element in the current set. + * + * @param {string|HTMLElement} fragment The HTML string or element to add. + * @return shoestring + * @this shoestring + */ + shoestring.fn.prepend = function( fragment ){ + if( typeof( fragment ) === "string" || fragment.nodeType !== undefined ){ + fragment = shoestring( fragment ); + } + + return this.each(function( i ){ + + for( var j = 0, jl = fragment.length; j < jl; j++ ){ + var insertEl = i > 0 ? fragment[ j ].cloneNode( true ) : fragment[ j ]; + if ( this.firstChild ){ + this.insertBefore( insertEl, this.firstChild ); + } else { + this.appendChild( insertEl ); + } + } + }); + }; + + + + /** + * Returns a `shoestring` object with the set of *one* siblingx before each element in the original set. + * + * @return shoestring + * @this shoestring + */ + shoestring.fn.prev = function(){ + + var result = []; + + // TODO need to implement map + this.each(function() { + var children, item, found; + + // get the child nodes for this member of the set + children = shoestring( this.parentNode )[0].childNodes; + + for( var i = children.length -1; i >= 0; i-- ){ + item = children.item( i ); + + // found the item we needed (found) which means current item value is + // the next node in the list, as long as it's viable grab it + // NOTE may need to be more permissive + if( found && item.nodeType === 1 ){ + result.push( item ); + break; + } + + // find the current item and mark it as found + if( item === this ){ + found = true; + } + } + }); + + return shoestring( result ); + }; + + + + /** + * Returns a `shoestring` object with the set of *all* siblings before each element in the original set. + * + * @return shoestring + * @this shoestring + */ + shoestring.fn.prevAll = function(){ + + var result = []; + + this.each(function() { + var $previous = shoestring( this ).prev(); + + while( $previous.length ){ + result.push( $previous[0] ); + $previous = $previous.prev(); + } + }); + + return shoestring( result ); + }; + + + + /** + * Remove an attribute from each element in the current set. + * + * @param {string} name The name of the attribute. + * @return shoestring + * @this shoestring + */ + shoestring.fn.removeAttr = function( name ){ + return this.each(function(){ + this.removeAttribute( name ); + }); + }; + + + + /** + * Remove a class from each DOM element in the set of elements. + * + * @param {string} className The name of the class to be removed. + * @return shoestring + * @this shoestring + */ + shoestring.fn.removeClass = function( cname ){ + var classes = cname.replace(/^\s+|\s+$/g, '').split( " " ); + + return this.each(function(){ + var newClassName, regex; + + for( var i = 0, il = classes.length; i < il; i++ ){ + if( this.className !== undefined ){ + regex = new RegExp( "(^|\\s)" + classes[ i ] + "($|\\s)", "gmi" ); + newClassName = this.className.replace( regex, " " ); + + this.className = newClassName.replace(/^\s+|\s+$/g, ''); + } + } + }); + }; + + + + /** + * Remove the current set of elements from the DOM. + * + * @return shoestring + * @this shoestring + */ + shoestring.fn.remove = function(){ + return this.each(function(){ + if( this.parentNode ) { + this.parentNode.removeChild( this ); + } + }); + }; + + + + /** + * Replace each element in the current set with that argument HTML string or HTMLElement. + * + * @param {string|HTMLElement} fragment The value to assign. + * @return shoestring + * @this shoestring + */ + shoestring.fn.replaceWith = function( fragment ){ + if( typeof( fragment ) === "string" ){ + fragment = shoestring( fragment ); + } + + var ret = []; + + if( fragment.length > 1 ){ + fragment = fragment.reverse(); + } + this.each(function( i ){ + var clone = this.cloneNode( true ), + insertEl; + ret.push( clone ); + + // If there is no parentNode, this is pointless, drop it. + if( !this.parentNode ){ return; } + + if( fragment.length === 1 ){ + insertEl = i > 0 ? fragment[ 0 ].cloneNode( true ) : fragment[ 0 ]; + this.parentNode.replaceChild( insertEl, this ); + } else { + for( var j = 0, jl = fragment.length; j < jl; j++ ){ + insertEl = i > 0 ? fragment[ j ].cloneNode( true ) : fragment[ j ]; + this.parentNode.insertBefore( insertEl, this.nextSibling ); + } + this.parentNode.removeChild( this ); + } + }); + + return shoestring( ret ); + }; + + + + /** + * Get all of the sibling elements for each element in the current set. + * + * @return shoestring + * @this shoestring + */ + shoestring.fn.siblings = function(){ + + if( !this.length ) { + return shoestring( [] ); + } + + var sibs = [], el = this[ 0 ].parentNode.firstChild; + + do { + if( el.nodeType === 1 && el !== this[ 0 ] ) { + sibs.push( el ); + } + + el = el.nextSibling; + } while( el ); + + return shoestring( sibs ); + }; + + + + var getText = function( elem ){ + var node, + ret = "", + i = 0, + nodeType = elem.nodeType; + + if ( !nodeType ) { + // If no nodeType, this is expected to be an array + while ( (node = elem[i++]) ) { + // Do not traverse comment nodes + ret += getText( node ); + } + } else if ( nodeType === 1 || nodeType === 9 || nodeType === 11 ) { + // Use textContent for elements + // innerText usage removed for consistency of new lines (jQuery #11153) + if ( typeof elem.textContent === "string" ) { + return elem.textContent; + } else { + // Traverse its children + for ( elem = elem.firstChild; elem; elem = elem.nextSibling ) { + ret += getText( elem ); + } + } + } else if ( nodeType === 3 || nodeType === 4 ) { + return elem.nodeValue; + } + // Do not include comment or processing instruction nodes + + return ret; + }; + + /** + * Recursively retrieve the text content of the each element in the current set. + * + * @return shoestring + * @this shoestring + */ + shoestring.fn.text = function() { + + return getText( this ); + }; + + + + + /** + * Get the value of the first element or set the value of all elements in the current set. + * + * @param {string} value The value to set. + * @return shoestring + * @this shoestring + */ + shoestring.fn.val = function( value ){ + var el; + if( value !== undefined ){ + return this.each(function(){ + if( this.tagName === "SELECT" ){ + var optionSet, option, + options = this.options, + values = [], + i = options.length, + newIndex; + + values[0] = value; + while ( i-- ) { + option = options[ i ]; + if ( (option.selected = shoestring.inArray( option.value, values ) >= 0) ) { + optionSet = true; + newIndex = i; + } + } + // force browsers to behave consistently when non-matching value is set + if ( !optionSet ) { + this.selectedIndex = -1; + } else { + this.selectedIndex = newIndex; + } + } else { + this.value = value; + } + }); + } else { + el = this[0]; + + if( el.tagName === "SELECT" ){ + if( el.selectedIndex < 0 ){ return ""; } + return el.options[ el.selectedIndex ].value; + } else { + return el.value; + } + } + }; + + + + /** + * Private function for setting/getting the offset property for height/width. + * + * **NOTE** Please use the [width](width.js.html) or [height](height.js.html) methods instead. + * + * @param {shoestring} set The set of elements. + * @param {string} name The string "height" or "width". + * @param {float|undefined} value The value to assign. + * @return shoestring + * @this window + */ + shoestring._dimension = function( set, name, value ){ + var offsetName; + + if( value === undefined ){ + offsetName = name.replace(/^[a-z]/, function( letter ) { + return letter.toUpperCase(); + }); + + return set[ 0 ][ "offset" + offsetName ]; + } else { + // support integer values as pixels + value = typeof value === "string" ? value : value + "px"; + + return set.each(function(){ + this.style[ name ] = value; + }); + } + }; + + + + /** + * Gets the width value of the first element or sets the width for the whole set. + * + * @param {float|undefined} value The value to assign. + * @return shoestring + * @this shoestring + */ + shoestring.fn.width = function( value ){ + return shoestring._dimension( this, "width", value ); + }; + + + + /** + * Wraps the child elements in the provided HTML. + * + * @param {string} html The wrapping HTML. + * @return shoestring + * @this shoestring + */ + shoestring.fn.wrapInner = function( html ){ + return this.each(function(){ + var inH = this.innerHTML; + + this.innerHTML = ""; + shoestring( this ).append( shoestring( html ).html( inH ) ); + }); + }; + + + + function initEventCache( el, evt ) { + if ( !el.shoestringData ) { + el.shoestringData = {}; + } + if ( !el.shoestringData.events ) { + el.shoestringData.events = {}; + } + if ( !el.shoestringData.loop ) { + el.shoestringData.loop = {}; + } + if ( !el.shoestringData.events[ evt ] ) { + el.shoestringData.events[ evt ] = []; + } + } + + function addToEventCache( el, evt, eventInfo ) { + var obj = {}; + obj.isCustomEvent = eventInfo.isCustomEvent; + obj.callback = eventInfo.callfunc; + obj.originalCallback = eventInfo.originalCallback; + obj.namespace = eventInfo.namespace; + + el.shoestringData.events[ evt ].push( obj ); + + if( eventInfo.customEventLoop ) { + el.shoestringData.loop[ evt ] = eventInfo.customEventLoop; + } + } + + /** + * Bind a callback to an event for the currrent set of elements. + * + * @param {string} evt The event(s) to watch for. + * @param {object,function} data Data to be included with each event or the callback. + * @param {function} originalCallback Callback to be invoked when data is define.d. + * @return shoestring + * @this shoestring + */ + shoestring.fn.bind = function( evt, data, originalCallback ){ + + if( typeof data === "function" ){ + originalCallback = data; + data = null; + } + + var evts = evt.split( " " ); + + // NOTE the `triggeredElement` is purely for custom events from IE + function encasedCallback( e, namespace, triggeredElement ){ + var result; + + if( e._namespace && e._namespace !== namespace ) { + return; + } + + e.data = data; + e.namespace = e._namespace; + + var returnTrue = function(){ + return true; + }; + + e.isDefaultPrevented = function(){ + return false; + }; + + var originalPreventDefault = e.preventDefault; + var preventDefaultConstructor = function(){ + if( originalPreventDefault ) { + return function(){ + e.isDefaultPrevented = returnTrue; + originalPreventDefault.call(e); + }; + } else { + return function(){ + e.isDefaultPrevented = returnTrue; + e.returnValue = false; + }; + } + }; + + // thanks https://github.com/jonathantneal/EventListener + e.target = triggeredElement || e.target || e.srcElement; + e.preventDefault = preventDefaultConstructor(); + e.stopPropagation = e.stopPropagation || function () { + e.cancelBubble = true; + }; + + result = originalCallback.apply(this, [ e ].concat( e._args ) ); + + if( result === false ){ + e.preventDefault(); + e.stopPropagation(); + } + + return result; + } + + return this.each(function(){ + var domEventCallback, + customEventCallback, + customEventLoop, + oEl = this; + + for( var i = 0, il = evts.length; i < il; i++ ){ + var split = evts[ i ].split( "." ), + evt = split[ 0 ], + namespace = split.length > 0 ? split[ 1 ] : null; + + domEventCallback = function( originalEvent ) { + if( oEl.ssEventTrigger ) { + originalEvent._namespace = oEl.ssEventTrigger._namespace; + originalEvent._args = oEl.ssEventTrigger._args; + + oEl.ssEventTrigger = null; + } + return encasedCallback.call( oEl, originalEvent, namespace ); + }; + customEventCallback = null; + customEventLoop = null; + + initEventCache( this, evt ); + + this.addEventListener( evt, domEventCallback, false ); + + addToEventCache( this, evt, { + callfunc: customEventCallback || domEventCallback, + isCustomEvent: !!customEventCallback, + customEventLoop: customEventLoop, + originalCallback: originalCallback, + namespace: namespace + }); + } + }); + }; + + shoestring.fn.on = shoestring.fn.bind; + + + + + /** + * Unbind a previous bound callback for an event. + * + * @param {string} event The event(s) the callback was bound to.. + * @param {function} callback Callback to unbind. + * @return shoestring + * @this shoestring + */ + shoestring.fn.unbind = function( event, callback ){ + + + var evts = event ? event.split( " " ) : []; + + return this.each(function(){ + if( !this.shoestringData || !this.shoestringData.events ) { + return; + } + + if( !evts.length ) { + unbindAll.call( this ); + } else { + var split, evt, namespace; + for( var i = 0, il = evts.length; i < il; i++ ){ + split = evts[ i ].split( "." ), + evt = split[ 0 ], + namespace = split.length > 0 ? split[ 1 ] : null; + + if( evt ) { + unbind.call( this, evt, namespace, callback ); + } else { + unbindAll.call( this, namespace, callback ); + } + } + } + }); + }; + + function unbind( evt, namespace, callback ) { + var bound = this.shoestringData.events[ evt ]; + if( !(bound && bound.length) ) { + return; + } + + var matched = [], j, jl; + for( j = 0, jl = bound.length; j < jl; j++ ) { + if( !namespace || namespace === bound[ j ].namespace ) { + if( callback === undefined || callback === bound[ j ].originalCallback ) { + this.removeEventListener( evt, bound[ j ].callback, false ); + matched.push( j ); + } + } + } + + for( j = 0, jl = matched.length; j < jl; j++ ) { + this.shoestringData.events[ evt ].splice( j, 1 ); + } + } + + function unbindAll( namespace, callback ) { + for( var evtKey in this.shoestringData.events ) { + unbind.call( this, evtKey, namespace, callback ); + } + } + + shoestring.fn.off = shoestring.fn.unbind; + + + /** + * Bind a callback to an event for the currrent set of elements, unbind after one occurence. + * + * @param {string} event The event(s) to watch for. + * @param {function} callback Callback to invoke on the event. + * @return shoestring + * @this shoestring + */ + shoestring.fn.one = function( event, callback ){ + var evts = event.split( " " ); + + return this.each(function(){ + var thisevt, cbs = {}, $t = shoestring( this ); + + for( var i = 0, il = evts.length; i < il; i++ ){ + thisevt = evts[ i ]; + + cbs[ thisevt ] = function( e ){ + var $t = shoestring( this ); + + for( var j in cbs ) { + $t.unbind( j, cbs[ j ] ); + } + + return callback.apply( this, [ e ].concat( e._args ) ); + }; + + $t.bind( thisevt, cbs[ thisevt ] ); + } + }); + }; + + + + /** + * Trigger an event on the first element in the set, no bubbling, no defaults. + * + * @param {string} event The event(s) to trigger. + * @param {object} args Arguments to append to callback invocations. + * @return shoestring + * @this shoestring + */ + shoestring.fn.triggerHandler = function( event, args ){ + var e = event.split( " " )[ 0 ], + el = this[ 0 ], + ret; + + // See this.fireEvent( 'on' + evts[ i ], document.createEventObject() ); instead of click() etc in trigger. + if( doc.createEvent && el.shoestringData && el.shoestringData.events && el.shoestringData.events[ e ] ){ + var bindings = el.shoestringData.events[ e ]; + for (var i in bindings ){ + if( bindings.hasOwnProperty( i ) ){ + event = doc.createEvent( "Event" ); + event.initEvent( e, true, true ); + event._args = args; + args.unshift( event ); + + ret = bindings[ i ].originalCallback.apply( event.target, args ); + } + } + } + + return ret; + }; + + + + /** + * Trigger an event on each of the DOM elements in the current set. + * + * @param {string} event The event(s) to trigger. + * @param {object} args Arguments to append to callback invocations. + * @return shoestring + * @this shoestring + */ + shoestring.fn.trigger = function( event, args ){ + var evts = event.split( " " ); + + return this.each(function(){ + var split, evt, namespace; + for( var i = 0, il = evts.length; i < il; i++ ){ + split = evts[ i ].split( "." ), + evt = split[ 0 ], + namespace = split.length > 0 ? split[ 1 ] : null; + + if( evt === "click" ){ + if( this.tagName === "INPUT" && this.type === "checkbox" && this.click ){ + this.click(); + return false; + } + } + + if( doc.createEvent ){ + var event = doc.createEvent( "Event" ); + event.initEvent( evt, true, true ); + event._args = args; + event._namespace = namespace; + + this.dispatchEvent( event ); + } + } + }); + }; + + + + return shoestring; +})); + +(function (root, factory) { + if (typeof define === 'function' && define.amd) { + define(["shoestring"], function (shoestring) { + return (root.Tablesaw = factory(shoestring, root)); + }); + } else if (typeof exports === 'object') { + module.exports = factory(require('shoestring'), root); + } else { + root.Tablesaw = factory(shoestring, 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 and are allowed, per the specification + this.$thead = this.$table + .children() + .filter("thead") + .eq(0); + + // multiple 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 is required, but none was found."); + } + + if (!this.$thead.find("th").length) { + throw new Error("tablesaw: no header cells found. Are you using ?"); + } + + // 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 = $("
") + .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 + 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(", ")); + } + + var parentNode = $sortableButton.length ? $sortableButton[0] : $header[0]; + var el; + while ((el = parentNode.firstChild)) { + $newHeader[0].appendChild(el); + } + }); + + if ($newHeader.length && !$cell.find("." + classes.cellContentLabels).length) { + $cell.wrapInner(""); + } + + // 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 ""; + } + }); + + this.$table.addClass(this.classes.columnToggleTable); + + tableId = this.$table.attr("id"); + id = tableId + "-popup"; + $btnContain = $("
"); + // TODO next major version: remove .btn + $menuButton = $( + "
" + + "" + + Tablesaw.i18n.columnToggleButton + + "" + ); + $popup = $("
"); + $menu = $("
"); + + 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(""); + } + + $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 = $("
and are allowed, per the specification + this.$thead = this.$table + .children() + .filter("thead") + .eq(0); + + // multiple 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 is required, but none was found."); + } + + if (!this.$thead.find("th").length) { + throw new Error("tablesaw: no header cells found. Are you using ?"); + } + + // 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 = $("
") + .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; +})(); diff --git a/js/tablesaw/src/tables.minimap.css b/js/tablesaw/src/tables.minimap.css new file mode 100755 index 0000000..deb41ef --- /dev/null +++ b/js/tablesaw/src/tables.minimap.css @@ -0,0 +1,29 @@ +.tablesaw-advance { + float: right; +} +.tablesaw-advance.minimap { + margin-right: .4em; +} +.tablesaw-advance-dots { + float: left; + margin: 0; + padding: 0; + list-style: none; +} +.tablesaw-advance-dots li { + display: table-cell; + margin: 0; + padding: .4em .2em; +} +.tablesaw-advance-dots li i { + width: .25em; + height: .25em; + background: #555; + border-radius: 100%; + display: inline-block; +} +.tablesaw-advance-dots-hide { + opacity: .25; + cursor: default; + pointer-events: none; +} \ No newline at end of file diff --git a/js/tablesaw/src/tables.minimap.js b/js/tablesaw/src/tables.minimap.js new file mode 100755 index 0000000..876d837 --- /dev/null +++ b/js/tablesaw/src/tables.minimap.js @@ -0,0 +1,88 @@ +/* +* tablesaw: A set of plugins for responsive tables +* minimap: a set of dots to show which columns are currently visible. +* Copyright (c) 2013 Filament Group, Inc. +* MIT License +*/ + +(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 = $('
'); + var $dotNav = $('
inside of
inside of