Merge branch 'bs5-datatable-filterdropdown' into 'v4.x'

Re-enable filter dropdown for Datatables

See merge request allianceauth/allianceauth!1590
This commit is contained in:
Ariel Rin 2024-02-15 01:56:21 +00:00
commit 2a762df9b3
5 changed files with 222 additions and 162 deletions

View File

@ -71,7 +71,7 @@
{% block extra_javascript %} {% block extra_javascript %}
{% include 'bundles/datatables-js-bs5.html' %} {% include 'bundles/datatables-js-bs5.html' %}
{% include 'bundles/moment-js.html' with locale=True %} {% include 'bundles/moment-js.html' with locale=True %}
{# {% include 'bundles/filterdropdown-js.html' %}#} {% include 'bundles/filterdropdown-js.html' %}
<script> <script>
$.fn.dataTable.moment = (format, locale) => { $.fn.dataTable.moment = (format, locale) => {
@ -117,7 +117,8 @@
idx: 6 idx: 6
} }
], ],
bootstrap: true bootstrap: true,
bootstrap_version: 5
}, },
"stateSave": true, "stateSave": true,
"stateDuration": 0 "stateDuration": 0

View File

@ -55,7 +55,7 @@
{% endblock content %} {% endblock content %}
{% block extra_javascript %} {% block extra_javascript %}
{% include "bundles/datatables-js-bs5.html" %} {% include "bundles/datatables-js-bs5.html" %}
{# {% include "bundles/filterdropdown-js.html" %}#} {% include "bundles/filterdropdown-js.html" %}
<script> <script>
$(document).ready(() => { $(document).ready(() => {
@ -75,7 +75,8 @@
idx: 0, idx: 0,
title: 'Source' title: 'Source'
}], }],
bootstrap: true bootstrap: true,
bootstrap_version: 5
}, },
"stateSave": true, "stateSave": true,
"stateDuration": 0, "stateDuration": 0,

View File

@ -60,7 +60,7 @@
{% block extra_javascript %} {% block extra_javascript %}
{% include "bundles/datatables-js-bs5.html" %} {% include "bundles/datatables-js-bs5.html" %}
{# {% include "bundles/filterdropdown-js.html" %}#} {% include "bundles/filterdropdown-js.html" %}
<script> <script>
$(document).ready(() => { $(document).ready(() => {
@ -86,6 +86,7 @@
} }
], ],
bootstrap: true, bootstrap: true,
bootstrap_version: 5
}, },
"stateSave": true, "stateSave": true,
"stateDuration": 0, "stateDuration": 0,

View File

@ -1,239 +1,296 @@
/* /**
* filterDropDown.js * filterDropDown.js
* *
* Copyright (C) 2017-18 Erik Kalkoken * Copyright (C) 2017-24 Erik Kalkoken
* *
* Extension for the jQuery plug-in DataTables (developed and tested with v1.10.15) * Extension for the jQuery plug-in DataTables (developed and tested with v1.13.7)
* *
* Version 0.4.0 * Version 0.5.0
* **/
**/ (($) => {
"use strict";
(function ($) { /**
* Parse initialization array and returns filterDef array to faster and easy use,
// parse initialization array and returns filterDef array to faster and easy use * also sets defaults for properties that are not set
// also sets defaults for properties that are not set *
function parseInitArray(initArray) { * @param initArray
// initialization and setting defaults * @returns {{autoSize: boolean, bootstrap_version: number, columnsIdxList: *[], columns: *[], bootstrap: boolean, label: string, ajax: null}}
let filterDef = { */
"columns": [], const parseInitArray = (initArray) => {
"columnsIdxList": [], /**
"bootstrap": false, * Default filter definition
"autoSize": true, *
"ajax": null, * @type {{autoSize: boolean, bootstrap_version: number, columnsIdxList: *[], columns: *[], bootstrap: boolean, label: string, ajax: null}}
"label": "Filter " */
const filterDef = {
columns: [],
columnsIdxList: [],
bootstrap: false,
bootstrap_version: 3,
autoSize: true,
ajax: null,
label: "Filter ",
}; };
// set filter properties if they have been defined // Set filter properties if they have been defined otherwise the defaults will be used
// otherwise the defaults will be used if (
if (("bootstrap" in initArray) && (typeof initArray.bootstrap === 'boolean')) { "bootstrap" in initArray &&
typeof initArray.bootstrap === "boolean"
) {
filterDef.bootstrap = initArray.bootstrap; filterDef.bootstrap = initArray.bootstrap;
} }
if (("autoSize" in initArray) && (typeof initArray.autoSize === 'boolean')) { if (
"bootstrap_version" in initArray &&
typeof initArray.bootstrap_version === "number"
) {
filterDef.bootstrap_version = initArray.bootstrap_version;
}
if (
"autoSize" in initArray &&
typeof initArray.autoSize === "boolean"
) {
filterDef.autoSize = initArray.autoSize; filterDef.autoSize = initArray.autoSize;
} }
if (("ajax" in initArray) && (typeof initArray.ajax === 'string')) { if ("ajax" in initArray && typeof initArray.ajax === "string") {
filterDef.ajax = initArray.ajax; filterDef.ajax = initArray.ajax;
} }
if (("label" in initArray) && (typeof initArray.label === 'string')) { if ("label" in initArray && typeof initArray.label === "string") {
filterDef.label = initArray.label; filterDef.label = initArray.label;
} }
// add definition for each column // Add definition for each column
if ("columns" in initArray) { if ("columns" in initArray) {
initArray.columns.forEach(function (initColumn) { initArray.columns.forEach((initColumn) => {
if (("idx" in initColumn) && (typeof initColumn.idx === 'number')) { if ("idx" in initColumn && typeof initColumn.idx === "number") {
// initialize column // Initialize column
let idx = initColumn.idx; const idx = initColumn.idx;
filterDef['columns'][idx] = {
"title": null, filterDef.columns[idx] = {
"maxWidth": null, title: null,
"autoSize": true maxWidth: null,
autoSize: true,
}; };
// add to list of indices in same order they appear in the init array // Add to a list of indices in the same order they appear in the init array
filterDef['columnsIdxList'].push(idx); filterDef.columnsIdxList.push(idx);
// set column properties if they have been defined // Set column properties if they have been defined otherwise the defaults will be used
// otherwise the defaults will be used if (
if (('title' in initColumn) "title" in initColumn &&
&& (typeof initColumn.title === 'string') typeof initColumn.title === "string"
) { ) {
filterDef['columns'][idx].title = initColumn.title; filterDef.columns[idx].title = initColumn.title;
} }
if (('maxWidth' in initColumn) if (
&& (typeof initColumn.maxWidth === 'string') "maxWidth" in initColumn &&
typeof initColumn.maxWidth === "string"
) { ) {
filterDef['columns'][idx].maxWidth = initColumn.maxWidth; filterDef.columns[idx].maxWidth = initColumn.maxWidth;
} }
if (('autoSize' in initColumn) if (
&& (typeof initColumn.autoSize === 'boolean') "autoSize" in initColumn &&
typeof initColumn.autoSize === "boolean"
) { ) {
filterDef['columns'][idx].autoSize = initColumn.autoSize; filterDef.columns[idx].autoSize = initColumn.autoSize;
} }
} }
}); });
} }
return filterDef; return filterDef;
} };
// Add option d to given select object /**
function addOption(select, d) { * Add option d to the given select object
if (d != "") { *
select.append('<option value="' + d + '">' + d + '</option>'); * @param select
} * @param d
*/
const addOption = (select, d) => {
if (d !== "") {
select.append(`<option value="${d}">${d}</option>`);
} }
};
// initalizing select for current column and applying event to react to changes /**
function initSelectForColumn(id, column) { * Initialize the select element for given column and apply event to react to changes
let select = $("#" + id + "_filterSelect" + column.index()); *
select.on('change', function () { * @param id
let val = $.fn.dataTable.util.escapeRegex($(this).val()); * @param column
column * @returns {*|jQuery|HTMLElement}
.search(val ? '^' + val + '$' : '', true, false) */
.draw(); const initSelectForColumn = (id, column) => {
const select = $(`#${id}_filterSelect${column.index()}`);
$(select).change(() => {
const val = $.fn.dataTable.util.escapeRegex($(select).val());
column.search(val ? `^${val}$` : "", true, false).draw();
}); });
return select
return select;
};
// Add filterDropDown container div, draw select elements with default options.
// Use preInit so that elements are created and correctly shown before data is loaded
$(document).on("preInit.dt", (e, settings) => {
if (e.namespace !== "dt") {
return;
} }
// Add filterDropDown container div, draw select elements with default options // Get the api object for the current dt table
// use preInit so that elements are created and correctly shown before data is loaded const api = new $.fn.dataTable.Api(settings);
$(document).on('preInit.dt', function (e, settings) {
if (e.namespace !== 'dt') return;
// get api object for current dt table // Get the id of the current table
var api = new $.fn.dataTable.Api(settings); const id = api.table().node().id;
// get id of current table // Get the initialization object for the current table to retrieve custom settings
var id = api.table().node().id; const initObj = api.init();
// get initialization object for current table to retrieve custom settings // Only proceed if the filter has been defined in the current table,
var initObj = api.init(); // otherwise don't do anything.
if (!("filterDropDown" in initObj)) {
return;
}
// only proceed if filter has been defined in current table, otherwise don't do anything. // Get the current filter definition from the init array
if (!("filterDropDown" in initObj)) return; const filterDef = parseInitArray(initObj.filterDropDown);
// get current filter definition from init array
var filterDef = parseInitArray(initObj.filterDropDown);
// only proceed if there are any columns defined // only proceed if there are any columns defined
if (filterDef.columns.length == 0) return; if (filterDef.columns.length === 0) {
return;
}
// get container div for current data table to add new elements to // Get container div for the current data table to add new elements to
var container = api.table().container(); const container = api.table().container();
// Add filter elements to DOM
const filterWrapperId = `${id}_filterWrapper`;
// Set CSS classes for the filter wrapper div depending on bootstrap setting
let divCssClass = `${filterWrapperId} ${
filterDef.bootstrap ? "form-inline" : ""
}`;
if (filterDef.bootstrap && filterDef.bootstrap_version === 5) {
divCssClass = `${filterWrapperId} input-group my-3`;
}
// add filter elements to DOM
var filterWrapperId = id + "_filterWrapper";
var divCssClass = filterWrapperId + " " + (
(filterDef.bootstrap)
? "form-inline"
: ""
);
$(container).prepend( $(container).prepend(
'<div id="' `<div id="${filterWrapperId}" class="${divCssClass}"><span class="pt-2">${filterDef.label}</span></div>`
+ filterWrapperId
+ '" class="'
+ divCssClass + '">'
+ filterDef.label
+ '</div>'
); );
api.columns(filterDef.columnsIdxList).every(function () { api.columns(filterDef.columnsIdxList).every(function () {
let idx = this.index(); const idx = this.index();
// set title of current column // set title of current column
let colName = (filterDef.columns[idx].title !== null) let colName =
filterDef.columns[idx].title !== null
? filterDef.columns[idx].title ? filterDef.columns[idx].title
: $(this.header()).html(); : $(this.header()).html();
if (colName == "") colName = 'column ' + (idx + 1); if (colName === "") {
colName = `column ${idx + 1}`;
// adding select element for current column to container
let selectId = id + "_filterSelect" + idx;
$('#' + filterWrapperId).append(
'<select id="'
+ selectId
+ '" class="form-control '
+ id
+ '_filterSelect"></select>'
);
// initalizing select for current column and applying event to react to changes
let select = $("#" + selectId).empty()
.append('<option value="">(' + colName + ')</option>');
// set max width of select elements to current width (which is defined by the size of the title)
// turn off on for very small screens for responsive design or if autoSize has been set to false
if (filterDef.autoSize && filterDef.columns[idx].autoSize && (screen.width > 768)) {
select.css('max-width', select.outerWidth());
} }
// apply optional css style if defined in init array // Adding the select element for current column to container
// will override automatic max width setting const selectId = `${id}_filterSelect${idx}`;
// Set CSS classes for the select element depending on bootstrap setting
let selectMarkup = `<select id="${selectId}" class="form-control ${id}_filterSelect"></select>`;
if (filterDef.bootstrap && filterDef.bootstrap_version === 5) {
selectMarkup = `<select id="${selectId}" class="form-select w-auto ms-2 ${id}_filterSelect"></select>`;
}
$("#" + filterWrapperId).append(selectMarkup);
// Initializing select for current column and applying event to react to changes
const select = $("#" + selectId)
.empty()
.append(`<option value="">(${colName})</option>`);
// Set max width of select elements to current width (which is defined by the size of the title)
// Turn off on for very small screens for responsive design, or if autoSize has been set to false
if (
filterDef.autoSize &&
filterDef.columns[idx].autoSize &&
screen.width > 768
) {
select.css("max-width", select.outerWidth());
}
// Apply optional css style if defined in the init array will override automatic max width setting
if (filterDef.columns[idx].maxWidth !== null) { if (filterDef.columns[idx].maxWidth !== null) {
select.css('max-width', filterDef.columns[idx].maxWidth); select.css("max-width", filterDef.columns[idx].maxWidth);
} }
}); });
}); });
// filter table and add available options to dropDowns // Filter table and add available options to dropDowns
$(document).on('init.dt', function (e, settings) { $(document).on("init.dt", (e, settings) => {
if (e.namespace !== 'dt') return; if (e.namespace !== "dt") {
return;
}
// get api object for current dt table // Get api object for current dt table
var api = new $.fn.dataTable.Api(settings); const api = new $.fn.dataTable.Api(settings);
// get id of current table // Get id of current table
var id = api.table().node().id; const id = api.table().node().id;
// get initialization object for current table to retrieve custom settings // Get the initialization object for current table to retrieve custom settings
var initObj = api.init(); const initObj = api.init();
// only proceed if filter has been defined in current table, otherwise don't do anything. // Only proceed if a filter has been defined in the current table, otherwise don't do anything.
if (!("filterDropDown" in initObj)) return; if (!("filterDropDown" in initObj)) {
return;
}
// get current filter definition // Get current filter definition
var filterDef = parseInitArray(initObj.filterDropDown); const filterDef = parseInitArray(initObj.filterDropDown);
if (filterDef.ajax == null) { if (filterDef.ajax == null) {
api.columns(filterDef.columnsIdxList).every(function () { api.columns(filterDef.columnsIdxList).every(function () {
let column = this const column = this;
let select = initSelectForColumn(id, column); const select = initSelectForColumn(id, column);
column.data().unique().sort().each(function (d) {
addOption(select, d) column
.data()
.unique()
.sort()
.each((d) => {
addOption(select, d);
}); });
}); });
} else { } else {
// fetch column options from server for server side processing // Fetch column options from server for server side processing
let columnsQuery = ( const columnsQuery = `columns=${encodeURIComponent(
"columns="
+ encodeURIComponent(
api.columns(filterDef.columnsIdxList).dataSrc().join() api.columns(filterDef.columnsIdxList).dataSrc().join()
) )}`;
)
$.getJSON(filterDef.ajax + "?" + columnsQuery, function (columnsOptions) { $.getJSON(`${filterDef.ajax}?${columnsQuery}`, (columnsOptions) => {
api.columns(filterDef.columnsIdxList).every(function () { api.columns(filterDef.columnsIdxList).every(function () {
let column = this; const column = this;
let select = initSelectForColumn(id, column); const select = initSelectForColumn(id, column);
let columnName = column.dataSrc() const columnName = column.dataSrc();
if (columnName in columnsOptions) { if (columnName in columnsOptions) {
columnsOptions[columnName].forEach(function (d) { columnsOptions[columnName].forEach((d) => {
addOption(select, d) addOption(select, d);
}); });
} else { } else {
console.warn( console.warn(
"Missing column '" + columnName + "' in ajax response." `Missing column '${columnName}' in ajax response.`
) );
} }
}); });
}); });
} }
}); });
})(jQuery);
}(jQuery));

View File

@ -1 +1 @@
!function(t){function n(t){let n={columns:[],columnsIdxList:[],bootstrap:!1,autoSize:!0,ajax:null,label:"Filter "};return"bootstrap"in t&&"boolean"==typeof t.bootstrap&&(n.bootstrap=t.bootstrap),"autoSize"in t&&"boolean"==typeof t.autoSize&&(n.autoSize=t.autoSize),"ajax"in t&&"string"==typeof t.ajax&&(n.ajax=t.ajax),"label"in t&&"string"==typeof t.label&&(n.label=t.label),"columns"in t&&t.columns.forEach(function(t){if("idx"in t&&"number"==typeof t.idx){let e=t.idx;n.columns[e]={title:null,maxWidth:null,autoSize:!0},n.columnsIdxList.push(e),"title"in t&&"string"==typeof t.title&&(n.columns[e].title=t.title),"maxWidth"in t&&"string"==typeof t.maxWidth&&(n.columns[e].maxWidth=t.maxWidth),"autoSize"in t&&"boolean"==typeof t.autoSize&&(n.columns[e].autoSize=t.autoSize)}}),n}function e(t,n){""!=n&&t.append('<option value="'+n+'">'+n+"</option>")}function i(n,e){let i=t("#"+n+"_filterSelect"+e.index());return i.on("change",function(){let n=t.fn.dataTable.util.escapeRegex(t(this).val());e.search(n?"^"+n+"$":"",!0,!1).draw()}),i}t(document).on("preInit.dt",function(e,i){if("dt"===e.namespace){var o=new t.fn.dataTable.Api(i),l=o.table().node().id,a=o.init();if("filterDropDown"in a){var u=n(a.filterDropDown);if(0!=u.columns.length){var s=o.table().container(),c=l+"_filterWrapper",r=c+" "+(u.bootstrap?"form-inline":"");t(s).prepend('<div id="'+c+'" class="'+r+'">'+u.label+"</div>"),o.columns(u.columnsIdxList).every(function(){let n=this.index(),e=null!==u.columns[n].title?u.columns[n].title:t(this.header()).html();""==e&&(e="column "+(n+1));let i=l+"_filterSelect"+n;t("#"+c).append('<select id="'+i+'" class="form-control '+l+'_filterSelect"></select>');let o=t("#"+i).empty().append('<option value="">('+e+")</option>");u.autoSize&&u.columns[n].autoSize&&screen.width>768&&o.css("max-width",o.outerWidth()),null!==u.columns[n].maxWidth&&o.css("max-width",u.columns[n].maxWidth)})}}}}),t(document).on("init.dt",function(o,l){if("dt"===o.namespace){var a=new t.fn.dataTable.Api(l),u=a.table().node().id,s=a.init();if("filterDropDown"in s){var c=n(s.filterDropDown);if(null==c.ajax)a.columns(c.columnsIdxList).every(function(){let t=i(u,this);this.data().unique().sort().each(function(n){e(t,n)})});else{let n="columns="+encodeURIComponent(a.columns(c.columnsIdxList).dataSrc().join());t.getJSON(c.ajax+"?"+n,function(t){a.columns(c.columnsIdxList).every(function(){let n=i(u,this),o=this.dataSrc();o in t?t[o].forEach(function(t){e(n,t)}):console.warn("Missing column '"+o+"' in ajax response.")})})}}}})}(jQuery); ($=>{"use strict";const parseInitArray=initArray=>{const filterDef={columns:[],columnsIdxList:[],bootstrap:false,bootstrap_version:3,autoSize:true,ajax:null,label:"Filter "};if("bootstrap"in initArray&&typeof initArray.bootstrap==="boolean"){filterDef.bootstrap=initArray.bootstrap}if("bootstrap_version"in initArray&&typeof initArray.bootstrap_version==="number"){filterDef.bootstrap_version=initArray.bootstrap_version}if("autoSize"in initArray&&typeof initArray.autoSize==="boolean"){filterDef.autoSize=initArray.autoSize}if("ajax"in initArray&&typeof initArray.ajax==="string"){filterDef.ajax=initArray.ajax}if("label"in initArray&&typeof initArray.label==="string"){filterDef.label=initArray.label}if("columns"in initArray){initArray.columns.forEach(initColumn=>{if("idx"in initColumn&&typeof initColumn.idx==="number"){const idx=initColumn.idx;filterDef.columns[idx]={title:null,maxWidth:null,autoSize:true};filterDef.columnsIdxList.push(idx);if("title"in initColumn&&typeof initColumn.title==="string"){filterDef.columns[idx].title=initColumn.title}if("maxWidth"in initColumn&&typeof initColumn.maxWidth==="string"){filterDef.columns[idx].maxWidth=initColumn.maxWidth}if("autoSize"in initColumn&&typeof initColumn.autoSize==="boolean"){filterDef.columns[idx].autoSize=initColumn.autoSize}}})}return filterDef};const addOption=(select,d)=>{if(d!==""){select.append(`<option value="${d}">${d}</option>`)}};const initSelectForColumn=(id,column)=>{const select=$(`#${id}_filterSelect${column.index()}`);$(select).change(()=>{const val=$.fn.dataTable.util.escapeRegex($(select).val());column.search(val?`^${val}$`:"",true,false).draw()});return select};$(document).on("preInit.dt",(e,settings)=>{if(e.namespace!=="dt"){return}const api=new $.fn.dataTable.Api(settings);const id=api.table().node().id;const initObj=api.init();if(!("filterDropDown"in initObj)){return}const filterDef=parseInitArray(initObj.filterDropDown);if(filterDef.columns.length===0){return}const container=api.table().container();const filterWrapperId=`${id}_filterWrapper`;let divCssClass=`${filterWrapperId} ${filterDef.bootstrap?"form-inline":""}`;if(filterDef.bootstrap&&filterDef.bootstrap_version===5){divCssClass=`${filterWrapperId} input-group my-3`}$(container).prepend(`<div id="${filterWrapperId}" class="${divCssClass}"><span class="pt-2">${filterDef.label}</span></div>`);api.columns(filterDef.columnsIdxList).every(function(){const idx=this.index();let colName=filterDef.columns[idx].title!==null?filterDef.columns[idx].title:$(this.header()).html();if(colName===""){colName=`column ${idx+1}`}const selectId=`${id}_filterSelect${idx}`;let selectMarkup=`<select id="${selectId}" class="form-control ${id}_filterSelect"></select>`;if(filterDef.bootstrap&&filterDef.bootstrap_version===5){selectMarkup=`<select id="${selectId}" class="form-select w-auto ms-2 ${id}_filterSelect"></select>`}$("#"+filterWrapperId).append(selectMarkup);const select=$("#"+selectId).empty().append(`<option value="">(${colName})</option>`);if(filterDef.autoSize&&filterDef.columns[idx].autoSize&&screen.width>768){select.css("max-width",select.outerWidth())}if(filterDef.columns[idx].maxWidth!==null){select.css("max-width",filterDef.columns[idx].maxWidth)}})});$(document).on("init.dt",(e,settings)=>{if(e.namespace!=="dt"){return}const api=new $.fn.dataTable.Api(settings);const id=api.table().node().id;const initObj=api.init();if(!("filterDropDown"in initObj)){return}const filterDef=parseInitArray(initObj.filterDropDown);if(filterDef.ajax==null){api.columns(filterDef.columnsIdxList).every(function(){const column=this;const select=initSelectForColumn(id,column);column.data().unique().sort().each(d=>{addOption(select,d)})})}else{const columnsQuery=`columns=${encodeURIComponent(api.columns(filterDef.columnsIdxList).dataSrc().join())}`;$.getJSON(`${filterDef.ajax}?${columnsQuery}`,columnsOptions=>{api.columns(filterDef.columnsIdxList).every(function(){const column=this;const select=initSelectForColumn(id,column);const columnName=column.dataSrc();if(columnName in columnsOptions){columnsOptions[columnName].forEach(d=>{addOption(select,d)})}else{console.warn(`Missing column '${columnName}' in ajax response.`)}})})}})})(jQuery);