tables.swipetoggle.js 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327
  1. /*
  2. * tablesaw: A set of plugins for responsive tables
  3. * Swipe Toggle: swipe gesture (or buttons) to navigate which columns are shown.
  4. * Copyright (c) 2013 Filament Group, Inc.
  5. * MIT License
  6. */
  7. ;(function( win, $, undefined ){
  8. $.extend( Tablesaw.config, {
  9. swipe: {
  10. horizontalThreshold: 15,
  11. verticalThreshold: 30
  12. }
  13. });
  14. function isIE8() {
  15. var div = document.createElement('div'),
  16. all = div.getElementsByTagName('i');
  17. div.innerHTML = '<!--[if lte IE 8]><i></i><![endif]-->';
  18. return !!all.length;
  19. }
  20. var classes = {
  21. // TODO duplicate class, also in tables.js
  22. toolbar: "tablesaw-bar",
  23. hideBtn: "disabled",
  24. persistWidths: "tablesaw-fix-persist",
  25. allColumnsVisible: 'tablesaw-all-cols-visible'
  26. };
  27. function createSwipeTable( $table ){
  28. var $btns = $( "<div class='tablesaw-advance'></div>" ),
  29. $prevBtn = $( "<a href='#' class='tablesaw-nav-btn btn btn-micro left' title='Previous Column'></a>" ).appendTo( $btns ),
  30. $nextBtn = $( "<a href='#' class='tablesaw-nav-btn btn btn-micro right' title='Next Column'></a>" ).appendTo( $btns ),
  31. $headerCells = $table.find( "thead th" ),
  32. $headerCellsNoPersist = $headerCells.not( '[data-tablesaw-priority="persist"]' ),
  33. headerWidths = [],
  34. $head = $( document.head || 'head' ),
  35. tableId = $table.attr( 'id' ),
  36. // TODO switch this to an nth-child feature test
  37. supportsNthChild = !isIE8();
  38. if( !$headerCells.length ) {
  39. throw new Error( "tablesaw swipe: no header cells found. Are you using <th> inside of <thead>?" );
  40. }
  41. // Calculate initial widths
  42. $table.css('width', 'auto');
  43. $headerCells.each(function() {
  44. headerWidths.push( $( this ).outerWidth() );
  45. });
  46. $table.css( 'width', '' );
  47. $btns.appendTo( $table.prev().filter( '.tablesaw-bar' ) );
  48. $table.addClass( "tablesaw-swipe" );
  49. if( !tableId ) {
  50. tableId = 'tableswipe-' + Math.round( Math.random() * 10000 );
  51. $table.attr( 'id', tableId );
  52. }
  53. function $getCells( headerCell ) {
  54. return $( headerCell.cells ).add( headerCell );
  55. }
  56. function showColumn( headerCell ) {
  57. $getCells( headerCell ).removeClass( 'tablesaw-cell-hidden' );
  58. }
  59. function hideColumn( headerCell ) {
  60. $getCells( headerCell ).addClass( 'tablesaw-cell-hidden' );
  61. }
  62. function persistColumn( headerCell ) {
  63. $getCells( headerCell ).addClass( 'tablesaw-cell-persist' );
  64. }
  65. function isPersistent( headerCell ) {
  66. return $( headerCell ).is( '[data-tablesaw-priority="persist"]' );
  67. }
  68. function unmaintainWidths() {
  69. $table.removeClass( classes.persistWidths );
  70. $( '#' + tableId + '-persist' ).remove();
  71. }
  72. function maintainWidths() {
  73. var prefix = '#' + tableId + '.tablesaw-swipe ',
  74. styles = [],
  75. tableWidth = $table.width(),
  76. hash = [],
  77. newHash;
  78. $headerCells.each(function( index ) {
  79. var width;
  80. if( isPersistent( this ) ) {
  81. width = $( this ).outerWidth();
  82. // Only save width on non-greedy columns (take up less than 75% of table width)
  83. if( width < tableWidth * 0.75 ) {
  84. hash.push( index + '-' + width );
  85. styles.push( prefix + ' .tablesaw-cell-persist:nth-child(' + ( index + 1 ) + ') { width: ' + width + 'px; }' );
  86. }
  87. }
  88. });
  89. newHash = hash.join( '_' );
  90. $table.addClass( classes.persistWidths );
  91. var $style = $( '#' + tableId + '-persist' );
  92. // If style element not yet added OR if the widths have changed
  93. if( !$style.length || $style.data( 'hash' ) !== newHash ) {
  94. // Remove existing
  95. $style.remove();
  96. if( styles.length ) {
  97. $( '<style>' + styles.join( "\n" ) + '</style>' )
  98. .attr( 'id', tableId + '-persist' )
  99. .data( 'hash', newHash )
  100. .appendTo( $head );
  101. }
  102. }
  103. }
  104. function getNext(){
  105. var next = [],
  106. checkFound;
  107. $headerCellsNoPersist.each(function( i ) {
  108. var $t = $( this ),
  109. isHidden = $t.css( "display" ) === "none" || $t.is( ".tablesaw-cell-hidden" );
  110. if( !isHidden && !checkFound ) {
  111. checkFound = true;
  112. next[ 0 ] = i;
  113. } else if( isHidden && checkFound ) {
  114. next[ 1 ] = i;
  115. return false;
  116. }
  117. });
  118. return next;
  119. }
  120. function getPrev(){
  121. var next = getNext();
  122. return [ next[ 1 ] - 1 , next[ 0 ] - 1 ];
  123. }
  124. function nextpair( fwd ){
  125. return fwd ? getNext() : getPrev();
  126. }
  127. function canAdvance( pair ){
  128. return pair[ 1 ] > -1 && pair[ 1 ] < $headerCellsNoPersist.length;
  129. }
  130. function matchesMedia() {
  131. var matchMedia = $table.attr( "data-tablesaw-swipe-media" );
  132. return !matchMedia || ( "matchMedia" in win ) && win.matchMedia( matchMedia ).matches;
  133. }
  134. function fakeBreakpoints() {
  135. if( !matchesMedia() ) {
  136. return;
  137. }
  138. var extraPaddingPixels = 20,
  139. containerWidth = $table.parent().width(),
  140. persist = [],
  141. sum = 0,
  142. sums = [],
  143. visibleNonPersistantCount = $headerCells.length;
  144. $headerCells.each(function( index ) {
  145. var $t = $( this ),
  146. isPersist = $t.is( '[data-tablesaw-priority="persist"]' );
  147. persist.push( isPersist );
  148. sum += headerWidths[ index ] + ( isPersist ? 0 : extraPaddingPixels );
  149. sums.push( sum );
  150. // is persistent or is hidden
  151. if( isPersist || sum > containerWidth ) {
  152. visibleNonPersistantCount--;
  153. }
  154. });
  155. var needsNonPersistentColumn = visibleNonPersistantCount === 0;
  156. $headerCells.each(function( index ) {
  157. if( persist[ index ] ) {
  158. // for visual box-shadow
  159. persistColumn( this );
  160. return;
  161. }
  162. if( sums[ index ] <= containerWidth || needsNonPersistentColumn ) {
  163. needsNonPersistentColumn = false;
  164. showColumn( this );
  165. } else {
  166. hideColumn( this );
  167. }
  168. });
  169. if( supportsNthChild ) {
  170. unmaintainWidths();
  171. }
  172. $table.trigger( 'tablesawcolumns' );
  173. }
  174. function advance( fwd ){
  175. var pair = nextpair( fwd );
  176. if( canAdvance( pair ) ){
  177. if( isNaN( pair[ 0 ] ) ){
  178. if( fwd ){
  179. pair[0] = 0;
  180. }
  181. else {
  182. pair[0] = $headerCellsNoPersist.length - 1;
  183. }
  184. }
  185. if( supportsNthChild ) {
  186. maintainWidths();
  187. }
  188. hideColumn( $headerCellsNoPersist.get( pair[ 0 ] ) );
  189. showColumn( $headerCellsNoPersist.get( pair[ 1 ] ) );
  190. $table.trigger( 'tablesawcolumns' );
  191. }
  192. }
  193. $prevBtn.add( $nextBtn ).click(function( e ){
  194. advance( !!$( e.target ).closest( $nextBtn ).length );
  195. e.preventDefault();
  196. });
  197. function getCoord( event, key ) {
  198. return ( event.touches || event.originalEvent.touches )[ 0 ][ key ];
  199. }
  200. $table
  201. .bind( "touchstart.swipetoggle", function( e ){
  202. var originX = getCoord( e, 'pageX' ),
  203. originY = getCoord( e, 'pageY' ),
  204. x,
  205. y;
  206. $( win ).off( "resize", fakeBreakpoints );
  207. $( this )
  208. .bind( "touchmove", function( e ){
  209. x = getCoord( e, 'pageX' );
  210. y = getCoord( e, 'pageY' );
  211. var cfg = Tablesaw.config.swipe;
  212. if( Math.abs( x - originX ) > cfg.horizontalThreshold && Math.abs( y - originY ) < cfg.verticalThreshold ) {
  213. e.preventDefault();
  214. }
  215. })
  216. .bind( "touchend.swipetoggle", function(){
  217. var cfg = Tablesaw.config.swipe;
  218. if( Math.abs( y - originY ) < cfg.verticalThreshold ) {
  219. if( x - originX < -1 * cfg.horizontalThreshold ){
  220. advance( true );
  221. }
  222. if( x - originX > cfg.horizontalThreshold ){
  223. advance( false );
  224. }
  225. }
  226. window.setTimeout(function() {
  227. $( win ).on( "resize", fakeBreakpoints );
  228. }, 300);
  229. $( this ).unbind( "touchmove touchend" );
  230. });
  231. })
  232. .bind( "tablesawcolumns.swipetoggle", function(){
  233. var canGoPrev = canAdvance( getPrev() );
  234. var canGoNext = canAdvance( getNext() );
  235. $prevBtn[ canGoPrev ? "removeClass" : "addClass" ]( classes.hideBtn );
  236. $nextBtn[ canGoNext ? "removeClass" : "addClass" ]( classes.hideBtn );
  237. $prevBtn.closest( "." + classes.toolbar )[ !canGoPrev && !canGoNext ? 'addClass' : 'removeClass' ]( classes.allColumnsVisible );
  238. })
  239. .bind( "tablesawnext.swipetoggle", function(){
  240. advance( true );
  241. } )
  242. .bind( "tablesawprev.swipetoggle", function(){
  243. advance( false );
  244. } )
  245. .bind( "tablesawdestroy.swipetoggle", function(){
  246. var $t = $( this );
  247. $t.removeClass( 'tablesaw-swipe' );
  248. $t.prev().filter( '.tablesaw-bar' ).find( '.tablesaw-advance' ).remove();
  249. $( win ).off( "resize", fakeBreakpoints );
  250. $t.unbind( ".swipetoggle" );
  251. });
  252. fakeBreakpoints();
  253. $( win ).on( "resize", fakeBreakpoints );
  254. }
  255. // on tablecreate, init
  256. $( document ).on( "tablesawcreate", function( e, Tablesaw ){
  257. if( Tablesaw.mode === 'swipe' ){
  258. createSwipeTable( Tablesaw.$table );
  259. }
  260. } );
  261. }( this, jQuery ));