chg: [tag] Continuation of integrating tagging plugin - WiP
- Filtering - CRUD of tagspull/72/head
parent
29595c6e22
commit
a4535ea42e
|
@ -35,6 +35,9 @@ class CRUDComponent extends Component
|
||||||
$options['filters'][] = 'quickFilter';
|
$options['filters'][] = 'quickFilter';
|
||||||
}
|
}
|
||||||
$options['filters'][] = 'filteringLabel';
|
$options['filters'][] = 'filteringLabel';
|
||||||
|
if ($this->taggingSupported()) {
|
||||||
|
$options['filters'][] = 'filteringTags';
|
||||||
|
}
|
||||||
|
|
||||||
$optionFilters = empty($options['filters']) ? [] : $options['filters'];
|
$optionFilters = empty($options['filters']) ? [] : $options['filters'];
|
||||||
foreach ($optionFilters as $i => $filter) {
|
foreach ($optionFilters as $i => $filter) {
|
||||||
|
@ -50,6 +53,9 @@ class CRUDComponent extends Component
|
||||||
if (!empty($options['contain'])) {
|
if (!empty($options['contain'])) {
|
||||||
$query->contain($options['contain']);
|
$query->contain($options['contain']);
|
||||||
}
|
}
|
||||||
|
if ($this->taggingSupported()) {
|
||||||
|
$query->contain('Tags');
|
||||||
|
}
|
||||||
if (!empty($options['fields'])) {
|
if (!empty($options['fields'])) {
|
||||||
$query->select($options['fields']);
|
$query->select($options['fields']);
|
||||||
}
|
}
|
||||||
|
@ -73,15 +79,17 @@ class CRUDComponent extends Component
|
||||||
$data = $this->Table->{$options['afterFind']}($data);
|
$data = $this->Table->{$options['afterFind']}($data);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!empty($options['contextFilters'])) {
|
$this->setFilteringContext($options['contextFilters'] ?? [], $params);
|
||||||
$this->setFilteringContext($options['contextFilters'], $params);
|
|
||||||
}
|
|
||||||
$this->Controller->set('data', $data);
|
$this->Controller->set('data', $data);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function filtering(): void
|
public function filtering(): void
|
||||||
{
|
{
|
||||||
|
if ($this->taggingSupported()) {
|
||||||
|
$this->Controller->set('taggingEnabled', true);
|
||||||
|
$this->setAllTags();
|
||||||
|
}
|
||||||
$filters = !empty($this->Controller->filters) ? $this->Controller->filters : [];
|
$filters = !empty($this->Controller->filters) ? $this->Controller->filters : [];
|
||||||
$this->Controller->set('filters', $filters);
|
$this->Controller->set('filters', $filters);
|
||||||
$this->Controller->viewBuilder()->setLayout('ajax');
|
$this->Controller->viewBuilder()->setLayout('ajax');
|
||||||
|
@ -246,8 +254,9 @@ class CRUDComponent extends Component
|
||||||
$this->getMetaTemplates();
|
$this->getMetaTemplates();
|
||||||
if ($this->taggingSupported()) {
|
if ($this->taggingSupported()) {
|
||||||
$params['contain'][] = 'Tags';
|
$params['contain'][] = 'Tags';
|
||||||
|
$this->setAllTags();
|
||||||
}
|
}
|
||||||
$data = $this->Table->get($id, isset($params['get']) ? $params['get'] : []);
|
$data = $this->Table->get($id, isset($params['get']) ? $params['get'] : $params);
|
||||||
$data = $this->getMetaFields($id, $data);
|
$data = $this->getMetaFields($id, $data);
|
||||||
if (!empty($params['fields'])) {
|
if (!empty($params['fields'])) {
|
||||||
$this->Controller->set('fields', $params['fields']);
|
$this->Controller->set('fields', $params['fields']);
|
||||||
|
@ -676,6 +685,8 @@ class CRUDComponent extends Component
|
||||||
{
|
{
|
||||||
$filteringLabel = !empty($params['filteringLabel']) ? $params['filteringLabel'] : '';
|
$filteringLabel = !empty($params['filteringLabel']) ? $params['filteringLabel'] : '';
|
||||||
unset($params['filteringLabel']);
|
unset($params['filteringLabel']);
|
||||||
|
$filteringTags = !empty($params['filteringTags']) && $this->taggingSupported() ? $params['filteringTags'] : '';
|
||||||
|
unset($params['filteringTags']);
|
||||||
$customFilteringFunction = '';
|
$customFilteringFunction = '';
|
||||||
$chosenFilter = '';
|
$chosenFilter = '';
|
||||||
if (!empty($options['contextFilters']['custom'])) {
|
if (!empty($options['contextFilters']['custom'])) {
|
||||||
|
@ -719,10 +730,26 @@ class CRUDComponent extends Component
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ($this->taggingSupported() && !empty($filteringTags)) {
|
||||||
|
$activeFilters['filteringTags'] = $filteringTags;
|
||||||
|
$query = $this->setTagFilters($query, $filteringTags);
|
||||||
|
}
|
||||||
|
|
||||||
$this->Controller->set('activeFilters', $activeFilters);
|
$this->Controller->set('activeFilters', $activeFilters);
|
||||||
return $query;
|
return $query;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected function setTagFilters($query, $tags)
|
||||||
|
{
|
||||||
|
$modelAlias = $this->Table->getAlias();
|
||||||
|
$subQuery = $this->Table->find('tagged', [
|
||||||
|
'label' => $tags,
|
||||||
|
'forceAnd' => true
|
||||||
|
])->select($modelAlias . '.id');
|
||||||
|
return $query = $query->where([$modelAlias . '.id IN' => $subQuery]);
|
||||||
|
}
|
||||||
|
|
||||||
protected function setNestedRelatedCondition($query, $filterParts, $filterValue)
|
protected function setNestedRelatedCondition($query, $filterParts, $filterValue)
|
||||||
{
|
{
|
||||||
$modelName = $filterParts[0];
|
$modelName = $filterParts[0];
|
||||||
|
|
|
@ -13,10 +13,12 @@ use Cake\ORM\TableRegistry;
|
||||||
|
|
||||||
class IndividualsController extends AppController
|
class IndividualsController extends AppController
|
||||||
{
|
{
|
||||||
|
public $filters = ['uuid', 'email', 'first_name', 'last_name', 'position', 'Organisations.id', 'Alignments.type'];
|
||||||
|
|
||||||
public function index()
|
public function index()
|
||||||
{
|
{
|
||||||
$this->CRUD->index([
|
$this->CRUD->index([
|
||||||
'filters' => ['uuid', 'email', 'first_name', 'last_name', 'position', 'Organisations.id', 'Alignments.type'],
|
'filters' => $this->filters,
|
||||||
'quickFilters' => ['uuid', 'email', 'first_name', 'last_name', 'position'],
|
'quickFilters' => ['uuid', 'email', 'first_name', 'last_name', 'position'],
|
||||||
'contextFilters' => [
|
'contextFilters' => [
|
||||||
'fields' => [
|
'fields' => [
|
||||||
|
@ -33,6 +35,11 @@ class IndividualsController extends AppController
|
||||||
$this->set('metaGroup', 'ContactDB');
|
$this->set('metaGroup', 'ContactDB');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function filtering()
|
||||||
|
{
|
||||||
|
$this->CRUD->filtering();
|
||||||
|
}
|
||||||
|
|
||||||
public function add()
|
public function add()
|
||||||
{
|
{
|
||||||
$this->CRUD->add();
|
$this->CRUD->add();
|
||||||
|
|
|
@ -0,0 +1,80 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Controller;
|
||||||
|
|
||||||
|
use App\Controller\AppController;
|
||||||
|
use Cake\Utility\Hash;
|
||||||
|
use Cake\Utility\Inflector;
|
||||||
|
use Cake\Utility\Text;
|
||||||
|
use Cake\Database\Expression\QueryExpression;
|
||||||
|
use Cake\Http\Exception\NotFoundException;
|
||||||
|
use Cake\Http\Exception\MethodNotAllowedException;
|
||||||
|
use Cake\Http\Exception\ForbiddenException;
|
||||||
|
use Cake\ORM\TableRegistry;
|
||||||
|
|
||||||
|
class TagsController extends AppController
|
||||||
|
{
|
||||||
|
|
||||||
|
public function initialize(): void
|
||||||
|
{
|
||||||
|
parent::initialize();
|
||||||
|
$this->Table = TableRegistry::getTableLocator()->get('Tags.Tags');
|
||||||
|
$this->CRUD->Table = $this->Table;
|
||||||
|
$this->CRUD->TableAlias = $this->CRUD->Table->getAlias();
|
||||||
|
$this->CRUD->ObjectAlias = Inflector::singularize($this->CRUD->TableAlias);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function index()
|
||||||
|
{
|
||||||
|
$this->CRUD->index([
|
||||||
|
'filters' => ['label', 'colour'],
|
||||||
|
'quickFilters' => [['label' => true], 'colour']
|
||||||
|
]);
|
||||||
|
$responsePayload = $this->CRUD->getResponsePayload();
|
||||||
|
if (!empty($responsePayload)) {
|
||||||
|
return $responsePayload;
|
||||||
|
}
|
||||||
|
$this->set('metaGroup', $this->isAdmin ? 'Administration' : 'Cerebrate');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function add()
|
||||||
|
{
|
||||||
|
$this->CRUD->add();
|
||||||
|
$responsePayload = $this->CRUD->getResponsePayload();
|
||||||
|
if (!empty($responsePayload)) {
|
||||||
|
return $responsePayload;
|
||||||
|
}
|
||||||
|
$this->set('metaGroup', $this->isAdmin ? 'Administration' : 'Cerebrate');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function view($id)
|
||||||
|
{
|
||||||
|
$this->CRUD->view($id);
|
||||||
|
$responsePayload = $this->CRUD->getResponsePayload();
|
||||||
|
if (!empty($responsePayload)) {
|
||||||
|
return $responsePayload;
|
||||||
|
}
|
||||||
|
$this->set('metaGroup', $this->isAdmin ? 'Administration' : 'Cerebrate');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function edit($id)
|
||||||
|
{
|
||||||
|
$this->CRUD->edit($id);
|
||||||
|
$responsePayload = $this->CRUD->getResponsePayload();
|
||||||
|
if (!empty($responsePayload)) {
|
||||||
|
return $responsePayload;
|
||||||
|
}
|
||||||
|
$this->set('metaGroup', $this->isAdmin ? 'Administration' : 'Cerebrate');
|
||||||
|
$this->render('add');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function delete($id)
|
||||||
|
{
|
||||||
|
$this->CRUD->delete($id);
|
||||||
|
$responsePayload = $this->CRUD->getResponsePayload();
|
||||||
|
if (!empty($responsePayload)) {
|
||||||
|
return $responsePayload;
|
||||||
|
}
|
||||||
|
$this->set('metaGroup', $this->isAdmin ? 'Administration' : 'Cerebrate');
|
||||||
|
}
|
||||||
|
}
|
|
@ -17,6 +17,7 @@ class IndividualsTable extends AppTable
|
||||||
$this->addBehavior('Tags.Tag', [
|
$this->addBehavior('Tags.Tag', [
|
||||||
'taggedCounter' => false,
|
'taggedCounter' => false,
|
||||||
'strategy' => 'array',
|
'strategy' => 'array',
|
||||||
|
'finderField' => 'label',
|
||||||
]);
|
]);
|
||||||
$this->hasMany(
|
$this->hasMany(
|
||||||
'Alignments',
|
'Alignments',
|
||||||
|
|
|
@ -33,14 +33,20 @@ class TagHelper extends Helper
|
||||||
'data-text-colour' => h($tag['text_colour']),
|
'data-text-colour' => h($tag['text_colour']),
|
||||||
];
|
];
|
||||||
}, $options['allTags']) : [];
|
}, $options['allTags']) : [];
|
||||||
$selectConfig = [
|
$classes = ['tag-input', 'flex-grow-1'];
|
||||||
'multiple' => true,
|
$url = '';
|
||||||
'class' => ['tag-input', 'd-none'],
|
if (!empty($this->getConfig('editable'))) {
|
||||||
'data-url' => $this->Url->build([
|
$url = $this->Url->build([
|
||||||
'controller' => $this->getView()->getName(),
|
'controller' => $this->getView()->getName(),
|
||||||
'action' => 'tag',
|
'action' => 'tag',
|
||||||
$this->getView()->get('entity')['id']
|
$this->getView()->get('entity')['id']
|
||||||
]),
|
]);
|
||||||
|
$classes[] = 'd-none';
|
||||||
|
}
|
||||||
|
$selectConfig = [
|
||||||
|
'multiple' => true,
|
||||||
|
'class' => $classes,
|
||||||
|
'data-url' => $url,
|
||||||
];
|
];
|
||||||
return $this->Form->select($field, $values, $selectConfig);
|
return $this->Form->select($field, $values, $selectConfig);
|
||||||
}
|
}
|
||||||
|
@ -48,24 +54,26 @@ class TagHelper extends Helper
|
||||||
protected function picker(array $options = [])
|
protected function picker(array $options = [])
|
||||||
{
|
{
|
||||||
$html = $this->Tag->control($options);
|
$html = $this->Tag->control($options);
|
||||||
$html .= $this->Bootstrap->button([
|
if (!empty($this->getConfig('editable'))) {
|
||||||
'size' => 'sm',
|
$html .= $this->Bootstrap->button([
|
||||||
'icon' => 'plus',
|
'size' => 'sm',
|
||||||
'variant' => 'secondary',
|
'icon' => 'plus',
|
||||||
'class' => ['badge'],
|
'variant' => 'secondary',
|
||||||
'params' => [
|
'class' => ['badge'],
|
||||||
'onclick' => 'createTagPicker(this)',
|
'params' => [
|
||||||
]
|
'onclick' => 'createTagPicker(this)',
|
||||||
]);
|
]
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
$html .= '<script>$(document).ready(function() { initSelect2Pickers() })</script>';
|
||||||
return $html;
|
return $html;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function tags(array $options = [])
|
public function tags(array $tags = [], array $options = [])
|
||||||
{
|
{
|
||||||
$this->_config = array_merge($this->defaultConfig, $options);
|
$this->_config = array_merge($this->defaultConfig, $options);
|
||||||
$tags = !empty($options['tags']) ? $options['tags'] : [];
|
|
||||||
$html = '<div class="tag-container-wrapper">';
|
$html = '<div class="tag-container-wrapper">';
|
||||||
$html .= '<div class="tag-container my-1">';
|
$html .= '<div class="tag-container my-1 d-flex">';
|
||||||
$html .= '<div class="tag-list d-inline-block">';
|
$html .= '<div class="tag-list d-inline-block">';
|
||||||
foreach ($tags as $tag) {
|
foreach ($tags as $tag) {
|
||||||
if (is_object($tag)) {
|
if (is_object($tag)) {
|
||||||
|
|
|
@ -23,7 +23,8 @@ echo $this->element('genericElements/IndexTable/index_table', [
|
||||||
'button' => __('Filter'),
|
'button' => __('Filter'),
|
||||||
'placeholder' => __('Enter value to search'),
|
'placeholder' => __('Enter value to search'),
|
||||||
'data' => '',
|
'data' => '',
|
||||||
'searchKey' => 'value'
|
'searchKey' => 'value',
|
||||||
|
'allowFilering' => true
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
],
|
],
|
||||||
|
@ -54,6 +55,11 @@ echo $this->element('genericElements/IndexTable/index_table', [
|
||||||
'element' => 'alignments',
|
'element' => 'alignments',
|
||||||
'scope' => $alignmentScope
|
'scope' => $alignmentScope
|
||||||
],
|
],
|
||||||
|
[
|
||||||
|
'name' => __('Tags'),
|
||||||
|
'data_path' => 'tags',
|
||||||
|
'element' => 'tags',
|
||||||
|
],
|
||||||
[
|
[
|
||||||
'name' => __('UUID'),
|
'name' => __('UUID'),
|
||||||
'sort' => 'uuid',
|
'sort' => 'uuid',
|
||||||
|
|
|
@ -0,0 +1,22 @@
|
||||||
|
<?php
|
||||||
|
echo $this->element('genericElements/Form/genericForm', array(
|
||||||
|
'data' => array(
|
||||||
|
'description' => __('Individuals are natural persons. They are meant to describe the basic information about an individual that may or may not be a user of this community. Users in genral require an individual object to identify the person behind them - however, no user account is required to store information about an individual. Individuals can have affiliations to organisations and broods as well as cryptographic keys, using which their messages can be verified and which can be used to securely contact them.'),
|
||||||
|
'model' => 'Organisations',
|
||||||
|
'fields' => array(
|
||||||
|
array(
|
||||||
|
'field' => 'label'
|
||||||
|
),
|
||||||
|
array(
|
||||||
|
'field' => 'colour',
|
||||||
|
'type' => 'color',
|
||||||
|
),
|
||||||
|
),
|
||||||
|
'metaTemplates' => empty($metaTemplates) ? [] : $metaTemplates,
|
||||||
|
'submit' => array(
|
||||||
|
'action' => $this->request->getParam('action')
|
||||||
|
)
|
||||||
|
)
|
||||||
|
));
|
||||||
|
?>
|
||||||
|
</div>
|
|
@ -0,0 +1,79 @@
|
||||||
|
<?php
|
||||||
|
echo $this->element('genericElements/IndexTable/index_table', [
|
||||||
|
'data' => [
|
||||||
|
'data' => $data,
|
||||||
|
'top_bar' => [
|
||||||
|
'children' => [
|
||||||
|
[
|
||||||
|
'type' => 'simple',
|
||||||
|
'children' => [
|
||||||
|
'data' => [
|
||||||
|
'type' => 'simple',
|
||||||
|
'text' => __('Add tag'),
|
||||||
|
'popover_url' => '/tags/add'
|
||||||
|
]
|
||||||
|
]
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'type' => 'context_filters',
|
||||||
|
'context_filters' => $filteringContexts
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'type' => 'search',
|
||||||
|
'button' => __('Filter'),
|
||||||
|
'placeholder' => __('Enter value to search'),
|
||||||
|
'data' => '',
|
||||||
|
'searchKey' => 'value'
|
||||||
|
]
|
||||||
|
]
|
||||||
|
],
|
||||||
|
'fields' => [
|
||||||
|
[
|
||||||
|
'name' => '#',
|
||||||
|
'sort' => 'id',
|
||||||
|
'data_path' => 'id',
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'name' => __('Label'),
|
||||||
|
'sort' => 'label',
|
||||||
|
'element' => 'tag'
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'name' => __('Counter'),
|
||||||
|
'sort' => 'couter',
|
||||||
|
'data_path' => 'counter',
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'name' => __('Colour'),
|
||||||
|
'sort' => 'colour',
|
||||||
|
'data_path' => 'colour',
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'name' => __('Created'),
|
||||||
|
'sort' => 'created',
|
||||||
|
'data_path' => 'created',
|
||||||
|
],
|
||||||
|
],
|
||||||
|
'title' => __('Tag index'),
|
||||||
|
'description' => __('The list of all tags existing on this instance'),
|
||||||
|
'actions' => [
|
||||||
|
[
|
||||||
|
'url' => '/tags/view',
|
||||||
|
'url_params_data_paths' => ['id'],
|
||||||
|
'icon' => 'eye'
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'open_modal' => '/tags/edit/[onclick_params_data_path]',
|
||||||
|
'modal_params_data_path' => 'id',
|
||||||
|
'icon' => 'edit'
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'open_modal' => '/tags/delete/[onclick_params_data_path]',
|
||||||
|
'modal_params_data_path' => 'id',
|
||||||
|
'icon' => 'trash'
|
||||||
|
],
|
||||||
|
]
|
||||||
|
]
|
||||||
|
]);
|
||||||
|
echo '</div>';
|
||||||
|
?>
|
|
@ -0,0 +1,32 @@
|
||||||
|
<?php
|
||||||
|
echo $this->element(
|
||||||
|
'/genericElements/SingleViews/single_view',
|
||||||
|
[
|
||||||
|
'data' => $entity,
|
||||||
|
'fields' => [
|
||||||
|
[
|
||||||
|
'key' => __('ID'),
|
||||||
|
'path' => 'id'
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'key' => __('Label'),
|
||||||
|
'path' => '',
|
||||||
|
'type' => 'tag',
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'key' => __('Counter'),
|
||||||
|
'path' => 'counter',
|
||||||
|
'type' => 'json',
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'key' => __('Colour'),
|
||||||
|
'path' => 'colour',
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'key' => __('Created'),
|
||||||
|
'path' => 'created',
|
||||||
|
],
|
||||||
|
],
|
||||||
|
'children' => []
|
||||||
|
]
|
||||||
|
);
|
|
@ -1,8 +1,8 @@
|
||||||
<?php
|
<?php
|
||||||
$tagsHtml = $this->Tag->tags([
|
$tagsHtml = $this->Tag->tags($entity['tags'], [
|
||||||
'allTags' => $allTags,
|
'allTags' => [],
|
||||||
'tags' => $entity['tags'],
|
|
||||||
'picker' => true,
|
'picker' => true,
|
||||||
|
'editable' => true,
|
||||||
]);
|
]);
|
||||||
?>
|
?>
|
||||||
<div class="form-group row">
|
<div class="form-group row">
|
||||||
|
|
|
@ -0,0 +1,6 @@
|
||||||
|
<?php
|
||||||
|
$tag = $row;
|
||||||
|
echo $this->Tag->tag($tag, [
|
||||||
|
]);
|
||||||
|
|
||||||
|
?>
|
|
@ -1,17 +1,5 @@
|
||||||
<?php
|
<?php
|
||||||
$tags = $this->Hash->extract($row, $field['data_path']);
|
$tags = $this->Hash->extract($row, $field['data_path']);
|
||||||
if (!empty($tags)) {
|
echo $this->Tag->tags($tags, [
|
||||||
if (empty($tags[0])) {
|
'tags'
|
||||||
$tags = array($tags);
|
]);
|
||||||
}
|
|
||||||
echo $this->element(
|
|
||||||
'ajaxTags',
|
|
||||||
array(
|
|
||||||
'attributeId' => 0,
|
|
||||||
'tags' => $tags,
|
|
||||||
'tagAccess' => false,
|
|
||||||
'static_tags_only' => 1
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
?>
|
|
||||||
|
|
|
@ -0,0 +1,3 @@
|
||||||
|
<?php
|
||||||
|
echo $this->Tag->tag($data, [
|
||||||
|
]);
|
|
@ -1,9 +1,8 @@
|
||||||
<?php
|
<?php
|
||||||
// $tags = Cake\Utility\Hash::extract($data, $field['path']);
|
// $tags = Cake\Utility\Hash::extract($data, $field['path']);
|
||||||
$tags = Cake\Utility\Hash::get($data, 'tags');
|
$tags = Cake\Utility\Hash::get($data, 'tags');
|
||||||
echo $this->Tag->tags([
|
echo $this->Tag->tags($tags, [
|
||||||
'allTags' => $allTags,
|
'allTags' => $allTags,
|
||||||
'tags' => $tags,
|
|
||||||
'picker' => true,
|
'picker' => true,
|
||||||
'editable' => true,
|
'editable' => true,
|
||||||
]);
|
]);
|
||||||
|
|
|
@ -15,7 +15,7 @@ $filteringForm = $this->Bootstrap->table(
|
||||||
[
|
[
|
||||||
'labelHtml' => sprintf('%s %s',
|
'labelHtml' => sprintf('%s %s',
|
||||||
__('Value'),
|
__('Value'),
|
||||||
sprintf('<sup class="fa fa-info" title="%s"><sup>', __('Supports strict match and LIKE match with the `%` character. Example: `%.com`'))
|
sprintf('<sup class="fa fa-info" title="%s"><sup>', __('Supports strict matches and LIKE matches with the `%` character. Example: `%.com`'))
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
__('Action')
|
__('Action')
|
||||||
|
@ -23,12 +23,28 @@ $filteringForm = $this->Bootstrap->table(
|
||||||
'items' => []
|
'items' => []
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
if ($taggingEnabled) {
|
||||||
|
$helpText = $this->Bootstrap->genNode('sup', [
|
||||||
|
'class' => ['ml-1 fa fa-info'],
|
||||||
|
'title' => __('Supports negation matches (with the `!` character) and LIKE matches (with the `%` character). Example: `!exportable`, `%able`'),
|
||||||
|
]);
|
||||||
|
$filteringTags = $this->Bootstrap->genNode('h5', [], __('Tags') . $helpText);
|
||||||
|
$filteringTags .= $this->Tag->tags([], [
|
||||||
|
'allTags' => $allTags,
|
||||||
|
'picker' => true,
|
||||||
|
'editable' => false,
|
||||||
|
]);
|
||||||
|
} else {
|
||||||
|
$filteringTags = '';
|
||||||
|
}
|
||||||
|
$modalBody = sprintf('%s%s', $filteringForm, $filteringTags);
|
||||||
|
|
||||||
|
|
||||||
echo $this->Bootstrap->modal([
|
echo $this->Bootstrap->modal([
|
||||||
'title' => __('Filtering options for {0}', Inflector::singularize($this->request->getParam('controller'))),
|
'title' => __('Filtering options for {0}', Inflector::singularize($this->request->getParam('controller'))),
|
||||||
'size' => 'lg',
|
'size' => 'lg',
|
||||||
'type' => 'confirm',
|
'type' => 'confirm',
|
||||||
'bodyHtml' => $filteringForm,
|
'bodyHtml' => $modalBody,
|
||||||
'confirmText' => __('Filter'),
|
'confirmText' => __('Filter'),
|
||||||
'confirmFunction' => 'filterIndex'
|
'confirmFunction' => 'filterIndex'
|
||||||
]);
|
]);
|
||||||
|
@ -54,7 +70,9 @@ echo $this->Bootstrap->modal([
|
||||||
}
|
}
|
||||||
activeFilters[fullFilter] = rowData['value']
|
activeFilters[fullFilter] = rowData['value']
|
||||||
})
|
})
|
||||||
const searchParam = (new URLSearchParams(activeFilters)).toString();
|
$select = modalObject.$modal.find('select.tag-input')
|
||||||
|
activeFilters['filteringTags'] = $select.select2('data').map(tag => tag.text)
|
||||||
|
const searchParam = jQuery.param(activeFilters);
|
||||||
const url = `/${controller}/${action}?${searchParam}`
|
const url = `/${controller}/${action}?${searchParam}`
|
||||||
|
|
||||||
const randomValue = getRandomValue()
|
const randomValue = getRandomValue()
|
||||||
|
@ -70,6 +88,8 @@ echo $this->Bootstrap->modal([
|
||||||
addControlRow($filteringTable)
|
addControlRow($filteringTable)
|
||||||
const randomValue = getRandomValue()
|
const randomValue = getRandomValue()
|
||||||
const activeFilters = $(`#toggleFilterButton-${randomValue}`).data('activeFilters')
|
const activeFilters = $(`#toggleFilterButton-${randomValue}`).data('activeFilters')
|
||||||
|
const tags = activeFilters['filteringTags'] !== undefined ? Object.assign({}, activeFilters)['filteringTags'] : []
|
||||||
|
delete activeFilters['filteringTags']
|
||||||
for (let [field, value] of Object.entries(activeFilters)) {
|
for (let [field, value] of Object.entries(activeFilters)) {
|
||||||
const fieldParts = field.split(' ')
|
const fieldParts = field.split(' ')
|
||||||
let operator = '='
|
let operator = '='
|
||||||
|
@ -81,6 +101,17 @@ echo $this->Bootstrap->modal([
|
||||||
}
|
}
|
||||||
addFilteringRow($filteringTable, field, value, operator)
|
addFilteringRow($filteringTable, field, value, operator)
|
||||||
}
|
}
|
||||||
|
$select = $filteringTable.closest('.modal-body').find('select.tag-input')
|
||||||
|
let passedTags = []
|
||||||
|
tags.forEach(tagname => {
|
||||||
|
if (!$select.find("option[value='" + tagname + "']")) {
|
||||||
|
passedTags.push(new Option(tagname, tagname, true, true))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
$select
|
||||||
|
.append(passedTags)
|
||||||
|
.val(tags)
|
||||||
|
.trigger('change')
|
||||||
}
|
}
|
||||||
|
|
||||||
function addControlRow($filteringTable) {
|
function addControlRow($filteringTable) {
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
<?php
|
<?php
|
||||||
echo $this->Tag->tags([
|
echo $this->Tag->tags($entity->tags, [
|
||||||
'allTags' => $allTags,
|
'allTags' => $allTags,
|
||||||
'tags' => $entity->tags,
|
|
||||||
'picker' => true,
|
'picker' => true,
|
||||||
'editable' => true,
|
'editable' => true,
|
||||||
]);
|
]);
|
||||||
|
|
|
@ -163,4 +163,8 @@ input[type="checkbox"]:disabled.change-cursor {
|
||||||
.picker-container .picker-action .btn:first-child {
|
.picker-container .picker-action .btn:first-child {
|
||||||
border-top-left-radius: 0 !important;
|
border-top-left-radius: 0 !important;
|
||||||
border-bottom-left-radius: 0 !important;
|
border-bottom-left-radius: 0 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tag {
|
||||||
|
filter: drop-shadow(2px 2px 2px rgba(0, 0, 0, 0.5));
|
||||||
}
|
}
|
|
@ -117,47 +117,37 @@ function getTextColour(hex) {
|
||||||
|
|
||||||
function createTagPicker(clicked) {
|
function createTagPicker(clicked) {
|
||||||
|
|
||||||
function templateTag(state) {
|
|
||||||
if (!state.id) {
|
|
||||||
return state.label;
|
|
||||||
}
|
|
||||||
if (state.colour === undefined) {
|
|
||||||
state.colour = $(state.element).data('colour')
|
|
||||||
}
|
|
||||||
return HtmlHelper.tag(state)
|
|
||||||
}
|
|
||||||
|
|
||||||
function closePicker($select, $container) {
|
function closePicker($select, $container) {
|
||||||
$select.appendTo($container)
|
$select.appendTo($container)
|
||||||
$container.parent().find('.picker-container').remove()
|
$container.parent().find('.picker-container').remove()
|
||||||
}
|
}
|
||||||
|
|
||||||
const $clicked = $(clicked)
|
function getEditableButtons($select, $container) {
|
||||||
const $container = $clicked.closest('.tag-container')
|
const $saveButton = $('<button></button>').addClass(['btn btn-primary btn-sm', 'align-self-start']).attr('type', 'button')
|
||||||
const $select = $container.parent().find('select.tag-input').removeClass('d-none').addClass('flex-grow-1')
|
|
||||||
closePicker($select, $container)
|
|
||||||
const $pickerContainer = $('<div></div>').addClass(['picker-container', 'd-flex'])
|
|
||||||
const $saveButton = $('<button></button>').addClass(['btn btn-primary btn-sm', 'align-self-start']).attr('type', 'button')
|
|
||||||
.append($('<span></span>').text('Save').prepend($('<i></i>').addClass('fa fa-save mr-1')))
|
.append($('<span></span>').text('Save').prepend($('<i></i>').addClass('fa fa-save mr-1')))
|
||||||
.click(function() {
|
.click(function() {
|
||||||
const tags = $select.select2('data').map(tag => tag.text)
|
const tags = $select.select2('data').map(tag => tag.text)
|
||||||
addTags($select.data('url'), tags, $(this))
|
addTags($select.data('url'), tags, $(this))
|
||||||
})
|
})
|
||||||
const $cancelButton = $('<button></button>').addClass(['btn btn-secondary btn-sm', 'align-self-start']).attr('type', 'button')
|
const $cancelButton = $('<button></button>').addClass(['btn btn-secondary btn-sm', 'align-self-start']).attr('type', 'button')
|
||||||
.append($('<span></span>').text('Cancel').prepend($('<i></i>').addClass('fa fa-times mr-1')))
|
.append($('<span></span>').text('Cancel').prepend($('<i></i>').addClass('fa fa-times mr-1')))
|
||||||
.click(function() {
|
.click(function() {
|
||||||
closePicker($select, $container)
|
closePicker($select, $container)
|
||||||
})
|
})
|
||||||
const $buttons = $('<span></span>').addClass(['picker-action', 'btn-group']).append($saveButton, $cancelButton)
|
const $buttons = $('<span></span>').addClass(['picker-action', 'btn-group']).append($saveButton, $cancelButton)
|
||||||
|
return $buttons
|
||||||
|
}
|
||||||
|
|
||||||
|
const $clicked = $(clicked)
|
||||||
|
const $container = $clicked.closest('.tag-container')
|
||||||
|
const $select = $container.parent().find('select.tag-input').removeClass('d-none')//.addClass('flex-grow-1')
|
||||||
|
closePicker($select, $container)
|
||||||
|
const $pickerContainer = $('<div></div>').addClass(['picker-container', 'd-flex'])
|
||||||
|
|
||||||
$select.prependTo($pickerContainer)
|
$select.prependTo($pickerContainer)
|
||||||
$pickerContainer.append($buttons)
|
$pickerContainer.append(getEditableButtons($select, $container))
|
||||||
$container.parent().append($pickerContainer)
|
$container.parent().append($pickerContainer)
|
||||||
$select.select2({
|
initSelect2Picker($select)
|
||||||
placeholder: 'Pick a tag',
|
|
||||||
tags: true,
|
|
||||||
templateResult: templateTag,
|
|
||||||
templateSelection: templateTag,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function deleteTag(url, tag, clicked) {
|
function deleteTag(url, tag, clicked) {
|
||||||
|
@ -213,6 +203,35 @@ function refreshTagList(result, $container) {
|
||||||
return UI.reload(url, $container)
|
return UI.reload(url, $container)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function initSelect2Pickers() {
|
||||||
|
$('select.tag-input').each(function() {
|
||||||
|
if (!$(this).hasClass("select2-hidden-accessible")) {
|
||||||
|
initSelect2Picker($(this))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function initSelect2Picker($select) {
|
||||||
|
|
||||||
|
function templateTag(state) {
|
||||||
|
if (!state.id) {
|
||||||
|
return state.label;
|
||||||
|
}
|
||||||
|
if (state.colour === undefined) {
|
||||||
|
state.colour = $(state.element).data('colour')
|
||||||
|
}
|
||||||
|
return HtmlHelper.tag(state)
|
||||||
|
}
|
||||||
|
|
||||||
|
$select.select2({
|
||||||
|
placeholder: 'Pick a tag',
|
||||||
|
tags: true,
|
||||||
|
width: '100%',
|
||||||
|
templateResult: templateTag,
|
||||||
|
templateSelection: templateTag,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
var UI
|
var UI
|
||||||
$(document).ready(() => {
|
$(document).ready(() => {
|
||||||
|
|
Loading…
Reference in New Issue