new: [genericElement:index_table] Added support of meta_fields searches
parent
549d9f3e1b
commit
d6d592ff8c
|
@ -106,6 +106,11 @@ class CRUDComponent extends Component
|
|||
$this->Controller->set('taggingEnabled', true);
|
||||
$this->setAllTags();
|
||||
}
|
||||
if ($this->metaFieldsSupported()) {
|
||||
$metaTemplates = $this->getMetaTemplates()->toArray();
|
||||
$this->Controller->set('metaFieldsEnabled', true);
|
||||
$this->Controller->set('metaTemplates', $metaTemplates);
|
||||
}
|
||||
$filters = !empty($this->Controller->filterFields) ? $this->Controller->filterFields : [];
|
||||
$this->Controller->set('filters', $filters);
|
||||
$this->Controller->viewBuilder()->setLayout('ajax');
|
||||
|
@ -954,10 +959,10 @@ class CRUDComponent extends Component
|
|||
}
|
||||
if (!empty($params['simpleFilters'])) {
|
||||
foreach ($params['simpleFilters'] as $filter => $filterValue) {
|
||||
$activeFilters[$filter] = $filterValue;
|
||||
if ($filter === 'quickFilter') {
|
||||
continue;
|
||||
}
|
||||
$activeFilters[$filter] = $filterValue;
|
||||
if (is_array($filterValue)) {
|
||||
$query->where([($filter . ' IN') => $filterValue]);
|
||||
} else {
|
||||
|
@ -979,10 +984,26 @@ class CRUDComponent extends Component
|
|||
$query = $this->setTagFilters($query, $filteringTags);
|
||||
}
|
||||
|
||||
if ($this->metaFieldsSupported()) {
|
||||
$filteringMetaFields = $this->getMetaFieldFiltersFromQuery();
|
||||
if (!empty($filteringMetaFields)) {
|
||||
$activeFilters['filteringMetaFields'] = $filteringMetaFields;
|
||||
}
|
||||
$query = $this->setMetaFieldFilters($query, $filteringMetaFields);
|
||||
}
|
||||
|
||||
$this->Controller->set('activeFilters', $activeFilters);
|
||||
return $query;
|
||||
}
|
||||
|
||||
protected function setMetaFieldFilters($query, $metaFieldFilters)
|
||||
{
|
||||
$modelAlias = $this->Table->getAlias();
|
||||
$subQuery = $this->Table->find('metaFieldValue', $metaFieldFilters)
|
||||
->select($modelAlias . '.id');
|
||||
return $query->where([$modelAlias . '.id IN' => $subQuery]);
|
||||
}
|
||||
|
||||
protected function setTagFilters($query, $tags)
|
||||
{
|
||||
$modelAlias = $this->Table->getAlias();
|
||||
|
@ -1241,6 +1262,25 @@ class CRUDComponent extends Component
|
|||
->toList();
|
||||
}
|
||||
|
||||
private function getMetaFieldFiltersFromQuery(): array
|
||||
{
|
||||
$filters = [];
|
||||
foreach ($this->request->getQueryParams() as $filterName => $value) {
|
||||
$prefix = '_metafield';
|
||||
if (substr($filterName, 0, strlen($prefix)) === $prefix) {
|
||||
$dissected = explode('_', substr($filterName, strlen($prefix)));
|
||||
if (count($dissected) == 3) { // Skip if template_id or template_field_id not provided
|
||||
$filters[] = [
|
||||
'meta_template_id' => intval($dissected[1]),
|
||||
'meta_template_field_id' => intval($dissected[2]),
|
||||
'value' => $value,
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
return $filters;
|
||||
}
|
||||
|
||||
private function renderViewInVariable($templateRelativeName, $data)
|
||||
{
|
||||
$builder = new ViewBuilder();
|
||||
|
|
|
@ -0,0 +1,189 @@
|
|||
<?php
|
||||
$filteringItems = [];
|
||||
foreach ($metaTemplates as $template_id => $metaTemplate) {
|
||||
foreach ($metaTemplate['meta_template_fields'] as $metaTemplateField) {
|
||||
$filteringItems[h($metaTemplate->name)][] = ['id' => $metaTemplateField->id, 'name' => h($metaTemplateField->field), 'template_id' => $template_id];
|
||||
}
|
||||
}
|
||||
|
||||
$filteringForm = $this->Bootstrap->table(
|
||||
[
|
||||
'small' => true,
|
||||
'striped' => false,
|
||||
'hover' => false,
|
||||
'tableClass' => ['indexMetaFieldsFilteringTable'],
|
||||
],
|
||||
[
|
||||
'fields' => [
|
||||
__('Meta Field'),
|
||||
__('Operator'),
|
||||
[
|
||||
'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`'))
|
||||
)
|
||||
],
|
||||
__('Action')
|
||||
],
|
||||
'items' => []
|
||||
]
|
||||
);
|
||||
?>
|
||||
|
||||
<?= $filteringForm; ?>
|
||||
<script>
|
||||
(function() {
|
||||
const availableFilters = <?= json_encode($filteringItems) ?>;
|
||||
|
||||
$(document).ready(() => {
|
||||
const $filteringTable = $('table.indexMetaFieldsFilteringTable')
|
||||
initFilteringTable($filteringTable)
|
||||
})
|
||||
|
||||
function initFilteringTable($filteringTable) {
|
||||
$filteringTable.find('tbody').empty()
|
||||
$filteringTable[0].getFiltersFunction = getFilters
|
||||
addControlRow($filteringTable)
|
||||
const randomValue = getRandomValue()
|
||||
const activeFilters = Object.assign({}, $(`#toggleFilterButton-${randomValue}`).data('activeFilters'))
|
||||
const metaFields = activeFilters['filteringMetaFields'] !== undefined ? Object.assign({}, activeFilters)['filteringMetaFields'] : []
|
||||
metaFields.forEach(metaField => {
|
||||
addFilteringRow($filteringTable, metaField.meta_template_field_id, metaField.value, '=')
|
||||
})
|
||||
}
|
||||
|
||||
function addControlRow($filteringTable) {
|
||||
const $selectField = genMetaFieldsSelectElement()
|
||||
.val(null).trigger('change')
|
||||
const $selectOperator = $('<select/>').addClass('fieldOperator form-select form-select-sm')
|
||||
.append([
|
||||
$('<option/>').text('=').val('='),
|
||||
$('<option/>').text('!=').val('!='),
|
||||
])
|
||||
const $row = $('<tr/>').attr('id', 'controlRow')
|
||||
.append(
|
||||
$('<td/>').append($selectField),
|
||||
$('<td/>').append($selectOperator),
|
||||
$('<td/>').append(
|
||||
$('<input>').attr('type', 'text').addClass('fieldValue form-control form-control-sm')
|
||||
),
|
||||
$('<td/>').append(
|
||||
$('<button/>').attr('type', 'button').addClass('btn btn-sm btn-primary')
|
||||
.append($('<span/>').addClass('fa fa-plus'))
|
||||
.click(addFiltering)
|
||||
)
|
||||
)
|
||||
$filteringTable.append($row)
|
||||
enableSelect2($selectField, $filteringTable.closest('.modal'))
|
||||
}
|
||||
|
||||
function enableSelect2($select, $dropdownParent) {
|
||||
$select.select2({
|
||||
dropdownParent: $dropdownParent,
|
||||
placeholder: '<?= __('Pick a meta field') ?>',
|
||||
allowClear: true,
|
||||
templateSelection: select2FormatState,
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
function select2FormatState(state) {
|
||||
if (!state.id) {
|
||||
return state.text;
|
||||
}
|
||||
const selectedData = $(state.element).data('meta_template_data')
|
||||
const $state = $('<span/>').append(
|
||||
$('<span/>').addClass('fw-light fs-8 me-1').text(`${selectedData.template_name} ::`),
|
||||
$('<span/>').text(selectedData.template_field_name)
|
||||
)
|
||||
return $state
|
||||
}
|
||||
|
||||
function addFilteringRow($filteringTable, field, value, operator) {
|
||||
const $selectField = genMetaFieldsSelectElement()
|
||||
$selectField.val(field).trigger('change.select2');
|
||||
const $selectOperator = $('<select/>').addClass('fieldOperator form-select form-select-sm')
|
||||
.append([
|
||||
$('<option/>').text('=').val('='),
|
||||
$('<option/>').text('!=').val('!='),
|
||||
]).val(operator)
|
||||
const $row = $('<tr/>')
|
||||
.append(
|
||||
$('<td/>').append($selectField),
|
||||
$('<td/>').append($selectOperator),
|
||||
$('<td/>').append(
|
||||
$('<input>').attr('type', 'text').addClass('fieldValue form-control form-control-sm').val(value)
|
||||
),
|
||||
$('<td/>').append(
|
||||
$('<button/>').attr('type', 'button').addClass('btn btn-sm btn-danger')
|
||||
.append($('<span/>').addClass('fa fa-trash'))
|
||||
.click(removeSelf)
|
||||
)
|
||||
)
|
||||
$filteringTable.append($row)
|
||||
enableSelect2($selectField, $filteringTable.closest('.modal'))
|
||||
}
|
||||
|
||||
function addFiltering() {
|
||||
const $table = $(this).closest('table.indexMetaFieldsFilteringTable')
|
||||
const $controlRow = $table.find('#controlRow')
|
||||
const field = $controlRow.find('select.fieldSelect').val()
|
||||
const value = $controlRow.find('input.fieldValue').val()
|
||||
const operator = $controlRow.find('select.fieldOperator').val()
|
||||
addFilteringRow($table, field, value, operator)
|
||||
$controlRow.find('input.fieldValue').val('')
|
||||
$controlRow.find('select.fieldSelect').val('').trigger('change.select2');
|
||||
}
|
||||
|
||||
function removeSelf() {
|
||||
const $row = $(this).closest('tr')
|
||||
const $controlRow = $row.closest('table.indexMetaFieldsFilteringTable').find('#controlRow')
|
||||
const field = $row.data('fieldName')
|
||||
$row.remove()
|
||||
}
|
||||
|
||||
function genMetaFieldsSelectElement() {
|
||||
const $selectField = $('<select/>').addClass('fieldSelect form-select form-select-sm')
|
||||
for (let [metaTemplateName, metaTemplateFields] of Object.entries(availableFilters)) {
|
||||
$selectField.append($('<optgroup/>').attr('label', metaTemplateName));
|
||||
metaTemplateFields.forEach(metaTemplateField => {
|
||||
$selectField.append($('<option/>')
|
||||
.val(metaTemplateField['id'])
|
||||
.text(metaTemplateField['name'])
|
||||
.data('meta_template_data', {
|
||||
template_id: metaTemplateField['template_id'],
|
||||
template_field_id: metaTemplateField['id'],
|
||||
template_name: metaTemplateName,
|
||||
template_field_name: metaTemplateField['name'],
|
||||
})
|
||||
)
|
||||
});
|
||||
}
|
||||
return $selectField
|
||||
}
|
||||
|
||||
function getFilters() {
|
||||
const $table = $(this)
|
||||
let activeFilters = [];
|
||||
$table.find('tbody tr').each(function() {
|
||||
const $row = $(this)
|
||||
if ($row.find('select.fieldSelect').select2('data').length > 0) {
|
||||
let rowData = {}
|
||||
selectedData = $($row.find('select.fieldSelect').select2('data')[0].element).data('meta_template_data')
|
||||
rowData['name'] = `_metafield.${selectedData.template_id}.${selectedData.template_field_id}`
|
||||
rowData['operator'] = $row.find('select.fieldOperator').val()
|
||||
rowData['value'] = $row.find('input.fieldValue').val()
|
||||
let fullFilter = rowData['name']
|
||||
if (rowData['operator'] == '!=') {
|
||||
fullFilter += ' !='
|
||||
}
|
||||
if (rowData['value'].length > 0) {
|
||||
activeFilters[fullFilter] = rowData['value']
|
||||
}
|
||||
}
|
||||
})
|
||||
return activeFilters
|
||||
}
|
||||
}())
|
||||
</script>
|
|
@ -19,6 +19,10 @@
|
|||
$filteringButton = '';
|
||||
if (!empty($data['allowFilering'])) {
|
||||
$activeFilters = !empty($activeFilters) ? $activeFilters : [];
|
||||
$numberActiveFilters = count($activeFilters);
|
||||
if (!empty($activeFilters['filteringMetaFields'])) {
|
||||
$numberActiveFilters += count($activeFilters['filteringMetaFields']) - 1;
|
||||
}
|
||||
$buttonConfig = [
|
||||
'icon' => 'filter',
|
||||
'params' => [
|
||||
|
@ -29,8 +33,8 @@
|
|||
if (count($activeFilters) > 0) {
|
||||
$buttonConfig['badge'] = [
|
||||
'variant' => 'light',
|
||||
'text' => count($activeFilters),
|
||||
'title' => __n('There is {0} active filter', 'There are {0} active filters', count($activeFilters), count($activeFilters))
|
||||
'text' => $numberActiveFilters,
|
||||
'title' => __n('There is {0} active filter', 'There are {0} active filters', $numberActiveFilters, $numberActiveFilters)
|
||||
];
|
||||
}
|
||||
$filteringButton = $this->Bootstrap->button($buttonConfig);
|
||||
|
|
|
@ -47,26 +47,39 @@ $filteringForm = $this->Bootstrap->table(
|
|||
]
|
||||
);
|
||||
|
||||
$filteringMetafields = '';
|
||||
if ($metaFieldsEnabled) {
|
||||
$helpText = $this->Bootstrap->genNode('sup', [
|
||||
'class' => ['ms-1 fa fa-info'],
|
||||
'title' => __('Include help'),
|
||||
'data-bs-toggle' => 'tooltip',
|
||||
]);
|
||||
$filteringMetafields = $this->Bootstrap->genNode('h5', [], __('Meta Fields') . $helpText);
|
||||
$filteringMetafields .= $this->element('genericElements/IndexTable/metafield_filtering', $metaTemplates);
|
||||
}
|
||||
|
||||
$filteringTags = '';
|
||||
if ($taggingEnabled) {
|
||||
$helpText = $this->Bootstrap->genNode('sup', [
|
||||
'class' => ['ms-1 fa fa-info'],
|
||||
'title' => __('Supports negation matches (with the `!` character) and LIKE matches (with the `%` character). Example: `!exportable`, `%able`'),
|
||||
'data-bs-toggle' => 'tooltip',
|
||||
]);
|
||||
$filteringTags = $this->Bootstrap->genNode('h5', [], __('Tags') . $helpText);
|
||||
$filteringTags = $this->Bootstrap->genNode('h5', [
|
||||
'class' => 'mt-2'
|
||||
], __('Tags') . $helpText);
|
||||
$filteringTags .= $this->Tag->tags([], [
|
||||
'allTags' => $allTags,
|
||||
'picker' => true,
|
||||
'editable' => false,
|
||||
]);
|
||||
} else {
|
||||
$filteringTags = '';
|
||||
}
|
||||
$modalBody = sprintf('%s%s', $filteringForm, $filteringTags);
|
||||
|
||||
$modalBody = implode('', [$filteringForm, $filteringMetafields, $filteringTags]);
|
||||
|
||||
echo $this->Bootstrap->modal([
|
||||
'title' => __('Filtering options for {0}', Inflector::singularize($this->request->getParam('controller'))),
|
||||
'size' => 'lg',
|
||||
'size' => !empty($metaFieldsEnabled) ? 'xl' : 'lg',
|
||||
'type' => 'confirm',
|
||||
'bodyHtml' => $modalBody,
|
||||
'confirmText' => __('Filter'),
|
||||
|
@ -84,7 +97,7 @@ echo $this->Bootstrap->modal([
|
|||
const controller = '<?= $this->request->getParam('controller') ?>';
|
||||
const action = 'index';
|
||||
const $tbody = modalObject.$modal.find('table.indexFilteringTable tbody')
|
||||
const $rows = $tbody.find('tr:not(#controlRow)')
|
||||
const $rows = $tbody.find('tr')
|
||||
const activeFilters = {}
|
||||
$rows.each(function() {
|
||||
const rowData = getDataFromRow($(this))
|
||||
|
@ -96,8 +109,16 @@ echo $this->Bootstrap->modal([
|
|||
activeFilters[fullFilter] = rowData['value']
|
||||
}
|
||||
})
|
||||
$select = modalObject.$modal.find('select.select2-input')
|
||||
activeFilters['filteringTags'] = $select.select2('data').map(tag => tag.text)
|
||||
if (modalObject.$modal.find('table.indexMetaFieldsFilteringTable').length > 0) {
|
||||
let metaFieldFilters = modalObject.$modal.find('table.indexMetaFieldsFilteringTable')[0].getFiltersFunction()
|
||||
// activeFilters['filteringMetaFields'] = metaFieldFilters !== undefined ? metaFieldFilters : [];
|
||||
metaFieldFilters = metaFieldFilters !== undefined ? metaFieldFilters : []
|
||||
for (let [metaFieldPath, metaFieldValue] of Object.entries(metaFieldFilters)) {
|
||||
activeFilters[metaFieldPath] = metaFieldValue
|
||||
}
|
||||
}
|
||||
$selectTag = modalObject.$modal.find('.tag-container select.select2-input')
|
||||
activeFilters['filteringTags'] = $selectTag.select2('data').map(tag => tag.text)
|
||||
const searchParam = jQuery.param(activeFilters);
|
||||
const url = `/${controller}/${action}?${searchParam}`
|
||||
|
||||
|
|
Loading…
Reference in New Issue