From d1ed97a59ffb5166879d3eb6cecf45a22e0a9da6 Mon Sep 17 00:00:00 2001 From: Mottie Date: Tue, 17 Dec 2013 17:39:39 -0600 Subject: [PATCH] Add column selector widget. Fixes #318. --- docs/example-widget-column-selector.html | 329 +++++++++++++++++++++++ docs/index.html | 7 +- js/widgets/widget-column-selector.js | 239 ++++++++++++++++ 3 files changed, 571 insertions(+), 4 deletions(-) create mode 100644 docs/example-widget-column-selector.html create mode 100644 js/widgets/widget-column-selector.js diff --git a/docs/example-widget-column-selector.html b/docs/example-widget-column-selector.html new file mode 100644 index 00000000..aaa9360a --- /dev/null +++ b/docs/example-widget-column-selector.html @@ -0,0 +1,329 @@ + + + + + jQuery plugin: Tablesorter 2.0 - Column Selector Widget (beta) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +

+
+ +
+ +

Notes

+
+
    +
  • This widget will only work in tablesorter version 2.8+ and jQuery version 1.7+.
  • +
  • This widget may not work properly if the table header includes rows with row or column spans.
  • +
  • The responsive part of this widget +
      +
    • Uses similar parameters as those used by jQuery mobile to set column priority.
    • +
    • Any named data-priority, other than the numbers 1 - 6, (e.g. "critical" or "persistent") will be treated as a column which will not be included in the column selector.
    • +
    • Note that this widget uses media queries, which will not work in IE8 and older browsers.
    • +
    +
  • +
  • The selector code uses css selectors, for optimal speed, to hide/show columns. These selectors will not work in IE8 and older browsers.
  • +
  • The column button & popup is completely customizable, and in this demo it includes css that may not work properly in older versions of IE.
  • +
+
+ +

Options

+
+

Column selector widget default options (added inside of tablesorter widgetOptions)

+
    +
  • columnSelector_container (null) - Target an element within the current page where the column selector will be inserted. +
      +
    • This can be either a jQuery selector string ( e.g. '#columnSelector' )
    • +
    • or, a jQuery object ( e.g. $('#columnSelector') ).
    • +
    +
  • +
  • columnSelector_columns ({}; empty object) - Assigns a column status for each selector: +
      +
    • To disable, or remove a column from the column selector, include the key word "disable" - this is one of many ways to remove a column from the column selector popup
    • +
    • Set a column status to true to initially display a column. This is the default for undefined columns.
    • +
    • Set a column status to false to initially hide a column.
    • +
    • Examples: +
      widgetOptions : {
      +  columnSelector_columns : {
      +    0 : "disable", /* disable; i.e. remove column from selector */
      +    1 : false,     /* start with column hidden */
      +    2 : true,      /* start with column visible; default for undefined columns */
      +  }
      +}
    • +
    • columnSelector_saveColumns (true) - Save the current manually set column status (not the column's responsive state). This option requires the storage utility contained within the tablesorter widgets file (jquery.tablesorter.widgets.js).
    • +
    • columnSelector_layout ('<label><input type="checkbox">{name}</label>') - This option defines the markup used for each column selector within the popup. The only required parameter is the {name} string which will be replaced with the appropriate column name/title.
    • + +
    • columnSelector_name ("data-selector-name") - The data-attribute within the table header cell which contains an alternate column selector name. +
        +
      • If the header cell does not have this attribute defined, the column selector name will be taken from the header cell internal text.
      • +
      • If defined, the text contained within this attribute will replace the {name} string within the layout option above.
      • +
      +
    • + +
    • columnSelector_mediaquery (true) - Set this option to add (true) or not add (false) the media query functionality of this widget.
    • +
    • columnSelector_mediaqueryName ("Auto: ") - When the media query checkbox is added (it also uses the columnSelector_layout markup), this is the name that is added. Set as "Auto" to signify to the user that columns disappearing and/or reappearing is automatically done.
    • +
    • columnSelector_mediaqueryState (true) - Set this option to false to start with the media query disabled (manual column selection mode).
    • +
    • columnSelector_breakpoints ([ "20em", "30em", "40em", "50em", "60em", "70em" ]) +
        +
      • This option defines the media query breakpoints with which to use when a column with the associated priority is hidden or revealed.
      • +
      • For example, the last entry "70em" (1,120px) is assigned to data-priority 6. When the browser width is below this dimension, all columns with a data-priority of six will be hidden. Then when a browser width less than "60em" (960px) is reached, all columns of data-priority 5 and above will be hidden. At "50em" (800px), all columns of data-priority 4 and above are hidden, etc.
      • +
      • Adjust these values as desired, but a maximum of six data-priorities is set.
      • +
      +
    • +
    • columnSelector_priority ("data-priority") - This is the assigned data-attribute which contains the defined data priority for a table column. +
        +
      • Values of 1 through 6 set the breakpoints of that particular column.
      • +
      • A value of 1 has the highest priority, meaning it is the last column(s) to be hidden when the browser width goes below "20em" (320px).
      • +
      • A value of 7 has the lowest priority, meaning it is the first column(s) to be hidden when the browser width goes below "70em" (1,120px).
      • +
      • Any named priority value, (e.g. "critical" or "persistent") will flag the widget to remove that column from the selector list.
      • +
      • Undefined priorities will default to a priority value of 1.
      • +
      +
    • +
    +
+ +

Removing a column from the selector

+
+ This is probably overkill, but there are numerous ways to remove a column from the selection popup: +
    +
  • Setting the data-priority to any non-numerical name (e.g. "critical" or "persistent").
  • +
  • Setting the column selector widget columns option columnSelector_columns : { 0 : 'disable' }.
  • +
  • The following methods in order of priority: +
      +
    • jQuery data data-columnSelector="false".
    • +
    • metadata class="{ columnSelector : false }".
    • +
    • headers option headers : { 0 : { columnSelector: false } }.
    • +
    • header class name class="columnSelector-false".
    • +
    +
  • +
+
+
+ +

+ +

Demo

+ + +
+ + +
+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameMajorSexEnglishJapaneseCalculusGeometry
NameMajorSexEnglishJapaneseCalculusGeometry
Student12Mathematicsfemale100757085
Student13Languagesfemale1008010090
Student14Languagesfemale50455590
Student15Languagesmale953510090
Student16Languagesfemale100503070
Student17Languagesfemale801005565
Student18Mathematicsmale30495575
Student19Languagesmale68908870
Student20Mathematicsmale40454080
Student21Languagesmale5045100100
Student22Mathematicsmale1009910090
Student23Languagesfemale85808080
Student01Languagesmale80707580
Student02Mathematicsmale908810090
Student03Languagesfemale85958085
Student04Languagesmale6055100100
Student05Languagesfemale68809580
Student06Mathematicsmale1009910090
Student07Mathematicsmale85689090
Student08Languagesmale100909085
Student09Mathematicsmale80506575
Student10Languagesmale8510010090
Student11Languagesmale8685100100
Student24Languagesfemale100911382
+ +

Css

+
+

+	
+ +

HTML

+
+
<!-- This selector markup is completely customizable -->
+<div class="columnSelectorWrapper">
+	<input id="colSelect1" type="checkbox" class="hidden">
+	<label class="columnSelectorButton" for="colSelect1">Column</label>
+	<div id="columnSelector" class="columnSelector">
+		<!-- this div is where the column selector is added -->
+	</div>
+</div>
+
+<table class="tablesorter">
+	<thead>
+		<tr>
+			<th data-priority="critical">Name</th>
+			<!-- Remove column from selection popup by including -->
+			<!-- data-priority="Anything other than 1-6" OR data-column-selector="false" OR  class="columnSelector-false" -->
+			<th class="columnSelector-false">Major</th>
+			<th data-priority="6">Sex</th>
+			<th data-priority="4">English</th>
+			<th data-priority="5">Japanese</th>
+			<th data-priority="3">Calculus</th>
+			<th data-priority="2">Geometry</th>
+		</tr>
+	</thead>
+	<tfoot>
+		<tr><th>Name</th><th>Major</th><th>Sex</th><th>English</th><th>Japanese</th><th>Calculus</th><th>Geometry</th></tr>
+	</tfoot>
+	<tbody>
+		<!-- ... -->
+	</tbody>
+</table>
+
+ +

Javascript

+
+

+	
+ +
+ + + + diff --git a/docs/index.html b/docs/index.html index 2c271c85..ba269e6f 100644 --- a/docs/index.html +++ b/docs/index.html @@ -391,10 +391,8 @@
  • Bootstrap (v2.4)
  • -
  • - Zebra stripe widget
    -
    -
  • +
  • Zebra stripe widget

  • +
  • Assorted date parsers (v2.8; v2.14).
  • Ignore leading articles parser (Ignore "A", "An" and "The" in titles) (v2.8).
  • Input/select parsers (used by Grouping rows widget) (v2.8; v2.14).
  • @@ -458,6 +456,7 @@
  • tablesorter basic demo using jQuery UI theme
  • tablesorter basic demo with pager plugin
  • tableSorter LESS theme; modify the colors dynamically in this LESS theme demo!
  • +
  • Beta Column Selector Widget (v2.14.6).
  • Beta pager widget (basic & ajax demos) (v2.12).
  • Beta Custom pager control script
  • Beta External filters using Select2 plugin
  • diff --git a/js/widgets/widget-column-selector.js b/js/widgets/widget-column-selector.js new file mode 100644 index 00000000..3e4930f0 --- /dev/null +++ b/js/widgets/widget-column-selector.js @@ -0,0 +1,239 @@ +/* Column Selector/Responsive table widget (beta) for TableSorter 12/17/2013 (v2.14.6) + * Requires tablesorter v2.8+ and jQuery 1.7+ + * by Justin Hallett & Rob Garrison + */ +/*jshint browser:true, jquery:true, unused:false */ +/*global jQuery: false */ +;(function($){ +"use strict"; + +var ts = $.tablesorter, +namespace = '.tscolsel', +tsColSel = ts.columnSelector = { + + queryAll : '@media only all { [columns] { display: none; } }', + queryBreak : '@media screen and (min-width: [size]) { [columns] { display: table-cell; } }', + + init: function(table, c, wo) { + var colSel; + + // unique table class name + c.tableId = 'tablesorter' + new Date().getTime(); + c.$table.addClass( c.tableId ); + + // build column selector/state array + colSel = c.selector = { $container : $(wo.columnSelector_container) }; + tsColSel.setupSelector(table, c, wo); + + if (wo.columnSelector_mediaquery) { + tsColSel.setupBreakpoints(c, wo); + } + + if (colSel.$container.length) { + colSel.$style = $('').prop('disabled', true).appendTo('head'); + tsColSel.updateCols(c, wo); + } + + }, + + setupSelector: function(table, c, wo) { + var name, + colSel = c.selector, + $container = colSel.$container, + // get stored column states + saved = wo.columnSelector_saveColumns && ts.storage ? ts.storage( table, 'tablesorter-columnSelector' ) : []; + + // initial states + colSel.states = []; + colSel.$column = []; + colSel.$wrapper = []; + colSel.$checkbox = []; + // populate the selector container + c.$table.children('thead').find('tr:first th', table).each(function() { + var $this = $(this), + // if no data-priority is assigned, default to 1, but don't remove it from the selector list + priority = $this.attr(wo.columnSelector_priority) || 1, + colId = $this.attr('data-column'); + + // if this column not hidable at all + // include getData check (includes "columnSelector-false" class, data attribute, etc) + if ( isNaN(priority) && priority.length > 0 || ts.getData(this, c.headers[colId], 'columnSelector') == 'false' || + ( wo.columnSelector_columns[colId] && wo.columnSelector_columns[colId] === 'disable') ) { + return true; // goto next + } + + // set default state + colSel.states[colId] = saved && typeof(saved[colId]) !== 'undefined' ? + saved[colId] : typeof(wo.columnSelector_columns[colId]) !== 'undefined' ? wo.columnSelector_columns[colId] : true; + colSel.$column[colId] = $(this); + + // set default col title + name = $this.attr(wo.columnSelector_name) || $this.text(); + + if ($container.length) { + colSel.$wrapper[colId] = $(wo.columnSelector_layout.replace(/\{name\}/g, name)).appendTo($container); + colSel.$checkbox[colId] = colSel.$wrapper[colId] + .find('input') + .attr('data-column', colId) + .prop('checked', colSel.states[colId]) + .bind('change', function(){ + colSel.states[colId] = this.checked; + tsColSel.updateCols(c, wo); + }).change(); + } + }); + + }, + + setupBreakpoints: function(c, wo){ + var colSel = c.selector; + + // add responsive breakpoints + if (wo.columnSelector_mediaquery) { + // used by window resize function + colSel.lastIndex = -1; + wo.columnSelector_breakpoints.sort(); + colSel.$breakpoints = $('').prop('disabled', true).appendTo('head'); + tsColSel.updateBreakpoints(c, wo); + c.$table.unbind('updateAll' + namespace).bind('updateAll' + namespace, function(){ + tsColSel.updateBreakpoints(c, wo); + tsColSel.updateCols(c, wo); + }); + } + + if (colSel.$container.length) { + // Add media queries toggle + if (wo.columnSelector_mediaquery && wo.columnSelector_mediaquery) { + $( wo.columnSelector_layout.replace(/\{name\}/g, wo.columnSelector_mediaqueryName) ) + .prependTo(colSel.$container) + .find('input') + .prop('checked', wo.columnSelector_mediaqueryState) + .bind('change', function(){ + wo.columnSelector_mediaqueryState = this.checked; + $.each( colSel.$checkbox, function(i, $cb){ + if ($cb) { + $cb[0].disabled = wo.columnSelector_mediaqueryState; + colSel.$wrapper[i].toggleClass('disabled', wo.columnSelector_mediaqueryState); + } + }); + tsColSel.updateBreakpoints(c, wo); + tsColSel.updateCols(c, wo); + }).change(); + } + // Add a bind on update to re-run col setup + c.$table.unbind('update' + namespace).bind('update' + namespace, function() { + tsColSel.updateCols(c, wo); + }); + } + }, + + + updateBreakpoints: function(c, wo){ + var priority, column, breaks, + colSel = c.selector, + prefix = '.' + c.tableId, + mediaAll = [], + breakpts = ''; + if (wo.columnSelector_mediaquery && !wo.columnSelector_mediaqueryState) { + colSel.$breakpoints.prop('disabled', true); + colSel.$style.prop('disabled', false); + return; + } + + // only 6 breakpoints (same as jQuery Mobile) + for (priority = 0; priority < 6; priority++){ + /*jshint loopfunc:true */ + breaks = []; + c.$headers.filter('[' + wo.columnSelector_priority + '=' + (priority + 1) + ']').each(function(){ + column = parseInt($(this).attr('data-column'), 10) + 1; + breaks.push(prefix + ' tr th:nth-child(' + column + ')'); + breaks.push(prefix + ' tr td:nth-child(' + column + ')'); + }); + if (breaks.length) { + mediaAll = mediaAll.concat( breaks ); + breakpts += tsColSel.queryBreak + .replace(/\[size\]/g, wo.columnSelector_breakpoints[priority]) + .replace(/\[columns\]/g, breaks.join(',')); + } + } + if (colSel.$style) { colSel.$style.prop('disabled', true); } + colSel.$breakpoints.prop('disabled', false) + .html( tsColSel.queryAll.replace(/\[columns\]/g, mediaAll.join(',')) + breakpts ); + + }, + + updateCols: function(c, wo) { + if (wo.columnSelector_mediaquery && wo.columnSelector_mediaqueryState) { + return; + } + var column, + styles = [], + prefix = '.' + c.tableId; + c.selector.$container.find('input[data-column]').each(function(){ + if (!this.checked) { + column = parseInt( $(this).attr('data-column'), 10 ) + 1; + styles.push(prefix + ' tr th:nth-child(' + column + ')'); + styles.push(prefix + ' tr td:nth-child(' + column + ')'); + } + }); + if (wo.columnSelector_mediaquery){ + c.selector.$breakpoints.prop('disabled', true); + } + if (c.selector.$style) { + c.selector.$style.prop('disabled', false).html( styles.join(',') + ' { display: none; }' ); + } + if (wo.columnSelector_saveColumns && ts.storage) { + ts.storage( c.$table[0], 'tablesorter-columnSelector', c.selector.states ); + } + } + +}; + +ts.addWidget({ + id: "columnSelector", + priority: 10, + options: { + // target the column selector markup + columnSelector_container : null, + // column status, true = display, false = hide + // disable = do not display on list + columnSelector_columns : {}, + // remember selected columns + columnSelector_saveColumns: true, + + // container layout + columnSelector_layout : '', + // data attribute containing column name to use in the selector container + columnSelector_name : 'data-selector-name', + + /* Responsive Media Query settings */ + // enable/disable mediaquery breakpoints + columnSelector_mediaquery: true, + // toggle checkbox name + columnSelector_mediaqueryName: 'Auto: ', + // breakpoints checkbox initial setting + columnSelector_mediaqueryState: true, + // responsive table hides columns with priority 1-6 at these breakpoints + // see http://view.jquerymobile.com/1.3.2/dist/demos/widgets/table-column-toggle/#Applyingapresetbreakpoint + // *** set to false to disable *** + columnSelector_breakpoints : [ '20em', '30em', '40em', '50em', '60em', '70em' ], + // data attribute containing column priority + // duplicates how jQuery mobile uses priorities: + // http://view.jquerymobile.com/1.3.2/dist/demos/widgets/table-column-toggle/ + columnSelector_priority : 'data-priority' + + }, + init: function(table, thisWidget, c, wo) { + tsColSel.init(table, c, wo); + }, + remove: function(table, c){ + var csel = c.selector; + csel.$container.empty(); + csel.$style.remove(); + csel.$breakpoints.remove(); + c.$table.unbind('updateAll' + namespace + ',update' + namespace); + } + +}); + +})(jQuery);