mirror of
https://github.com/bunkerity/bunkerweb
synced 2026-05-24 09:28:37 +00:00
isomorphic filters + update fields + better style
* create isomorphic tabulator filters but getting the right data based on the component object available keys * create an extendTabulator function for formatters and filters overrides * add fields to filter table on component + udpate fields to emit all field input * add css style for stripes table and some hover fix * enhance filter logic to work with tabulator flaws (impossible to add one filter at a time and impossible to filter multiple fields with one filter by default, added) + avoid filters on some cases (no value or select with value "all", ...)
This commit is contained in:
parent
98fc174a22
commit
d7b512c7a1
7 changed files with 306 additions and 24 deletions
|
|
@ -38,10 +38,14 @@ const props = defineProps({
|
|||
default: {},
|
||||
},
|
||||
});
|
||||
|
||||
// emits
|
||||
const emit = defineEmits(["inp"]);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Checkbox
|
||||
@inp="(value) => $emit('inp', value)"
|
||||
v-if="props.setting.inpType === 'checkbox'"
|
||||
:id="props.setting.id || ''"
|
||||
:columns="props.setting.columns || false"
|
||||
|
|
@ -60,6 +64,7 @@ const props = defineProps({
|
|||
:attrs="props.setting.attrs || {}"
|
||||
/>
|
||||
<Select
|
||||
@inp="(value) => $emit('inp', value)"
|
||||
v-if="props.setting.inpType === 'select'"
|
||||
:id="props.setting.id || ''"
|
||||
:columns="props.setting.columns || false"
|
||||
|
|
@ -83,6 +88,7 @@ const props = defineProps({
|
|||
:attrs="props.setting.attrs || {}"
|
||||
/>
|
||||
<Datepicker
|
||||
@inp="(value) => $emit('inp', value)"
|
||||
v-if="props.setting.inpType === 'datepicker'"
|
||||
:id="props.setting.id || ''"
|
||||
:columns="props.setting.columns || false"
|
||||
|
|
@ -104,6 +110,7 @@ const props = defineProps({
|
|||
:attrs="props.setting.attrs || {}"
|
||||
/>
|
||||
<Input
|
||||
@inp="(value) => $emit('inp', value)"
|
||||
v-if="props.setting.inpType === 'input'"
|
||||
:id="props.setting.id || ''"
|
||||
:columns="props.setting.columns || false"
|
||||
|
|
@ -127,6 +134,7 @@ const props = defineProps({
|
|||
:attrs="props.setting.attrs || {}"
|
||||
/>
|
||||
<Editor
|
||||
@inp="(value) => $emit('inp', value)"
|
||||
v-if="props.setting.inpType === 'editor'"
|
||||
:id="props.setting.id || ''"
|
||||
:columns="props.setting.columns || false"
|
||||
|
|
|
|||
|
|
@ -40,7 +40,7 @@ import { useUUID } from "@utils/global.js";
|
|||
* ],
|
||||
* }
|
||||
* @param {string} [id=uuidv4()] - Unique id
|
||||
* @param {string} type - text, email, password, number, tel, url
|
||||
* @param {string} [type="text"] - text, email, password, number, tel, url
|
||||
* @param {string} label - The label of the field. Can be a translation key or by default raw text.
|
||||
* @param {string} name - The name of the field. Case no label, this is the fallback. Can be a translation key or by default raw text.* @param {string} label
|
||||
* @param {string} value
|
||||
|
|
@ -79,7 +79,8 @@ const props = defineProps({
|
|||
},
|
||||
type: {
|
||||
type: String,
|
||||
required: true,
|
||||
required: false,
|
||||
default: "text",
|
||||
},
|
||||
attrs: {
|
||||
type: Object,
|
||||
|
|
@ -224,7 +225,7 @@ onMounted(() => {
|
|||
props.placeholder
|
||||
? $t(
|
||||
props.placeholder,
|
||||
$t('dashboard_placeholder', props.placeholder),
|
||||
$t('dashboard_placeholder', props.placeholder)
|
||||
)
|
||||
: ''
|
||||
"
|
||||
|
|
|
|||
|
|
@ -5,12 +5,15 @@ import Text from "@components/Widget/Text.vue";
|
|||
import Fields from "@components/Form/Fields.vue";
|
||||
import Button from "@components/Widget/Button.vue";
|
||||
import ButtonGroup from "@components/Widget/ButtonGroup.vue";
|
||||
import Container from "@components/Widget/Container.vue";
|
||||
import { TabulatorFull as Tabulator } from "tabulator-tables"; //import Tabulator library
|
||||
import { useEqualStr } from "@utils/global.js";
|
||||
import {
|
||||
addColumnsSorter,
|
||||
addColumnsWidth,
|
||||
a18yTable,
|
||||
applyTableFilter,
|
||||
overrideDefaultFilters,
|
||||
} from "@utils/tabulator.js";
|
||||
import { useTableStore } from "@store/global.js";
|
||||
|
||||
|
|
@ -26,6 +29,16 @@ const props = defineProps({
|
|||
required: true,
|
||||
default: "table-component",
|
||||
},
|
||||
isStriped: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: true,
|
||||
},
|
||||
filters: {
|
||||
type: Array,
|
||||
required: false,
|
||||
default: [],
|
||||
},
|
||||
columns: {
|
||||
type: Array,
|
||||
required: true,
|
||||
|
|
@ -78,6 +91,7 @@ const tableEl = ref(null); //reference to your table element
|
|||
const table = reactive({
|
||||
test: true,
|
||||
instance: null,
|
||||
filters: {},
|
||||
columns: props.columns,
|
||||
items: props.items,
|
||||
customComponents: [],
|
||||
|
|
@ -144,13 +158,15 @@ function addCustomComponent(type, values, elDOM) {
|
|||
}
|
||||
|
||||
/**
|
||||
* @name addComponentsFormats
|
||||
* @description Add all custom components on a list to later add them to each tabulator cell.
|
||||
* We are using the Tabulart.extendModule() that allow use to execute a custom function when we are matching a custom formatter.
|
||||
* We need to define on rows the formatter that we want to use to render the custom component.
|
||||
* @name extendTabulator
|
||||
* @description Wrapper that will do some extend or override on the Tabulator instance:
|
||||
* 1 - Add custom components to a list in order to render them and teleport them when formatting the cell.
|
||||
* 2 - Add custom formatters for each custom components in order to force Tabulator to render empty string.
|
||||
* 3 - Override default filters to add custom filters for each custom components (because we need to access a specific key in the props object).
|
||||
* We are using the Tabular.extendModule() that allow use to do this.
|
||||
* @returns {void}
|
||||
*/
|
||||
function addComponentsFormats() {
|
||||
function extendTabulator() {
|
||||
const formatOpts = {};
|
||||
for (let i = 0; i < customComponents.length; i++) {
|
||||
const module = customComponents[i];
|
||||
|
|
@ -160,10 +176,27 @@ function addComponentsFormats() {
|
|||
};
|
||||
}
|
||||
Tabulator.extendModule("format", "formatters", formatOpts);
|
||||
Tabulator.extendModule("filter", "filters", overrideDefaultFilters());
|
||||
}
|
||||
|
||||
/**
|
||||
* @name filterTable
|
||||
* @description We can't directly send the current filter input to filter the table because the Tabulator will filter everything at once.
|
||||
* So we need to get the value and store on the table.filters dict to merge all filters and apply them at once.
|
||||
* We will use the applyTableFilter() to apply the filters. Additionnal checks (like empty value) are done on the applyTableFilter() function.
|
||||
* @param {object} tableInstance - The tableInstance is the current table instance.
|
||||
* @param {object} filter - the filter dict is here the setting filter data
|
||||
* @param {string} value - the value is the current value return by the filter input.
|
||||
* @returns {void}
|
||||
*/
|
||||
function filterTable(filter, value = "") {
|
||||
// Merge all filters
|
||||
table.filters[filter.setting.id] = { ...filter, value: value };
|
||||
applyTableFilter(table.instance, table.filters);
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
addComponentsFormats();
|
||||
extendTabulator();
|
||||
table.instance = new Tabulator(tableEl.value, table.options);
|
||||
table.instance.on("tableBuilt", () => {
|
||||
table.instance.redraw();
|
||||
|
|
@ -180,7 +213,19 @@ onUnmounted(() => {
|
|||
</script>
|
||||
|
||||
<template>
|
||||
<div data-is="table" ref="tableEl"></div>
|
||||
<Container :containerClass="'layout-settings'">
|
||||
<template v-for="filter in props.filters">
|
||||
<Fields
|
||||
:setting="filter.setting"
|
||||
@inp="(value) => filterTable(filter, value)"
|
||||
/>
|
||||
</template>
|
||||
</Container>
|
||||
<div
|
||||
:class="[props.isStriped ? 'striped' : '']"
|
||||
data-is="table"
|
||||
ref="tableEl"
|
||||
></div>
|
||||
<template
|
||||
:key="table.customComponents"
|
||||
v-for="comp in table.customComponents"
|
||||
|
|
|
|||
|
|
@ -29,7 +29,45 @@ const tableStore = useTableStore();
|
|||
|
||||
const columns = [
|
||||
{ title: "Name", field: "text", formatter: "text" },
|
||||
{ title: "Icon", field: "icon", formatter: "icons" },
|
||||
{
|
||||
title: "Icon",
|
||||
field: "icon",
|
||||
formatter: "icons",
|
||||
},
|
||||
];
|
||||
|
||||
// Because we are going to use built-in filters, we can't use the Filter component
|
||||
// So we need this format in order to create under the hood fields that will be linked to the tabulator filter
|
||||
// We need to pass on the setting key the same props as the Fields component. For example a "=" tabulator filter will be used with a select field, this one need "values" array to work.
|
||||
// type : Choose between available tabulator built-in filters ("keywords", "like", "!=", ">", "<", ">=", "<=", "in", "regex", "!=")
|
||||
const filters = [
|
||||
{
|
||||
type: "like",
|
||||
fields: ["text"],
|
||||
setting: {
|
||||
id: "test-input",
|
||||
name: "test-input",
|
||||
label: "Test input",
|
||||
value: "",
|
||||
inpType: "input",
|
||||
columns: { pc: 3, tablet: 4, mobile: 12 },
|
||||
},
|
||||
},
|
||||
{
|
||||
type: "=",
|
||||
fields: ["icon"],
|
||||
setting: {
|
||||
id: "test-select",
|
||||
name: "test-select",
|
||||
label: "Test select",
|
||||
value: "all",
|
||||
values: ["all", "box", "document"],
|
||||
setting: { inpType: "input" },
|
||||
inpType: "select",
|
||||
columns: { pc: 3, tablet: 4, mobile: 12 },
|
||||
onlyDown: true,
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
const items = [
|
||||
|
|
@ -111,19 +149,20 @@ const builder = [
|
|||
id: "table-test",
|
||||
columns: columns,
|
||||
items: items,
|
||||
filters: filters,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
onMounted(() => {
|
||||
setTimeout(() => {
|
||||
const table = tableStore.getTableById("table-test");
|
||||
console.log(table);
|
||||
table.setFilter("text", "keywords", "fesfs");
|
||||
}, 1000);
|
||||
});
|
||||
// Interact with table instance from another component
|
||||
// onMounted(() => {
|
||||
// setTimeout(() => {
|
||||
// const table = tableStore.getTableById("table-test");
|
||||
// console.log(table);
|
||||
// table.setFilter("text", "keywords", "fesfs");
|
||||
// }, 1000);
|
||||
// });
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
|
|
|||
|
|
@ -141,4 +141,192 @@ function _a18yFooter() {
|
|||
}
|
||||
}
|
||||
|
||||
export { addColumnsSorter, addColumnsWidth, a18yTable };
|
||||
/**
|
||||
* @name applyTableFilter
|
||||
* @description Apply setting filter to the tabulator instance.
|
||||
* We can't easily add filter after another, so we need to remove the previous one and add all new ones at once.
|
||||
* @example { "filter-1" : { type: "keywords", fields: ["text", "icon"], setting: {}, value : "test" }}
|
||||
* @param {object} tableInstance - The table instance to apply the filter.
|
||||
* @param {object} filters - All filters to apply to the table.
|
||||
* @param {string|number|regex} value - The value to apply to the filter.
|
||||
* @returns {void}
|
||||
*/
|
||||
function applyTableFilter(tableInstance, filters) {
|
||||
// loop on dict filters
|
||||
const filtersSend = [];
|
||||
for (const [key, filter] of Object.entries(filters)) {
|
||||
const inpType = filter.setting.inpType;
|
||||
const filterType = filter.type;
|
||||
const value = filter.value;
|
||||
const fields = filter.fields;
|
||||
|
||||
// Cases we don't want to apply filter
|
||||
if (value === "") continue;
|
||||
if (value === "all" && inpType === "select") continue;
|
||||
|
||||
// format value if needed
|
||||
if (filterType === "number") value = +value;
|
||||
if (filterType === "regex") value = new RegExp(value, "i");
|
||||
for (let i = 0; i < fields.length; i++) {
|
||||
filtersSend.push({ field: fields[i], type: filterType, value: value });
|
||||
}
|
||||
}
|
||||
tableInstance.setFilter(filtersSend);
|
||||
}
|
||||
|
||||
/**
|
||||
* @name overrideDefaultFilters
|
||||
* @description Create isomorphic filters for the tabulator library.
|
||||
* Override default filters retrieving the right value for each custom components.
|
||||
* @returns {object} - The custom filter function.
|
||||
*/
|
||||
function overrideDefaultFilters() {
|
||||
//
|
||||
const getRightKey = (rowValue) => {
|
||||
const buttons = rowValue?.buttons
|
||||
? rowValue?.buttons?.map((btn) => btn.text).join(" ")
|
||||
: null;
|
||||
|
||||
return (
|
||||
rowValue?.iconName?.toLowerCase() ||
|
||||
rowValue?.value?.toLowerCase() ||
|
||||
rowValue?.text.toLowerCase() ||
|
||||
buttons ||
|
||||
rowValue
|
||||
);
|
||||
};
|
||||
|
||||
return {
|
||||
//equal to
|
||||
"=": function (filterVal, rowVal, rowData, filterParams) {
|
||||
const value = getRightKey(rowVal);
|
||||
return value == filterVal ? true : false;
|
||||
},
|
||||
|
||||
//less than
|
||||
"<": function (filterVal, rowVal, rowData, filterParams) {
|
||||
const value = getRightKey(rowVal);
|
||||
return value < filterVal ? true : false;
|
||||
},
|
||||
|
||||
//less than or equal to
|
||||
"<=": function (filterVal, rowVal, rowData, filterParams) {
|
||||
const value = getRightKey(rowVal);
|
||||
return value <= filterVal ? true : false;
|
||||
},
|
||||
|
||||
//greater than
|
||||
">": function (filterVal, rowVal, rowData, filterParams) {
|
||||
const value = getRightKey(rowVal);
|
||||
return value > filterVal ? true : false;
|
||||
},
|
||||
|
||||
//greater than or equal to
|
||||
">=": function (filterVal, rowVal, rowData, filterParams) {
|
||||
const value = getRightKey(rowVal);
|
||||
return value >= filterVal ? true : false;
|
||||
},
|
||||
|
||||
//not equal to
|
||||
"!=": function (filterVal, rowVal, rowData, filterParams) {
|
||||
const value = getRightKey(rowVal);
|
||||
return value != filterVal ? true : false;
|
||||
},
|
||||
|
||||
regex: function (filterVal, rowVal, rowData, filterParams) {
|
||||
const value = getRightKey(rowVal);
|
||||
|
||||
if (typeof filterVal == "string") filterVal = new RegExp(filterVal);
|
||||
|
||||
return filterVal.test(value);
|
||||
},
|
||||
|
||||
//contains the string
|
||||
like: function (filterVal, rowVal, rowData, filterParams) {
|
||||
const value = getRightKey(rowVal);
|
||||
|
||||
if (filterVal === null || typeof filterVal === "undefined")
|
||||
return value === filterVal ? true : false;
|
||||
|
||||
if (typeof value !== "undefined" && value !== null)
|
||||
return (
|
||||
String(value).toLowerCase().indexOf(filterVal.toLowerCase()) > -1
|
||||
);
|
||||
|
||||
return false;
|
||||
},
|
||||
|
||||
//contains the keywords
|
||||
keywords: function (filterVal, rowVal, rowData, filterParams) {
|
||||
let value = getRightKey(rowVal);
|
||||
const keywords = filterVal
|
||||
.toLowerCase()
|
||||
.split(
|
||||
typeof filterParams.separator === "undefined"
|
||||
? " "
|
||||
: filterParams.separator
|
||||
);
|
||||
|
||||
value = String(
|
||||
value === null || typeof value === "undefined" ? "" : value
|
||||
).toLowerCase();
|
||||
|
||||
const matches = [];
|
||||
|
||||
keywords.forEach((keyword) => {
|
||||
if (value.includes(keyword)) {
|
||||
matches.push(true);
|
||||
}
|
||||
});
|
||||
|
||||
return filterParams.matchAll
|
||||
? matches.length === keywords.length
|
||||
: !!matches.length;
|
||||
},
|
||||
|
||||
//starts with the string
|
||||
starts: function (filterVal, rowVal, rowData, filterParams) {
|
||||
const value = getRightKey(rowVal);
|
||||
|
||||
if (filterVal === null || typeof filterVal === "undefined")
|
||||
return value === filterVal ? true : false;
|
||||
|
||||
if (typeof value !== "undefined" && value !== null)
|
||||
return String(value).toLowerCase().startsWith(filterVal.toLowerCase());
|
||||
|
||||
return false;
|
||||
},
|
||||
|
||||
//ends with the string
|
||||
ends: function (filterVal, rowVal, rowData, filterParams) {
|
||||
const value = getRightKey(rowVal);
|
||||
|
||||
if (filterVal === null || typeof filterVal === "undefined")
|
||||
return value === filterVal ? true : false;
|
||||
|
||||
if (typeof value !== "undefined" && value !== null)
|
||||
return String(value).toLowerCase().endsWith(filterVal.toLowerCase());
|
||||
|
||||
return false;
|
||||
},
|
||||
|
||||
//in array
|
||||
in: function (filterVal, rowVal, rowData, filterParams) {
|
||||
const value = getRightKey(rowVal);
|
||||
|
||||
if (Array.isArray(filterVal))
|
||||
return filterVal.length ? filterVal.indexOf(value) > -1 : true;
|
||||
|
||||
console.warn("Filter Error - filter value is not an array:", filterVal);
|
||||
return false;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export {
|
||||
addColumnsSorter,
|
||||
addColumnsWidth,
|
||||
a18yTable,
|
||||
applyTableFilter,
|
||||
overrideDefaultFilters,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -4636,7 +4636,7 @@ body.tabulator-print-fullscreen-hide > *:not(.tabulator-print-fullscreen) {
|
|||
}
|
||||
|
||||
.tabulator.striped .tabulator-row:nth-child(even) {
|
||||
background-color: #f2f2f2;
|
||||
background-color: #f6f6f6;
|
||||
}
|
||||
|
||||
.tabulator.celled {
|
||||
|
|
@ -4894,7 +4894,7 @@ body.tabulator-print-fullscreen-hide > *:not(.tabulator-print-fullscreen) {
|
|||
border-color: #e5e7eb !important;
|
||||
}
|
||||
|
||||
.dark .tabulator-row:nth-child(even) {
|
||||
.dark .tabulator.striped .tabulator-row:nth-child(even) {
|
||||
background-color: #4b5563 !important;
|
||||
}
|
||||
|
||||
|
|
@ -5108,7 +5108,8 @@ body.tabulator-print-fullscreen-hide > *:not(.tabulator-print-fullscreen) {
|
|||
.tabulator
|
||||
.tabulator-header
|
||||
.tabulator-col.tabulator-sortable.tabulator-col-sorter-element:hover,
|
||||
.dark .tabulator-row.tabulator-selectable:hover {
|
||||
.dark .tabulator-row.tabulator-selectable:hover,
|
||||
.dark .tabulator.striped .tabulator-row:nth-child(even):hover {
|
||||
background: #1f2937 !important;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
Loading…
Reference in a new issue