chg: [behavior:tags] Custom finder and small improvements
parent
61255e2837
commit
eed5b9226a
|
@ -745,7 +745,7 @@ class CRUDComponent extends Component
|
|||
'label' => $tags,
|
||||
'forceAnd' => true
|
||||
])->select($modelAlias . '.id');
|
||||
return $query = $query->where([$modelAlias . '.id IN' => $subQuery]);
|
||||
return $query->where([$modelAlias . '.id IN' => $subQuery]);
|
||||
}
|
||||
|
||||
protected function setNestedRelatedCondition($query, $filterParts, $filterValue)
|
||||
|
|
|
@ -11,6 +11,7 @@ class TagBehavior extends Behavior
|
|||
{
|
||||
|
||||
protected $_defaultConfig = [
|
||||
'finderField' => 'label',
|
||||
'tagsAssoc' => [
|
||||
'className' => 'Tags',
|
||||
// 'joinTable' => 'tagged', // uncomment me!
|
||||
|
@ -148,9 +149,7 @@ class TagBehavior extends Behavior
|
|||
public function normalizeTags($tags) {
|
||||
|
||||
$result = [];
|
||||
|
||||
$modelAlias = $this->_table->getAlias();
|
||||
|
||||
$common = [
|
||||
'_joinData' => [
|
||||
'fk_model' => $modelAlias
|
||||
|
@ -209,4 +208,150 @@ class TagBehavior extends Behavior
|
|||
->select('Tags.id');
|
||||
return $query->first();
|
||||
}
|
||||
|
||||
public function findByTag(Query $query, array $options) {
|
||||
$finderField = $optionsKey = $this->getConfig('finderField');
|
||||
if (!$finderField) {
|
||||
$finderField = $optionsKey = 'label';
|
||||
}
|
||||
|
||||
if (!isset($options[$optionsKey])) {
|
||||
throw new RuntimeException(__('Expected key `{0}` not present in find(\'tagged\') options argument.', $optionsKey));
|
||||
}
|
||||
$isAndOperator = isset($options['OperatorAND']) ? $options['OperatorAND'] : true;
|
||||
$filterValue = $options[$optionsKey];
|
||||
if (!$filterValue) {
|
||||
return $query;
|
||||
}
|
||||
|
||||
$filterValue = $this->dissectArgs($filterValue);
|
||||
if (!empty($filterValue['NOT']) || !empty($filterValue['LIKE'])) {
|
||||
return $this->findByComplexQueryConditions($query, $filterValue, $finderField, $isAndOperator);
|
||||
}
|
||||
|
||||
$subQuery = $this->buildQuerySnippet($filterValue, $finderField, $isAndOperator);
|
||||
if (is_string($subQuery)) {
|
||||
$query->matching('Tags', function ($q) use ($finderField, $subQuery) {
|
||||
$key = 'Tags.' . $finderField;
|
||||
return $q->where([
|
||||
$key => $subQuery,
|
||||
]);
|
||||
});
|
||||
return $query;
|
||||
}
|
||||
|
||||
$modelAlias = $this->_table->getAlias();
|
||||
return $query->where([$modelAlias . '.id IN' => $subQuery]);
|
||||
}
|
||||
|
||||
public function findUntagged(Query $query, array $options) {
|
||||
$modelAlias = $this->_table->getAlias();
|
||||
$foreignKey = $this->getConfig('tagsAssoc.foreignKey');
|
||||
$conditions = ['fk_model' => $modelAlias];
|
||||
$this->_table->hasOne('NoTags', [
|
||||
'className' => $this->getConfig('taggedAssoc.className'),
|
||||
'foreignKey' => $foreignKey,
|
||||
'conditions' => $conditions
|
||||
]);
|
||||
$query = $query->contain(['NoTags'])->where(['NoTags.id IS' => null]);
|
||||
return $query;
|
||||
}
|
||||
|
||||
protected function dissectArgs($filterValue): array
|
||||
{
|
||||
if (!is_array($filterValue)) {
|
||||
return $filterValue;
|
||||
}
|
||||
$dissected = [
|
||||
'AND' => [],
|
||||
'NOT' => [],
|
||||
'LIKE' => [],
|
||||
];
|
||||
foreach ($filterValue as $value) {
|
||||
if (substr($value, 0, 1) == '!') {
|
||||
$dissected['NOT'][] = substr($value, 1);
|
||||
}
|
||||
else if (strpos($value, '%') != false) {
|
||||
$dissected['LIKE'][] = $value;
|
||||
} else {
|
||||
$dissected['AND'][] = $value;
|
||||
}
|
||||
}
|
||||
if (empty($dissected['NOT']) && empty($dissected['LIKE'])) {
|
||||
return $dissected['AND'];
|
||||
}
|
||||
return $dissected;
|
||||
}
|
||||
|
||||
protected function buildQuerySnippet($filterValue, string $finderField, bool $OperatorAND=true)
|
||||
{
|
||||
if (!is_array($filterValue)) {
|
||||
return $filterValue;
|
||||
}
|
||||
$key = 'Tags.' . $finderField;
|
||||
$foreignKey = $this->getConfig('tagsAssoc.foreignKey');
|
||||
$conditions = [
|
||||
$key . ' IN' => $filterValue,
|
||||
];
|
||||
|
||||
$query = $this->_table->Tagged->find();
|
||||
if ($OperatorAND) {
|
||||
$query->contain(['Tags'])
|
||||
->group('Tagged.' . $foreignKey)
|
||||
->having('COUNT(*) = ' . count($filterValue))
|
||||
->select('Tagged.' . $foreignKey)
|
||||
->where($conditions);
|
||||
} else {
|
||||
$query->contain(['Tags'])
|
||||
->select('Tagged.' . $foreignKey)
|
||||
->where($conditions);
|
||||
}
|
||||
return $query;
|
||||
}
|
||||
|
||||
protected function findByComplexQueryConditions($query, $filterValue, string $finderField, bool $OperatorAND=true)
|
||||
{
|
||||
$key = 'Tags.' . $finderField;
|
||||
$taggedAlias = 'Tagged';
|
||||
$foreignKey = $this->getConfig('tagsAssoc.foreignKey');
|
||||
|
||||
if (!empty($filterValue['AND'])) {
|
||||
$subQuery = $this->buildQuerySnippet($filterValue['AND'], $finderField, $OperatorAND);
|
||||
$modelAlias = $this->_table->getAlias();
|
||||
$query->where([$modelAlias . '.id IN' => $subQuery]);
|
||||
}
|
||||
|
||||
if (!empty($filterValue['NOT'])) {
|
||||
$subQuery = $this->buildQuerySnippet($filterValue['NOT'], $finderField, false);
|
||||
$modelAlias = $this->_table->getAlias();
|
||||
$query->where([$modelAlias . '.id NOT IN' => $subQuery]);
|
||||
}
|
||||
|
||||
if (!empty($filterValue['LIKE'])) {
|
||||
$conditions = ['OR' => []];
|
||||
foreach($filterValue['LIKE'] as $likeValue) {
|
||||
$conditions['OR'][] = [
|
||||
$key . ' LIKE' => $likeValue,
|
||||
];
|
||||
}
|
||||
$subQuery = $this->buildQuerySnippet($filterValue['NOT'], $finderField, $OperatorAND);
|
||||
if ($OperatorAND) {
|
||||
$subQuery = $this->_table->Tagged->find()
|
||||
->contain(['Tags'])
|
||||
->group('Tagged.' . $foreignKey)
|
||||
->having('COUNT(*) >= ' . count($filterValue['LIKE']))
|
||||
->select('Tagged.' . $foreignKey)
|
||||
->where($conditions);
|
||||
} else {
|
||||
$subQuery = $this->_table->Tagged->find()
|
||||
->contain(['Tags'])
|
||||
->select('Tagged.' . $foreignKey)
|
||||
->where($conditions);
|
||||
}
|
||||
$modelAlias = $this->_table->getAlias();
|
||||
$query->where([$modelAlias . '.id IN' => $subQuery]);
|
||||
}
|
||||
|
||||
return $query;
|
||||
}
|
||||
}
|
|
@ -87,7 +87,7 @@ echo $this->Bootstrap->modal([
|
|||
$filteringTable.find('tbody').empty()
|
||||
addControlRow($filteringTable)
|
||||
const randomValue = getRandomValue()
|
||||
const activeFilters = $(`#toggleFilterButton-${randomValue}`).data('activeFilters')
|
||||
const activeFilters = Object.assign({}, $(`#toggleFilterButton-${randomValue}`).data('activeFilters'))
|
||||
const tags = activeFilters['filteringTags'] !== undefined ? Object.assign({}, activeFilters)['filteringTags'] : []
|
||||
delete activeFilters['filteringTags']
|
||||
for (let [field, value] of Object.entries(activeFilters)) {
|
||||
|
|
|
@ -1125,7 +1125,7 @@ class HtmlHelper {
|
|||
options.colour = '#924da6'
|
||||
}
|
||||
const $tag = $('<span/>')
|
||||
.addClass(['tag', 'badge', 'border'])
|
||||
.addClass(['tag', 'badge', 'align-text-top'])
|
||||
.css({color: getTextColour(options.colour), 'background-color': options.colour})
|
||||
.text(options.text)
|
||||
|
||||
|
|
|
@ -162,14 +162,14 @@ function deleteTag(url, tags, clicked) {
|
|||
statusNode: $statusNode,
|
||||
skipFeedback: true,
|
||||
}
|
||||
return AJAXApi.quickFetchAndPostForm(url, data, APIOptions).then((result) => {
|
||||
return AJAXApi.quickFetchAndPostForm(url, data, APIOptions).then((apiResult) => {
|
||||
let $container = $statusNode.closest('.tag-container-wrapper')
|
||||
refreshTagList(result, $container).then(($tagContainer) => {
|
||||
refreshTagList(apiResult, $container).then(($tagContainer) => {
|
||||
$container = $tagContainer // old container might not exist anymore since it was replaced after the refresh
|
||||
})
|
||||
const theToast = UI.toast({
|
||||
variant: 'success',
|
||||
title: result.message,
|
||||
title: apiResult.message,
|
||||
bodyHtml: $('<div/>').append(
|
||||
$('<span/>').text('Cancel untag operation.'),
|
||||
$('<button/>').addClass(['btn', 'btn-primary', 'btn-sm', 'ml-3']).text('Restore tag').click(function() {
|
||||
|
@ -193,15 +193,15 @@ function addTags(url, tags, $statusNode) {
|
|||
const APIOptions = {
|
||||
statusNode: $statusNode
|
||||
}
|
||||
return AJAXApi.quickFetchAndPostForm(url, data, APIOptions).then((result) => {
|
||||
return AJAXApi.quickFetchAndPostForm(url, data, APIOptions).then((apiResult) => {
|
||||
const $container = $statusNode.closest('.tag-container-wrapper')
|
||||
refreshTagList(result, $container)
|
||||
refreshTagList(apiResult, $container)
|
||||
}).catch((e) => {})
|
||||
}
|
||||
|
||||
function refreshTagList(result, $container) {
|
||||
const controllerName = result.url.split('/')[1]
|
||||
const entityId = result.data.id
|
||||
function refreshTagList(apiResult, $container) {
|
||||
const controllerName = apiResult.url.split('/')[1]
|
||||
const entityId = apiResult.data.id
|
||||
const url = `/${controllerName}/viewTags/${entityId}`
|
||||
return UI.reload(url, $container)
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue