tablesaw-rails 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,135 @@
1
+ /*
2
+ * tablesaw: A set of plugins for responsive tables
3
+ * Stack and Column Toggle tables
4
+ * Copyright (c) 2013 Filament Group, Inc.
5
+ * MIT License
6
+ */
7
+
8
+ ;(function( $ ) {
9
+ var pluginName = "table",
10
+ classes = {
11
+ toolbar: "tablesaw-bar"
12
+ },
13
+ events = {
14
+ create: "tablesawcreate",
15
+ destroy: "tablesawdestroy",
16
+ refresh: "tablesawrefresh"
17
+ },
18
+ defaultMode = "stack",
19
+ initSelector = "table[data-mode],table[data-sortable]";
20
+
21
+ var Table = function( element ) {
22
+ if( !element ) {
23
+ throw new Error( "Tablesaw requires an element." );
24
+ }
25
+
26
+ this.table = element;
27
+ this.$table = $( element );
28
+
29
+ this.mode = this.$table.attr( "data-mode" ) || defaultMode;
30
+
31
+ this.init();
32
+ };
33
+
34
+ Table.prototype.init = function() {
35
+ // assign an id if there is none
36
+ if ( !this.$table.attr( "id" ) ) {
37
+ this.$table.attr( "id", pluginName + "-" + Math.round( Math.random() * 10000 ) );
38
+ }
39
+
40
+ this.createToolbar();
41
+
42
+ var colstart = this._initCells();
43
+
44
+ this.$table.trigger( events.create, [ this, colstart ] );
45
+ };
46
+
47
+ Table.prototype._initCells = function() {
48
+ var colstart,
49
+ thrs = this.table.querySelectorAll( "thead tr" ),
50
+ self = this;
51
+
52
+ $( thrs ).each( function(){
53
+ var coltally = 0;
54
+
55
+ $( this ).children().each( function(){
56
+ var span = parseInt( this.getAttribute( "colspan" ), 10 ),
57
+ sel = ":nth-child(" + ( coltally + 1 ) + ")";
58
+
59
+ colstart = coltally + 1;
60
+
61
+ if( span ){
62
+ for( var k = 0; k < span - 1; k++ ){
63
+ coltally++;
64
+ sel += ", :nth-child(" + ( coltally + 1 ) + ")";
65
+ }
66
+ }
67
+
68
+ // Store "cells" data on header as a reference to all cells in the same column as this TH
69
+ this.cells = self.$table.find("tr").not( $( thrs ).eq( 0 ) ).not( this ).children( sel );
70
+ coltally++;
71
+ });
72
+ });
73
+
74
+ return colstart;
75
+ };
76
+
77
+ Table.prototype.refresh = function() {
78
+ this._initCells();
79
+
80
+ this.$table.trigger( events.refresh );
81
+ };
82
+
83
+ Table.prototype.createToolbar = function() {
84
+ // Insert the toolbar
85
+ // TODO move this into a separate component
86
+ var $toolbar = this.$table.prev( '.' + classes.toolbar );
87
+ if( !$toolbar.length ) {
88
+ $toolbar = $( '<div>' )
89
+ .addClass( classes.toolbar )
90
+ .insertBefore( this.$table );
91
+ }
92
+ this.$toolbar = $toolbar;
93
+
94
+ if( this.mode ) {
95
+ this.$toolbar.addClass( 'mode-' + this.mode );
96
+ }
97
+ };
98
+
99
+ Table.prototype.destroy = function() {
100
+ // Don’t remove the toolbar. Some of the table features are not yet destroy-friendly.
101
+ this.$table.prev( '.' + classes.toolbar ).each(function() {
102
+ this.className = this.className.replace( /\bmode\-\w*\b/gi, '' );
103
+ });
104
+
105
+ var tableId = this.$table.attr( 'id' );
106
+ $( document ).unbind( "." + tableId );
107
+ $( window ).unbind( "." + tableId );
108
+
109
+ // other plugins
110
+ this.$table.trigger( events.destroy, [ this ] );
111
+
112
+ this.$table.removeAttr( 'data-mode' );
113
+
114
+ this.$table.removeData( pluginName );
115
+ };
116
+
117
+ // Collection method.
118
+ $.fn[ pluginName ] = function() {
119
+ return this.each( function() {
120
+ var $t = $( this );
121
+
122
+ if( $t.data( pluginName ) ){
123
+ return;
124
+ }
125
+
126
+ var table = new Table( this );
127
+ $t.data( pluginName, table );
128
+ });
129
+ };
130
+
131
+ $( document ).on( "enhance.tablesaw", function( e ) {
132
+ $( e.target ).find( initSelector )[ pluginName ]();
133
+ });
134
+
135
+ }( jQuery ));
@@ -0,0 +1,81 @@
1
+ /*
2
+ * tablesaw: A set of plugins for responsive tables
3
+ * minimap: a set of dots to show which columns are currently visible.
4
+ * Copyright (c) 2013 Filament Group, Inc.
5
+ * MIT License
6
+ */
7
+
8
+ ;(function( win, $, undefined ){
9
+
10
+ var MM = {
11
+ attr: {
12
+ init: 'data-minimap'
13
+ }
14
+ };
15
+
16
+ function createMiniMap( $table ){
17
+
18
+ var $btns = $( '<div class="tablesaw-advance minimap">' ),
19
+ $dotNav = $( '<ul class="tablesaw-advance-dots">' ).appendTo( $btns ),
20
+ hideDot = 'tablesaw-advance-dots-hide',
21
+ $headerCells = $table.find( 'thead th' );
22
+
23
+ // populate dots
24
+ $headerCells.each(function(){
25
+ $dotNav.append( '<li><i></i></li>' );
26
+ });
27
+
28
+ $btns.appendTo( $table.prev( '.tablesaw-bar' ) );
29
+
30
+ function showMinimap( $table ) {
31
+ var mq = $table.attr( MM.attr.init );
32
+ return !mq || win.matchMedia && win.matchMedia( mq ).matches;
33
+ }
34
+
35
+ function showHideNav(){
36
+ if( !showMinimap( $table ) ) {
37
+ $btns.hide();
38
+ return;
39
+ }
40
+ $btns.show();
41
+
42
+ // show/hide dots
43
+ var dots = $dotNav.find( "li" ).removeClass( hideDot );
44
+ $table.find( "thead th" ).each(function(i){
45
+ if( $( this ).css( "display" ) === "none" ){
46
+ dots.eq( i ).addClass( hideDot );
47
+ }
48
+ });
49
+ }
50
+
51
+ // run on init and resize
52
+ showHideNav();
53
+ $( win ).on( "resize", showHideNav );
54
+
55
+
56
+ $table
57
+ .bind( "tablesawcolumns.minimap", function(){
58
+ showHideNav();
59
+ })
60
+ .bind( "tablesawdestroy.minimap", function(){
61
+ var $t = $( this );
62
+
63
+ $t.prev( '.tablesaw-bar' ).find( '.tablesaw-advance' ).remove();
64
+ $( win ).off( "resize", showHideNav );
65
+
66
+ $t.unbind( ".minimap" );
67
+ });
68
+ }
69
+
70
+
71
+
72
+ // on tablecreate, init
73
+ $( document ).on( "tablesawcreate", function( e, Tablesaw ){
74
+
75
+ if( ( Tablesaw.mode === 'swipe' || Tablesaw.mode === 'columntoggle' ) && Tablesaw.$table.is( '[ ' + MM.attr.init + ']' ) ){
76
+ createMiniMap( Tablesaw.$table );
77
+ }
78
+
79
+ } );
80
+
81
+ }( this, jQuery ));
@@ -0,0 +1,87 @@
1
+ /*
2
+ * tablesaw: A set of plugins for responsive tables
3
+ * Mode Switch: UI element to allow the user to change table modes: stack/swipe/columntoggle
4
+ * Copyright (c) 2013 Filament Group, Inc.
5
+ * MIT License
6
+ */
7
+
8
+ ;(function( win, $ ) {
9
+
10
+ var S = {
11
+ selectors: {
12
+ init: 'table[data-mode-switch]'
13
+ },
14
+ attributes: {
15
+ excludeMode: 'data-mode-exclude'
16
+ },
17
+ classes: {
18
+ main: 'tablesaw-modeswitch',
19
+ toolbar: 'tablesaw-toolbar'
20
+ },
21
+ modes: [ 'stack', 'swipe', 'columntoggle' ],
22
+ i18n: {
23
+ modes: [ 'Stack', 'Swipe', 'Toggle' ],
24
+ columns: 'Col<span class="a11y-sm">umn</span>s'
25
+ },
26
+ init: function( table ) {
27
+ var $table = $( table ),
28
+ ignoreMode = $table.attr( S.attributes.excludeMode ),
29
+ $toolbar = $table.prev( '.tablesaw-bar' ),
30
+ modeVal = '',
31
+ $switcher = $( '<div>' ).addClass( S.classes.main + ' ' + S.classes.toolbar ).html(function() {
32
+ var html = [ '<label>' + S.i18n.columns + ':' ],
33
+ dataMode = $table.attr( 'data-mode' ),
34
+ isSelected;
35
+
36
+ html.push( '<span class="btn btn-small">&#160;<select>' );
37
+ for( var j=0, k = S.modes.length; j<k; j++ ) {
38
+ if( ignoreMode && ignoreMode.toLowerCase() === S.modes[ j ] ) {
39
+ continue;
40
+ }
41
+
42
+ isSelected = dataMode === S.modes[ j ];
43
+
44
+ if( isSelected ) {
45
+ modeVal = S.modes[ j ];
46
+ }
47
+
48
+ html.push( '<option' +
49
+ ( isSelected ? ' selected' : '' ) +
50
+ ' value="' + S.modes[ j ] + '">' + S.i18n.modes[ j ] + '</option>' );
51
+ }
52
+ html.push( '</select></span></label>' );
53
+
54
+ return html.join('');
55
+ });
56
+
57
+ var $otherToolbarItems = $toolbar.find( '.tablesaw-advance' ).eq( 0 );
58
+ if( $otherToolbarItems.length ) {
59
+ $switcher.insertBefore( $otherToolbarItems );
60
+ } else {
61
+ $switcher.appendTo( $toolbar );
62
+ }
63
+
64
+ $switcher.find( '.btn' ).tablesawbtn();
65
+ $switcher.find( 'select' ).bind( 'change', S.onModeChange );
66
+ },
67
+ onModeChange: function() {
68
+ var $t = $( this ),
69
+ $switcher = $t.closest( '.' + S.classes.main ),
70
+ $table = $t.closest( '.tablesaw-bar' ).nextUntil( $table ).eq( 0 ),
71
+ val = $t.val();
72
+
73
+ $switcher.remove();
74
+ $table.data( 'table' ).destroy();
75
+
76
+ $table.attr( 'data-mode', val );
77
+ $table.table();
78
+ }
79
+ };
80
+
81
+ $( win.document ).on( "tablesawcreate", function( e, Tablesaw ) {
82
+ if( Tablesaw.$table.is( S.selectors.init ) ) {
83
+ S.init( Tablesaw.table );
84
+ }
85
+ });
86
+
87
+ })( this, jQuery );
@@ -0,0 +1,274 @@
1
+ /*
2
+ * tablesaw: A set of plugins for responsive tables
3
+ * Sortable column headers
4
+ * Copyright (c) 2013 Filament Group, Inc.
5
+ * MIT License
6
+ */
7
+
8
+ ;(function( $ ) {
9
+ function getSortValue( cell ) {
10
+ return $.map( cell.childNodes, function( el ) {
11
+ var $el = $( el );
12
+ if( $el.is( 'input, select' ) ) {
13
+ return $el.val();
14
+ } else if( $el.hasClass( 'tablesaw-cell-label' ) ) {
15
+ return;
16
+ }
17
+ return $.trim( $el.text() );
18
+ }).join( '' );
19
+ }
20
+
21
+ var topLevelPluginName = "tablesaw-sortable",
22
+ pluginName = "sortable",
23
+ initSelector = "table[data-" + pluginName + "]",
24
+ sortableSwitchSelector = "[data-" + pluginName + "-switch]",
25
+ classes = {
26
+ head: pluginName + "-head",
27
+ ascend: pluginName + "-ascending",
28
+ descend: pluginName + "-descending",
29
+ switcher: topLevelPluginName + "-switch",
30
+ tableToolbar: 'tablesaw-toolbar'
31
+ },
32
+ i18n = {
33
+ sort: 'Sort'
34
+ },
35
+ methods = {
36
+ _create: function( o ){
37
+ return $( this ).each(function() {
38
+ var init = $( this ).data( "init" + pluginName );
39
+ if( init ) {
40
+ return false;
41
+ }
42
+ $( this )
43
+ .data( "init"+ pluginName, true )
44
+ .trigger( "beforecreate." + pluginName )
45
+ [ pluginName ]( "_init" , o )
46
+ .trigger( "create." + pluginName );
47
+ });
48
+ },
49
+ _init: function(){
50
+ var el = $( this ),
51
+ heads,
52
+ $switcher;
53
+
54
+ var addClassToTable = function(){
55
+ el.addClass( topLevelPluginName );
56
+ },
57
+ addClassToHeads = function( h ){
58
+ $.each( h , function( i , v ){
59
+ $( v ).addClass( classes.head );
60
+ });
61
+ },
62
+ makeHeadsActionable = function( h , fn ){
63
+ $.each( h , function( i , v ){
64
+ var b = $( "<button />" );
65
+ b.bind( "click" , { col: v } , fn );
66
+ $( v ).wrapInner( b );
67
+ });
68
+ },
69
+ clearOthers = function( sibs ){
70
+ $.each( sibs , function( i , v ){
71
+ var col = $( v );
72
+ col.removeAttr( "data-sortable-default-col" );
73
+ col.removeClass( classes.ascend );
74
+ col.removeClass( classes.descend );
75
+ });
76
+ },
77
+ headsOnAction = function( e ){
78
+ if( $( e.target ).is( 'a[href]' ) ) {
79
+ return;
80
+ }
81
+
82
+ e.stopPropagation();
83
+ var head = $( this ).parent(),
84
+ v = e.data.col,
85
+ newSortValue = heads.index( head );
86
+
87
+ clearOthers( head.siblings() );
88
+ if( head.hasClass( classes.descend ) ){
89
+ el[ pluginName ]( "sortBy" , v , true);
90
+ newSortValue += '_asc';
91
+ } else {
92
+ el[ pluginName ]( "sortBy" , v );
93
+ newSortValue += '_desc';
94
+ }
95
+ if( $switcher ) {
96
+ $switcher.find( 'select' ).val( newSortValue ).trigger( 'refresh' );
97
+ }
98
+
99
+ e.preventDefault();
100
+ },
101
+ handleDefault = function( heads ){
102
+ $.each( heads , function( idx , el ){
103
+ var $el = $( el );
104
+ if( $el.is( "[data-sortable-default-col]" ) ){
105
+ if( !$el.hasClass( classes.descend ) ) {
106
+ $el.addClass( classes.ascend );
107
+ }
108
+ }
109
+ });
110
+ },
111
+ addSwitcher = function( heads ){
112
+ $switcher = $( '<div>' ).addClass( classes.switcher ).addClass( classes.tableToolbar ).html(function() {
113
+ var html = [ '<label>' + i18n.sort + ':' ];
114
+
115
+ html.push( '<span class="btn btn-small">&#160;<select>' );
116
+ heads.each(function( j ) {
117
+ var $t = $( this ),
118
+ isDefaultCol = $t.is( '[data-sortable-default-col]' ),
119
+ isDescending = $t.hasClass( classes.descend ),
120
+ isNumeric = false;
121
+
122
+ // Check only the first three rows to see if the column is numbers.
123
+ $( this.cells ).slice( 0, 3 ).each(function() {
124
+ if( !isNaN( parseInt( getSortValue( this ), 10 ) ) ) {
125
+ isNumeric = true;
126
+ return false;
127
+ }
128
+ });
129
+
130
+ html.push( '<option' + ( isDefaultCol && !isDescending ? ' selected' : '' ) + ' value="' + j + '_asc">' + $t.text() + ' ' + ( isNumeric ? '↑' : '(A-Z)' ) + '</option>' );
131
+ html.push( '<option' + ( isDefaultCol && isDescending ? ' selected' : '' ) + ' value="' + j + '_desc">' + $t.text() + ' ' + ( isNumeric ? '↓' : '(Z-A)' ) + '</option>' );
132
+ });
133
+ html.push( '</select></span></label>' );
134
+
135
+ return html.join('');
136
+ });
137
+
138
+ var $toolbar = el.prev( '.tablesaw-bar' ),
139
+ $firstChild = $toolbar.children().eq( 0 );
140
+
141
+ if( $firstChild.length ) {
142
+ $switcher.insertBefore( $firstChild );
143
+ } else {
144
+ $switcher.appendTo( $toolbar );
145
+ }
146
+ $switcher.find( '.btn' ).tablesawbtn();
147
+ $switcher.find( 'select' ).on( 'change', function() {
148
+ var val = $( this ).val().split( '_' ),
149
+ head = heads.eq( val[ 0 ] );
150
+
151
+ clearOthers( head.siblings() );
152
+ el.sortable( 'sortBy', head.get( 0 ), val[ 1 ] === 'asc' );
153
+ });
154
+ };
155
+
156
+ addClassToTable();
157
+ heads = el.find( "thead th[data-" + pluginName + "-col]" );
158
+ addClassToHeads( heads );
159
+ makeHeadsActionable( heads , headsOnAction );
160
+ handleDefault( heads );
161
+
162
+ if( el.is( sortableSwitchSelector ) ) {
163
+ addSwitcher( heads, el.find('tbody tr:nth-child(-n+3)') );
164
+ }
165
+ },
166
+ getColumnNumber: function( col ){
167
+ return $( col ).prevAll().length;
168
+ },
169
+ getTableRows: function(){
170
+ return $( this ).find( "tbody tr" );
171
+ },
172
+ sortRows: function( rows , colNum , ascending, col ){
173
+ var cells, fn, sorted;
174
+ var getCells = function( rows ){
175
+ var cells = [];
176
+ $.each( rows , function( i , r ){
177
+ cells.push({
178
+ cell: getSortValue( $( r ).children().get( colNum ) ),
179
+ rowNum: i
180
+ });
181
+ });
182
+ return cells;
183
+ },
184
+ getSortFxn = function( ascending, forceNumeric ){
185
+ var fn,
186
+ regex = /[^\d\.]/g;
187
+ if( ascending ){
188
+ fn = function( a , b ){
189
+ if( forceNumeric || !isNaN( parseFloat( a.cell ) ) ) {
190
+ return parseFloat( a.cell.replace( regex, '' ) ) - parseFloat( b.cell.replace( regex, '' ) );
191
+ } else {
192
+ return a.cell.toLowerCase() > b.cell.toLowerCase() ? 1 : -1;
193
+ }
194
+ };
195
+ } else {
196
+ fn = function( a , b ){
197
+ if( forceNumeric || !isNaN( parseFloat( a.cell ) ) ) {
198
+ return parseFloat( b.cell.replace( regex, '' ) ) - parseFloat( a.cell.replace( regex, '' ) );
199
+ } else {
200
+ return a.cell.toLowerCase() < b.cell.toLowerCase() ? 1 : -1;
201
+ }
202
+ };
203
+ }
204
+ return fn;
205
+ },
206
+ applyToRows = function( sorted , rows ){
207
+ var newRows = [], i, l, cur;
208
+ for( i = 0, l = sorted.length ; i < l ; i++ ){
209
+ cur = sorted[ i ].rowNum;
210
+ newRows.push( rows[cur] );
211
+ }
212
+ return newRows;
213
+ };
214
+
215
+ cells = getCells( rows );
216
+ fn = getSortFxn( ascending, $( col ).is( '[data-sortable-numeric]' ) );
217
+ sorted = cells.sort( fn );
218
+ rows = applyToRows( sorted , rows );
219
+ return rows;
220
+ },
221
+ replaceTableRows: function( rows ){
222
+ var el = $( this ),
223
+ body = el.find( "tbody" );
224
+ body.html( rows );
225
+ },
226
+ makeColDefault: function( col , a ){
227
+ var c = $( col );
228
+ c.attr( "data-sortable-default-col" , "true" );
229
+ if( a ){
230
+ c.removeClass( classes.descend );
231
+ c.addClass( classes.ascend );
232
+ } else {
233
+ c.removeClass( classes.ascend );
234
+ c.addClass( classes.descend );
235
+ }
236
+ },
237
+ sortBy: function( col , ascending ){
238
+ var el = $( this ), colNum, rows;
239
+
240
+ colNum = el[ pluginName ]( "getColumnNumber" , col );
241
+ rows = el[ pluginName ]( "getTableRows" );
242
+ rows = el[ pluginName ]( "sortRows" , rows , colNum , ascending, col );
243
+ el[ pluginName ]( "replaceTableRows" , rows );
244
+ el[ pluginName ]( "makeColDefault" , col , ascending );
245
+ }
246
+ };
247
+
248
+ // Collection method.
249
+ $.fn[ pluginName ] = function( arrg ) {
250
+ var args = Array.prototype.slice.call( arguments , 1),
251
+ returnVal;
252
+
253
+ // if it's a method
254
+ if( arrg && typeof( arrg ) === "string" ){
255
+ returnVal = $.fn[ pluginName ].prototype[ arrg ].apply( this[0], args );
256
+ return (typeof returnVal !== "undefined")? returnVal:$(this);
257
+ }
258
+ // check init
259
+ if( !$( this ).data( pluginName + "data" ) ){
260
+ $( this ).data( pluginName + "active", true );
261
+ $.fn[ pluginName ].prototype._create.call( this , arrg );
262
+ }
263
+ return $(this);
264
+ };
265
+ // add methods
266
+ $.extend( $.fn[ pluginName ].prototype, methods );
267
+
268
+ $( document ).on( "tablesawcreate", function( e, Tablesaw ) {
269
+ if( Tablesaw.$table.is( initSelector ) ) {
270
+ Tablesaw.$table[ pluginName ]();
271
+ }
272
+ });
273
+
274
+ }( jQuery ));