├── .gitignore ├── LICENSE ├── README.md ├── assets ├── responsiveTable.js ├── style.css ├── tablesaw │ ├── icons │ │ ├── icons.css │ │ ├── png │ │ │ ├── arrow-gray-down.png │ │ │ ├── arrow-gray-left.png │ │ │ ├── arrow-gray-right.png │ │ │ ├── check.png │ │ │ ├── sort-ascending.png │ │ │ └── sort-descending.png │ │ └── preview.html │ ├── tablesaw.css │ └── tablesaw.js └── tabletop.js ├── config-example.json ├── configs └── .keep ├── index.html ├── render.py ├── requirements.txt ├── templates └── index.html └── webapp ├── app.js ├── build.zip ├── lib ├── css │ └── style.css └── js │ ├── FileSaver.js │ ├── bootstrap-collapse.js │ ├── bootstrap-transition.js │ ├── jszip-utils-ie.min.js │ ├── jszip-utils.min.js │ ├── jszip.js │ ├── jszip.min.js │ └── tabletop.js └── preview.html /.gitignore: -------------------------------------------------------------------------------- 1 | config.json 2 | build/ 3 | configs/*.json 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2014-2018 Institute for Nonprofit News 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Responsive tables 2 | 3 | [Generate responsive tables using the webapp](http://inn.github.io/responsive-tables/) 4 | 5 | 🚨 This project is **not maintained or supported,** as of [February 16, 2018](https://github.com/INN/responsive-tables/commit/5ab7f9673a9431ebbbb471e19fdd4e0787f87b2f). 🚨 6 | 7 | ## What is it? 8 | 9 | This repo contains two utilities that do the same thing: 10 | 11 | - A [simple webapp](http://inn.github.io/responsive-tables/) that takes your Google Drive spreadsheet key, lets you format the columns, then generates a .zip with a ready-for-deployment responsive table and all required assets. 12 | - A simple `render.py` script that reads from `config.json` to find your Google Drive spreadsheet key and column formatting information then generates a build directory with a ready-for-deployment responsive table and all required assets. 13 | 14 | It works best with tables that have 5-7 columns. 15 | 16 | ## Webapp Usage 17 | 18 | [Use the webapp](http://inn.github.io/responsive-tables/). We've published it on GitHub pages for ease of use. 19 | 20 | 1. Paste in your URL 21 | 2. Configure your column headers and the table title 22 | 3. Optionally, add your Google Analytics ID 23 | 4. Optionally, preview the table 24 | 5. Download the `.zip` and unzip it to your server 25 | 6. [Embed the table in your site](#embedding-the-table) 26 | 27 | ## Script usage 28 | 29 | If you want to use the script instead of the webapp, go ahead and install this repository on your computer. 30 | 31 | ### Setup 32 | 33 | You'll need [Jinja2](http://jinja.pocoo.org/) and Python for this project. 34 | 35 | sudo easy_install pip 36 | pip install -r requirements.txt 37 | 38 | Install this repository: 39 | 40 | git clone https://github.com/INN/responsive-tables.git 41 | 42 | [Publish](https://support.google.com/docs/answer/37579?authuser=0) your Google Spreadsheet. 43 | 44 | ### Configuration 45 | 46 | Copy `config-example.json` to `config.json`. Open `config.json` and add your spreadsheet's key. The key is a long sequence of apparently-random characters, such as `10yccwbMYeIHdcRQazaNOaHSkpoSa1SUJEtWBfWPsgx0`, found in the URL of the page you use to edit the spreadsheet. An example URL is https://docs.google.com/spreadsheets/d/10yccwbMYeIHdcRQazaNOaHSkpoSa1SUJEtWBfWPsgx0/edit#gid=0, which was used to generate [this embedded example](http://labs.inn.org/discounts/). The key may include dashes. The key is also included in the URL found in the "Document link" field of the [Publish to web](https://support.google.com/docs/answer/183965/?hl=en&authuser=0) dialog. 47 | 48 | Fill in `title` to set the table's title tag. 49 | 50 | Fill in `ua_code` to enable Google Analytics. 51 | 52 | { 53 | "ua_code": "googleanalyticsuacodegoeshere", 54 | "title": "Title tag", 55 | "key": "yourspreadsheetkeygoeshere", 56 | ... 57 | 58 | Next, define the columns to display in your table. For example: 59 | 60 | ... 61 | "columns": [ 62 | ["tool", "Tool"], 63 | ["category", "Category"], 64 | ["typeofresource", "Type of resource"], 65 | ["developingmember", "Developing member"], 66 | ["description", "Description"], 67 | ["url", "link"], 68 | ["notes", "notes"] 69 | ] 70 | } 71 | 72 | Each item in the `columns` array follows the format `["simplifiedlabel", "Label for display"]`. Simplified labels are the column headings in your document with spaces and underscores removed, and uppercase letters made lowercase. Allowed characters are lowercase a-z, numbers, and the `-` character. 73 | 74 | ### Render 75 | 76 | Once you've filled in `config.json`, run: 77 | 78 | ./render.py 79 | 80 | This will create a build directory and place a copy of your rendered table and all assets inside it. You can deploy the contents of this directory to your host as-is. 81 | 82 | If the project renders successfully but you have a blank page, make sure your document is [published](https://support.google.com/docs/answer/183965?rd=1&authuser=0) and that permissions allow anyone with the link to view the document. 83 | 84 | ### Multiple configurations 85 | 86 | As a convenience, you can create config files and store them in the `configs` directory to ease re-rendering of tables. 87 | 88 | For example, if you create a config file named `myspecialtable.json`, then you can tell `render.py` to render that specific configuration like so: 89 | 90 | ./render.py -c myspecialtable 91 | 92 | ### Deploying to server 93 | 94 | Copy the contents of the `build` directory to a directory on your website. 95 | 96 | ## Embedding the table 97 | 98 | We're using [pym.js](http://blog.apps.npr.org/pym.js/) to make our tables responsive when embedded via iframe. 99 | 100 | To embed a table, you can use this snippet to get started: 101 | 102 |
103 | 104 | 111 | 112 | Be sure to replace `http://yourdomain.com/path/to/index.html` with the actual URL of your table. 113 | 114 | ## Troubleshooting 115 | 116 | **Data in wrong column**: 117 | 118 | Are the simplified labels in your `config.json` composed only of lowercase letters, numbers, and dashes? 119 | 120 | **No data shows**: 121 | 122 | Is your spreadsheet [published](https://support.google.com/docs/answer/183965?rd=1&authuser=0)? 123 | 124 | ## Libraries used 125 | 126 | This little project uses: 127 | 128 | - [pym.js](http://blog.apps.npr.org/pym.js/), 129 | - [tabletop](https://github.com/jsoma/tabletop), 130 | - [tablesaw](https://github.com/filamentgroup/tablesaw/) 131 | - [JSzip](https://stuk.github.io/jszip/) and [JSzip-utils](https://stuk.github.io/jszip-utils/) 132 | - [FileSaver.js](http://eligrey.com/blog/post/saving-generated-files-on-the-client-side) 133 | 134 | Check them out! 135 | 136 | -------------------------------------------------------------------------------- /assets/responsiveTable.js: -------------------------------------------------------------------------------- 1 | var ResponsiveTable; 2 | 3 | (function(){ 4 | var $ = jQuery; 5 | 6 | ResponsiveTable = function(spreadsheetKey, columns) { 7 | var self = this; 8 | 9 | self.columns = columns; 10 | 11 | self.init = function(dataSpreadsheet) { 12 | self.pym = new pym.Child({ polling: 500 }); 13 | self.ttop = Tabletop.init({ 14 | key: dataSpreadsheet, 15 | callback: self.writeTable, 16 | simpleSheet: true, 17 | debug: false 18 | }); 19 | } 20 | 21 | self.sortData = function(dataSource) { 22 | dataSource.sort(function(a, b){ 23 | var one = a[self.columns[0][0]], 24 | two = b[self.columns[0][0]]; 25 | 26 | if (!isNaN(Number(one))) 27 | one = Number(one); 28 | if (!isNaN(Number(two))) 29 | two = Number(two); 30 | 31 | if(one < two) return -1; 32 | if(one > two) return 1; 33 | return 0; 34 | }); 35 | return dataSource 36 | }; 37 | 38 | self.writeTable = function(dataSource){ 39 | dataSource = self.sortData(dataSource); 40 | $('#data').html(''); 41 | self.createTableColumns('#data table'); 42 | self.populateTable(dataSource, '#data table'); 43 | $('#data table').table().fadeIn(); 44 | self.pym.sendHeight(); 45 | }; 46 | 47 | self.createTableColumns = function(table){ 48 | var thead = $(''); 49 | 50 | $.each(self.columns, function(i, v) { 51 | if (i == 0) 52 | thead.find('tr').append('' + v[1] + ''); 53 | else 54 | thead.find('tr').append('' + v[1] + ''); 55 | }); 56 | 57 | $(table).data('columns', self.columns); 58 | $(table).append(thead); 59 | } 60 | 61 | self.populateTable = function(dataSource, table) { 62 | var tab = $(table); 63 | columns = $.map($(table).data('columns'), function(d) { return d[0]; }); 64 | 65 | tab.append(''); 66 | 67 | $.each(dataSource, function(idx, data) { 68 | var row = $(''); 69 | $.each(data, function(key, val) { 70 | if (columns.indexOf(key) >= 0) { 71 | var cell = $(''); 72 | 73 | if (columns.indexOf(key) == 0) 74 | cell.data('priority', 'persist'); 75 | else 76 | cell.data('priority', columns.indexOf(key)); 77 | 78 | // Turn urls into actual anchors 79 | if (val.match) { 80 | var matches = val.match(/(https?:\/\/)([\dA-Za-z\.-]+)\.([A-Za-z\.]{2,6})([\/\w \.-]*)*\/?(\?.*)?(\#.*)?/g); 81 | if (matches) { 82 | var replacement = '' + matches[0] + ''; 83 | val = val.replace(matches[0], replacement); 84 | } 85 | } 86 | 87 | cell.html(val); 88 | row.append(cell); 89 | } 90 | }); 91 | tab.find('tbody').append(row); 92 | }); 93 | } 94 | 95 | $(document).ready(self.init.bind(self, spreadsheetKey)); 96 | }; 97 | 98 | })(); 99 | -------------------------------------------------------------------------------- /assets/style.css: -------------------------------------------------------------------------------- 1 | #data table { 2 | font: 100%/1.2 sans-serif; 3 | font-size: 0.75em; 4 | } 5 | 6 | #data table tr:nth-child(even) { 7 | background-color: #efefef; 8 | } 9 | 10 | #data table td.tool { 11 | font-weight: bold; 12 | } 13 | 14 | #data .tablesaw td { 15 | line-height: 1.4em; 16 | } 17 | 18 | #data a.ellipsis-link { 19 | display: inline-block; 20 | max-width: 180px; 21 | white-space: nowrap; 22 | overflow: hidden; 23 | text-overflow: ellipsis; 24 | } 25 | 26 | #data b.tablesaw-cell-label { 27 | width: 100%; 28 | margin: 0 0 6px 0; 29 | } 30 | 31 | @media screen and (max-width: 70em) { 32 | #data table { 33 | font-size: 0.7em; 34 | } 35 | } 36 | 37 | @media screen and (max-width: 60em) { 38 | a.ellipsis-link { 39 | max-width: 310px; 40 | } 41 | #data table { 42 | font: 100%/1.2 sans-serif; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /assets/tablesaw/icons/icons.css: -------------------------------------------------------------------------------- 1 | .tablesaw-bar .tablesaw-columntoggle-btnwrap > a.btn, 2 | .icon-arrow-gray-down { background-image: url('png/arrow-gray-down.png'); background-repeat: no-repeat; } 3 | 4 | .tablesaw-bar .tablesaw-advance > .btn.left, 5 | .icon-arrow-gray-left { background-image: url('png/arrow-gray-left.png'); background-repeat: no-repeat; } 6 | 7 | .tablesaw-bar .tablesaw-advance > .btn.right, 8 | .icon-arrow-gray-right { background-image: url('png/arrow-gray-right.png'); background-repeat: no-repeat; } 9 | 10 | .tablesaw-bar .btn-selected.btn-checkbox:after, 11 | .icon-check { background-image: url('png/check.png'); background-repeat: no-repeat; } 12 | 13 | .tablesaw-sortable .sortable-head.sortable-ascending button:after, 14 | .icon-sort-ascending { background-image: url('png/sort-ascending.png'); background-repeat: no-repeat; } 15 | 16 | .tablesaw-sortable .sortable-head.sortable-descending button:after, 17 | .icon-sort-descending { background-image: url('png/sort-descending.png'); background-repeat: no-repeat; } 18 | 19 | -------------------------------------------------------------------------------- /assets/tablesaw/icons/png/arrow-gray-down.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/INN/responsive-tables/f2173792a253c19b6b33fd2d785fd3c44fd5a79b/assets/tablesaw/icons/png/arrow-gray-down.png -------------------------------------------------------------------------------- /assets/tablesaw/icons/png/arrow-gray-left.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/INN/responsive-tables/f2173792a253c19b6b33fd2d785fd3c44fd5a79b/assets/tablesaw/icons/png/arrow-gray-left.png -------------------------------------------------------------------------------- /assets/tablesaw/icons/png/arrow-gray-right.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/INN/responsive-tables/f2173792a253c19b6b33fd2d785fd3c44fd5a79b/assets/tablesaw/icons/png/arrow-gray-right.png -------------------------------------------------------------------------------- /assets/tablesaw/icons/png/check.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/INN/responsive-tables/f2173792a253c19b6b33fd2d785fd3c44fd5a79b/assets/tablesaw/icons/png/check.png -------------------------------------------------------------------------------- /assets/tablesaw/icons/png/sort-ascending.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/INN/responsive-tables/f2173792a253c19b6b33fd2d785fd3c44fd5a79b/assets/tablesaw/icons/png/sort-ascending.png -------------------------------------------------------------------------------- /assets/tablesaw/icons/png/sort-descending.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/INN/responsive-tables/f2173792a253c19b6b33fd2d785fd3c44fd5a79b/assets/tablesaw/icons/png/sort-descending.png -------------------------------------------------------------------------------- /assets/tablesaw/icons/preview.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Icons Preview! 5 | 12 | 17 | 18 | 19 | 20 | 21 | 22 |
.icon-arrow-gray-down:

23 | 24 | 25 | 26 |
.icon-arrow-gray-left:

27 | 28 | 29 | 30 |
.icon-arrow-gray-right:

31 | 32 | 33 | 34 |
.icon-check:

35 | 36 | 37 | 38 |
.icon-sort-ascending:

39 | 40 | 41 | 42 |
.icon-sort-descending:

43 | 44 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /assets/tablesaw/tablesaw.css: -------------------------------------------------------------------------------- 1 | /*! Tablesaw - v0.1.2 - 2014-05-19 2 | * https://github.com/filamentgroup/tablesaw 3 | * Copyright (c) 2014 Filament Group; Licensed MIT */ 4 | 5 | table.tablesaw { 6 | empty-cells: show; 7 | max-width: 100%; 8 | width: 100%; 9 | } 10 | 11 | .tablesaw { 12 | border-collapse: collapse; 13 | width: 100%; 14 | } 15 | 16 | /* Structure */ 17 | 18 | .tablesaw { 19 | border: 0; 20 | padding: 0; 21 | } 22 | 23 | .tablesaw th, 24 | .tablesaw td { 25 | -webkit-box-sizing: border-box; 26 | -moz-box-sizing: border-box; 27 | box-sizing: border-box; 28 | padding: .5em .7em; 29 | } 30 | 31 | .tablesaw thead tr:first-child th { 32 | padding-top: .9em; 33 | padding-bottom: .7em; 34 | } 35 | 36 | 37 | 38 | .tablesaw-enhanced .tablesaw-bar .btn { 39 | border: 1px solid #ccc; 40 | background: none; 41 | background-color: #fafafa; 42 | -webkit-box-shadow: 0 1px 0 rgba(255,255,255,1); 43 | box-shadow: 0 1px 0 rgba(255,255,255,1); 44 | color: #4a4a4a; 45 | clear: both; 46 | cursor: pointer; 47 | display: block; 48 | font: bold 20px/1 sans-serif; 49 | margin: 0; 50 | padding: .5em .85em .4em .85em; 51 | position: relative; 52 | text-align: center; 53 | text-decoration: none; 54 | text-transform: capitalize; 55 | text-shadow: 0 1px 0 #fff; 56 | width: 100%; 57 | /* Theming */ 58 | background-image: -webkit-gradient(linear, left top, left bottom, from(rgba( 255,255,255,.1 )), color-stop(50%, rgba( 255,255,255,.1 )), color-stop(55%, rgba( 170,170,170,.1 )), to(rgba( 120,120,120,.15 ))); 59 | background-image: -webkit-linear-gradient(top, rgba( 255,255,255,.1 ) 0%, rgba( 255,255,255,.1 ) 50%, rgba( 170,170,170,.1 ) 55%, rgba( 120,120,120,.15 ) 100%); 60 | background-image: linear-gradient( top, rgba( 255,255,255,.1 ) 0%, rgba( 255,255,255,.1 ) 50%, rgba( 170,170,170,.1 ) 55%, rgba( 120,120,120,.15 ) 100% ); 61 | -webkit-appearance: none !important; 62 | -moz-appearance: none !important; 63 | -webkit-box-sizing: border-box; 64 | -moz-box-sizing: border-box; 65 | box-sizing: border-box; 66 | -webkit-font-smoothing: antialiased; 67 | border-radius: .25em; 68 | opacity: 1; 69 | } 70 | 71 | .tablesaw-enhanced .tablesaw-bar a.btn, 72 | .tablesaw-enhanced .tablesaw-bar button.btn, 73 | .tablesaw-enhanced .tablesaw-bar [type="submit"].btn { 74 | color: #1c95d4; 75 | } 76 | 77 | .tablesaw-enhanced .tablesaw-bar .btn:hover { 78 | text-decoration: none; 79 | } 80 | 81 | /* Default radio/checkbox styling horizonal controlgroups. */ 82 | 83 | .tablesaw-bar .btn-selected:not(.ui-disabled):not(.btn-checkbox) { 84 | background-color: #29abe2; 85 | background-image: -webkit-gradient(linear, left top, left bottom, from(rgba( 25,25,100,.25 )), to(rgba( 255,255,255,0 ))); 86 | background-image: -webkit-linear-gradient(top, rgba( 25,25,100,.25 ) 0%, rgba( 255,255,255,0 ) 70%); 87 | background-image: linear-gradient( top, rgba( 25,25,100,.25 ) 0%, rgba( 255,255,255,0 ) 70% ); 88 | color: #fff; 89 | -webkit-box-shadow: inset 0 2px 8px rgba(0,0,0,.1); 90 | box-shadow: inset 0 2px 8px rgba(0,0,0,.1); 91 | text-shadow: 0 -1px 0 rgba(0,0,0,.3); 92 | } 93 | 94 | .ie-lte8 .tablesaw-bar .btn-selected { 95 | background-color: #29abe2; 96 | color: #fff; 97 | } 98 | 99 | .ie-lte8 .tablesaw-bar .btn-selected.ui-disabled, 100 | .ie-lte8 .tablesaw-bar .btn-selected.btn-checkbox { 101 | background-color: #fafafa; 102 | color: #4d4d4d; 103 | } 104 | 105 | .tablesaw-enhanced .tablesaw-bar .btn:not(.ui-disabled):active { 106 | background-color: #ddd; 107 | background-image: -webkit-gradient(linear, left top, left bottom, from(rgba( 100,100,100,.35 )), to(rgba( 255,255,255,0 ))); 108 | background-image: -webkit-linear-gradient(top, rgba( 100,100,100,.35 ) 0%, rgba( 255,255,255,0 ) 70%); 109 | background-image: linear-gradient( top, rgba( 100,100,100,.35 ) 0%, rgba( 255,255,255,0 ) 70% ); 110 | } 111 | 112 | .ie-lte8 .tablesaw-bar .btn:active { 113 | background-color: #ddd; 114 | } 115 | 116 | .ie-lte8 .tablesaw-bar .btn.ui-disabled:active { 117 | background-color: #fafafa; 118 | } 119 | 120 | .tablesaw-enhanced .tablesaw-bar .btn:not(.ui-disabled):hover, 121 | .tablesaw-enhanced .tablesaw-bar .btn:not(.ui-disabled):focus { 122 | color: #208de3; 123 | background-color: #fff; 124 | outline: none; 125 | } 126 | 127 | .tablesaw-bar .btn:not(.ui-disabled):focus { 128 | -webkit-box-shadow: 0 0 .35em #4faeef !important; 129 | box-shadow: 0 0 .35em #4faeef !important; 130 | } 131 | 132 | .tablesaw-bar .btn.btn-checkbox:not(.ui-disabled) input:focus+* { 133 | color: #208de3; 134 | } 135 | 136 | .ie-lte8 .tablesaw-bar .btn:hover, 137 | .ie-lte8 .tablesaw-bar .btn:focus { 138 | color: #208de3; 139 | background-color: #fff; 140 | border-color: #aaa; 141 | outline: none; 142 | } 143 | 144 | .ie-lte8 .tablesaw-bar .btn.ui-disabled:hover, 145 | .ie-lte8 .tablesaw-bar .btn.ui-disabled:focus { 146 | color: #4d4d4d; 147 | background-color: #fafafa; 148 | border-color: #ccc; 149 | outline: auto; 150 | } 151 | 152 | .tablesaw-bar .btn-group > .btn:hover+.btn { 153 | border-left-color: #aaa; 154 | } 155 | 156 | .tablesaw-bar .ui-disabled { 157 | opacity: .5 !important; 158 | } 159 | 160 | .tablesaw-bar .btn-select select, 161 | .tablesaw-bar .btn-checkbox input { 162 | background: none; 163 | border: none; 164 | display: block; 165 | position: absolute; 166 | font-weight: inherit; 167 | left: 0; 168 | top: 0; 169 | margin: 0; 170 | width: 100%; 171 | height: 100%; 172 | z-index: 2; 173 | min-height: 1em; 174 | } 175 | 176 | .tablesaw-bar .btn-select select, 177 | .tablesaw-bar .btn-checkbox input { 178 | opacity: 0; 179 | display: inline-block; 180 | color: transparent; 181 | } 182 | 183 | .tablesaw-bar .btn select option { 184 | background: #fff; 185 | color: #000; 186 | font-family: sans-serif; 187 | } 188 | 189 | .ie-lte8 .tablesaw-bar .btn-select select, 190 | .ie-lte8 .tablesaw-bar .btn-checkbox input { 191 | filter: alpha(opacity=0); 192 | } 193 | 194 | .tablesaw-enhanced .tablesaw-bar .btn.btn-select { 195 | color: #4d4d4d; 196 | padding-right: 2.5em; 197 | min-width: 7.25em; 198 | text-align: left; 199 | text-indent: 0; 200 | } 201 | 202 | .ie-lte8 .tablesaw-bar .btn-select { 203 | min-width: 6.1em; 204 | } 205 | 206 | .tablesaw-bar .btn.btn-small, 207 | .tablesaw-bar .btn.btn-micro { 208 | display: inline-block; 209 | width: auto; 210 | height: auto; 211 | position: relative; 212 | top: 0; 213 | } 214 | 215 | .tablesaw-bar .btn.btn-small { 216 | font-size: 17px; 217 | line-height: 19px; 218 | padding: .3em 1em .3em 1em; 219 | } 220 | 221 | .tablesaw-bar .btn.btn-micro { 222 | font-size: 13px; 223 | padding: .4em .7em .25em .7em; 224 | } 225 | 226 | .tablesaw-bar .btn-checkbox.btn-small { 227 | padding-left: 2em; 228 | } 229 | 230 | .tablesaw-bar .btn-checkbox.btn-micro { 231 | padding-left: 1.8em; 232 | } 233 | 234 | .tablesaw-enhanced .tablesaw-bar .btn-checkbox, 235 | .tablesaw-enhanced .tablesaw-bar .btn-select { 236 | text-align: left; 237 | } 238 | 239 | .tablesaw-enhanced .tablesaw-bar .btn-checkbox { 240 | padding-left: 2.35em; 241 | } 242 | 243 | .tablesaw-bar .btn-checkbox:after, 244 | .tablesaw-bar .btn-select:after { 245 | background: #e5e5e5; 246 | background: rgba(0,0,0,.1); 247 | -webkit-box-shadow: 0 2px 2px rgba(255,255,255,.25); 248 | box-shadow: 0 2px 2px rgba(255,255,255,.25); 249 | content: " "; 250 | display: block; 251 | position: absolute; 252 | } 253 | 254 | .tablesaw-bar .btn-checkbox:after { 255 | left: .5em; 256 | top: 50%; 257 | margin-top: -.575em; 258 | height: 1.15em; 259 | width: 1.15em; 260 | background-color: #eee; 261 | background-image: -webkit-gradient(linear, left top, left bottom, from(rgba( 0,0,0,.15 )), to(rgba( 255,255,255,0 ))); 262 | background-image: -webkit-linear-gradient(top, rgba( 0,0,0,.15 ) 0%, rgba( 255,255,255,0 ) 70%); 263 | background-image: linear-gradient( top, rgba( 0,0,0,.15 ) 0%, rgba( 255,255,255,0 ) 70% ); 264 | background-repeat: none; 265 | border-radius: .25em; 266 | -webkit-box-shadow: 0 1px 0 rgba(255,255,255,1), inset 0 1px 5px rgba(100,100,100,.15); 267 | box-shadow: 0 1px 0 rgba(255,255,255,1), inset 0 1px 5px rgba(100,100,100,.15); 268 | } 269 | 270 | .ie-lte8 .tablesaw-bar .btn-checkbox:after { 271 | border: 1px solid #ccc; 272 | } 273 | 274 | .tablesaw-bar .btn-checkbox.btn-small:after { 275 | left: .3em; 276 | } 277 | 278 | .tablesaw-bar .btn-checkbox.btn-micro:after { 279 | left: .25em; 280 | } 281 | 282 | .tablesaw-bar .btn-select.btn-small, 283 | .tablesaw-bar .btn-select.btn-micro { 284 | padding-right: 1.5em; 285 | } 286 | 287 | .tablesaw-bar .btn-select:after { 288 | background: none; 289 | background-repeat: no-repeat; 290 | background-position: .25em .45em; 291 | content: "\25bc"; 292 | font-size: .55em; 293 | padding-top: 1.2em; 294 | padding-left: 1em; 295 | left: auto; 296 | right: 0; 297 | margin: 0; 298 | top: 0; 299 | bottom: 0; 300 | width: 1.8em; 301 | } 302 | 303 | .tablesaw-bar .btn-select.btn-small:after, 304 | .tablesaw-bar .btn-select.btn-micro:after { 305 | width: 1.2em; 306 | font-size: .5em; 307 | padding-top: 1em; 308 | padding-right: .5em; 309 | line-height: 1.65; 310 | background: none; 311 | -webkit-box-shadow: none; 312 | box-shadow: none; 313 | border-left-width: 0; 314 | } 315 | 316 | .tablesaw-bar .btn-selected.btn-checkbox:after { 317 | -webkit-box-shadow: 0 1px 0 rgba(255,255,255,1), inset 0 1px 5px rgba(0,75,115,.45); 318 | box-shadow: 0 1px 0 rgba(255,255,255,1), inset 0 1px 5px rgba(0,75,115,.45); 319 | background-color: #34a3de; 320 | color: #fff; 321 | -webkit-background-size: 115% 115%; 322 | background-size: 115% 115%; 323 | background-position: -1px -1px; 324 | } 325 | 326 | /* Themes */ 327 | 328 | /* Simplified */ 329 | 330 | .tablesaw-bar .btn-group .btn.theme-simple, 331 | .tablesaw-bar .btn-group .btn.theme-simple:first-child, 332 | .tablesaw-bar .btn-group .btn.theme-simple:last-child, 333 | .tablesaw-bar .btn.theme-simple { 334 | border: none; 335 | border-radius: 0; 336 | margin-bottom: .35em; 337 | margin-top: .35em; 338 | } 339 | 340 | .tablesaw-bar .btn.theme-simple { 341 | background: transparent; 342 | border-width: 0; 343 | -webkit-box-shadow: none; 344 | box-shadow: none; 345 | display: block; 346 | font-weight: normal; 347 | } 348 | 349 | .tablesaw-bar .btn.theme-simple.btn-selected { 350 | font-weight: bold; 351 | } 352 | 353 | .tablesaw-bar .btn.theme-simple:not(.ui-disabled):hover, 354 | .tablesaw-bar .btn.theme-simple:not(.ui-disabled):focus { 355 | background: transparent; 356 | } 357 | 358 | .tablesaw-bar .btn.theme-simple:active { 359 | background: none; 360 | } 361 | 362 | .tablesaw-bar .btn.theme-simple.ruled { 363 | border-width: 1px 0; 364 | } 365 | 366 | .tablesaw-bar .btn.theme-simple+.theme-simple { 367 | border-top: none; 368 | border-radius: 0; 369 | } 370 | 371 | /* Column navigation buttons for swipe and columntoggle tables */ 372 | 373 | .tablesaw-bar .tablesaw-advance { 374 | position: relative; 375 | top: -2.6em; 376 | } 377 | 378 | .tablesaw-advance .btn { 379 | -webkit-appearance: none; 380 | -moz-appearance: none; 381 | -webkit-box-sizing: border-box; 382 | -moz-box-sizing: border-box; 383 | box-sizing: border-box; 384 | text-shadow: 0 1px 0 #fff; 385 | border-radius: .25em; 386 | } 387 | 388 | .tablesaw-advance .btn.btn-micro { 389 | font-size: .8125em; 390 | padding: .4em .7em .25em .7em; 391 | } 392 | 393 | .tablesaw-bar .tablesaw-advance a.tablesaw-nav-btn { 394 | display: inline-block; 395 | overflow: hidden; 396 | width: 1.8em; 397 | height: 1.8em; 398 | background-position: 50% 50%; 399 | margin-left: .5em; 400 | } 401 | 402 | .tablesaw-advance a.tablesaw-nav-btn.disabled { 403 | opacity: .25; 404 | cursor: default; 405 | pointer-events: none; 406 | } 407 | 408 | /* Table Toolbar */ 409 | 410 | .tablesaw-bar { 411 | clear: both; 412 | font-family: sans-serif; 413 | } 414 | 415 | .tablesaw-bar.mode-swipe, 416 | .tablesaw-bar.mode-columntoggle { 417 | margin-top: -2em; 418 | position: relative; 419 | top: 2em; 420 | } 421 | 422 | .tablesaw-toolbar { 423 | font-size: .875em; 424 | } 425 | 426 | .tablesaw-toolbar label { 427 | padding: .5em 0; 428 | clear: both; 429 | display: block; 430 | color: #888; 431 | margin-right: .5em; 432 | text-transform: uppercase; 433 | } 434 | 435 | .tablesaw-bar .btn, 436 | .tablesaw-enhanced .tablesaw-bar .btn { 437 | margin-top: .5em; 438 | margin-bottom: .5em; 439 | } 440 | 441 | .tablesaw-bar .btn-select, 442 | .tablesaw-enhanced .tablesaw-bar .btn-select { 443 | margin-bottom: 0; 444 | } 445 | 446 | .tablesaw-bar .tablesaw-toolbar .btn { 447 | margin-left: .4em; 448 | margin-top: 0; 449 | text-transform: uppercase; 450 | border: none; 451 | -webkit-box-shadow: none; 452 | box-shadow: none; 453 | background: transparent; 454 | font-family: sans-serif; 455 | font-size: 1em; 456 | padding-left: .3em; 457 | } 458 | 459 | .tablesaw-bar .tablesaw-toolbar .btn-select { 460 | min-width: 0; 461 | } 462 | 463 | .tablesaw-bar .tablesaw-toolbar .btn-select:after { 464 | padding-top: .9em; 465 | } 466 | 467 | .tablesaw-bar .tablesaw-toolbar select { 468 | color: #888; 469 | text-transform: none; 470 | background: transparent; 471 | } 472 | 473 | .tablesaw-toolbar ~ table { 474 | clear: both; 475 | } 476 | 477 | .tablesaw-toolbar .a11y-sm { 478 | clip: rect(0 0 0 0); 479 | height: 1px; 480 | overflow: hidden; 481 | position: absolute; 482 | width: 1px; 483 | } 484 | 485 | @media (min-width: 24em) { 486 | .tablesaw-toolbar .a11y-sm { 487 | clip: none; 488 | height: auto; 489 | width: auto; 490 | position: static; 491 | overflow: visible; 492 | } 493 | } 494 | 495 | @media (min-width: 60em) { 496 | .tablesaw-bar.mode-swipe, 497 | .tablesaw-bar.mode-columntoggle { 498 | margin-top: 0; 499 | top: 0; 500 | } 501 | 502 | .tablesaw-bar .tablesaw-advance { 503 | position: static; 504 | } 505 | 506 | .tablesaw-bar { 507 | margin-top: 0; 508 | position: static; 509 | } 510 | } 511 | 512 | 513 | 514 | table.tablesaw tbody th { 515 | font-weight: bold; 516 | } 517 | 518 | table.tablesaw thead th, 519 | table.tablesaw thead td { 520 | color: #444; 521 | font-size: .9em; 522 | } 523 | 524 | .tablesaw th, 525 | .tablesaw td { 526 | line-height: 1em; 527 | text-align: left; 528 | vertical-align: middle; 529 | } 530 | 531 | .tablesaw td, 532 | .tablesaw tbody th { 533 | vertical-align: middle; 534 | font-size: 1.17em; 535 | /* 19px */ 536 | } 537 | 538 | .tablesaw td .btn, 539 | .tablesaw tbody th .btn { 540 | margin: 0; 541 | } 542 | 543 | .tablesaw thead { 544 | border: 1px solid #e5e5e4; 545 | background: #e2dfdc; 546 | background-image: -webkit-gradient(linear, left top, left bottom, from(#fff), to(#e2dfdc)); 547 | background-image: -webkit-linear-gradient(top, #fff, #e2dfdc); 548 | background-image: linear-gradient(to bottom, #fff, #e2dfdc); 549 | } 550 | 551 | .tablesaw thead th { 552 | font-weight: 100; 553 | color: #777; 554 | text-transform: uppercase; 555 | text-shadow: 0 1px 0 #fff; 556 | text-align: left; 557 | } 558 | 559 | .tablesaw thead tr:first-child th { 560 | font-weight: normal; 561 | font-family: sans-serif; 562 | border-right: 1px solid #e4e1de; 563 | } 564 | 565 | /* Table rows have a gray bottom stroke by default */ 566 | 567 | .tablesaw tbody tr { 568 | border-bottom: 1px solid #dfdfdf; 569 | } 570 | 571 | .tablesaw caption { 572 | text-align: left; 573 | margin-bottom: 1.4em; 574 | opacity: 50%; 575 | } 576 | 577 | /* Stack */ 578 | 579 | .tablesaw-cell-label-top { 580 | text-transform: uppercase; 581 | font-size: .9em; 582 | font-weight: normal; 583 | } 584 | 585 | .tablesaw-cell-label { 586 | font-size: .65em; 587 | text-transform: uppercase; 588 | color: #888; 589 | font-family: sans-serif; 590 | } 591 | 592 | @media (min-width: 60em) { 593 | .tablesaw td { 594 | line-height: 2em; 595 | } 596 | } 597 | 598 | /* Table rows have a gray bottom stroke by default */ 599 | 600 | .tablesaw-stack tbody tr { 601 | border-bottom: 1px solid #dfdfdf; 602 | } 603 | 604 | .tablesaw-stack td .tablesaw-cell-label, 605 | .tablesaw-stack th .tablesaw-cell-label { 606 | display: none; 607 | } 608 | 609 | /* Mobile first styles: Begin with the stacked presentation at narrow widths */ 610 | 611 | @media only all { 612 | /* Show the table cells as a block level element */ 613 | 614 | .tablesaw-stack td, 615 | .tablesaw-stack th { 616 | text-align: left; 617 | display: block; 618 | } 619 | 620 | .tablesaw-stack tr { 621 | clear: both; 622 | display: table-row; 623 | } 624 | 625 | /* Make the label elements a percentage width */ 626 | 627 | .tablesaw-stack td .tablesaw-cell-label, 628 | .tablesaw-stack th .tablesaw-cell-label { 629 | display: block; 630 | padding: 0 .6em 0 0; 631 | min-width: 30%; 632 | display: inline-block; 633 | } 634 | 635 | /* For grouped headers, have a different style to visually separate the levels by classing the first label in each col group */ 636 | 637 | .tablesaw-stack th .tablesaw-cell-label-top, 638 | .tablesaw-stack td .tablesaw-cell-label-top { 639 | display: block; 640 | padding: .4em 0; 641 | margin: .4em 0; 642 | } 643 | 644 | .tablesaw-cell-label { 645 | display: block; 646 | } 647 | 648 | /* Avoid double strokes when stacked */ 649 | 650 | .tablesaw-stack tbody th.group { 651 | margin-top: -1px; 652 | } 653 | 654 | /* Avoid double strokes when stacked */ 655 | 656 | .tablesaw-stack th.group b.tablesaw-cell-label { 657 | display: none !important; 658 | } 659 | } 660 | 661 | /*@media (max-width: 39.9375em) {*/ 662 | @media (max-width: 59.9375em) { 663 | .tablesaw-stack thead td, 664 | .tablesaw-stack thead th { 665 | display: none; 666 | } 667 | 668 | .tablesaw-stack tbody td, 669 | .tablesaw-stack tbody th { 670 | clear: left; 671 | float: left; 672 | width: 100%; 673 | } 674 | } 675 | 676 | /* Media query to show as a standard table at 560px (35em x 16px) or wider */ 677 | 678 | @media (min-width: 60em) { 679 | .tablesaw-stack tr { 680 | display: table-row; 681 | } 682 | 683 | /* Show the table header rows */ 684 | 685 | .tablesaw-stack td, 686 | .tablesaw-stack th, 687 | .tablesaw-stack thead td, 688 | .tablesaw-stack thead th { 689 | display: table-cell; 690 | margin: 0; 691 | } 692 | 693 | /* Hide the labels in each cell */ 694 | 695 | .tablesaw-stack td .tablesaw-cell-label, 696 | .tablesaw-stack th .tablesaw-cell-label { 697 | display: none !important; 698 | } 699 | } 700 | 701 | .tablesaw-fix-persist { 702 | table-layout: fixed; 703 | } 704 | 705 | /* see Gruntfile.js for grunticon selector */ 706 | 707 | .btn.tablesaw-columntoggle-btn { 708 | float: right; 709 | } 710 | 711 | .btn.tablesaw-columntoggle-btn span { 712 | text-indent: -9999px; 713 | display: inline-block; 714 | } 715 | 716 | .tablesaw-columntoggle-btnwrap .dialog-content { 717 | padding: 0; 718 | } 719 | 720 | .tablesaw-columntoggle tbody td { 721 | line-height: 1.5; 722 | } 723 | 724 | /* Remove top/bottom margins around the fieldcontain on check list */ 725 | 726 | .tablesaw-columntoggle-popup fieldset { 727 | margin: 0; 728 | } 729 | 730 | /* Hide all prioritized columns by default */ 731 | 732 | @media only all { 733 | .tablesaw-columntoggle th.tablesaw-priority-6, 734 | .tablesaw-columntoggle td.tablesaw-priority-6, 735 | .tablesaw-columntoggle th.tablesaw-priority-5, 736 | .tablesaw-columntoggle td.tablesaw-priority-5, 737 | .tablesaw-columntoggle th.tablesaw-priority-4, 738 | .tablesaw-columntoggle td.tablesaw-priority-4, 739 | .tablesaw-columntoggle th.tablesaw-priority-3, 740 | .tablesaw-columntoggle td.tablesaw-priority-3, 741 | .tablesaw-columntoggle th.tablesaw-priority-2, 742 | .tablesaw-columntoggle td.tablesaw-priority-2, 743 | .tablesaw-columntoggle th.tablesaw-priority-1, 744 | .tablesaw-columntoggle td.tablesaw-priority-1 { 745 | display: none; 746 | } 747 | } 748 | 749 | .tablesaw-columntoggle-btnwrap { 750 | position: relative; 751 | float: right; 752 | } 753 | 754 | .tablesaw-columntoggle-btnwrap .dialog-content { 755 | top: 0 !important; 756 | right: 1em; 757 | left: auto !important; 758 | width: 12em; 759 | max-width: 18em; 760 | margin: -.5em auto 0; 761 | } 762 | 763 | .tablesaw-columntoggle-btnwrap .btn-group .btn.theme-simple:first-child { 764 | margin-top: 0; 765 | border-top-right-radius: .5em; 766 | border-top-left-radius: .5em; 767 | } 768 | 769 | .tablesaw-columntoggle-btnwrap .btn-group .btn.theme-simple:last-child { 770 | border-bottom-right-radius: .5em; 771 | border-bottom-left-radius: .5em; 772 | } 773 | 774 | .tablesaw-columntoggle-btnwrap .btn-group .btn.theme-simple { 775 | margin-bottom: .1em; 776 | margin-top: .1em; 777 | } 778 | 779 | .tablesaw-columntoggle-btnwrap .dialog-content:focus { 780 | outline-style: none; 781 | } 782 | 783 | .dialog-table-coltoggle { 784 | border-radius: .5em; 785 | } 786 | 787 | /* Preset breakpoints if "" class added to table */ 788 | 789 | /* Show priority 1 at 320px (20em x 16px) */ 790 | 791 | @media screen and (min-width: 20em) { 792 | .tablesaw-columntoggle th.tablesaw-priority-1, 793 | .tablesaw-columntoggle td.tablesaw-priority-1 { 794 | display: table-cell; 795 | } 796 | } 797 | 798 | /* Show priority 2 at 480px (30em x 16px) */ 799 | 800 | @media screen and (min-width: 30em) { 801 | .tablesaw-columntoggle th.tablesaw-priority-2, 802 | .tablesaw-columntoggle td.tablesaw-priority-2 { 803 | display: table-cell; 804 | } 805 | } 806 | 807 | /* Show priority 3 at 640px (60em x 16px) */ 808 | 809 | @media screen and (min-width: 60em) { 810 | .tablesaw-columntoggle th.tablesaw-priority-3, 811 | .tablesaw-columntoggle td.tablesaw-priority-3 { 812 | display: table-cell; 813 | } 814 | 815 | .tablesaw-columntoggle tbody td { 816 | line-height: 2; 817 | } 818 | } 819 | 820 | /* Show priority 4 at 800px (60em x 16px) */ 821 | 822 | @media screen and (min-width: 60em) { 823 | .tablesaw-columntoggle th.tablesaw-priority-4, 824 | .tablesaw-columntoggle td.tablesaw-priority-4 { 825 | display: table-cell; 826 | } 827 | } 828 | 829 | /* Show priority 5 at 960px (60em x 16px) */ 830 | 831 | @media screen and (min-width: 60em) { 832 | .tablesaw-columntoggle th.tablesaw-priority-5, 833 | .tablesaw-columntoggle td.tablesaw-priority-5 { 834 | display: table-cell; 835 | } 836 | } 837 | 838 | /* Show priority 6 at 1,120px (70em x 16px) */ 839 | 840 | @media screen and (min-width: 70em) { 841 | .tablesaw-columntoggle th.tablesaw-priority-6, 842 | .tablesaw-columntoggle td.tablesaw-priority-6 { 843 | display: table-cell; 844 | } 845 | } 846 | 847 | @media only all { 848 | .tablesaw-swipe .tablesaw-cell-persist { 849 | border-right: 1px solid #e4e1de; 850 | } 851 | 852 | .tablesaw-swipe .tablesaw-cell-persist { 853 | -webkit-box-shadow: 3px 0 4px -1px #e4e1de; 854 | box-shadow: 3px 0 4px -1px #e4e1de; 855 | } 856 | 857 | /* Unchecked manually: Always hide */ 858 | 859 | .tablesaw-swipe th.tablesaw-cell-hidden, 860 | .tablesaw-swipe td.tablesaw-cell-hidden, 861 | .tablesaw-columntoggle th.tablesaw-cell-hidden, 862 | .tablesaw-columntoggle td.tablesaw-cell-hidden { 863 | display: none; 864 | } 865 | 866 | /* Checked manually: Always show */ 867 | 868 | .tablesaw-columntoggle th.tablesaw-cell-visible, 869 | .tablesaw-columntoggle td.tablesaw-cell-visible { 870 | display: table-cell; 871 | } 872 | } 873 | 874 | /* Vertical Button Grouping (default) */ 875 | 876 | .tablesaw-columntoggle-popup .btn-group { 877 | margin: 0; 878 | padding: 0; 879 | } 880 | 881 | .tablesaw-columntoggle-popup .btn-group .btn { 882 | margin: 0; 883 | } 884 | 885 | .tablesaw-columntoggle-popup .btn-group > .btn { 886 | margin-top: 0; 887 | border-radius: 0; 888 | border-top-width: 0; 889 | width: 100%; 890 | } 891 | 892 | .tablesaw-columntoggle-popup .btn-group > .btn:first-child { 893 | border-top-width: 1px; 894 | border-top-left-radius: .25em; 895 | border-top-right-radius: .25em; 896 | } 897 | 898 | .tablesaw-columntoggle-popup .btn-group > .btn:last-child { 899 | border-bottom-left-radius: .25em; 900 | border-bottom-right-radius: .25em; 901 | } 902 | 903 | .tablesaw-sortable, 904 | .tablesaw-sortable thead, 905 | .tablesaw-sortable thead tr, 906 | .tablesaw-sortable thead tr th { 907 | position: relative; 908 | } 909 | 910 | .tablesaw-sortable thead tr th { 911 | padding-right: 1.6em; 912 | vertical-align: top; 913 | } 914 | 915 | .tablesaw-sortable th.sortable-head, 916 | .tablesaw-sortable tr:first-child th.sortable-head { 917 | padding: 0; 918 | } 919 | 920 | .tablesaw-sortable th.sortable-head button { 921 | padding-top: .9em; 922 | padding-bottom: .7em; 923 | padding-left: .6em; 924 | padding-right: 1.6em; 925 | } 926 | 927 | .tablesaw-sortable .sortable-head button { 928 | min-width: 100%; 929 | color: inherit; 930 | background: transparent; 931 | border: 0; 932 | padding: 0; 933 | text-align: left; 934 | font: inherit; 935 | text-transform: inherit; 936 | position: relative; 937 | } 938 | 939 | .tablesaw-sortable .sortable-head.sortable-ascending button:after, 940 | .tablesaw-sortable .sortable-head.sortable-descending button:after { 941 | width: 7px; 942 | height: 10px; 943 | content: "\0020"; 944 | position: absolute; 945 | right: .5em; 946 | } 947 | 948 | .tablesaw-sortable .not-applicable:after { 949 | content: "--"; 950 | display: block; 951 | } 952 | 953 | .tablesaw-sortable .not-applicable span { 954 | display: none; 955 | } 956 | 957 | .tablesaw-sortable-switch { 958 | float: left; 959 | width: 100%; 960 | } 961 | 962 | @media (min-width: 60em) { 963 | .tablesaw-sortable-switch { 964 | width: auto; 965 | } 966 | } 967 | 968 | .tablesaw-advance { 969 | display: block; 970 | float: right; 971 | } 972 | 973 | .tablesaw-advance.minimap { 974 | margin-right: .4em; 975 | } 976 | 977 | .tablesaw-advance-dots { 978 | float: left; 979 | margin: 0; 980 | padding: 0; 981 | list-style: none; 982 | } 983 | 984 | .tablesaw-advance-dots li { 985 | display: table-cell; 986 | margin: 0; 987 | padding: .4em .2em; 988 | } 989 | 990 | .tablesaw-advance-dots li i { 991 | width: .25em; 992 | height: .25em; 993 | background: #555; 994 | border-radius: 100%; 995 | display: inline-block; 996 | } 997 | 998 | .tablesaw-advance-dots-hide { 999 | opacity: .25; 1000 | cursor: default; 1001 | pointer-events: none; 1002 | } 1003 | 1004 | .tablesaw-modeswitch { 1005 | clear: both; 1006 | } 1007 | 1008 | .tablesaw-sortable-switch + .tablesaw-modeswitch { 1009 | border-top: 1px solid #eae8e6; 1010 | } 1011 | 1012 | @media (min-width: 60em) { 1013 | .tablesaw-modeswitch { 1014 | float: left; 1015 | clear: none; 1016 | } 1017 | 1018 | .tablesaw-sortable-switch + .tablesaw-modeswitch { 1019 | border-top: none; 1020 | } 1021 | } 1022 | -------------------------------------------------------------------------------- /assets/tablesaw/tablesaw.js: -------------------------------------------------------------------------------- 1 | /*! Tablesaw - v0.1.2 - 2014-05-19 2 | * https://github.com/filamentgroup/tablesaw 3 | * Copyright (c) 2014 Filament Group; Licensed MIT */ 4 | ;(function( $ ) { 5 | var div = document.createElement('div'), 6 | all = div.getElementsByTagName('i'), 7 | $doc = $( document.documentElement ); 8 | 9 | div.innerHTML = ''; 10 | if( all[ 0 ] ) { 11 | $doc.addClass( 'ie-lte8' ); 12 | } 13 | 14 | // Cut the mustard 15 | if( !( 'querySelector' in document ) || ( window.blackberry && !window.WebKitPoint ) || window.operamini ) { 16 | return; 17 | } else { 18 | $doc.addClass( 'tablesaw-enhanced' ); 19 | 20 | // DOM-ready auto-init of plugins. 21 | // Many plugins bind to an "enhance" event to init themselves on dom ready, or when new markup is inserted into the DOM 22 | $( function(){ 23 | $( document ).trigger( "enhance.tablesaw" ); 24 | }); 25 | } 26 | 27 | })( jQuery ); 28 | ;(function( $ ) { 29 | var pluginName = "table", 30 | classes = { 31 | toolbar: "tablesaw-bar" 32 | }, 33 | events = { 34 | create: "tablesawcreate", 35 | destroy: "tablesawdestroy" 36 | }, 37 | defaultMode = "stack", 38 | initSelector = "table[data-mode],table[data-sortable]"; 39 | 40 | var Table = function( element ) { 41 | if( !element ) { 42 | throw new Error( "Tablesaw requires an element." ); 43 | } 44 | 45 | this.table = element; 46 | this.$table = $( element ); 47 | 48 | this.mode = this.$table.attr( "data-mode" ) || defaultMode; 49 | 50 | this.init(); 51 | }; 52 | 53 | Table.prototype.init = function() { 54 | // assign an id if there is none 55 | if ( !this.$table.attr( "id" ) ) { 56 | this.$table.attr( "id", pluginName + "-" + Math.round( Math.random() * 10000 ) ); 57 | } 58 | 59 | this.createToolbar(); 60 | 61 | // Add header cells 62 | var colstart, 63 | thrs = this.table.querySelectorAll( "thead tr" ), 64 | self = this; 65 | 66 | $( thrs ).each( function(){ 67 | var coltally = 0; 68 | 69 | $( this ).children().each( function(){ 70 | var span = parseInt( this.getAttribute( "colspan" ), 10 ), 71 | sel = ":nth-child(" + ( coltally + 1 ) + ")"; 72 | 73 | colstart = coltally + 1; 74 | 75 | if( span ){ 76 | for( var k = 0; k < span - 1; k++ ){ 77 | coltally++; 78 | sel += ", :nth-child(" + ( coltally + 1 ) + ")"; 79 | } 80 | } 81 | 82 | // Store "cells" data on header as a reference to all cells in the same column as this TH 83 | this.cells = self.$table.find("tr").not( $( thrs ).eq( 0 ) ).not( this ).children( sel ); 84 | coltally++; 85 | 86 | }); 87 | }); 88 | 89 | this.$table.trigger( events.create, [ this.mode, colstart ] ); 90 | }; 91 | 92 | Table.prototype.createToolbar = function() { 93 | // Insert the toolbar 94 | // TODO move this into a separate component 95 | var $toolbar = this.$table.prev( '.' + classes.toolbar ); 96 | if( !$toolbar.length ) { 97 | $toolbar = $( '
' ) 98 | .addClass( classes.toolbar ) 99 | .insertBefore( this.$table ); 100 | } 101 | this.$toolbar = $toolbar; 102 | 103 | if( this.mode ) { 104 | this.$toolbar.addClass( 'mode-' + this.mode ); 105 | } 106 | }; 107 | 108 | Table.prototype.destroy = function() { 109 | // Don’t remove the toolbar. Some of the table features are not yet destroy-friendly. 110 | this.$table.prev( '.' + classes.toolbar ).each(function() { 111 | this.className = this.className.replace( /\bmode\-\w*\b/gi, '' ); 112 | }); 113 | 114 | $( window ).off( 'resize.' + this.$table.attr( 'id' ) ); 115 | 116 | // other plugins 117 | this.$table.trigger( events.destroy, [ this.mode ] ); 118 | 119 | this.$table.removeAttr( 'data-mode' ); 120 | 121 | this.$table.removeData( pluginName ); 122 | }; 123 | 124 | // Collection method. 125 | $.fn[ pluginName ] = function() { 126 | return this.each( function() { 127 | var $t = $( this ); 128 | 129 | if( $t.data( pluginName ) ){ 130 | return; 131 | } 132 | 133 | var table = new Table( this ); 134 | $t.data( pluginName, table ); 135 | }); 136 | }; 137 | 138 | $( document ).on( "enhance.tablesaw", function( e ) { 139 | $( e.target ).find( initSelector )[ pluginName ](); 140 | }); 141 | 142 | }( jQuery )); 143 | 144 | ;(function( win, $, undefined ){ 145 | 146 | var classes = { 147 | stackTable: 'tablesaw-stack', 148 | cellLabels: 'tablesaw-cell-label' 149 | }; 150 | 151 | var data = { 152 | obj: 'tablesaw-stack' 153 | }; 154 | 155 | var attrs = { 156 | labelless: 'data-no-labels' 157 | }; 158 | 159 | var Stack = function( element ) { 160 | 161 | this.$table = $( element ); 162 | 163 | this.labelless = this.$table.is( '[' + attrs.labelless + ']' ); 164 | 165 | if( !this.labelless ) { 166 | // allHeaders references headers, plus all THs in the thead, which may include several rows, or not 167 | this.allHeaders = this.$table.find( "th" ); 168 | } 169 | 170 | this.$table.data( data.obj, this ); 171 | }; 172 | 173 | Stack.prototype.init = function( colstart ) { 174 | this.$table.addClass( classes.stackTable ); 175 | 176 | if( this.labelless ) { 177 | return; 178 | } 179 | 180 | // get headers in reverse order so that top-level headers are appended last 181 | var reverseHeaders = $( this.allHeaders ); 182 | 183 | // create the hide/show toggles 184 | reverseHeaders.each(function(){ 185 | var $cells = $( this.cells ), 186 | hierarchyClass = $cells.not( this ).filter( "thead th" ).length && " tablesaw-cell-label-top", 187 | text = $(this).text(); 188 | 189 | if( text !== "" ){ 190 | if( hierarchyClass ){ 191 | var iteration = parseInt( $( this ).attr( "colspan" ), 10 ), 192 | filter = ""; 193 | 194 | if( iteration ){ 195 | filter = "td:nth-child("+ iteration +"n + " + ( colstart ) +")"; 196 | } 197 | $cells.filter( filter ).prepend( "" + text + "" ); 198 | } else { 199 | $cells.prepend( "" + text + "" ); 200 | } 201 | } 202 | }); 203 | }; 204 | 205 | Stack.prototype.destroy = function() { 206 | this.$table.removeClass( classes.stackTable ); 207 | this.$table.find( '.' + classes.cellLabels ).remove(); 208 | }; 209 | 210 | // on tablecreate, init 211 | $( document ).on( "tablesawcreate", "table", function( e, mode, colstart ){ 212 | if( mode === 'stack' ){ 213 | var table = new Stack( this ); 214 | table.init( colstart ); 215 | } 216 | 217 | } ); 218 | 219 | $( document ).on( "tablesawdestroy", "table", function( e, mode ){ 220 | 221 | if( mode === 'stack' ){ 222 | $( this ).data( data.obj ).destroy(); 223 | } 224 | 225 | } ); 226 | 227 | }( this, jQuery )); 228 | ;(function( $ ) { 229 | var pluginName = "tablesawbtn", 230 | initSelector = ".btn", 231 | activeClass = "btn-selected", 232 | methods = { 233 | _create: function(){ 234 | return $( this ).each(function() { 235 | $( this ) 236 | .trigger( "beforecreate." + pluginName ) 237 | [ pluginName ]( "_init" ) 238 | .trigger( "create." + pluginName ); 239 | }); 240 | }, 241 | _init: function(){ 242 | var oEl = $( this ), 243 | disabled = this.disabled !== undefined && this.disabled !== false, 244 | input = this.getElementsByTagName( "input" )[ 0 ], 245 | sel = this.getElementsByTagName( "select" )[ 0 ]; 246 | 247 | if( input ) { 248 | $( this ) 249 | .addClass( "btn-" + input.type ) 250 | [ pluginName ]( "_formbtn", input ); 251 | } 252 | if( sel ) { 253 | $( this ) 254 | .addClass( "btn-select" ) 255 | [ pluginName ]( "_select", sel ); 256 | } 257 | if( disabled ) { 258 | oEl.addClass( "ui-disabled" ); 259 | } 260 | return oEl; 261 | }, 262 | _formbtn: function( input ) { 263 | var active = function( el, input ) { 264 | if( input.type === "radio" && input.checked ) { 265 | var group = input.getAttribute( "name" ); 266 | 267 | $( "[name='" + group + "']" ).each(function() { 268 | $( this ).parent().removeClass( activeClass ); 269 | }); 270 | el[ input.checked ? "addClass" : "removeClass" ]( activeClass ); 271 | } else if ( input.type === "checkbox" ) { 272 | el[ input.checked ? "addClass" : "removeClass" ]( activeClass ); 273 | } 274 | }; 275 | 276 | active( $( this ), input ); 277 | $( this ).bind("click", function() { 278 | active( $( this ), input ); 279 | }); 280 | }, 281 | _select: function( sel ) { 282 | var update = function( oEl, sel ) { 283 | var opts = $( sel ).find( "option" ), 284 | label, el, children; 285 | 286 | opts.each(function() { 287 | var opt = this; 288 | if( opt.selected ) { 289 | label = document.createTextNode( opt.text ); 290 | } 291 | }); 292 | 293 | children = oEl.childNodes; 294 | if( opts.length > 0 ){ 295 | for( var i = 0, l = children.length; i < l; i++ ) { 296 | el = children[ i ]; 297 | 298 | if( el && el.nodeType === 3 ) { 299 | oEl.replaceChild( label, el ); 300 | } 301 | } 302 | } 303 | }; 304 | 305 | update( this, sel ); 306 | $( this ).bind( "change refresh", function() { 307 | update( this, sel ); 308 | }); 309 | } 310 | }; 311 | 312 | // Collection method. 313 | $.fn[ pluginName ] = function( arrg, a, b, c ) { 314 | return this.each(function() { 315 | 316 | // if it's a method 317 | if( arrg && typeof( arrg ) === "string" ){ 318 | return $.fn[ pluginName ].prototype[ arrg ].call( this, a, b, c ); 319 | } 320 | 321 | // don't re-init 322 | if( $( this ).data( pluginName + "active" ) ){ 323 | return $( this ); 324 | } 325 | 326 | // otherwise, init 327 | 328 | $( this ).data( pluginName + "active", true ); 329 | $.fn[ pluginName ].prototype._create.call( this ); 330 | }); 331 | }; 332 | 333 | // add methods 334 | $.extend( $.fn[ pluginName ].prototype, methods ); 335 | 336 | $( document ).on( "enhance", function( e ) { 337 | $( initSelector, e.target )[ pluginName ](); 338 | }); 339 | 340 | }( jQuery )); 341 | ;(function( win, $, undefined ){ 342 | 343 | var ColumnToggle = function( element ) { 344 | 345 | this.$table = $( element ); 346 | 347 | this.classes = { 348 | columnToggleTable: 'tablesaw-columntoggle', 349 | columnBtnContain: 'tablesaw-columntoggle-btnwrap tablesaw-advance', 350 | dialogClass: this.$table.attr( 'data-dialog-class' ) || '', 351 | columnBtn: 'tablesaw-columntoggle-btn tablesaw-nav-btn', 352 | columnBtnSide: this.$table.attr( 'data-column-btn-side' ) || 'right', 353 | popup: 'tablesaw-columntoggle-popup', 354 | priorityPrefix: 'tablesaw-priority-', 355 | // TODO duplicate class, also in tables.js 356 | toolbar: 'tablesaw-bar' 357 | }; 358 | 359 | this.i18n = { 360 | columnBtnText: 'Columns', 361 | columnsDialogError: 'No eligible columns.' 362 | }; 363 | 364 | // Expose headers and allHeaders properties on the widget 365 | // headers references the THs within the first TR in the table 366 | this.headers = this.$table.find( 'tr:first > th' ); 367 | 368 | this.$table.data( 'tablesaw-coltoggle', this ); 369 | }; 370 | 371 | ColumnToggle.prototype.init = function() { 372 | 373 | var tableId, 374 | id, 375 | $menuButton, 376 | $popup, 377 | $menu, 378 | $btnContain, 379 | self = this; 380 | 381 | this.$table.addClass( this.classes.columnToggleTable ); 382 | 383 | tableId = this.$table.attr( "id" ); 384 | id = tableId + "-popup"; 385 | $btnContain = $( "
" ); 386 | $menuButton = $( "" + 387 | "" + this.i18n.columnBtnText + "" ); 388 | $popup = $( "
" ); 389 | $menu = $( "
" ); 390 | 391 | var hasNonPersistentHeaders = false; 392 | $( this.headers ).not( "td" ).each( function() { 393 | var $this = $( this ), 394 | priority = $this.attr("data-priority"), 395 | $cells = $this.add( this.cells ); 396 | 397 | if( priority && priority !== "persist" ) { 398 | $cells.addClass( self.classes.priorityPrefix + priority ); 399 | 400 | $("" ) 401 | .appendTo( $menu ) 402 | .trigger('enhance') 403 | .children( 0 ) 404 | .data( "cells", $cells ); 405 | 406 | hasNonPersistentHeaders = true; 407 | } 408 | }); 409 | 410 | if( !hasNonPersistentHeaders ) { 411 | $menu.append( '' ); 412 | } 413 | 414 | $menu.find( '.btn' ).tablesawbtn(); 415 | $menu.appendTo( $popup ); 416 | 417 | // bind change event listeners to inputs - TODO: move to a private method? 418 | $menu.on( "change", function(e) { 419 | var checked = e.target.checked; 420 | 421 | $( e.target ).data( "cells" ) 422 | .toggleClass( "tablesaw-cell-hidden", !checked ) 423 | .toggleClass( "tablesaw-cell-visible", checked ); 424 | 425 | self.$table.trigger( 'tablesawcolumns' ); 426 | }); 427 | 428 | $menuButton.appendTo( $btnContain ); 429 | $btnContain.appendTo( this.$table.prev( '.' + this.classes.toolbar ) ); 430 | $popup 431 | .appendTo( $btnContain ) 432 | .dialog( true ); 433 | 434 | this.$menu = $menu; 435 | 436 | $(window).on( "resize." + tableId, function(){ 437 | self.refreshToggle(); 438 | } ); 439 | 440 | this.refreshToggle(); 441 | }; 442 | 443 | ColumnToggle.prototype.refreshToggle = function() { 444 | this.$menu.find( "input" ).each( function() { 445 | var $this = $( this ); 446 | 447 | this.checked = $this.data( "cells" ).eq( 0 ).css( "display" ) === "table-cell"; 448 | 449 | $this.parent()[ this.checked ? "addClass" : "removeClass" ]( "btn-selected" ); 450 | }); 451 | }; 452 | 453 | ColumnToggle.prototype.refreshPriority = function(){ 454 | var self = this; 455 | $(this.headers).not( "td" ).each( function() { 456 | var $this = $( this ), 457 | priority = $this.attr("data-priority"), 458 | $cells = $this.add( this.cells ); 459 | 460 | if( priority && priority !== "persist" ) { 461 | $cells.addClass( self.classes.priorityPrefix + priority ); 462 | } else { 463 | $cells.each(function() { 464 | // remove all priority classes. 465 | this.className = this.className.replace( /\bui\-table\-priority\-\d\b/g, '' ); 466 | }); 467 | } 468 | }); 469 | }; 470 | 471 | ColumnToggle.prototype.destroy = function() { 472 | this.$table.removeClass( this.classes.columnToggleTable ); 473 | this.$table.find( 'th, td' ).each(function() { 474 | var $cell = $( this ); 475 | $cell.removeClass( 'tablesaw-cell-hidden' ) 476 | .removeClass( 'tablesaw-cell-visible' ); 477 | 478 | this.className = this.className.replace( /\bui\-table\-priority\-\d\b/g, '' ); 479 | }); 480 | }; 481 | 482 | // on tablecreate, init 483 | $( document ).on( "tablesawcreate", "table", function( e, mode ){ 484 | 485 | if( mode === 'columntoggle' ){ 486 | var table = new ColumnToggle( this ); 487 | table.init(); 488 | } 489 | 490 | } ); 491 | 492 | $( document ).on( "tablesawdestroy", "table", function( e, mode ){ 493 | if( mode === 'columntoggle' ){ 494 | $( this ).data( 'tablesaw-coltoggle' ).destroy(); 495 | } 496 | } ); 497 | 498 | }( this, jQuery )); 499 | ;(function( win, $, undefined ){ 500 | 501 | 502 | function createSwipeTable( $table ){ 503 | 504 | var $btns = $( "
" ), 505 | $prevBtn = $( "" ).appendTo( $btns ), 506 | $nextBtn = $( "" ).appendTo( $btns ), 507 | hideBtn = 'disabled', 508 | persistWidths = 'tablesaw-fix-persist', 509 | $headerCells = $table.find( "thead th" ), 510 | $headerCellsNoPersist = $headerCells.not( '[data-priority="persist"]' ), 511 | headerWidths = [], 512 | $head = $( document.head || 'head' ), 513 | tableId = $table.attr( 'id' ); 514 | 515 | // Calculate initial widths 516 | $table.css('width', 'auto'); 517 | $headerCells.each(function() { 518 | headerWidths.push( $( this ).outerWidth() ); 519 | }); 520 | $table.css( 'width', '' ); 521 | 522 | $btns.appendTo( $table.prev( '.tablesaw-bar' ) ); 523 | 524 | $table.addClass( "tablesaw-swipe" ); 525 | 526 | if( !tableId ) { 527 | tableId = 'tableswipe-' + Math.round( Math.random() * 10000 ); 528 | $table.attr( 'id', tableId ); 529 | } 530 | 531 | function $getCells( headerCell ) { 532 | return $( headerCell.cells ).add( headerCell ); 533 | } 534 | 535 | function showColumn( headerCell ) { 536 | $getCells( headerCell ).removeClass( 'tablesaw-cell-hidden' ); 537 | } 538 | 539 | function hideColumn( headerCell ) { 540 | $getCells( headerCell ).addClass( 'tablesaw-cell-hidden' ); 541 | } 542 | 543 | function persistColumn( headerCell ) { 544 | $getCells( headerCell ).addClass( 'tablesaw-cell-persist' ); 545 | } 546 | 547 | function isPersistent( headerCell ) { 548 | return $( headerCell ).is( '[data-priority="persist"]' ); 549 | } 550 | 551 | function unmaintainWidths() { 552 | $table.removeClass( persistWidths ); 553 | $( '#' + tableId + '-persist' ).remove(); 554 | } 555 | 556 | function maintainWidths() { 557 | var prefix = '#' + tableId + ' ', 558 | styles = [], 559 | tableWidth = $table.width(); 560 | 561 | $headerCells.each(function( index ) { 562 | var width; 563 | if( isPersistent( this ) ) { 564 | width = $( this ).outerWidth(); 565 | 566 | // Only save width on non-greedy columns (take up less than 75% of table width) 567 | if( width < tableWidth * 0.75 ) { 568 | styles.push( prefix + ' .tablesaw-cell-persist:nth-child(' + ( index + 1 ) + ') { width: ' + width + 'px; }' ); 569 | } 570 | } 571 | }); 572 | 573 | unmaintainWidths(); 574 | $table.addClass( persistWidths ); 575 | $head.append( $( '