2021-03-10 09:43:36 +01:00
|
|
|
<?php
|
2021-10-21 10:20:07 +02:00
|
|
|
|
2021-03-10 09:43:36 +01:00
|
|
|
use Cake\Utility\Inflector;
|
|
|
|
|
2021-10-21 10:20:07 +02:00
|
|
|
$tableItems = array_map(function ($fieldName) {
|
|
|
|
return [
|
|
|
|
'fieldname' => $fieldName,
|
|
|
|
];
|
|
|
|
}, $filters);
|
2022-10-27 15:56:39 +02:00
|
|
|
$formTypeMap = $this->Form->getConfig('typeMap');
|
2021-10-21 10:20:07 +02:00
|
|
|
|
2021-03-10 09:43:36 +01:00
|
|
|
$filteringForm = $this->Bootstrap->table(
|
|
|
|
[
|
|
|
|
'small' => true,
|
|
|
|
'striped' => false,
|
|
|
|
'hover' => false,
|
|
|
|
'tableClass' => ['indexFilteringTable'],
|
|
|
|
],
|
|
|
|
[
|
2021-10-21 10:20:07 +02:00
|
|
|
'fields' => [
|
|
|
|
[
|
2022-11-24 12:50:11 +01:00
|
|
|
'path' => 'fieldname', 'label' => __('Field'), 'formatter' => function ($field, $row) {
|
2021-10-21 10:20:07 +02:00
|
|
|
return sprintf('<span class="fieldName" data-fieldname="%s">%s</span>', h($field), h($field));
|
|
|
|
}
|
|
|
|
],
|
|
|
|
[
|
2022-11-24 12:50:11 +01:00
|
|
|
'path' => 'operator', 'label' => __('Operator'), 'formatter' => function ($field, $row) use ($typeMap) {
|
2022-10-27 15:56:39 +02:00
|
|
|
$fieldName = $row['fieldname'];
|
|
|
|
$type = $typeMap[$fieldName] ?? 'text';
|
2021-10-21 10:20:07 +02:00
|
|
|
$options = [
|
|
|
|
sprintf('<option value="%s">%s</option>', '=', '='),
|
|
|
|
sprintf('<option value="%s">%s</option>', '!=', '!='),
|
|
|
|
];
|
2022-10-27 15:56:39 +02:00
|
|
|
if ($type === 'datetime') {
|
|
|
|
$options = [
|
|
|
|
sprintf('<option value="%s">%s</option>', '>=', '>='),
|
|
|
|
sprintf('<option value="%s">%s</option>', '<=', '<='),
|
|
|
|
];
|
|
|
|
}
|
2021-10-21 10:20:07 +02:00
|
|
|
return sprintf('<select class="fieldOperator form-select form-select-sm">%s</select>', implode('', $options));
|
|
|
|
}
|
|
|
|
],
|
|
|
|
[
|
2022-11-24 12:50:11 +01:00
|
|
|
'path' => 'value',
|
2021-10-21 10:20:07 +02:00
|
|
|
'labelHtml' => sprintf(
|
|
|
|
'%s %s',
|
|
|
|
__('Value'),
|
|
|
|
sprintf('<sup class="fa fa-info" title="%s"><sup>', __('Supports strict matches and LIKE matches with the `%` character. Example: `%.com`'))
|
|
|
|
),
|
2023-02-23 12:55:18 +01:00
|
|
|
'formatter' => function ($field, $row) use ($typeMap, $formTypeMap, $filtersConfig) {
|
2022-10-27 15:56:39 +02:00
|
|
|
$fieldName = $row['fieldname'];
|
|
|
|
$formType = $formTypeMap[$typeMap[$fieldName]] ?? 'text';
|
2023-02-23 12:55:18 +01:00
|
|
|
$fieldData = [
|
|
|
|
'field' => $fieldName,
|
|
|
|
'type' => $formType,
|
|
|
|
'label' => '',
|
|
|
|
'class' => 'fieldValue form-control-sm'
|
|
|
|
];
|
2023-12-13 14:30:53 +01:00
|
|
|
if (!empty($filtersConfig[$fieldName])) {
|
|
|
|
if (!empty($filtersConfig[$fieldName]['options'])) {
|
|
|
|
$fieldData['options'] = $filtersConfig[$fieldName]['options'];
|
|
|
|
}
|
|
|
|
if (!empty($filtersConfig[$fieldName]['select2'])) {
|
|
|
|
$fieldData['type'] = 'dropdown';
|
|
|
|
$fieldData['select2'] = true;
|
|
|
|
}
|
|
|
|
if (!empty($filtersConfig[$fieldName]['multiple'])) {
|
|
|
|
$fieldData['type'] = 'dropdown';
|
|
|
|
$fieldData['multiple'] = true;
|
|
|
|
$fieldData['select2'] = [
|
|
|
|
'tags' => true,
|
|
|
|
'tokenSeparators' => [',', ' '],
|
|
|
|
];
|
|
|
|
}
|
2023-02-23 12:55:18 +01:00
|
|
|
}
|
2022-10-27 15:56:39 +02:00
|
|
|
$this->Form->setTemplates([
|
|
|
|
'formGroup' => '<div class="col-sm-10">{{input}}</div>',
|
|
|
|
]);
|
|
|
|
return $this->element('genericElements/Form/fieldScaffold', [
|
2023-02-23 12:55:18 +01:00
|
|
|
'fieldData' => $fieldData,
|
2022-10-27 15:56:39 +02:00
|
|
|
'params' => []
|
|
|
|
]);
|
2021-10-21 10:20:07 +02:00
|
|
|
}
|
|
|
|
],
|
2021-03-10 09:43:36 +01:00
|
|
|
],
|
2021-10-21 10:20:07 +02:00
|
|
|
'items' => $tableItems
|
|
|
|
]
|
|
|
|
);
|
2021-03-10 09:43:36 +01:00
|
|
|
|
2021-11-10 12:07:27 +01:00
|
|
|
$filteringMetafields = '';
|
|
|
|
if ($metaFieldsEnabled) {
|
2022-11-24 12:50:11 +01:00
|
|
|
$helpText = $this->Bootstrap->node('sup', [
|
2021-11-10 12:07:27 +01:00
|
|
|
'class' => ['ms-1 fa fa-info'],
|
|
|
|
'title' => __('Include help'),
|
|
|
|
'data-bs-toggle' => 'tooltip',
|
|
|
|
]);
|
2022-11-24 12:50:11 +01:00
|
|
|
$filteringMetafields = $this->Bootstrap->node('h5', [], __('Meta Fields') . $helpText);
|
2021-11-10 12:07:27 +01:00
|
|
|
$filteringMetafields .= $this->element('genericElements/IndexTable/metafield_filtering', $metaTemplates);
|
|
|
|
}
|
|
|
|
|
|
|
|
$filteringTags = '';
|
2021-08-30 15:11:21 +02:00
|
|
|
if ($taggingEnabled) {
|
2022-11-24 12:50:11 +01:00
|
|
|
$helpText = $this->Bootstrap->node('sup', [
|
2021-09-17 17:51:45 +02:00
|
|
|
'class' => ['ms-1 fa fa-info'],
|
2024-04-02 11:40:54 +02:00
|
|
|
'title' => __('Supports negation matches (with the `!` character) and LIKE matches (with the `%` character). Example: `!exportable`, `%able`'),
|
2021-09-17 18:12:24 +02:00
|
|
|
'data-bs-toggle' => 'tooltip',
|
2021-08-30 15:11:21 +02:00
|
|
|
]);
|
2022-11-24 12:50:11 +01:00
|
|
|
$filteringTags = $this->Bootstrap->node('h5', [
|
2021-11-10 12:07:27 +01:00
|
|
|
'class' => 'mt-2'
|
|
|
|
], __('Tags') . $helpText);
|
2021-08-30 15:11:21 +02:00
|
|
|
$filteringTags .= $this->Tag->tags([], [
|
|
|
|
'allTags' => $allTags,
|
|
|
|
'picker' => true,
|
|
|
|
'editable' => false,
|
|
|
|
]);
|
|
|
|
}
|
2021-11-10 12:07:27 +01:00
|
|
|
|
|
|
|
$modalBody = implode('', [$filteringForm, $filteringMetafields, $filteringTags]);
|
2021-08-30 15:11:21 +02:00
|
|
|
|
2021-03-10 09:43:36 +01:00
|
|
|
echo $this->Bootstrap->modal([
|
|
|
|
'title' => __('Filtering options for {0}', Inflector::singularize($this->request->getParam('controller'))),
|
2021-11-10 12:07:27 +01:00
|
|
|
'size' => !empty($metaFieldsEnabled) ? 'xl' : 'lg',
|
2021-03-10 09:43:36 +01:00
|
|
|
'type' => 'confirm',
|
2021-08-30 15:11:21 +02:00
|
|
|
'bodyHtml' => $modalBody,
|
2022-11-24 12:50:11 +01:00
|
|
|
'confirmButton' => [
|
|
|
|
'text' => __('Filter'),
|
|
|
|
],
|
2021-03-23 10:04:06 +01:00
|
|
|
'confirmFunction' => 'filterIndex'
|
2021-03-10 09:43:36 +01:00
|
|
|
]);
|
|
|
|
?>
|
|
|
|
|
|
|
|
<script>
|
|
|
|
$(document).ready(() => {
|
|
|
|
const $filteringTable = $('table.indexFilteringTable')
|
|
|
|
initFilteringTable($filteringTable)
|
|
|
|
})
|
|
|
|
|
2021-03-23 10:04:06 +01:00
|
|
|
function filterIndex(modalObject, tmpApi) {
|
2021-03-10 09:43:36 +01:00
|
|
|
const controller = '<?= $this->request->getParam('controller') ?>';
|
|
|
|
const action = 'index';
|
2021-03-23 10:04:06 +01:00
|
|
|
const $tbody = modalObject.$modal.find('table.indexFilteringTable tbody')
|
2021-11-10 12:07:27 +01:00
|
|
|
const $rows = $tbody.find('tr')
|
2021-03-10 09:43:36 +01:00
|
|
|
const activeFilters = {}
|
|
|
|
$rows.each(function() {
|
|
|
|
const rowData = getDataFromRow($(this))
|
|
|
|
let fullFilter = rowData['name']
|
2022-10-27 15:56:39 +02:00
|
|
|
if (rowData['operator'] != '=') {
|
|
|
|
fullFilter += ` ${rowData['operator']}`
|
2021-03-10 09:43:36 +01:00
|
|
|
}
|
2021-10-21 10:20:07 +02:00
|
|
|
if (rowData['value'].length > 0) {
|
|
|
|
activeFilters[fullFilter] = rowData['value']
|
|
|
|
}
|
2021-03-10 09:43:36 +01:00
|
|
|
})
|
2021-11-10 12:07:27 +01:00
|
|
|
if (modalObject.$modal.find('table.indexMetaFieldsFilteringTable').length > 0) {
|
|
|
|
let metaFieldFilters = modalObject.$modal.find('table.indexMetaFieldsFilteringTable')[0].getFiltersFunction()
|
|
|
|
metaFieldFilters = metaFieldFilters !== undefined ? metaFieldFilters : []
|
|
|
|
for (let [metaFieldPath, metaFieldValue] of Object.entries(metaFieldFilters)) {
|
|
|
|
activeFilters[metaFieldPath] = metaFieldValue
|
|
|
|
}
|
|
|
|
}
|
|
|
|
$selectTag = modalObject.$modal.find('.tag-container select.select2-input')
|
2022-10-25 10:29:14 +02:00
|
|
|
activeFilters['filteringTags'] = $selectTag.length > 0 ? $selectTag.select2('data').map(tag => tag.text) : []
|
2021-08-30 15:11:21 +02:00
|
|
|
const searchParam = jQuery.param(activeFilters);
|
2021-03-10 09:43:36 +01:00
|
|
|
const url = `/${controller}/${action}?${searchParam}`
|
|
|
|
|
|
|
|
const randomValue = getRandomValue()
|
|
|
|
UI.reload(url, $(`#table-container-${randomValue}`), $(`#table-container-${randomValue} table.table`), [{
|
|
|
|
node: $(`#toggleFilterButton-${randomValue}`),
|
|
|
|
config: {}
|
|
|
|
}])
|
|
|
|
}
|
|
|
|
|
|
|
|
function initFilteringTable($filteringTable) {
|
|
|
|
const $controlRow = $filteringTable.find('#controlRow')
|
|
|
|
const randomValue = getRandomValue()
|
2021-09-01 16:12:56 +02:00
|
|
|
const activeFilters = Object.assign({}, $(`#toggleFilterButton-${randomValue}`).data('activeFilters'))
|
2021-08-30 15:11:21 +02:00
|
|
|
const tags = activeFilters['filteringTags'] !== undefined ? Object.assign({}, activeFilters)['filteringTags'] : []
|
|
|
|
delete activeFilters['filteringTags']
|
2021-03-10 09:43:36 +01:00
|
|
|
for (let [field, value] of Object.entries(activeFilters)) {
|
|
|
|
const fieldParts = field.split(' ')
|
|
|
|
let operator = '='
|
2022-10-27 15:56:39 +02:00
|
|
|
if (fieldParts.length == 2) {
|
|
|
|
operator = fieldParts[1]
|
2021-03-10 09:43:36 +01:00
|
|
|
field = fieldParts[0]
|
|
|
|
} else if (fieldParts.length > 2) {
|
|
|
|
console.error('Field contains multiple spaces. ' + field)
|
|
|
|
}
|
2021-10-21 10:20:07 +02:00
|
|
|
setFilteringValues($filteringTable, field, value, operator)
|
2021-03-10 09:43:36 +01:00
|
|
|
}
|
2023-02-23 12:55:18 +01:00
|
|
|
if (tags.length > 0) {
|
|
|
|
setFilteringTags($filteringTable, tags)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function setFilteringValues($filteringTable, field, value, operator) {
|
|
|
|
$row = $filteringTable.find('td > span.fieldName').filter(function() {
|
|
|
|
return $(this).data('fieldname') == field
|
|
|
|
}).closest('tr')
|
2023-12-13 14:30:53 +01:00
|
|
|
$row.addClass('table-warning')
|
2023-02-23 12:55:18 +01:00
|
|
|
$row.find('.fieldOperator').val(operator)
|
|
|
|
const $formElement = $row.find('.fieldValue');
|
|
|
|
if ($formElement.attr('type') === 'datetime-local') {
|
|
|
|
$formElement.val(moment(value).format('yyyy-MM-DDThh:mm:ss'))
|
|
|
|
} else if ($formElement.is('select') && Array.isArray(value)) {
|
|
|
|
let newOptions = [];
|
|
|
|
value.forEach(aValue => {
|
|
|
|
const existingOption = $formElement.find('option').filter(function() {
|
2023-12-13 14:30:53 +01:00
|
|
|
if ($(this).val() === aValue) {
|
|
|
|
$(this).prop('selected', true)
|
|
|
|
return true
|
|
|
|
}
|
2023-02-23 12:55:18 +01:00
|
|
|
})
|
|
|
|
if (existingOption.length == 0) {
|
|
|
|
newOptions.push(new Option(aValue, aValue, true, true))
|
|
|
|
}
|
|
|
|
})
|
|
|
|
$formElement.append(newOptions).trigger('change');
|
|
|
|
} else {
|
|
|
|
$formElement.val(value)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function setFilteringTags($filteringTable, tags) {
|
2021-10-25 16:20:36 +02:00
|
|
|
$select = $filteringTable.closest('.modal-body').find('select.select2-input')
|
2021-08-30 15:11:21 +02:00
|
|
|
let passedTags = []
|
|
|
|
tags.forEach(tagname => {
|
2021-08-31 11:19:15 +02:00
|
|
|
const existingOption = $select.find('option').filter(function() {
|
|
|
|
return $(this).val() === tagname
|
|
|
|
})
|
|
|
|
if (existingOption.length == 0) {
|
2021-08-30 15:11:21 +02:00
|
|
|
passedTags.push(new Option(tagname, tagname, true, true))
|
|
|
|
}
|
|
|
|
})
|
|
|
|
$select
|
|
|
|
.append(passedTags)
|
|
|
|
.val(tags)
|
|
|
|
.trigger('change')
|
2021-03-10 09:43:36 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
function getDataFromRow($row) {
|
|
|
|
const rowData = {};
|
2021-10-21 10:20:07 +02:00
|
|
|
rowData['name'] = $row.find('td > span.fieldName').data('fieldname')
|
2021-03-10 09:43:36 +01:00
|
|
|
rowData['operator'] = $row.find('select.fieldOperator').val()
|
2022-10-27 15:56:39 +02:00
|
|
|
const $formElement = $row.find('.fieldValue');
|
|
|
|
if ($formElement.attr('type') === 'datetime-local') {
|
2022-11-15 11:27:12 +01:00
|
|
|
rowData['value'] = $formElement.val().length > 0 ? moment($formElement.val()).toISOString() : $formElement.val()
|
2022-10-27 15:56:39 +02:00
|
|
|
} else {
|
|
|
|
rowData['value'] = $formElement.val()
|
|
|
|
}
|
2021-03-10 09:43:36 +01:00
|
|
|
return rowData
|
|
|
|
}
|
|
|
|
|
|
|
|
function getRandomValue() {
|
|
|
|
const $container = $('div[id^="table-container-"]')
|
|
|
|
const randomValue = $container.attr('id').split('-')[2]
|
|
|
|
return randomValue
|
|
|
|
}
|
|
|
|
</script>
|