THIS IS COOL! - A javascript code to let user choose which column want to see

Hi guys,

I was looking for a code that let me choose (edit on flight) the column I wanna see in a table - this is great for big tables and small screens - and I just found this code - https://jsfiddle.net/HvA4s/ :

 

$('#edit').click(function() {
    var headers = $('#table th').map(function() {
        var th =  $(this);
        return {
            text: th.text(),
            shown: th.css('display') != 'none'
        };
    });
var h = ['<div id=tableEditor><button id=done>Done</button><table><thead><tr>'];
$.each(headers, function() {
    h.push('<th><input type=checkbox',
           (this.shown ? ' checked ' : ' '),
           '/> ',
           this.text,
           '</th>');
});
h.push('</tr></thead></table></div>');
$('body').append(h.join(''));

$('#done').click(function() {
    var showHeaders = $('#tableEditor input').map(function() { return this.checked; });
    $.each(showHeaders, function(i, show) {
        var cssIndex = i + 1;
        var tags = $('#table th:nth-child(' + cssIndex + '), #table td:nth-child(' + cssIndex + ')');
        if (show)
            tags.show();
        else
            tags.hide();
    });
    
    $('#tableEditor').remove();
    return false;
});

return false;</pre>

 The problem is that I can't integrate with knack, because I dont have the knowledge to deal with javascript. So I was wondering if anyone interested in using this code would tell me what should I change in this code to make it work with knack tables.

Thanks a lot!

This code is a bit old. I rewrote it to ES6 standards, and refactored a few things to make it slightly more legible. I also switched to using local storage instead of cookies.

All you need to do is paste the code below in your app and change the 'view_xx' for the view that has your table.

If anyone needs more help feel free to reach out. I work on Knack apps nearly full time
kelson@ksensetech.com
https://www.ksensetech.com/

Here is the code.

$(document).on("knack-view-render.view_xx", function (event, view, data) {

  showHideTableCols(view.key);

});

function showHideTableCols(viewKey) {

  //Adds the required dom elements to the dom

  const addDomElements = () => {

    //add css

    const customCSS = Knack.$("#kn-custom-css").text();

    const addition = "\n.hidden{display:none !important}";

    Knack.$("#kn-custom-css").text(customCSS + addition);

    //add new dom elems

    Knack.$(`#${viewKey} .view-header`).append(

      '<a id="show_hide_cols">Show / Hide Columns</a>'

    );

    Knack.$(`#${viewKey} .view-header`).append(

      '<div id="hiddenOptions" class="hidden"></div>'

    );

    const checkBoxesHtml = createCheckboxes();

    Knack.$("#hiddenOptions").append(checkBoxesHtml);

  };

  //Returns an array of obj with column lables and fieldIds

  const getColumns = () => {

    const columnBodyElems = Knack.$(

      `#${viewKey} tbody tr:first-child td[class^=field]`

    );

    //get all the fields by class

    const fields = columnBodyElems.toArray().map((col) => {

      const classes = Knack.$(col).attr("class");

      const position = classes.search("field_");

      const field = classes.split(" ")[position].trim();

      //get the headerlabel

      const headerLabel = Knack.$(`.${field}`)

        .find("span a span")

        .text()

        .trim();

      return {

        headerLabel,

        field,

      };

    });

    return fields;

  };

  const localStorageName = `${viewKey}_hiddenCols`;

  const handleLocalStorage = {

    get: () => {

      let currentLs = localStorage.getItem(localStorageName);

      if (!currentLs) {

        localStorage.setItem(localStorageName, JSON.stringify([]));

        return [];

      }

      return JSON.parse(currentLs);

    },

    add: (fieldId) => {

      const currentLs = handleLocalStorage.get();

      if (!currentLs) {

        const newLs = [fieldId];

        localStorage.setItem(localStorageName, JSON.stringify(newLs));

        return;

      }

      const newLs = [...currentLs, fieldId];

      localStorage.setItem(localStorageName, JSON.stringify(newLs));

    },

    remove: (fieldId) => {

      const currentLs = handleLocalStorage.get();

      const newLs = currentLs.filter((field) => {

        return field !== fieldId;

      });

      localStorage.setItem(localStorageName, JSON.stringify(newLs));

    },

  };

  const createCheckboxes = () => {

    const currentStorage = handleLocalStorage.get();

    let allCheckboxesHtml = "";

    getColumns().forEach((column) => {

      let isChecked =

        currentStorage.indexOf(column.field) > -1 ? "" : "checked";

      allCheckboxesHtml += `

              <div>

                  <input type="checkbox" id="toggle_${column.field}" name="${column.headerLabel}" ${isChecked}>

                  <label for="${column.headerLabel}">${column.headerLabel}</label>

              </div>`;

    });

    return allCheckboxesHtml;

  };

  const addEventListeners = () => {

    //checkbox input

    Knack.$("[id^=toggle_field]").on("click", function () {

      const fieldId = Knack.$(this).attr("id").split("toggle_")[1];

      Knack.$(`.${fieldId}`).toggleClass("hidden");

      if (Knack.$(this).is(":checked")) {

        handleLocalStorage.remove(fieldId);

      } else {

        handleLocalStorage.add(fieldId);

      }

    });

    //anchor elem to show/hide checkboxes

    Knack.$("#show_hide_cols").on("click", function () {

      Knack.$("#hiddenOptions").toggleClass("hidden");

    });

  };

  const hideHiddenCols = () => {

    //get local storage

    const hiddenColumns = handleLocalStorage.get();

    if (hiddenColumns) {

      hiddenColumns.forEach((column) => {

        Knack.$(`.${column}`).addClass("hidden");

      });

    }

  };

  addDomElements();

  hideHiddenCols();

  addEventListeners();

}

IMPLEMENTED!!

This is fantastic! thank you so much gentlemen!

With the great Knack's support team help, they pointed out the we need to change ' div.kn-records-nav.clearfix' for ' div.kn-records-nav', as .clearfix does not exists in standard theme.

Did anyone find a solution to make this work with knack standard theme?

Marc, I'm also having problems to make the filter button appear with the new knack standard theme. Anyone figured out how to solve it?

 

Here is my code that was working with old theme:

 

// Filtro de Colunas
$(document).on('kn-table-wrapper.view_289.view_1111.view_1118.view_1119.view_1488.view_940.view_1105.view_1073.view_1411.view_1410.view_1375.view_1469.view_1761', function(event, view, data) {
addTableColumnChoose(view);
});


var addTableColumnChoose = function(view) {
//See http://helpdesk.knackhq.com/support/discussions/topics/5000074312 and https://jsfiddle.net/HvA4s/
// added support for cookies to keep selected columns between renders and sessions
var clearFix = $('#' + view.key + ' div.kn-records-nav.clearfix');
clearFix.append("<div class='clear'><a href='#' class='choose-columns'>▼ Ocultar Coluna(s)</a></div>");
var hstring = getCookie("hstring_"+view.key);
if (hstring != ''){
var headers = JSON.parse(hstring);
$.each(headers, function(i, show) {
var cssIndex = i + 1;
var tags = $('#' + view.key + ' table th:nth-child(' + cssIndex + '), #' + view.key + ' table td:nth-child(' + cssIndex + ')');
if (show)
tags.show();
else
tags.hide();

});
}

$('#' + view.key + ' .choose-columns').click(function() {
// remove other open columns set dialog on the same page
if( $('#tableChooseColumns')!=null) {$('#tableChooseColumns').remove();}
var headers = $('#' + view.key + ' table th').map(function() {
var th = $(this);
return {text: th.text(), shown: th.css('display') != 'none'};
});
var hs;
h = ['<div id=tableChooseColumns><table><thead><tr><td style="height: 0px; border-bottom: 2px solid; padding: 7px 0px 7px 0px;">FILTRAR</td></tr></thead><tbody><tr>'];
$.each(headers, function() {
h.push('<tr><td style="padding:5px 0px 3px 0px;"><input type=checkbox',
(this.shown ? ' checked ' : ' '),
'/> ',
this.text,
'</td></tr>');
});
h.push('</tr></tbody></table><button id=done>Aplicar</button></div>');
hs = h.join('');

$('body').append(hs);
var pos = $('#' + view.key + ' .choose-columns').position();
$('#tableChooseColumns').css({'position': 'absolute','right': '24px', 'margin-top': '-50px', 'z-index': '9999', 'top': pos.top,'padding': '5px', 'background': 'rgb(118, 138, 158)', 'box-shadow': '0 0px 0px 0 rgba(0, 0, 0, 0.2), 0px 4px 10px 0 rgba(0, 0, 0, 0.19)', 'font-size': '14px', 'color': '#fff', 'border': '2px solid #fff', 'border-radius': '12px'});
$('#done').click(function() {
var showHeaders = $('#tableChooseColumns input').map(function() { return this.checked; });
var columns =[];
$.each(showHeaders, function(i, show) {
var cssIndex = i + 1;
var tags = $('#' + view.key + ' table th:nth-child(' + cssIndex + '), #' + view.key + ' table td:nth-child(' + cssIndex + ')');
if (show)
tags.show(300,"swing");
else
tags.hide(300,"swing");
columns.push(show);
});

$('#tableChooseColumns').remove();
setCookie("hstring_"+view.key,JSON.stringify(columns),100);

mixpanel.track("Preferences changes",{"Page":("Knack: "+view.scene.name), "View":view.title});
return false;
});
return false;
});
}

/* Cookie support -----------------------------------------------*/

function setCookie(cname, cvalue, exdays) {
var d = new Date();
d.setTime(d.getTime() + (exdays*24*60*60*1000));
var expires = "expires="+ d.toUTCString();
document.cookie = cname + "=" + cvalue + "; " + expires;
}

function getCookie(cname) {
var name = cname + "=";
var ca = document.cookie.split(';');
for(var i = 0; i <ca.length; i++) {
var c = ca[i];
while (c.charAt(0)==' ') {
c = c.substring(1);
}
if (c.indexOf(name) == 0) {
return c.substring(name.length,c.length);
}
}
return "";
}

// handler to dismiss dialog just with outside click
$(document).click(function(event) {
if(!$(event.target).closest('#tableChooseColumns').length) {
if($('#tableChooseColumns').is(":visible")) {
$('#tableChooseColumns').remove();
}
}
})

Hi All,

 

Somehow I'm not able to get this work - in the beta theme.

I'm quite new with JavaScript so I'm probably missing something here; what part of the code do I have to change so it works for my knack application. So far I have changed the

$(document).on('knack-view-render.view_743', function(event, view, data) {
  addTableColumnChoose(view);
});

part of the code, but without any results... 

Any suggestions?

Thanks,

 

Marc

 

 

 

Hi There,

Thanks for sharing this code. It has been veryful. I'm now having problems with this code. Whenever I click on the "Choose Columns" button, something strange happens. The fields is covered under the table. I tried playing around with the code, but no luck. Any suggestions?

Added z-order to the styles to bring it in front:

$('#tableChooseColumns').css({'position': 'absolute','z-index':'9999'...

Guys, this works for me in Chrome but in IE and Edge it hides behind the table. Does anyone know of a fix to po this to the front?

Peter

one more thing, sorry.

I wanted to change the OK button appearance, so I created new class for it in css. This class is used in the button creation function in Javascript code. 

class definition for CSS below:

 

.button_a {
    background-color: #3c3c90;
    border: none;
    border-radius: 3px;
    color: white;
    padding: 4px 8px;
    text-align: center;
    text-decoration: none;
    display: inline-block;
    font-size: 14px;
}

 

Hi,

just one more little improvement.

As I am using right now this code to include tables with lots of columns, I wanted to have the possibility to hide most of less important when the user enters the page for the first time. 

Thanks to great code by Brad, it was quite easy. 

You can define initially hidden columns in the handler code by specifying 'false' in the columns table below:

 

$(document).on('knack-view-render.view_743', function(event, view, data) {
  var starting = getCookie("hstring_"+view.key);
  // put here your initial settings for columns visibility.
  // false means this column will be hidden first time a user access the page.
  // the length of the 'coulmns' array doesn't matter it takes. just use as much element as you want.
  var columns = [true,false,false,true,true];
  if (starting == '') setCookie("hstring_"+view.key,JSON.stringify(columns),100);
  addTableColumnChoose(view);
});

 all the best

Hi again,

I have verified the problem.

The code below works with both tables and search views now.

I have learnt a lot about jQuery selectors by this occasion. Now I understand better what Brad wrote in his code.

Once again thanks to Brad for excellent inspiration. This simple tool resolves one of my biggest problems with knack :-)

 

$(document).on('knack-view-render.view_743', function(event, view, data) {
  addTableColumnChoose(view);
});

var addTableColumnChoose = function(view) {
//See http://helpdesk.knackhq.com/support/discussions/topics/5000074312 and https://jsfiddle.net/HvA4s/
// added support for cookies to keep selected columns between renders and sessions
var clearFix = $(’#’ + view.key + ’ div.kn-records-nav.clearfix’);
clearFix.append("<div class=‘clear’><a href=’#’ class=‘choose-columns’>Choose Columns</a></div>");
var hstring = getCookie(“hstring_”+view.key);
if (hstring != ‘’){
var headers = JSON.parse(hstring);
$.each(headers, function(i, show) {
var cssIndex = i + 1;
var tags = $(’#’ + view.key + ’ table th:nth-child(’ + cssIndex + ‘), #’ + view.key + ’ table td:nth-child(’ + cssIndex + ‘)’);
if (show)
tags.show();
else
tags.hide();

        });

}

$('#' + view.key + ' .choose-columns').click(function() {
  // remove other open columns set dialog on the same page
  if( $('#tableChooseColumns')!=null) {$('#tableChooseColumns').remove();}
    var headers = $('#' + view.key + ' table th').map(function() {
        var th =  $(this);
        return {text: th.text(), shown: th.css('display') != 'none'};
    });
  var hs;
  h = ['&lt;div id=tableChooseColumns&gt;&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th style="text-align:center;padding:5px;"&gt;&lt;button id=done class="button_a"&gt;OK&lt;/button&gt;&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;'];
  $.each(headers, function() {
    h.push('&lt;tr&gt;&lt;td style="padding:5px;"&gt;&lt;input type=checkbox',
           (this.shown ? ' checked ' : ' '),
           '/&gt; ',
           this.text,
           '&lt;/td&gt;&lt;/tr&gt;');
    });
    h.push('&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/div&gt;');
    hs = h.join('');

    $('body').append(hs);
    var pos = $('#' + view.key + ' .choose-columns').position();
  $('#tableChooseColumns').css({'position': 'absolute','left': '20px', 'top': pos.top,'padding': '5px', 'border': '1px solid #666', 'border-radius':'3px','background': '#fff'});
    $('#done').click(function() {
        var showHeaders = $('#tableChooseColumns input').map(function() { return this.checked; });
        var columns =[];
       $.each(showHeaders, function(i, show) {
            var cssIndex = i + 1;
            var tags = $('#' + view.key + ' table th:nth-child(' + cssIndex + '), #' + view.key + ' table td:nth-child(' + cssIndex + ')');
            if (show)
                tags.show(300,"swing");
            else
                tags.hide(300,"swing");
            columns.push(show);
        });
     
        $('#tableChooseColumns').remove();
        setCookie("hstring_"+view.key,JSON.stringify(columns),100);

      mixpanel.track("Preferences changes",{"Page":("Knack: "+view.scene.name), "View":view.title});
      return false;
    });
    return false;
});

}

/* Cookie support -----------------------------------------------*/

function setCookie(cname, cvalue, exdays) {
var d = new Date();
d.setTime(d.getTime() + (exdays2460601000));
var expires = “expires=”+ d.toUTCString();
document.cookie = cname + “=” + cvalue + "; " + expires;
}

function getCookie(cname) {
var name = cname + “=”;
var ca = document.cookie.split(’;’);
for(var i = 0; i <ca.length; i++) {
var c = ca[i];
while (c.charAt(0)==’ ') {
c = c.substring(1);
}
if (c.indexOf(name) == 0) {
return c.substring(name.length,c.length);
}
}
return “”;
}

// handler to dismiss dialog just with outside click
$(document).click(function(event) {
if(!$(event.target).closest(’#tableChooseColumns’).length) {
if($(’#tableChooseColumns’).is(":visible")) {
$(’#tableChooseColumns’).remove();
}
}
})
 

 BTW, I have added also simple animation to hide/show columns. Now you have visual clue what columns were added or removed.

All the best,

Andrzej 

Hi, 

I didn't tried it at search views. In fact I do not use them at all. I will try to look at this in next days.

Andrzej

Hi guys.

I still cant make it work on search results. Already tried everything I know. Take a look at my code. Dont know what to do now

 

});
$(document).on('knack-view-render.view_289.view_1111.view_1118.view_1119', function(event, view, data) {
  addTableColumnChoose(view);
});
$(document).on('knack-view-render.search', function(event, view, data) {
  addTableColumnChoose(view);
});

 

 

Nice!

 

Great stuff Andrzej, appreciate you sharing your work.

One little thing more:

this simple snippet might be useful if you want to dismiss dialog by clicking out side it. 

Found here: stack overflow thread

 

$(document).click(function(event) { 
    if(!$(event.target).closest('#tableChooseColumns').length) {
        if($('#tableChooseColumns').is(":visible")) {
            $('#tableChooseColumns').remove();
        }
    }        
})

 

It is really cool.

But it does not work well with table pagination and filtering. 

I have made few changes:

1 - include cookie support for keeping changes to columns visibility between view renderings and even browser sessions.

2 - some tweaks to dialog appearance including vertical table instead of horizontal - this works better for me.

You have to change first two instructions of course to match your table views keys.


Once again thanks for sharing the original code :)

 

$(document).on('knack-view-render.view_604', function(event, view, data) {
  addTableColumnChoose(view);
});
$(document).on('knack-view-render.view_605', function(event, view, data) {
  addTableColumnChoose(view);
});

var addTableColumnChoose = function(view) {
//See http://helpdesk.knackhq.com/support/discussions/topics/5000074312 and https://jsfiddle.net/HvA4s/
// added support for cookies to keep selected columns between renders and sessions
var clearFix = $(’#’ + view.key + ’ > div.kn-records-nav.clearfix’);
clearFix.append("<div class=‘clear’><a href=’#’ class=‘choose-columns’>Choose columns</a></div>");
var hstring = getCookie(“hstring_”+view.key);
if (hstring != ‘’){
var headers = JSON.parse(hstring);
$.each(headers, function(i, show) {
var cssIndex = i + 1;
var tags = $(’#’ + view.key + ’ > table th:nth-child(’ + cssIndex + ‘), #’ + view.key + ’ > table td:nth-child(’ + cssIndex + ‘)’);
if (show)
tags.show();
else
tags.hide();

        });

}

$('#' + view.key + ' .choose-columns').click(function() {
  // remove other open columns set dialog on the same page
  if( $('#tableChooseColumns')!=null) {$('#tableChooseColumns').remove();}
    var headers = $('#' + view.key + ' &gt; table th').map(function() {
        var th =  $(this);
        return {text: th.text(), shown: th.css('display') != 'none'};
    });
  var hs;
    h = ['&lt;div id=tableChooseColumns&gt;&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th style="text-align:center;padding:5px;"&gt;&lt;button id=done&gt;OK&lt;/button&gt;&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;'];
  $.each(headers, function() {
    h.push('&lt;tr&gt;&lt;td style="padding:5px;"&gt;&lt;input type=checkbox',
           (this.shown ? ' checked ' : ' '),
           '/&gt; ',
           this.text,
           '&lt;/td&gt;&lt;/tr&gt;');
    });
    h.push('&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/div&gt;');
    hs = h.join('');

    $('body').append(hs);
    var pos = $('#' + view.key + ' .choose-columns').position();
  $('#tableChooseColumns').css({'position': 'absolute','left': '20px', 'top': pos.top,'padding': '5px', 'border': '1px solid #666', 'border-radius':'3px','background': '#fff'});
    $('#done').click(function() {
        var showHeaders = $('#tableChooseColumns input').map(function() { return this.checked; });
        var columns =[];
       $.each(showHeaders, function(i, show) {
            var cssIndex = i + 1;
            var tags = $('#' + view.key + ' &gt; table th:nth-child(' + cssIndex + '), #' + view.key + ' &gt; table td:nth-child(' + cssIndex + ')');
            if (show)
                tags.show();
            else
                tags.hide();
            columns.push(show);
        });
     
        $('#tableChooseColumns').remove();
        setCookie("hstring_"+view.key,JSON.stringify(columns),100);
      return false;
    });
    return false;
});

}

/* Cookie support -----------------------------------------------*/

function setCookie(cname, cvalue, exdays) {
var d = new Date();
d.setTime(d.getTime() + (exdays2460601000));
var expires = “expires=”+ d.toUTCString();
document.cookie = cname + “=” + cvalue + "; " + expires;
}

function getCookie(cname) {
var name = cname + “=”;
var ca = document.cookie.split(’;’);
for(var i = 0; i <ca.length; i++) {
var c = ca[i];
while (c.charAt(0)==’ ') {
c = c.substring(1);
}
if (c.indexOf(name) == 0) {
return c.substring(name.length,c.length);
}
}
return “”;
}

 

Tks Brad, but knack-view-render.search is not working on search results. Don't know why..