Mottie/tablesorter

Column Selector addon

Closed this issue · 55 comments

Thought I'd post it here I'm not saying it's written the best but I use it lots here.

here is how you init it

$('#table')
  .tablesorter({
     widgets: ['zebra', 'columns', 'stickyHeaders', 'filter'],
      sortList: [[15,0], [16,0]]
  })
  .tablesorterPager({
    container: $('#pager'),
    size: 100 
  })
  .tablesorterColumnSelector({
    container: $('#columnSelector'),
    layout: 'vertical',
    columns: {
      0: ['disable'], /* disabled no allowed to unselect it */
      1: [false], /* start hidden */
      11: [false],
      12: [false],
      17: [false]
    }
  });

I'd love to get this added in and work on it a bit more but it's handy for large tables or reports that certain ppl need certain info and other need a the same data but different cols.

here is the code, the css here is very very basic I use a slide out pane for mine, but thought I'd share something a little more generic.

addons/columnselector/jquery.tablesorter.columnselector.css

.tablesorter-columnSelector {
    cursor: default;
}

.tablesorter-columnSelector label.tablesorterColumnSelector-inputLabel {
    display: inline;
    font-family: arial, verdana, sans-serif;
    cursor: pointer;
}

.tablesorter-columnSelector input.tablesorterColumnSelector-input {
    margin-right: 5px !important;
    vertical-align: top;
}

.tablesorter-columnSelector label.noshow {
    color: #999;
}

addons/columnselector/jquery.tablesorter.columnselector.js

/*!
 * tablesorter columnselector plugin
 */
/*jshint browser:true, jquery:true */
;(function($) {
    "use strict";
    $.extend({tablesorterColumnSelector: new function() {
        this.defaults = {
            // target the column selector markup
            container: null,

            // Layout for container, vertical or horizontal
            layout: 'vertical',

            // column status, true = display, false = hide
            //                disable = do not display on list
            column: [],
        };

        var $this = this,

        enableColumnSelector = function(table, c, triggered) {
            var k;
            var columnSelectorPane = $(c.container).html();

            // populate the selector container
            var rowid = -1;
            var state = [];
            $('thead tr:first th', table).each(function() {
                rowid++;
                var name = $(this).text();
                state[rowid] = true;
                if (c.columns[rowid] !== undefined) {
                    // if this row not hidable at all
                    if (c.columns[rowid][0] !== undefined &&
                        c.columns[rowid][0] == 'disable')
                        return true; // goto next

                    // set default state
                    if (c.columns[rowid][0] !== undefined)
                        state[rowid] = c.columns[rowid][0];

                    // set default col title
                    if (c.columns[rowid][1] !== undefined)
                        name = c.columns[rowid][1];
                }

                columnSelectorPane += '<label id="label-col' + rowid + '" class="tablesorterColumnSelector-inputLabel"><input class="tablesorterColumnSelector-input" type="checkbox" value="' + rowid + '" name="col' + rowid + '" />' + name + "</label>\n";
                if (c.layout == 'vertical') {
                    columnSelectorPane += '<br />' + "\n";
                }
            });
            $(c.container).html(columnSelectorPane);

            // Set user requested defaults, if none do nothing
            for (k = 0; k < state.length; k++ ) {
                $('input[name="col' + k + '"]', c.container).prop('checked', state[k]);
                // it's false so lets turn it off
                if (!state[k]) {
                    toggleCol(table, c, k);
                }

                // add the action on click
                $('input[name="col' + k + '"]', c.container).change(function() {
                    toggleCol(table, c, $(this).val());
                });
            }
        },
        toggleCol = function(table, c, colNum) {
            // 0 == 1 in nth-child so +1 in those calls
            colNum = parseInt(colNum);

            if ($('input[name="col' + colNum + '"]', c.container).is(':checked')) {
                $('table.tablesorter thead tr td:nth-child(' + (colNum + 1) + '), table.tablesorter thead tr th:nth-child(' + (colNum + 1) + ')').show();
                $('table.tablesorter tbody tr td:nth-child(' + (colNum + 1) + '), table.tablesorter tbody tr th:nth-child(' + (colNum + 1) + ')').show();
                $('table.tablesorter tfoot tr.canhide td:nth-child(' + (colNum + 1) + '), table.tablesorter tfoot tr.canhide th:nth-child(' + (colNum + 1) + ')').show();
                $('#label-col' + colNum, c.container).removeClass('noshow');
            } else {
                $('table.tablesorter thead tr td:nth-child(' + (colNum + 1) + '), table.tablesorter thead tr th:nth-child(' + (colNum + 1) + ')').hide();
                $('table.tablesorter tbody tr td:nth-child(' + (colNum + 1) + '), table.tablesorter tbody tr th:nth-child(' + (colNum + 1) + ')').hide();
                $('table.tablesorter tfoot tr.canhide td:nth-child(' + (colNum + 1) + '), table.tablesorter tfoot tr.canhide th:nth-child(' + (colNum + 1) + ')').hide();
                $('#label-col' + colNum, c.container).addClass('noshow');
            }
        };

        $this.construct = function(settings) {
            return this.each(function() {
                var config = this.config,
                c = config.columnSelector = $.extend( {}, $.tablesorterColumnSelector.defaults, settings ),
                table = this,
                $t = $(table),
                columnSelector = $(c.container).addClass('tablesorter-columnSelector').show();
                // clear initialized flag
                c.initialized = false;
                enableColumnSelector(table, c, false);

                // column selector pane initialized
                c.initialized = true;
                $(table).trigger('columnSelectorInitialized', c);
            });
        };

    }()
});

// extend plugin scope
$.fn.extend({
    tablesorterColumnSelector: $.tablesorterColumnSelector.construct
});

})(jQuery);

here is an example of how mine looks as an idea, again I have to blur things sorry about that.

screen shot 2013-05-30 at 11 01 01 am

screen shot 2013-05-30 at 11 01 11 am

Very cool! Thanks for sharing.

I was working on something similar to the jQuery Mobile responsive table which would combine responsiveness with a column selector.

But sadly, starting next week I'll be on a sabbatical and likely won't be able to work on any side projects for at least a two to three months.

that does make me sad I love your work on this and use it LOTS, but I'll keep trying to add things and posted them here for when you return ;) I'm not even close to as good as you at this but I'm learning ;)

Just a note on this, I need to add a .bind on update to re check and hide cols, found this while using the ajax type loader, new rows don't hide the cols as required.

New js file with auto update on update ;)

/*!
 * tablesorter columnselector plugin
 */
/*jshint browser:true, jquery:true */
;(function($) {
    "use strict";
    $.extend({tablesorterColumnSelector: new function() {
        this.defaults = {
            // target the column selector markup
            container: null,

            // Layout for container, vertical or horizontal
            layout: 'vertical',

            // column status, true = display, false = hide
            //                disable = do not display on list
            column: [],
        };

        var $this = this,

        enableColumnSelector = function(table, c, triggered) {
            var columnSelectorPane = $(c.container).html();

            // populate the selector container
            var rowid = -1;
            var state = [];
            $('thead tr:first th', table).each(function() {
                rowid++;
                var name = $(this).text();
                state[rowid] = true;
                if (c.columns[rowid] !== undefined) {
                    // if this row not hidable at all
                    if (c.columns[rowid][0] !== undefined &&
                        c.columns[rowid][0] == 'disable')
                        return true; // goto next

                    // set default state
                    if (c.columns[rowid][0] !== undefined)
                        state[rowid] = c.columns[rowid][0];

                    // set default col title
                    if (c.columns[rowid][1] !== undefined)
                        name = c.columns[rowid][1];
                }

                columnSelectorPane += '<label id="label-col' + rowid + '" class="tablesorterColumnSelector-inputLabel"><input class="tablesorterColumnSelector-input" type="checkbox" value="' + rowid + '" name="col' + rowid + '" />' + name + "</label>\n";
                if (c.layout == 'vertical') {
                    columnSelectorPane += '<br />' + "\n";
                }
            });
            $(c.container).html(columnSelectorPane);

            // Set user requested defaults, if none do nothing
            updateCols(table, c, state);

            // Add a bind on update to re-run col setup
            $(table).bind('update', function() {
                alert('Hiding Cols NOW');
                updateCols(table, c, state);
            });
        },
        updateCols = function(table, c, state) {
            var k;

            for (k = 0; k < state.length; k++ ) {
                $('input[name="col' + k + '"]', c.container).prop('checked', state[k]);

                // it's false so lets turn it off
                if (!state[k]) {
                    toggleCol(table, c, k);
                }

                // add the action on click
                $('input[name="col' + k + '"]', c.container).change(function() {
                    toggleCol(table, c, $(this).val());
                });
            }
        },
        toggleCol = function(table, c, colNum) {
            // 0 == 1 in nth-child so +1 in those calls
            colNum = parseInt(colNum);

            if ($('input[name="col' + colNum + '"]', c.container).is(':checked')) {
                $('table.tablesorter thead tr td:nth-child(' + (colNum + 1) + '), table.tablesorter thead tr th:nth-child(' + (colNum + 1) + ')').show();
                $('table.tablesorter tbody tr td:nth-child(' + (colNum + 1) + '), table.tablesorter tbody tr th:nth-child(' + (colNum + 1) + ')').show();
                $('table.tablesorter tfoot tr.canhide td:nth-child(' + (colNum + 1) + '), table.tablesorter tfoot tr.canhide th:nth-child(' + (colNum + 1) + ')').show();
                $('#label-col' + colNum, c.container).removeClass('noshow');
            } else {
                $('table.tablesorter thead tr td:nth-child(' + (colNum + 1) + '), table.tablesorter thead tr th:nth-child(' + (colNum + 1) + ')').hide();
                $('table.tablesorter tbody tr td:nth-child(' + (colNum + 1) + '), table.tablesorter tbody tr th:nth-child(' + (colNum + 1) + ')').hide();
                $('table.tablesorter tfoot tr.canhide td:nth-child(' + (colNum + 1) + '), table.tablesorter tfoot tr.canhide th:nth-child(' + (colNum + 1) + ')').hide();
                $('#label-col' + colNum, c.container).addClass('noshow');
            }
        };

        $this.construct = function(settings) {
            return this.each(function() {
                var config = this.config,
                c = config.columnSelector = $.extend( {}, $.tablesorterColumnSelector.defaults, settings ),
                table = this,
                $t = $(table),
                columnSelector = $(c.container).addClass('tablesorter-columnSelector').show();
                // clear initialized flag
                c.initialized = false;
                enableColumnSelector(table, c, false);

                // column selector pane initialized
                c.initialized = true;
                $(table).trigger('columnSelectorInitialized', c);
            });
        };

    }()
});

// extend plugin scope
$.fn.extend({
    tablesorterColumnSelector: $.tablesorterColumnSelector.construct
});

})(jQuery);

Hello @TheSin- Thank you for this plugin. It has greatly helped me on a project i am working on. I tried to extend its behaviour so that i can read if a column is set to be visible via a data attribute set on the column header. I had no success doing this.

See a snippet of my code:

// set default state

//state[rowid] = c.columns[rowid][0];
state[rowid] = $('table.tablesorter thead tr:first th:nth-child(' + rowid + ')').data('visible');       
<thead>
<tr>
<th data-visible="true">
</th>
</tr>
</thead>

Can you please help in this regard? Thanks very much for your anticipated help

I'm sorry I'm old and I haven't started with data attributes till recently, but I will see what i can do to get it to work with them. I have a busy weekend but maybe early week I could look at it. I've playing with the idea of adding this in as a widget rather then an add-on as well.

Make it a widget... dooo it! ;P

if I do can it get accepted?? it's the last thing I have for local modifications then I can use a git module for this instead which will be sweet! :D

LOL of course... but my OCD may have me digging & modifying your code twitch

I am more then okay with that, I wish you would help me help you :D

That would be splendid. Thank you very much. I am looking forward to getting a quick fix and also using your plugin when you get around to it.

Best Regards

Looks like today is the day for this one, since I just updated and it broke the add-on ;)

Where do you want the widget css??

Hello @TheSin- Thank you for this plugin. It has greatly helped me on a project i am working on. I tried to extend its behaviour so that i can read if a column is set to be visible via a data attribute set on the column header. I had no success doing this.

Hey I just finished converting this to a widget, or at least first run at it ;) and I added the data-visible attribute, it'll only show when hidden and it'll be data-visible="false", here is the new widget version

Config is the same as the original, as is the css. Only difference is that you put the config into the WidgetOptions Object. Include the new file and add it tot he widgets line. Again this is the first version I'm sure lots can and will change I just needed it to work with 2.14.x

js/widgets/widget-columnSelector.js

/* columnSelector widget (beta) for TableSorter 12/5/2013 (v2.14.3) */
/*jshint browser:true, jquery:true, unused:false */
;(function($){
"use strict";
var tscs,
    ts = $.tablesorter;

ts.addWidget({
    id: "columnSelector",
    priority: 65, // load pager after stickyHeaders widget
    options : {
        // target the columnSelector markup
        container   : '#columnSelector',

        // Layout for container, vertical or horizontal
        layout: 'vertical',

        // column status, true = display, false = hide
        //                disable = do not display on list
        // Default for all cols is 'true', if not listed
        columns: []
    },
    init: function(table){
        tscs.init(table);
    },
    format: function(table, c){
        if (!(c.columnSelector && c.columnSelector.initialized)){
            return tscs.initComplete(table, c);
        }
    }
});

/* columnSelector widget functions */
tscs = ts.columnSelector = {

    init: function(table) {
        // check if tablesorter has initialized
        if (table.hasInitialized && table.config.columnSelector.initialized) { return; }

        var t,
            c = table.config,
            wo = c.widgetOptions.columnSelector,

            // save columnSelector variables
            cs = c.columnSelector = $.extend({
                layout: wo.layout,
                columns: wo.columns
            }, c.columnSelector);

        if (c.debug) {
            ts.log('columnSelector initializing');
        }

        cs.$container = $(wo.container).addClass('tablesorter-columnSelector').show();

        // clear initialized flag
        cs.initialized = false;

        // before initialization event
        c.$table.trigger('columnSelectorBeforeInitialized', c);
    },

    initComplete: function(table, c) {
        var cs = c.columnSelector;

        tscs.setup(table, c);

        // columnSelector initialized
        cs.initialized = true;
        c.$table.trigger('columnSelectorInitialized', c);
    },

    setup: function(table, c) {
        var wo = c.columnSelector;
        var columnSelectorPane = wo.$container.html()

        // populate the selector container
        var rowid = -1;
        var state = [];
        $('thead tr:first th', table).each(function() {
            rowid++;
            var name = $(this).text();
            state[rowid] = true;
            if (wo.columns[rowid] !== undefined) {
                // if this row not hidable at all
                if (wo.columns[rowid][0] !== undefined &&
                    wo.columns[rowid][0] == 'disable')
                    return true; // goto next

                // set default state
                if (wo.columns[rowid][0] !== undefined)
                    state[rowid] = wo.columns[rowid][0];

                // set default col title
                if (wo.columns[rowid][1] !== undefined)
                    name = wo.columns[rowid][1];
            }

            columnSelectorPane += '<label id="label-col' + rowid + '" class="tablesorterColumnSelector-inputLabel"><input class="tablesorterColumnSelector-input" type="checkbox" value="' + rowid + '" name="col' + rowid + '" />' + name + "</label>\n";
            if (wo.layout == 'vertical') {
                columnSelectorPane += '<br />' + "\n";
            }
        });
        $(wo.$container).html(columnSelectorPane);

        // Set user requested defaults, if none do nothing
        tscs.updateCols(table, c, state);

        // Add a bind on update to re-run col setup
        $(table).bind('update', function() {
            tscs.updateCols(table, c, state);
        });
    },

    updateCols: function(table, c, state) {
        var k,
            wo = c.columnSelector;

        for (k = 0; k < state.length; k++ ) {
            $('input[name="col' + k + '"]', wo.$container).prop('checked', state[k]);

            // it's false so lets turn it off
            if (!state[k]) {
                tscs.toggleCol(table, c, k);
            }

            // add the action on click
            $('input[name="col' + k + '"]', wo.$container).change(function() {
                tscs.toggleCol(table, c, $(this).val());
            });
        }
    },

    toggleCol: function(table, c, colNum) {
        var wo = c.columnSelector;
        colNum = parseInt(colNum);
        // 0 == 1 in nth-child so + 1 in those calls
        var nthcolNum = colNum + 1;

                var hsh = $(table).hasClass('hasStickyHeaders');
                var sh = hsh ? c.$sticky : '';

        if ($('input[name="col' + colNum + '"]', wo.$container).is(':checked')) {
            $('thead, tbody', table).find('tr').find('td:nth-child(' + nthcolNum + '), th:nth-child(' + nthcolNum + ')').show();
            $('thead, tbody', table).find('tr').find('td:nth-child(' + nthcolNum + '), th:nth-child(' + nthcolNum + ')').removeClass('filtered');
            $('tfoot', table).find('tr.canhide').find('td:nth-child(' + nthcolNum + '), th:nth-child(' + nthcolNum + ')').show();
            $('tfoot', table).find('tr.canhide').find('td:nth-child(' + nthcolNum + '), th:nth-child(' + nthcolNum + ')').removeClass('filtered');
                       // Check and Deal with StickyHeaders
                        if (hsh) {
                                $('thead, tbody', sh).find('tr').find('td:nth-child(' + nthcolNum + '), th:nth-child(' + nthcolNum + ')').show();
                                $('thead, tbody', sh).find('tr').find('td:nth-child(' + nthcolNum + '), th:nth-child(' + nthcolNum + ')').removeClass('filtered'); 
                        }
            $('#label-col' + colNum, wo.$container).removeClass('noshow');
        } else {
            $('thead, tbody', table).find('tr').find('td:nth-child(' + nthcolNum + '), th:nth-child(' + nthcolNum + ')').hide();
            $('thead, tbody', table).find('tr').find('td:nth-child(' + nthcolNum + '), th:nth-child(' + nthcolNum + ')').addClass('filtered');
            $('tfoot', table).find('tr.canhide').find('td:nth-child(' + nthcolNum + '), th:nth-child(' + nthcolNum + ')').hide();
            $('tfoot', table).find('tr.canhide').find('td:nth-child(' + nthcolNum + '), th:nth-child(' + nthcolNum + ')').addClass('filtered');
                        // Check and Deal with StickyHeaders
                        if (hsh) {
                                $('thead, tbody', sh).find('tr').find('td:nth-ch
ild(' + nthcolNum + '), th:nth-child(' + nthcolNum + ')').hide();                                $('thead, tbody', sh).find('tr').find('td:nth-child(' + nthcolNum + '), th:nth-child(' + nthcolNum + ')').addClass('filtered');
                        }
            $('#label-col' + colNum, wo.$container).addClass('noshow');
        }
    }
};

})(jQuery);

I just modified it, it wont' update data values, I want to keep it more consistent with the current table sorter so when they are hidden I add a filtered class to the td, the pager does the same to the tr. You can easier tell if it's hidden now via filter or you could easily add a data attribute. Just Bind to columnSelectorInitialized and add the data attribute to all td/th with filtered class.

Modified again for the current stickyHeaders widget.

Hey @TheSin-!

For the "extra" widgets (not in the widgets.js file), I usually just include the css with the demo. It's easier than going into each theme and modifying it to work with every widget.

I was looking at this code from when you first added it here, I was thinking it might be better (faster) to embed a block of css that uses nth-child() selectors to hide the columns instead of using jQuery. I'll start working on it this weekend... I've been so busy with other non-programming stuff that I haven't had a chance to do anything with this plugin.

yeah that is what I thought too i didn't want to mod each file ;)

sweet, this was one of the first things I wrote for table sorter when I found it and this conversion was just that, I just converted from add-on to widget as fast as I could to keep it working ;) I'm heading to the UK tomorrow and I have to present to a round table 5 times a day for 2 days on this project, so I have to get things working and for some god forsaken reason I thought today was a good day to update tablesorter ;) All this new goodness was too much to ignore ;)

Either way I hope me making it a widget helps get it accepted a little easier, I'll work on a demo for it too, maybe during my 7 hour flight ;)

LOL ok, good luck on the trip!

And sheez stop worrying about the widget being accepted.

hehe not so much worried as it would make my life easier I could then just use a git module for it, and not have my own copy of it in my git ;)

Just found a bug with this widget when I use the new pager widget via ajax. the headers keeps the proper col selections but the body data is showing the hidden rows again. I just need to get the proper order for this or trigger to bind to so the hide runs after the update.

I think this is related to issue #461 since I use a bind to 'update' which triggers the call to updateCols.

Hey before you dig too much deeper into this addon/widget... I've started working on it too. I started with the code you have above and turned it into a widget, now I'm making it responsive and use css only instead of jQuery to hide the columns. Let me get to a stopping point (it's not finished yet) and post a demo for you to check out.

sweet that is great work, I think the issue I'm having isn't this widget at all I think it's how the new pager widget works with ajax. for example, going to the next page is never triggering an update, so this widget doesn't re apply. Also it seems like update is triggered before filterStart on load with the new pager via ajax which was not the case in the past. I think we might need to get a trigger order listed someplace and be able to make unit tests to make sure they don't change order which different options like using ajax vs static data.

currently my static data tables work 100% with this widget. Which the same setup but changing it to ajax I have lots of issues, with this one and with my custom loading overlay.

part of it I think is the widget, I need to save changes back into the widget config so when you changes pages it saves the changes and doesn't go back to the original setup. But I'm still working through it all. At this point I think that is my largest issue, the widget needs to save the columns changes ingot he config object.

I can see now where the issue between pages is. I use a var called state, but I never updated it.

Here is my latest @Mottie , this one saves the state between loads using ajax.

/* columnSelector widget (beta) for TableSorter 12/5/2013 (v2.14.3) */
/*jshint browser:true, jquery:true, unused:false */
;(function($){
"use strict";
var tscs,
    ts = $.tablesorter;

ts.addWidget({
    id: "columnSelector",
    priority: 99, // load pager after stickyHeaders widget 65, changed to 99 for sortEnd and filterEnd
    options : {
        // target the columnSelector markup
        container   : '#columnSelector',

        // Layout for container, vertical or horizontal
        layout: 'vertical',

        // column status, true = display, false = hide
        //                disable = do not display on list
        // Default for all cols is 'true', if not listed
        columns: []
    },
    init: function(table){
        tscs.init(table);
    },
    format: function(table, c){
        if (!(c.columnSelector && c.columnSelector.initialized)){
            return tscs.initComplete(table, c);
        }
    }
});

/* columnSelector widget functions */
tscs = ts.columnSelector = {

    init: function(table) {
        // check if tablesorter has initialized
        if (table.hasInitialized && table.config.columnSelector.initialized) { return; }

        var t,
            c = table.config,
            wo = c.widgetOptions.columnSelector,

            // save columnSelector variables
            cs = c.columnSelector = $.extend({
                layout: wo.layout,
                columns: wo.columns
            }, c.columnSelector);

        if (c.debug) {
            ts.log('columnSelector initializing');
        }

        cs.$container = $(wo.container).addClass('tablesorter-columnSelector').show();

        // clear initialized flag
        cs.initialized = false;

        // before initialization event
        c.$table.trigger('columnSelectorBeforeInitialized', c);
    },

    initComplete: function(table, c) {
        var cs = c.columnSelector;

        tscs.setup(table, c);

        // columnSelector initialized
        cs.initialized = true;
        c.$table.trigger('columnSelectorInitialized', c);
    },

    setup: function(table, c) {
        var wo = c.columnSelector,
            columnSelectorPane = wo.$container.html();

        // populate the selector container
        var rowid = -1;
        $('thead tr:first th', table).each(function() {
            rowid++;
            var name = $(this).text();
            if (wo.columns[rowid] !== undefined) {
                // set default col title
                if (wo.columns[rowid][1] !== undefined)
                    name = wo.columns[rowid][1];
            }

            columnSelectorPane += '<label id="' + rowid + '" class="tablesorterColumnSelector-inputLabel"><input class="tablesorterColumnSelector-input" type="checkbox" value="' + rowid + '" name="col' + rowid + '" />' + name + "</label>\n";
            if (wo.layout == 'vertical') {
                columnSelectorPane += '<br />' + "\n";
            }
        });
        $(wo.$container).html(columnSelectorPane);

        // Set user requested defaults, if none do nothing
        tscs.updateCols(table, c, true);

        // Add a bind on update to re-run col setup
        $(table).bind('updateComplete', function() {
            tscs.updateCols(table, c, false);
        });
    },

    getStates: function(table, c, useConfig) {
        var wo = c.columnSelector,
            columnSelectorPane = wo.$container,
            state = [];

        $('label', $(columnSelectorPane)).each(function() {
            var rowid = $(this).attr('id');

            state[rowid] = true;
            if (useConfig) {
                if (wo.columns[rowid] !== undefined) {
                    // if this row not hidable at all
                    if (wo.columns[rowid][0] !== undefined &&
                        wo.columns[rowid][0] == 'disable')
                        return true; // goto next

                    // set default state
                    if (wo.columns[rowid][0] !== undefined)
                        state[rowid] = wo.columns[rowid][0];
                }
            } else {
                state[rowid] = $('input[name="col' + rowid + '"]', wo.$container).is(':checked');
            }
        });

        return state;
    },

    updateCols: function(table, c, useConfig) {
        var k,
            wo = c.columnSelector,
            state = tscs.getStates(table, c, useConfig);

        for (k = 0; k < state.length; k++ ) {
            $('input[name="col' + k + '"]', wo.$container).prop('checked', state[k]);

            // it's false so lets turn it off
            if (!state[k]) {
                tscs.toggleCol(table, c, k);
            }

            // add the action on click
            $('input[name="col' + k + '"]', wo.$container).change(function() {
                tscs.toggleCol(table, c, $(this).val());
            });
        }
    },

    toggleCol: function(table, c, colNum) {
        var wo = c.columnSelector;
        colNum = parseInt(colNum);
        // 0 == 1 in nth-child so + 1 in those calls
        var nthcolNum = colNum + 1;

        var hsh = $(table).hasClass('hasStickyHeaders');
        var sh = hsh ? c.$sticky : '';

        if ($('input[name="col' + colNum + '"]', wo.$container).is(':checked')) {
            $('thead, tbody', table).find('tr').find('td:nth-child(' + nthcolNum + '), th:nth-child(' + nthcolNum + ')').show();
            $('thead, tbody', table).find('tr').find('td:nth-child(' + nthcolNum + '), th:nth-child(' + nthcolNum + ')').removeClass('filtered');

            $('tfoot', table).find('tr.canhide').find('td:nth-child(' + nthcolNum + '), th:nth-child(' + nthcolNum + ')').show();
            $('tfoot', table).find('tr.canhide').find('td:nth-child(' + nthcolNum + '), th:nth-child(' + nthcolNum + ')').removeClass('filtered');
            // Check and Deal with StickyHeaders
            if (hsh) {
                $('thead, tbody', sh).find('tr').find('td:nth-child(' + nthcolNum + '), th:nth-child(' + nthcolNum + ')').show();
                $('thead, tbody', sh).find('tr').find('td:nth-child(' + nthcolNum + '), th:nth-child(' + nthcolNum + ')').removeClass('filtered');
            }
            $('#label-col' + colNum, wo.$container).removeClass('noshow');
        } else {
            $('thead, tbody', table).find('tr').find('td:nth-child(' + nthcolNum + '), th:nth-child(' + nthcolNum + ')').hide();
            $('thead, tbody', table).find('tr').find('td:nth-child(' + nthcolNum + '), th:nth-child(' + nthcolNum + ')').addClass('filtered');
            $('tfoot', table).find('tr.canhide').find('td:nth-child(' + nthcolNum + '), th:nth-child(' + nthcolNum + ')').hide();
            $('tfoot', table).find('tr.canhide').find('td:nth-child(' + nthcolNum + '), th:nth-child(' + nthcolNum + ')').addClass('filtered');
            // Check and Deal with StickyHeaders
            if (hsh) {
                $('thead, tbody', sh).find('tr').find('td:nth-child(' + nthcolNum + '), th:nth-child(' + nthcolNum + ')').hide();
                $('thead, tbody', sh).find('tr').find('td:nth-child(' + nthcolNum + '), th:nth-child(' + nthcolNum + ')').addClass('filtered');
            }
            $('#label-col' + colNum, wo.$container).addClass('noshow');
        }
    }
};

})(jQuery);

Here is my still work-in-progress version (demo)

I'm busy with other stuff today, but the things I still need to fix are:

  • Correctly update the check boxes when they are manually changed vs changed by media queries.
  • Manually changing the check box overrides the media query.
  • Add styling to check boxes/container

I'm sure there are other bugs I still haven't discovered.

oh I really like the idea with the layout so it's fully user configurable.

I'm still going through it but I can't see why the first 3 selectors are set the false on load when I only see you setting the first one to disable. also the ones that are false to start can not be enabled at all.

They are set to false on load because of the media queries... the result window is "that" narrow. See this reference.

I guess I also need to add a save feature to remember your settings.

okay working with it a bit more, now I see that the missing cols where due to breakpoint, though I'm not really sure this is fitting for this widget. I removed them all then added just the grades but I still couldn't see them. I understand why but based on my display size I can NEVER see the data so that doesn't seem practical at all in any application. I can see auto setting them to hidden, but making it so they can never be shown seems well there really wouldn't see a point is even showing the checkboxes at that point. honestly that option should default to false.

other wise it looks really good, needs to be tested with the sticky headers and the pager in ajax mode though to really see it, I think priority 10 will cause lots of issues at that point. Really really not looking forward to rewriting half my site for the options/name change though :(

LOL be patient... I can rename it. I was just sharing my work-in-progress.

I didn't see any sense in the layout: 'vertical', option though, since it can be done with css. So I changed it to the label-input template. But the widget options needs an identifier in front (e.g. selector_container) otherwise widget options could get mixed up.

I originally wanted to make an all-in-one column selector with responsiveness and reflow, but that's probably getting carried away. Maybe you're right, I can remove the responsiveness from this widget and add it to a different widget altogether.

Though, I did make it so you can set the responsive_breakpoints option to false to disable the media queries.

Oops I replied to the other thread on the naming, it's no big I'm going to have to go through it all anyhow, and it's my own fault for not making my options consistent with yours ;)

Yeah I really really like how you handle layout. MUCH better then mine ;)

yeah there dis nothing wrong with the breakpoints I just think the default should be false, when I commented it out they still stayed. I had to set it to false specifically for it to work. And again I think it'd be a very very useful option but I think it should only set the default state of the col, not disable it all together, allow the user to option to scroll he they choose to, or at least remove a col to enable an alternative one ;) but that might just be my views and I can always just use false on breakpoints anyhow. It's a neat option for mobile users for sure.

Well, as I said above, that is a bug I'm still working on... I want the manual checking of checkboxes to override the media queries.

Also, what did you think of me using the same options as jQuery Mobile? I mean the priority settings and using "critical" being the same as "disable".

ahhh I'm just new to the terms, I didn't know what "media query" was TBH :) and I really didn't know it had to do with breakpoints ;) it all makes sense now ;)

I think that i a good idea, to easier and closer things are to other main stream projects the better it is. And really I think critical is a better descriptor then disable ;) I think like a programmer not a user, so to me I'm disabling to option, but really it's being disabled because the data is critical which form the user's point makes more sense I think.

You know, I think I'm just going to add an extra checkbox to the container to turn the media queries on and off. When "on" the other check-boxes are disabled and ignored. That might be the easiest solution, because figuring out what when a checkbox is checked and saying, okay that means I never want to hide that column or should I restore a column when the screen gets wider... actually an indeterminate (three-state) check box would be ideal in this case; but I think turning the whole darn thing on and off is easier.

ohhh those checkboxes are neat, but I agree just all the way might be easier. I might give it a shot and see if I can figure it out, I was thinking more of having the "media query" have an option.

mediaquery: <value>
value can be false, true or initial

if false don't use at all, true is how you have it now, and initial would just setup up the initial checkboxes but not use it after that. At least that was my idea on it.

Ok, I think I'm done... I just need to document it all. New demo.

$(function() {

    $("table").tablesorter({
        theme: 'blue',
        widgets: ['zebra', 'columnSelector', 'stickyHeaders'],
        widgetOptions : {
            // target the column selector markup
            columnSelector_container : $('#columnSelector'),
            // column status, true = display, false = hide
            // disable = do not display on list
            columnSelector_columns : {
                0: 'disable' /*,  disabled no allowed to unselect it */
                /* 1: false // start hidden */
            },
            // remember selected columns
            columnSelector_saveColumns: true,

            // container layout
            columnSelector_layout : '<label><input type="checkbox">{name}</label>',
            // 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'

        }
    });

});

looks great in the demo, I'm going to copy it into my project and start updating my code base and I'll get back to this, any idea when you'll get it into git, (not a release but into git)?

looks like breakpoint doesn't have a default?

        widgetOptions: {
            columnSelector_container: '#columnSelector',
            columnSelector_columns: {
                1: false,
                5: false,
                6: false,
                7: false
            },
            columnSelector_mediaquery: false
        },
[Error] TypeError: 'undefined' is not an object (evaluating 'c.selector.$breakpoints.prop')
updateCols (widget-columnSelector.js, line 178)
(anonymous function) (widget-columnSelector.js, line 80)
dispatch (jquery.min.js, line 3332)
eventHandle (jquery.min.js, line 2941)
trigger (jquery.min.js, line 3210)
(anonymous function) (jquery.min.js, line 3869)
each (jquery.min.js, line 660)
each (jquery.min.js, line 271)
trigger (jquery.min.js, line 3868)
(anonymous function) (jquery.min.js, line 3922)
(anonymous function) (widget-columnSelector.js, line 81)
each (jquery.min.js, line 660)
each (jquery.min.js, line 271)
setupSelector (widget-columnSelector.js, line 52)
init (widget-columnSelector.js, line 26)
init (widget-columnSelector.js, line 223)
(anonymous function) (jquery.tablesorter.js, line 1295)
each (jquery.min.js, line 660)
applyWidget (jquery.tablesorter.js, line 1288)
setup (jquery.tablesorter.js, line 960)
(anonymous function) (jquery.tablesorter.js, line 901)
each (jquery.min.js, line 660)
each (jquery.min.js, line 271)
construct (jquery.tablesorter.js, line 892)
(anonymous function) (agedstock.php, line 59)
fire (jquery.min.js, line 1075)
fireWith (jquery.min.js, line 1193)
ready (jquery.min.js, line 435)
DOMContentLoaded (jquery.min.js, line 949)

at line 178 changing this

               c.selector.$breakpoints.prop('disabled', true);
               c.selector.$style.prop('disabled', false).html( styles.join(',') + ' { display: none; }' );

to

                if (wo.columnSelector_mediaquery) {
                        c.selector.$breakpoints.prop('disabled', true);
                        c.selector.$style.prop('disabled', false).html( styles.join(',') + ' { display: none; }' );
                }

this is not the right fix at all, it needs to set these things but these objects aren't set if media is off. so I just need to set them regardless I think, at least style.

okay this is working, BUT the initial rows that i set to false are not working, so I'm still debuting that change, but here is line 178 now

                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; }' );
                }

line 66 needs to be

colSel.states[colId] = saved && saved.length >= colId ? saved[colId] : typeof(wo.columnSelector_columns[colId]) !== 'undefined' ? wo.columnSelector_columns[colId] : true;

need the typeof compare for the default to work since it can be false.

otherwise I think it's good.

actually it should be

colSel.states[colId] = saved && typeof(saved[colId]) !== 'undefined' ? saved[colId] : typeof(wo.columnSelector_columns[colId]) !== 'undefined' ? wo.columnSelector_columns[colId] : true;

the other way "saved.length >= colId" was giving false positives for col 0, and was setting it to undefined aka false.

I have now converted my entire project to use this with my 2 changes, everything seems to be running smoothly, I have lost a small amount of functions since no classes are added and the extra data attribute for filtered cols is gone (was requested by someone further up) but otherwise I think it's good enough to make an official appearance now ;)

Great work Mottie.

Thanks, here is the latest demo.

Honestly, I don't know when I'll have this up on git. I still haven't done any documentation for this widget and I'm going on a mini-vacation next week (until after new years day).

LOL I still want to add the reflow option into this widget... * twitch *

mind if I get a demo up and add it? I switched to submodules so I can't update my production env till this is in the rep so it's propagates to the submodules ;)

also that last demo is missing the "typeof(saved[colId]) !== 'undefined'" it fixes an issue with col0 in some rare cases.

LOL fine, I already have a demo in place, I just need to add some notes about the options... give me a little time and I'll push it to git today.

hehe thanks, I don't mind doing it as it's my issue and I know you are busy ;) I'm just really anxious to get this new table sorter out into the wild ;)

Oh, and new fiddle with the code I was missing: http://jsfiddle.net/Mottie/3pS6v/4/

perfect.

Longest issue thread EVER!... Ok, I've pushed my changes to git only. I have yet to update the version number.

sweet thank you, do you mind if I rename everything though? it's very inconsistent with the rest of the widgets. The widget is called columnSelect but the files(s) are column-selector

That's fine.

perfect thank you, updated

Which did you rename? The file name or the widget? LOL

the file, the widget was already right, plus renaming all the config options would have been a PITA :)

6a6712f