Implement toggleable table fields

This commit is contained in:
Mark Veidemanis 2022-08-09 07:20:30 +01:00
parent 89b38111cd
commit d1076ca2b5
Signed by: m
GPG Key ID: 5ACFCEED46C0904F
6 changed files with 574 additions and 308 deletions

View File

@ -0,0 +1,205 @@
// Author: Grzegorz Tężycki
$(document).ready(function(){
// In web storage is saved structure like that:
// localStorage['django_tables2_column_shifter'] = {
// 'table_class_container1' : {
// 'id' : 'on',
// 'col1' : 'off',
// 'col2' : 'on',
// 'col3' : 'on',
// },
// 'table_class_container2' : {
// 'id' : 'on',
// 'col1' : 'on'
// },
// }
// main name for key in web storage
var COLUMN_SHIFTER_STORAGE_ACCESOR = "django_tables2_column_shifter";
// Return storage structure for shifter
// If structure does'n exist in web storage
// will be return empty object
var get_column_shifter_storage = function(){
var storage = localStorage.getItem(COLUMN_SHIFTER_STORAGE_ACCESOR);
if (storage === null) {
storage = {};
} else {
storage = JSON.parse(storage);
}
return storage;
};
// Save structure in web storage
var set_column_shifter_storage = function(storage){
var json_storage = JSON.stringify(storage)
localStorage.setItem(COLUMN_SHIFTER_STORAGE_ACCESOR, json_storage);
};
// Remember state for single button
var save_btn_state = function($btn){
// Take css class for container with table
var table_class_container = $btn.data("table-class-container");
// Take html object with table
var $table_class_container = $("#" + table_class_container);
// Take single button statne ("on" / "off")
var state = $btn.data("state");
// td-class is a real column name in table
var td_class = $btn.data("td-class");
var storage = get_column_shifter_storage();
// Table id
var id = $table_class_container.attr("id");
// Checking if the ID is already in storage
if (id in storage) {
data = storage[id]
} else {
data = {}
storage[id] = data;
}
// Save state for table column in storage
data[td_class] = state;
set_column_shifter_storage(storage);
};
// Load states for buttons from storage for single tabel
var load_states = function($table_class_container) {
var storage = get_column_shifter_storage();
// Table id
var id = $table_class_container.attr("id");
var data = {};
// Checking if the ID is already in storage
if (id in storage) {
data = storage[id]
// For each shifter button set state
$table_class_container.find(".btn-shift-column").each(function(){
var $btn = $(this);
var td_class = $btn.data("td-class");
// If name of column is in store then get state
// and set state
if (td_class in data) {
var state = data[td_class]
set_btn_state($btn, state);
}
});
}
};
// Show table content and hide spiner
var show_table_content = function($table_class_container){
$table_class_container.find("#loader").hide();
$table_class_container.find(".table-container").show();
const event = new Event('restore-scroll');
document.dispatchEvent(event);
};
// Load buttons states for all button in page
var load_state_for_all_containters = function(){
$(".column-shifter-container").each(function(){
$table_class_container = $(this);
// Load states for all buttons in single container
load_states($table_class_container);
// When states was loaded then table must be show and
// loader (spiner) must be hide
show_table_content($table_class_container);
});
};
// change visibility column for single button
// if button has state "on" then show column
// else then column will be hide
shift_column = function( $btn ){
// button state
var state = $btn.data("state");
// td-class is a real column name in table
var td_class = $btn.data("td-class");
var table_class_container = $btn.data("table-class-container");
var $table_class_container = $("#" + table_class_container);
var $table = $table_class_container.find("table");
var $cels = $table.find("." + td_class);
if ( state === "on" ) {
$cels.show();
} else {
$cels.hide();
}
};
// Shift visibility for all columns
shift_columns = function(){
var cols = $(".btn-shift-column");
var i, len = cols.length;
for (i=0; i < len; i++) {
shift_column($(cols[i]));
}
};
// Set icon imgae visibility for button state
var set_icon_for_state = function( $btn, state ) {
if (state === "on") {
$btn.find("span.uncheck").hide();
$btn.find("span.check").show();
} else {
$btn.find("span.check").hide();
$btn.find("span.uncheck").show();
}
};
// Set state for single button
var set_btn_state = function($btn, state){
$btn.data('state', state);
set_icon_for_state($btn, state);
}
// Change state for single button
var change_btn_state = function($btn){
var state = $btn.data("state");
if (state === "on") {
state = "off"
} else {
state = "on"
}
set_btn_state($btn, state);
};
// Run show/hide when click on button
$(".btn-shift-column").on("click", function(event){
var $btn = $(this);
event.stopPropagation();
change_btn_state($btn);
shift_column($btn);
save_btn_state($btn);
});
// Load saved states for all tables
load_state_for_all_containters();
// show or hide columns based on data from web storage
shift_columns();
// Add API method for retrieving non-visible cols for table
// Pass the 0-based index of the table or leave the parameter
// empty to return the hidden cols for the 1st table found
$.django_tables2_column_shifter_hidden = function(idx) {
if(idx==undefined) {
idx = 0;
}
return $('.table-container').eq(idx).find('.btn-shift-column').filter(function(z) {
return $(this).data('state')=='off'
}).map(function(z) {
return $(this).data('td-class')
}).toArray();
}
});

2
core/static/js/jquery.min.js vendored Normal file

File diff suppressed because one or more lines are too long

View File

@ -21,6 +21,20 @@
<script defer src="{% static 'js/remove-me.js' %}" integrity="sha384-6fHcFNoQ8QEI3ZDgw9Z/A6Brk64gF7AnFbLgdrumo8/kBbsKQ/wo7wPegj5WkzuG" crossorigin="anonymous"></script> <script defer src="{% static 'js/remove-me.js' %}" integrity="sha384-6fHcFNoQ8QEI3ZDgw9Z/A6Brk64gF7AnFbLgdrumo8/kBbsKQ/wo7wPegj5WkzuG" crossorigin="anonymous"></script>
<script defer src="{% static 'js/hyperscript.min.js' %}" integrity="sha384-6GYN8BDHOJkkru6zcpGOUa//1mn+5iZ/MyT6mq34WFIpuOeLF52kSi721q0SsYF9" crossorigin="anonymous"></script> <script defer src="{% static 'js/hyperscript.min.js' %}" integrity="sha384-6GYN8BDHOJkkru6zcpGOUa//1mn+5iZ/MyT6mq34WFIpuOeLF52kSi721q0SsYF9" crossorigin="anonymous"></script>
<script src="{% static 'js/bulma-tagsinput.min.js' %}" integrity="sha384-GmnKCsPJIPPZbNVXpkGRmKdxOa0PQLnOM/hQLIHvMRERySuyvFqKGc76iHTGUY+d" crossorigin="anonymous"></script> <script src="{% static 'js/bulma-tagsinput.min.js' %}" integrity="sha384-GmnKCsPJIPPZbNVXpkGRmKdxOa0PQLnOM/hQLIHvMRERySuyvFqKGc76iHTGUY+d" crossorigin="anonymous"></script>
<script src="{% static 'js/jquery.min.js' %}"></script>
<script>
document.addEventListener("restore-scroll", function(event) {
var scrollpos = localStorage.getItem('scrollpos');
if (scrollpos) {
window.scrollTo(0, scrollpos)
};
});
document.addEventListener("htmx:beforeSwap", function(event) {
localStorage.setItem('scrollpos', window.scrollY);
});
</script>
<script> <script>
document.addEventListener('DOMContentLoaded', () => { document.addEventListener('DOMContentLoaded', () => {
@ -72,8 +86,10 @@
.htmx-request.htmx-indicator{ .htmx-request.htmx-indicator{
opacity:1 opacity:1
} }
.dropdown-content {
height: 20em;
overflow: auto;
}
</style> </style>
</head> </head>
<body> <body>

View File

@ -3,6 +3,7 @@
{% load joinsep %} {% load joinsep %}
{% include 'partials/notify.html' %} {% include 'partials/notify.html' %}
{% if table %} {% if table %}
<script src="{% static 'js/column-shifter.js' %}"></script>
<div style="display: none" id="jsonData" data-json="{{ data }}"> <div style="display: none" id="jsonData" data-json="{{ data }}">
</div> </div>
@ -31,11 +32,7 @@
</div> </div>
<script src="{% static 'chart.js' %}"></script> <script src="{% static 'chart.js' %}"></script>
</div> </div>
<div class="box">
<div class="table-container">
{% include 'ui/drilldown/table_results_partial.html' %} {% include 'ui/drilldown/table_results_partial.html' %}
</div>
</div>
{% endif %} {% endif %}
{# Update the tags in case the user changed the query #} {# Update the tags in case the user changed the query #}
{# Check for focus and refocus #} {# Check for focus and refocus #}

View File

@ -1,10 +1,58 @@
{% load django_tables2 %} {% load django_tables2 %}
{% load i18n %} {% load i18n %}
{% load django_tables2_bulma_template %} {% load django_tables2_bulma_template %}
{% load static %}
{% block table-wrapper %} {% block table-wrapper %}
<div class="container"> <div id="drilldown-table" class="container box column-shifter-container" style="position:relative; z-index:1;">
{% block table %} {% block table %}
<div class="nowrap-parent">
<div class="nowrap-child">
<div class="dropdown" id="dropdown">
<div class="dropdown-trigger">
<button id="dropdown-trigger" class="button dropdown-toggle" aria-haspopup="true" aria-controls="dropdown-menu">
<span>Show/hide fields</span>
<span class="icon is-small">
<i class="fas fa-angle-down" aria-hidden="true"></i>
</span>
</button>
</div>
<div class="dropdown-menu" id="dropdown-menu" role="menu">
<div class="dropdown-content" style="position:absolute; z-index:10;">
{% for column in table.columns %}
{% if column.name in show %}
<a class="btn-shift-column dropdown-item"
data-td-class="{{ column.name }}"
data-state="on"
{% if not forloop.last %} style="border-bottom:1px solid #ccc;" {%endif %}
data-table-class-container="drilldown-table">
<span class="check icon" data-tooltip="Visible" style="display:none;">
<i class="fa-solid fa-check"></i>
</span>
<span class="uncheck icon" data-tooltip="Hidden" style="display:none;">
<i class="fa-solid fa-xmark"></i>
</span>
{{ column.header }}
</a>
{% endif %}
{% endfor %}
</div>
</div>
</div>
</div>
<div class="nowrap-child">
<span id="loader" class="button is-light has-text-link is-loading">Static</span>
</div>
</div>
<script>
var dropdown_button = document.getElementById("dropdown-trigger");
var dropdown = document.getElementById("dropdown");
dropdown_button.addEventListener('click', function(e) {
// elements[i].preventDefault();
dropdown.classList.toggle('is-active');
});
</script>
<div class="table-container" style="display:none;">
<table {% render_attrs table.attrs class="table is-striped is-hoverable is-fullwidth" %}> <table {% render_attrs table.attrs class="table is-striped is-hoverable is-fullwidth" %}>
{% block table.thead %} {% block table.thead %}
{% if table.show_header %} {% if table.show_header %}
@ -12,10 +60,12 @@
{% block table.thead.row %} {% block table.thead.row %}
<tr> <tr>
{% for column in table.columns %} {% for column in table.columns %}
{% if column.name in show and not column.name in hide %} {% if column.name in show and not column.name in hide %}
{% block table.thead.th %} {% block table.thead.th %}
<th {% render_attrs column.attrs.th class="" %}> <th class="orderable {{ column.name }}"
{% if column.name in invisible %}
style="display:none;"
{% endif %}>
<div class="nowrap-parent"> <div class="nowrap-parent">
{% if column.orderable %} {% if column.orderable %}
<div class="nowrap-child"> <div class="nowrap-child">
@ -37,7 +87,7 @@
hx-trigger="click" hx-trigger="click"
hx-target="#results" hx-target="#results"
hx-swap="innerHTML" hx-swap="innerHTML"
hx-indicator=".progress" hx-indicator="#spinner"
style="cursor: pointer;"> style="cursor: pointer;">
{{ column.header }} {{ column.header }}
</a> </a>
@ -62,30 +112,22 @@
<tbody {{ table.attrs.tbody.as_html }}> <tbody {{ table.attrs.tbody.as_html }}>
{% for row in table.paginated_rows %} {% for row in table.paginated_rows %}
{% block table.tbody.row %} {% block table.tbody.row %}
<tr class="{% if row.cells.exemption == True %} <tr class="{% if row.cells.exemption == True %}has-background-grey-lighter{% elif row.cells.type == 'join' %}has-background-success-light{% elif row.cells.type == 'quit' %}has-background-danger-light{% elif row.cells.type == 'kick' %}has-background-danger-light{% elif row.cells.type == 'part' %}has-background-warning-light{% elif row.cells.type == 'mode' %}has-background-info-light{% endif %}">
has-background-grey-lighter
{% elif row.cells.type == 'join' %}
has-background-success-light
{% elif row.cells.type == 'quit' %}
has-background-danger-light
{% elif row.cells.type == 'kick' %}
has-background-danger-light
{% elif row.cells.type == 'part' %}
has-background-warning-light
{% elif row.cells.type == 'mode' %}
has-background-info-light
{% endif %}">
{% for column, cell in row.items %} {% for column, cell in row.items %}
{% if column.name in show and not column.name in hide %} {% if column.name in show and not column.name in hide %}
{% block table.tbody.td %} {% block table.tbody.td %}
{% if cell == '—' %} {% if column.name in invisible %}
<td> <td class="{{ column.name }}" style="display:none;">
{{ cell }}
</td>
{% elif cell == '—' %}
<td class="{{ column.name }}">
<span class="icon"> <span class="icon">
<i class="fa-solid fa-file-slash"></i> <i class="fa-solid fa-file-slash"></i>
</span> </span>
</td> </td>
{% elif column.name == 'src' %} {% elif column.name == 'src' %}
<td> <td class="{{ column.name }}">
<a class="has-text-link is-underlined" <a class="has-text-link is-underlined"
onclick="populateSearch('src', '{{ cell|escapejs }}')"> onclick="populateSearch('src', '{{ cell|escapejs }}')">
{% if row.cells.src == 'irc' %} {% if row.cells.src == 'irc' %}
@ -100,12 +142,12 @@
</a> </a>
</td> </td>
{% elif column.name == 'ts' %} {% elif column.name == 'ts' %}
<td> <td class="{{ column.name }}">
<p>{{ row.cells.date }}</p> <p>{{ row.cells.date }}</p>
<p>{{ row.cells.time }}</p> <p>{{ row.cells.time }}</p>
</td> </td>
{% elif column.name == 'type' %} {% elif column.name == 'type' %}
<td> <td class="{{ column.name }}">
<a class="has-text-link is-underlined" <a class="has-text-link is-underlined"
onclick="populateSearch('type', '{{ cell|escapejs }}')"> onclick="populateSearch('type', '{{ cell|escapejs }}')">
{% if row.cells.type == 'msg' %} {% if row.cells.type == 'msg' %}
@ -146,16 +188,16 @@
</a> </a>
</td> </td>
{% elif column.name == 'msg' %} {% elif column.name == 'msg' %}
<td style="max-width: 10em" class="wrap">{{ row.cells.msg }}</td> <td class="{{ column.name }}" style="max-width: 10em" class="wrap">{{ row.cells.msg }}</td>
{% elif column.name == 'host' %} {% elif column.name == 'host' %}
<td> <td class="{{ column.name }}">
<a class="has-text-link is-underlined" <a class="has-text-link is-underlined"
onclick="populateSearch('host', '{{ cell|escapejs }}')"> onclick="populateSearch('host', '{{ cell|escapejs }}')">
{{ cell }} {{ cell }}
</a> </a>
</td> </td>
{% elif column.name == 'nick' %} {% elif column.name == 'nick' %}
<td> <td class="{{ column.name }}">
<div class="nowrap-parent"> <div class="nowrap-parent">
<div class="nowrap-child"> <div class="nowrap-child">
{% if row.cells.online is True %} {% if row.cells.online is True %}
@ -198,7 +240,7 @@
</div> </div>
</td> </td>
{% elif column.name == 'channel' %} {% elif column.name == 'channel' %}
<td> <td class="{{ column.name }}">
{% if cell != '—' %} {% if cell != '—' %}
<div class="nowrap-parent"> <div class="nowrap-parent">
<a class="nowrap-child has-text-link is-underlined" <a class="nowrap-child has-text-link is-underlined"
@ -218,36 +260,36 @@
{% endif %} {% endif %}
</td> </td>
{% elif column.name == 'net' %} {% elif column.name == 'net' %}
<td> <td class="{{ column.name }}">
<a class="has-text-link is-underlined" <a class="has-text-link is-underlined"
onclick="populateSearch('net', '{{ cell|escapejs }}')"> onclick="populateSearch('net', '{{ cell|escapejs }}')">
{{ cell }} {{ cell }}
</a> </a>
</td> </td>
{% elif column.name == 'nick_id' %} {% elif column.name == 'nick_id' %}
<td> <td class="{{ column.name }}">
<a class="has-text-link is-underlined" <a class="has-text-link is-underlined"
onclick="populateSearch('nick_id', '{{ cell|escapejs }}')"> onclick="populateSearch('nick_id', '{{ cell|escapejs }}')">
{{ cell }} {{ cell }}
</a> </a>
</td> </td>
{% elif column.name == 'user_id' %} {% elif column.name == 'user_id' %}
<td> <td class="{{ column.name }}">
<a class="has-text-link is-underlined" <a class="has-text-link is-underlined"
onclick="populateSearch('user_id', '{{ cell|escapejs }}')"> onclick="populateSearch('user_id', '{{ cell|escapejs }}')">
{{ cell }} {{ cell }}
</a> </a>
</td> </td>
{% elif column.name == 'channel_id' %} {% elif column.name == 'channel_id' %}
<td> <td class="{{ column.name }}">
<a class="has-text-link is-underlined" <a class="has-text-link is-underlined"
onclick="populateSearch('channel_id', '{{ cell|escapejs }}')"> onclick="populateSearch('channel_id', '{{ cell|escapejs }}')">
{{ cell }} {{ cell }}
</a> </a>
</td> </td>
{% elif cell == True or cell == False %} {% elif cell is True or cell is False %}
<td> <td class="{{ column.name }}">
{% if cell == True %} {% if cell is True %}
<span class="icon has-text-success"> <span class="icon has-text-success">
<i class="fa-solid fa-check"></i> <i class="fa-solid fa-check"></i>
</span> </span>
@ -258,7 +300,7 @@
{% endif %} {% endif %}
</td> </td>
{% else %} {% else %}
<td> <td class="{{ column.name }}">
{{ cell }} {{ cell }}
</td> </td>
{% endif %} {% endif %}
@ -270,7 +312,7 @@
{% empty %} {% empty %}
{% if table.empty_text %} {% if table.empty_text %}
{% block table.tbody.empty_text %} {% block table.tbody.empty_text %}
<tr><td colspan="{{ table.columns|length }}">{{ table.empty_text }}</td></tr> <tr><td class="{{ column.name }}" colspan="{{ table.columns|length }}">{{ table.empty_text }}</td></tr>
{% endblock table.tbody.empty_text %} {% endblock table.tbody.empty_text %}
{% endif %} {% endif %}
{% endfor %} {% endfor %}
@ -283,7 +325,7 @@
<tr> <tr>
{% for column in table.columns %} {% for column in table.columns %}
{% block table.tfoot.td %} {% block table.tfoot.td %}
<td {{ column.attrs.tf.as_html }}>{{ column.footer }}</td> <td class="{{ column.name }}" {{ column.attrs.tf.as_html }}>{{ column.footer }}</td>
{% endblock table.tfoot.td %} {% endblock table.tfoot.td %}
{% endfor %} {% endfor %}
</tr> </tr>
@ -292,6 +334,7 @@
{% endif %} {% endif %}
{% endblock table.tfoot %} {% endblock table.tfoot %}
</table> </table>
</div>
{% endblock table %} {% endblock table %}
{% block pagination %} {% block pagination %}
{% if table.page and table.paginator.num_pages > 1 %} {% if table.page and table.paginator.num_pages > 1 %}
@ -305,7 +348,7 @@
hx-trigger="click" hx-trigger="click"
hx-target="#results" hx-target="#results"
hx-swap="innerHTML" hx-swap="innerHTML"
hx-indicator=".progress" hx-indicator="#spinner"
{% else %} {% else %}
href="#" href="#"
disabled disabled
@ -317,14 +360,15 @@
</a> </a>
{% endblock pagination.previous %} {% endblock pagination.previous %}
{% block pagination.next %} {% block pagination.next %}
<a class="pagination-next is-flex-grow-0 {% if not table.page.has_next %}is-hidden-mobile{% endif %}" <a
class="pagination-next is-flex-grow-0 {% if not table.page.has_next %}is-hidden-mobile{% endif %}"
{% if table.page.has_next %} {% if table.page.has_next %}
hx-get="search/{% querystring table.prefixed_page_field=table.page.next_page_number %}&{{ uri }}" hx-get="search/{% querystring table.prefixed_page_field=table.page.next_page_number %}&{{ uri }}"
hx-headers='{"X-CSRFToken": "{{ csrf_token }}"}' hx-headers='{"X-CSRFToken": "{{ csrf_token }}"}'
hx-trigger="click" hx-trigger="click"
hx-target="#results" hx-target="#results"
hx-swap="innerHTML" hx-swap="innerHTML"
hx-indicator=".progress" hx-indicator="#spinner"
{% else %} {% else %}
href="#" href="#"
disabled disabled
@ -341,7 +385,8 @@
<ul class="pagination-list is-flex-grow-0" style="order:2;"> <ul class="pagination-list is-flex-grow-0" style="order:2;">
{% for p in table.page|table_page_range:table.paginator %} {% for p in table.page|table_page_range:table.paginator %}
<li> <li>
<a class="pagination-link {% if p == table.page.number %}is-current{% endif %}" <a
class="pagination-link {% if p == table.page.number %}is-current{% endif %}"
aria-label="Page {{ p }}" block aria-label="Page {{ p }}" block
{% if p == table.page.number %}aria-current="page"{% endif %} {% if p == table.page.number %}aria-current="page"{% endif %}
{% if p == table.page.number %} {% if p == table.page.number %}
@ -352,7 +397,7 @@
hx-trigger="click" hx-trigger="click"
hx-target="#results" hx-target="#results"
hx-swap="innerHTML" hx-swap="innerHTML"
hx-indicator=".progress" hx-indicator="#spinner"
{% endif %} {% endif %}
> >
{% if p == '...' %} {% if p == '...' %}

View File

@ -205,7 +205,7 @@ class DrilldownTableView(SingleTableView):
for k, v in self.context.items(): for k, v in self.context.items():
if k not in context: if k not in context:
context[k] = v context[k] = v
context["hide"] = [ context["invisible"] = [
"date", "date",
"time", "time",
"id", "id",
@ -215,6 +215,7 @@ class DrilldownTableView(SingleTableView):
"num_chans", "num_chans",
"exemption", "exemption",
"version_sentiment", "version_sentiment",
"num",
] ]
context["show"] = show context["show"] = show
if request.method == "GET": if request.method == "GET":