new: [mailing-list] Added mailing list feature - WiP

pull/93/head
Sami Mokaddem 2021-10-25 16:20:36 +02:00
parent b9bcb14bea
commit fe9fbe2e99
No known key found for this signature in database
GPG Key ID: 164C473F627A06FA
17 changed files with 788 additions and 13 deletions

View File

@ -0,0 +1,122 @@
<?php
declare(strict_types=1);
use Migrations\AbstractMigration;
use Phinx\Db\Adapter\MysqlAdapter;
class MailingLists extends AbstractMigration
{
/**
* Change Method.
*
* More information on this method is available here:
* https://book.cakephp.org/phinx/0/en/migrations.html#the-change-method
* @return void
*/
public $autoId = false; // turn off automatic `id` column create. We want it to be `int(10) unsigned`
public function change()
{
$mailinglists = $this->table('mailing_lists', [
'signed' => false,
'collation' => 'utf8mb4_unicode_ci',
]);
$mailinglists
->addColumn('id', 'integer', [
'autoIncrement' => true,
'limit' => 10,
'signed' => false,
])
->addPrimaryKey('id')
->addColumn('uuid', 'uuid', [
'default' => null,
'null' => false,
])
->addColumn('name', 'string', [
'default' => null,
'null' => false,
'limit' => 191,
'comment' => 'The name of the mailing list',
])
->addColumn('recipients', 'string', [
'default' => null,
'null' => true,
'limit' => 191,
'comment' => 'Human-readable description of who the intended recipients.',
])
->addColumn('description', 'text', [
'default' => null,
'null' => true,
'comment' => 'Additional description of the mailing list'
])
->addColumn('user_id', 'integer', [
'default' => null,
'null' => true,
'signed' => false,
'length' => 10,
])
->addColumn('active', 'boolean', [
'default' => 0,
'null' => false,
])
->addColumn('deleted', 'boolean', [
'default' => 0,
'null' => false,
])
->addColumn('created', 'datetime', [
'default' => null,
'null' => false,
])
->addColumn('modified', 'datetime', [
'default' => null,
'null' => false,
]);
$mailinglists->addForeignKey('user_id', 'users', 'id', ['delete' => 'CASCADE', 'update' => 'CASCADE']);
$mailinglists->addIndex(['uuid'], ['unique' => true])
->addIndex('name')
->addIndex('recipients')
->addIndex('user_id')
->addIndex('active')
->addIndex('deleted')
->addIndex('created')
->addIndex('modified');
$mailinglists->create();
$mailinglists_individuals = $this->table('mailing_lists_individuals', [
'signed' => false,
'collation' => 'utf8mb4_unicode_ci',
]);
$mailinglists_individuals
->addColumn('id', 'integer', [
'autoIncrement' => true,
'limit' => 10,
'signed' => false,
])
->addPrimaryKey('id')
->addColumn('mailing_list_id', 'integer', [
'default' => null,
'null' => true,
'signed' => false,
'length' => 10,
])
->addColumn('individual_id', 'integer', [
'default' => null,
'null' => true,
'signed' => false,
'length' => 10,
]);
$mailinglists_individuals->addIndex(['mailing_list_id', 'individual_id'], ['unique' => true]);
$mailinglists_individuals->create();
}
}

View File

@ -33,7 +33,7 @@ class TagHelper extends Helper
'data-text-colour' => h($tag['text_colour']), 'data-text-colour' => h($tag['text_colour']),
]; ];
}, $options['allTags']) : []; }, $options['allTags']) : [];
$classes = ['tag-input', 'flex-grow-1']; $classes = ['select2-input', 'flex-grow-1'];
$url = ''; $url = '';
if (!empty($this->getConfig('editable'))) { if (!empty($this->getConfig('editable'))) {
$url = $this->Url->build([ $url = $this->Url->build([

View File

@ -23,7 +23,7 @@ function createTagPicker(clicked) {
const $clicked = $(clicked) const $clicked = $(clicked)
const $container = $clicked.closest('.tag-container') const $container = $clicked.closest('.tag-container')
const $select = $container.parent().find('select.tag-input').removeClass('d-none') const $select = $container.parent().find('select.select2-input').removeClass('d-none')
closePicker($select, $container) closePicker($select, $container)
const $pickerContainer = $('<div></div>').addClass(['picker-container', 'd-flex']) const $pickerContainer = $('<div></div>').addClass(['picker-container', 'd-flex'])
@ -90,7 +90,7 @@ function refreshTagList(apiResult, $container) {
} }
function initSelect2Pickers() { function initSelect2Pickers() {
$('select.tag-input').each(function() { $('select.select2-input').each(function() {
if (!$(this).hasClass("select2-hidden-accessible")) { if (!$(this).hasClass("select2-hidden-accessible")) {
initSelect2Picker($(this)) initSelect2Picker($(this))
} }

View File

@ -38,6 +38,11 @@ class Sidemenu {
'label' => __('Sharing Groups'), 'label' => __('Sharing Groups'),
'icon' => $this->iconTable['SharingGroups'], 'icon' => $this->iconTable['SharingGroups'],
'url' => '/sharingGroups/index', 'url' => '/sharingGroups/index',
],
'MailingLists' => [
'label' => __('Mailing Lists'),
'icon' => $this->iconTable['MailingLists'],
'url' => '/mailingLists/index',
] ]
], ],
__('Synchronisation') => [ __('Synchronisation') => [

View File

@ -24,6 +24,7 @@ class NavigationComponent extends Component
'Organisations' => 'building', 'Organisations' => 'building',
'EncryptionKeys' => 'key', 'EncryptionKeys' => 'key',
'SharingGroups' => 'user-friends', 'SharingGroups' => 'user-friends',
'MailingLists' => 'mail-bulk',
'Broods' => 'network-wired', 'Broods' => 'network-wired',
'Roles' => 'id-badge', 'Roles' => 'id-badge',
'Users' => 'users', 'Users' => 'users',
@ -158,6 +159,7 @@ class NavigationComponent extends Component
'Tags', 'Tags',
'LocalTools', 'LocalTools',
'UserSettings', 'UserSettings',
'MailingLists',
]; ];
foreach ($CRUDControllers as $controller) { foreach ($CRUDControllers as $controller) {
$bcf->setDefaultCRUDForModel($controller); $bcf->setDefaultCRUDForModel($controller);

View File

@ -0,0 +1,196 @@
<?php
namespace App\Controller;
use App\Controller\AppController;
use Cake\Utility\Inflector;
use Cake\Utility\Hash;
use Cake\Utility\Text;
use \Cake\Database\Expression\QueryExpression;
use Cake\Error\Debugger;
class MailingListsController extends AppController
{
public $filterFields = ['MailingLists.uuid', 'MailingLists.name', 'description', 'releasability'];
public $quickFilterFields = ['MailingLists.uuid', ['MailingLists.name' => true], ['description' => true], ['releasability' => true]];
public $containFields = ['Users', 'Individuals'];
public function index()
{
$this->CRUD->index([
'contain' => $this->containFields,
'filters' => $this->filterFields,
'quickFilters' => $this->quickFilterFields
]);
$responsePayload = $this->CRUD->getResponsePayload();
if (!empty($responsePayload)) {
return $responsePayload;
}
}
public function add()
{
$this->CRUD->add([
'override' => [
'user_id' => $this->ACL->getUser()['id']
]
]);
$responsePayload = $this->CRUD->getResponsePayload();
if (!empty($responsePayload)) {
return $responsePayload;
}
}
public function view($id)
{
$this->CRUD->view($id, [
'contain' => $this->containFields
]);
$responsePayload = $this->CRUD->getResponsePayload();
if (!empty($responsePayload)) {
return $responsePayload;
}
}
public function edit($id = false)
{
$this->CRUD->edit($id);
$responsePayload = $this->CRUD->getResponsePayload();
if (!empty($responsePayload)) {
return $responsePayload;
}
$this->render('add');
}
public function delete($id)
{
$this->CRUD->delete($id);
$responsePayload = $this->CRUD->getResponsePayload();
if (!empty($responsePayload)) {
return $responsePayload;
}
}
public function listIndividuals($mailinglist_id)
{
$individuals = $this->MailingLists->get($mailinglist_id, [
'contain' => 'Individuals'
])->individuals;
$params = $this->ParamHandler->harvestParams(['quickFilter']);
if (!empty($params['quickFilter'])) {
// foreach ($sharingGroup['sharing_group_orgs'] as $k => $org) {
// if (strpos($org['name'], $params['quickFilter']) === false) {
// unset($sharingGroup['sharing_group_orgs'][$k]);
// }
// }
// $sharingGroup['sharing_group_orgs'] = array_values($sharingGroup['sharing_group_orgs']);
}
if ($this->ParamHandler->isRest()) {
return $this->RestResponse->viewData($individuals, 'json');
}
$this->set('mailing_list_id', $mailinglist_id);
$this->set('individuals', $individuals);
}
public function addIndividual($mailinglist_id)
{
$mailingList = $this->MailingLists->get($mailinglist_id, [
'contain' => 'Individuals'
]);
$conditions = [];
$dropdownData = [
'individuals' => $this->MailingLists->Individuals->getTarget()->find('list', [
'sort' => ['name' => 'asc'],
'conditions' => $conditions
])
];
if ($this->request->is('post') || $this->request->is('put')) {
$memberIDs = $this->request->getData()['individuals'];
$members = $this->MailingLists->Individuals->getTarget()->find()->where([
'id IN' => $memberIDs
])->all()->toArray();
$success = (bool)$this->MailingLists->Individuals->link($mailingList, $members);
if ($success) {
$message = __n('%s individual added to the mailing list.', '%s Individuals added to the mailing list.', count($members), count($members));
$mailingList = $this->MailingLists->get($mailingList->id);
} else {
$message = __n('The individual could not be added to the mailing list.', 'The Individuals could not be added to the mailing list.', count($members));
}
$this->CRUD->setResponseForController('add_individuals', $success, $message, $mailingList, $mailingList->getErrors());
$responsePayload = $this->CRUD->getResponsePayload();
if (!empty($responsePayload)) {
return $responsePayload;
}
}
$this->set(compact('dropdownData'));
$this->set('mailinglist_id', $mailinglist_id);
$this->set('mailingList', $mailingList);
}
public function removeIndividual($mailinglist_id, $individual_id=null)
{
$mailingList = $this->MailingLists->get($mailinglist_id, [
'contain' => 'Individuals'
]);
$individual = [];
if (!is_null($individual_id)) {
$individual = $this->MailingLists->Individuals->get($individual_id);
}
if ($this->request->is('post') || $this->request->is('delete')) {
$success = false;
if (!is_null($individual_id)) {
$individual = $this->MailingLists->Individuals->get($individual_id);
$success = (bool)$this->MailingLists->Individuals->unlink($mailingList, [$individual]);
if ($success) {
$message = __('Individual removed from the mailing list.');
$mailingList = $this->MailingLists->get($mailingList->id);
} else {
$message = __n('Individual could not be removed from the mailing list.');
}
$this->CRUD->setResponseForController('remove_individuals', $success, $message, $mailingList, $mailingList->getErrors());
} else {
$params = $this->ParamHandler->harvestParams(['ids']);
if (!empty($params['ids'])) {
$params['ids'] = json_decode($params['ids']);
}
if (empty($params['ids'])) {
throw new NotFoundException(__('Invalid {0}.', Inflector::singularize($this->MailingLists->Individuals->getAlias())));
}
$individuals = $this->MailingLists->Individuals->find()->where([
'id IN' => array_map('intval', $params['ids'])
])->all()->toArray();
$unlinkSuccesses = 0;
foreach ($individuals as $individual) {
$success = (bool)$this->MailingLists->Individuals->unlink($mailingList, [$individual]);
$results[] = $success;
if ($success) {
$unlinkSuccesses++;
}
}
$mailingList = $this->MailingLists->get($mailingList->id);
$success = $unlinkSuccesses == count($individuals);
$message = __(
'{0} {1} have been removed.',
$unlinkSuccesses == count($individuals) ? __('All') : sprintf('%s / %s', $unlinkSuccesses, count($individuals)),
Inflector::singularize($this->MailingLists->Individuals->getAlias())
);
$this->CRUD->setResponseForController('remove_individuals', $success, $message, $mailingList, []);
}
$responsePayload = $this->CRUD->getResponsePayload();
if (!empty($responsePayload)) {
return $responsePayload;
}
}
$this->set('mailinglist_id', $mailinglist_id);
$this->set('mailingList', $mailingList);
if (!empty($individual)) {
$this->set('deletionText', __('Are you sure you want to remove `{0} ({1})` from the mailing list?', $individual->full_name, $individual->email));
} else {
$this->set('deletionText', __('Are you sure you want to remove multiples individuals from the mailing list?'));
}
$this->set('postLinkParameters', ['action' => 'removeIndividual', $mailinglist_id, $individual_id]);
$this->viewBuilder()->setLayout('ajax');
$this->render('/genericTemplates/delete');
}
}

View File

@ -0,0 +1,21 @@
<?php
namespace App\Model\Entity;
use App\Model\Entity\AppModel;
use Cake\ORM\Entity;
class MailingList extends AppModel
{
protected $_accessible = [
'*' => true,
'id' => false,
'uuid' => false,
'user_id' => false,
];
protected $_accessibleOnNew = [
'uuid' => true,
'user_id' => true,
];
}

View File

@ -36,6 +36,7 @@ class IndividualsTable extends AppTable
$this->belongsToMany('Organisations', [ $this->belongsToMany('Organisations', [
'through' => 'Alignments', 'through' => 'Alignments',
]); ]);
$this->belongsToMany('MailingLists');
$this->setDisplayField('email'); $this->setDisplayField('email');
} }

View File

@ -0,0 +1,49 @@
<?php
namespace App\Model\Table;
use App\Model\Table\AppTable;
use Cake\ORM\Table;
use Cake\Validation\Validator;
use Cake\ORM\RulesChecker;
use Cake\ORM\TableRegistry;
class MailingListsTable extends AppTable
{
public function initialize(array $config): void
{
parent::initialize($config);
$this->addBehavior('UUID');
$this->addBehavior('Timestamp');
$this->belongsTo(
'Users'
);
// $this->belongsToMany(
// 'Individuals',
// [
// 'className' => 'Individuals',
// 'foreignKey' => 'individual_id',
// 'joinTable' => 'sgo',
// 'targetForeignKey' => 'organisation_id'
// ]
// );
$this->belongsToMany('Individuals', [
'joinTable' => 'mailing_lists_individuals',
]);
$this->setDisplayField('name');
}
public function validationDefault(Validator $validator): Validator
{
$validator
->requirePresence(['name', 'releasability'], 'create');
return $validator;
}
public function buildRules(RulesChecker $rules): RulesChecker
{
return $rules;
}
}

View File

@ -0,0 +1,35 @@
<?php
echo $this->element('genericElements/Form/genericForm', [
'data' => [
'description' => __('Mailing list are email distribution lists containing individuals.'),
'model' => 'MailingLists',
'fields' => [
[
'field' => 'name'
],
[
'field' => 'uuid',
'label' => 'UUID',
'type' => 'uuid'
],
[
'field' => 'releasability',
'type' => 'textarea'
],
[
'field' => 'description',
'type' => 'textarea'
],
[
'field' => 'active',
'type' => 'checkbox',
'default' => 1
],
],
'submit' => [
'action' => $this->request->getParam('action')
]
]
]);
?>
</div>

View File

@ -0,0 +1,24 @@
<?php
echo $this->element('genericElements/Form/genericForm', [
'data' => [
'title' => __('Add members to mailing list {0} [{1}]', h($mailingList->name), h($mailingList->id)),
// 'description' => __('Mailing list are email distribution lists containing individuals.'),
'model' => 'MailingLists',
'fields' => [
[
'field' => 'individuals',
'type' => 'dropdown',
'multiple' => true,
'select2' => true,
'label' => __('Members'),
'class' => 'select2-input',
'options' => $dropdownData['individuals']
],
],
'submit' => [
'action' => $this->request->getParam('action')
],
],
]);
?>
</div>

View File

@ -0,0 +1,108 @@
<?php
echo $this->element('genericElements/IndexTable/index_table', [
'data' => [
'data' => $data,
'top_bar' => [
'children' => [
[
'type' => 'simple',
'children' => [
'data' => [
'type' => 'simple',
'text' => __('Add mailing list'),
'popover_url' => '/MailingLists/add'
]
]
],
[
'type' => 'search',
'button' => __('Filter'),
'placeholder' => __('Enter value to search'),
'data' => '',
'searchKey' => 'value'
],
[
'type' => 'table_action',
'table_setting_id' => 'mailinglist_index',
]
]
],
'fields' => [
[
'name' => '#',
'sort' => 'id',
'class' => 'short',
'data_path' => 'id',
],
[
'name' => __('Name'),
'sort' => 'name',
'data_path' => 'name',
],
[
'name' => __('Owner'),
'data_path' => 'user_id',
'element' => 'user'
],
[
'name' => __('UUID'),
'sort' => 'uuid',
'data_path' => 'uuid',
],
[
'name' => __('Members'),
'data_path' => 'individuals',
'element' => 'count_summary',
],
[
'name' => __('Intended recipients'),
'data_path' => 'recipients',
],
[
'name' => __('Description'),
'data_path' => 'description',
],
[
'name' => __('Active'),
'data_path' => 'active',
'sort' => 'active',
'element' => 'boolean',
],
[
'name' => __('Deleted'),
'data_path' => 'deleted',
'sort' => 'deleted',
'element' => 'boolean',
],
// [
// 'name' => __('Members'),
// 'data_path' => 'alignments',
// 'element' => 'count_summary',
// 'url' => '/sharingGroups/view/{{id}}',
// 'url_data_path' => 'id'
// ]
],
'title' => __('Mailing Lists Index'),
'description' => __('Mailing list are email distribution lists containing individuals.'),
'actions' => [
[
'url' => '/mailingLists/view',
'url_params_data_paths' => ['id'],
'icon' => 'eye'
],
[
'open_modal' => '/mailingLists/edit/[onclick_params_data_path]',
'modal_params_data_path' => 'id',
'icon' => 'edit'
],
[
'open_modal' => '/mailingLists/delete/[onclick_params_data_path]',
'modal_params_data_path' => 'id',
'icon' => 'trash'
],
]
]
]);
?>

View File

@ -0,0 +1,140 @@
<?php
echo $this->element('genericElements/IndexTable/index_table', [
'data' => [
'data' => $individuals,
'skip_pagination' => 1,
'top_bar' => [
'children' => [
[
'type' => 'multi_select_actions',
'children' => [
[
'text' => __('Remove members'),
'variant' => 'danger',
'onclick' => 'removeMembers',
]
],
'data' => [
'id' => [
'value_path' => 'id'
]
]
],
[
'type' => 'simple',
'children' => [
'data' => [
'type' => 'simple',
'text' => __('Add member'),
'popover_url' => '/mailingLists/addIndividual/' . h($mailing_list_id),
'reload_url' => '/mailingLists/listIndividuals/' . h($mailing_list_id)
]
]
],
[
'type' => 'search',
'button' => __('Filter'),
'placeholder' => __('Enter value to search'),
'data' => '',
'searchKey' => 'value'
]
]
],
'fields' => [
[
'name' => '#',
'sort' => 'id',
'data_path' => 'id',
],
[
'name' => __('First name'),
'data_path' => 'first_name',
],
[
'name' => __('Last name'),
'data_path' => 'last_name',
],
[
'name' => __('Email'),
'data_path' => 'email',
],
[
'name' => __('UUID'),
'sort' => 'uuid',
'data_path' => 'uuid',
]
],
'actions' => [
[
'url' => '/individuals/view',
'url_params_data_paths' => ['id'],
'icon' => 'eye'
],
[
'open_modal' => '/mailingLists/removeIndividual/' . h($mailing_list_id) . '/[onclick_params_data_path]',
'modal_params_data_path' => 'id',
'reload_url' => '/mailingLists/listIndividuals/' . h($mailing_list_id),
'icon' => 'trash'
],
]
]
]);
?>
<script>
function removeMembers(idList, selectedData, $table) {
const successCallback = function([data, modalObject]) {
UI.reload('/mailingLists/listIndividuals/<?= h($mailing_list_id) ?>', UI.getContainerForTable($table), $table)
}
const failCallback = ([data, modalObject]) => {
const tableData = selectedData.map(row => {
entryInError = data.filter(error => error.data.id == row.id)[0]
$faIcon = $('<i class="fa"></i>').addClass(entryInError.success ? 'fa-check text-success' : 'fa-times text-danger')
return [row.id, row.first_name, row.last_name, row.email, entryInError.message, JSON.stringify(entryInError.errors), $faIcon]
});
handleMessageTable(
modalObject.$modal,
['<?= __('ID') ?>', '<?= __('First name') ?>', '<?= __('Last name') ?>', '<?= __('email') ?>', '<?= __('Message') ?>', '<?= __('Error') ?>', '<?= __('State') ?>'],
tableData
)
const $footer = $(modalObject.ajaxApi.statusNode).parent()
modalObject.ajaxApi.statusNode.remove()
const $cancelButton = $footer.find('button[data-bs-dismiss="modal"]')
$cancelButton.text('<?= __('OK') ?>').removeClass('btn-secondary').addClass('btn-primary')
}
UI.submissionModal('/mailingLists/removeIndividual/<?= h($mailing_list_id) ?>', successCallback, failCallback).then(([modalObject, ajaxApi]) => {
const $idsInput = modalObject.$modal.find('form').find('input#ids-field')
$idsInput.val(JSON.stringify(idList))
const tableData = selectedData.map(row => {
return [row.id, row.first_name, row.last_name, row.email]
});
handleMessageTable(
modalObject.$modal,
['<?= __('ID') ?>', '<?= __('First name') ?>', '<?= __('Last name') ?>', '<?= __('email') ?>'],
tableData
)
})
function constructMessageTable(header, data) {
return HtmlHelper.table(
header,
data, {
small: true,
borderless: true,
tableClass: ['message-table', 'mt-4 mb-0'],
}
)
}
function handleMessageTable($modal, header, data) {
const $modalBody = $modal.find('.modal-body')
const $messageTable = $modalBody.find('table.message-table')
const messageTableHTML = constructMessageTable(header, data)[0].outerHTML
if ($messageTable.length) {
$messageTable.html(messageTableHTML)
} else {
$modalBody.append(messageTableHTML)
}
}
}
</script>

View File

@ -0,0 +1,52 @@
<?php
echo $this->element(
'/genericElements/SingleViews/single_view',
[
'data' => $entity,
'fields' => [
[
'key' => __('ID'),
'path' => 'id'
],
[
'key' => __('UUID'),
'path' => 'uuid'
],
[
'key' => __('Name'),
'path' => 'name'
],
[
'key' => __('Owner'),
'path' => 'user_id',
'url' => '/users/view/{{0}}',
'url_vars' => 'user_id'
],
[
'key' => __('Releasability'),
'path' => 'releasability'
],
[
'key' => __('Description'),
'path' => 'description'
],
[
'key' => __('Active'),
'path' => 'active',
'type' => 'boolean'
],
[
'key' => __('Deleted'),
'path' => 'deleted',
'type' => 'boolean'
]
],
'children' => [
[
'url' => '/mailingLists/listIndividuals/{{0}}',
'url_params' => ['id'],
'title' => __('Individuals')
]
]
]
);

View File

@ -7,4 +7,7 @@
'disabled' => $fieldData['disabled'] ?? false, 'disabled' => $fieldData['disabled'] ?? false,
'class' => ($fieldData['class'] ?? '') . ' formDropdown form-select' 'class' => ($fieldData['class'] ?? '') . ' formDropdown form-select'
]; ];
if (!empty($fieldData['label'])) {
$controlParams['label'] = $fieldData['label'];
}
echo $this->FormFieldMassage->prepareFormElement($this->Form, $controlParams, $fieldData); echo $this->FormFieldMassage->prepareFormElement($this->Form, $controlParams, $fieldData);

View File

@ -1,5 +1,5 @@
<?php <?php
/* /*
* Generic form builder * Generic form builder
* *
* Simply pass a JSON with the following keys set: * Simply pass a JSON with the following keys set:
@ -26,6 +26,7 @@
$data['url'] = ["controller" => $this->request->getParam('controller'), "action" => $this->request->getParam('url')]; $data['url'] = ["controller" => $this->request->getParam('controller'), "action" => $this->request->getParam('url')];
} }
$formRandomValue = Cake\Utility\Security::randomString(8); $formRandomValue = Cake\Utility\Security::randomString(8);
$initSelect2 = false;
$formCreate = $this->Form->create($entity, ['id' => 'form-' . $formRandomValue]); $formCreate = $this->Form->create($entity, ['id' => 'form-' . $formRandomValue]);
$default_template = [ $default_template = [
'inputContainer' => '<div class="row mb-3">{{content}}</div>', 'inputContainer' => '<div class="row mb-3">{{content}}</div>',
@ -52,6 +53,7 @@
continue; continue;
} }
} }
$initSelect2 = $initSelect2 || (!empty($fieldData['type']) && $fieldData['type'] == 'dropdown' && !empty($fieldData['select2']));
$formTemplate = $default_template; $formTemplate = $default_template;
if (!empty($fieldData['floating-label'])) { if (!empty($fieldData['floating-label'])) {
$formTemplate['inputContainer'] = '<div class="form-floating input {{type}}{{required}}">{{content}}</div>'; $formTemplate['inputContainer'] = '<div class="form-floating input {{type}}{{required}}">{{content}}</div>';
@ -66,7 +68,8 @@
continue; continue;
} }
$fieldsString .= $this->element( $fieldsString .= $this->element(
'genericElements/Form/fieldScaffold', [ 'genericElements/Form/fieldScaffold',
[
'fieldData' => $fieldData, 'fieldData' => $fieldData,
'form' => $this->Form, 'form' => $this->Form,
'simpleFieldWhitelist' => $simpleFieldWhitelist 'simpleFieldWhitelist' => $simpleFieldWhitelist
@ -76,7 +79,8 @@
} }
if (!empty($data['metaTemplates']) && $data['metaTemplates']->count() > 0) { if (!empty($data['metaTemplates']) && $data['metaTemplates']->count() > 0) {
$metaTemplateString = $this->element( $metaTemplateString = $this->element(
'genericElements/Form/metaTemplateScaffold', [ 'genericElements/Form/metaTemplateScaffold',
[
'metaTemplatesData' => $data['metaTemplates'], 'metaTemplatesData' => $data['metaTemplates'],
'form' => $this->Form, 'form' => $this->Form,
] ]
@ -100,6 +104,7 @@
$actionName = h(\Cake\Utility\Inflector::humanize($this->request->getParam('action'))); $actionName = h(\Cake\Utility\Inflector::humanize($this->request->getParam('action')));
$modelName = h(\Cake\Utility\Inflector::humanize(\Cake\Utility\Inflector::singularize($this->request->getParam('controller')))); $modelName = h(\Cake\Utility\Inflector::humanize(\Cake\Utility\Inflector::singularize($this->request->getParam('controller'))));
if (!empty($ajax)) { if (!empty($ajax)) {
$seedModal = 'mseed-' . mt_rand();
echo $this->element('genericElements/genericModal', [ echo $this->element('genericElements/genericModal', [
'title' => empty($data['title']) ? sprintf('%s %s', $actionName, $modelName) : h($data['title']), 'title' => empty($data['title']) ? sprintf('%s %s', $actionName, $modelName) : h($data['title']),
'body' => sprintf( 'body' => sprintf(
@ -112,7 +117,8 @@
$formCreate, $formCreate,
$fieldsString, $fieldsString,
empty($metaTemplateString) ? '' : $this->element( empty($metaTemplateString) ? '' : $this->element(
'genericElements/accordion_scaffold', [ 'genericElements/accordion_scaffold',
[
'children' => [ 'children' => [
[ [
'body' => $metaTemplateString, 'body' => $metaTemplateString,
@ -124,7 +130,7 @@
$formEnd $formEnd
), ),
'actionButton' => $this->element('genericElements/Form/submitButton', $submitButtonData), 'actionButton' => $this->element('genericElements/Form/submitButton', $submitButtonData),
'class' => 'modal-lg' 'class' => "modal-lg {$seedModal}"
]); ]);
} else if (!empty($raw)) { } else if (!empty($raw)) {
echo sprintf( echo sprintf(
@ -137,7 +143,8 @@
$formCreate, $formCreate,
$fieldsString, $fieldsString,
empty($metaTemplateString) ? '' : $this->element( empty($metaTemplateString) ? '' : $this->element(
'genericElements/accordion_scaffold', [ 'genericElements/accordion_scaffold',
[
'children' => [ 'children' => [
[ [
'body' => $metaTemplateString, 'body' => $metaTemplateString,
@ -161,7 +168,8 @@
), ),
sprintf('<div class="panel">%s</div>', $fieldsString), sprintf('<div class="panel">%s</div>', $fieldsString),
empty($metaTemplateString) ? '' : $this->element( empty($metaTemplateString) ? '' : $this->element(
'genericElements/accordion_scaffold', [ 'genericElements/accordion_scaffold',
[
'children' => [ 'children' => [
[ [
'body' => $metaTemplateString, 'body' => $metaTemplateString,
@ -184,5 +192,14 @@
$('.formDropdown').on('change', function() { $('.formDropdown').on('change', function() {
executeStateDependencyChecks('#' + this.id); executeStateDependencyChecks('#' + this.id);
}) })
<?php if (!empty($initSelect2)): ?>
<?php
$dropdownParent = !empty($seedModal) ? sprintf("$('.modal-dialog.%s .modal-body')", $seedModal) : "$(document.body)";
?>
$('select.select2-input').select2({
dropdownParent: <?= $dropdownParent ?>,
width: '100%',
})
<?php endif; ?>
}); });
</script> </script>

View File

@ -96,7 +96,7 @@ echo $this->Bootstrap->modal([
activeFilters[fullFilter] = rowData['value'] activeFilters[fullFilter] = rowData['value']
} }
}) })
$select = modalObject.$modal.find('select.tag-input') $select = modalObject.$modal.find('select.select2-input')
activeFilters['filteringTags'] = $select.select2('data').map(tag => tag.text) activeFilters['filteringTags'] = $select.select2('data').map(tag => tag.text)
const searchParam = jQuery.param(activeFilters); const searchParam = jQuery.param(activeFilters);
const url = `/${controller}/${action}?${searchParam}` const url = `/${controller}/${action}?${searchParam}`
@ -125,7 +125,7 @@ echo $this->Bootstrap->modal([
} }
setFilteringValues($filteringTable, field, value, operator) setFilteringValues($filteringTable, field, value, operator)
} }
$select = $filteringTable.closest('.modal-body').find('select.tag-input') $select = $filteringTable.closest('.modal-body').find('select.select2-input')
let passedTags = [] let passedTags = []
tags.forEach(tagname => { tags.forEach(tagname => {
const existingOption = $select.find('option').filter(function() { const existingOption = $select.find('option').filter(function() {