chg: [component:CRUD] Improved flexibility

pull/37/head
mokaddem 2021-01-11 12:48:58 +01:00
parent 2d26bc597f
commit 899fa27a45
13 changed files with 111 additions and 42 deletions

View File

@ -48,6 +48,23 @@ class CRUDComponent extends Component
$this->Controller->set('data', $data); $this->Controller->set('data', $data);
} }
} }
/**
* getResponsePayload Returns the adaquate response payload based on the request context
*
* @return false or Array
*/
public function getResponsePayload()
{
if ($this->Controller->ParamHandler->isRest()) {
return $this->Controller->restResponsePayload;
} else if ($this->Controller->ParamHandler->isAjax() && $this->request->is(['post', 'put'])) {
if (empty($this->Controller->isFailResponse) || empty($this->Controller->ajax_with_html_on_failure)) {
return $this->Controller->ajaxResponsePayload;
}
}
return false;
}
private function getMetaTemplates() private function getMetaTemplates()
{ {
@ -301,8 +318,9 @@ class CRUDComponent extends Component
if ($this->Table->delete($data)) { if ($this->Table->delete($data)) {
$message = __('{0} deleted.', $this->ObjectAlias); $message = __('{0} deleted.', $this->ObjectAlias);
if ($this->Controller->ParamHandler->isRest()) { if ($this->Controller->ParamHandler->isRest()) {
$data = $this->Table->get($id); $this->Controller->restResponsePayload = $this->RestResponse->viewData($data, 'json');
$this->Controller->restResponsePayload = $this->RestResponse->saveSuccessResponse($this->TableAlias, 'delete', $id, 'json', $message); } else if ($this->Controller->ParamHandler->isAjax()) {
$this->Controller->ajaxResponsePayload = $this->Controller->RestResponse->ajaxSuccessResponse($this->ObjectAlias, 'delete', $data, $message);
} else { } else {
$this->Controller->Flash->success($message); $this->Controller->Flash->success($message);
$this->Controller->redirect($this->Controller->referer()); $this->Controller->redirect($this->Controller->referer());
@ -493,8 +511,17 @@ class CRUDComponent extends Component
} }
} }
private function getFilteringContextFromField($context) private function getFilteringContextFromField($field)
{ {
return $this->Table->find()->distinct([$context])->all()->extract($context)->toList(); $exploded = explode('.', $field);
if (count($exploded) > 1) {
$model = $exploded[0];
$subField = $exploded[1];
return $this->Table->{$model}->find()
->distinct([$subField])
->extract($subField)->toList();
} else {
return $this->Table->find()->distinct([$field])->all()->extract($field)->toList();
}
} }
} }

View File

@ -15,8 +15,14 @@ class IndividualsController extends AppController
public function index() public function index()
{ {
$this->CRUD->index([ $this->CRUD->index([
'filters' => ['uuid', 'email', 'first_name', 'last_name', 'position', 'Organisations.id'], 'filters' => ['uuid', 'email', 'first_name', 'last_name', 'position', 'Organisations.id', 'Alignments.type'],
'quickFilters' => ['uuid', 'email', 'first_name', 'last_name', 'position'], 'quickFilters' => ['uuid', 'email', 'first_name', 'last_name', 'position'],
'contextFilters' => [
'allow_all' => true,
'fields' => [
'Alignments.type'
]
],
'contain' => ['Alignments' => 'Organisations'] 'contain' => ['Alignments' => 'Organisations']
]); ]);
if ($this->ParamHandler->isRest()) { if ($this->ParamHandler->isRest()) {
@ -29,8 +35,9 @@ class IndividualsController extends AppController
public function add() public function add()
{ {
$this->CRUD->add(); $this->CRUD->add();
if ($this->ParamHandler->isRest()) { $responsePayload = $this->CRUD->getResponsePayload();
return $this->restResponsePayload; if (!empty($responsePayload)) {
return $responsePayload;
} }
$this->set('metaGroup', 'ContactDB'); $this->set('metaGroup', 'ContactDB');
} }
@ -38,8 +45,9 @@ class IndividualsController extends AppController
public function view($id) public function view($id)
{ {
$this->CRUD->view($id, ['contain' => ['Alignments' => 'Organisations']]); $this->CRUD->view($id, ['contain' => ['Alignments' => 'Organisations']]);
if ($this->ParamHandler->isRest()) { $responsePayload = $this->CRUD->getResponsePayload();
return $this->restResponsePayload; if (!empty($responsePayload)) {
return $responsePayload;
} }
$this->set('metaGroup', 'ContactDB'); $this->set('metaGroup', 'ContactDB');
} }
@ -47,10 +55,9 @@ class IndividualsController extends AppController
public function edit($id) public function edit($id)
{ {
$this->CRUD->edit($id); $this->CRUD->edit($id);
if ($this->ParamHandler->isRest()) { $responsePayload = $this->CRUD->getResponsePayload();
return $this->restResponsePayload; if (!empty($responsePayload)) {
} else if($this->ParamHandler->isAjax() && $this->request->is(['post', 'put'])) { return $responsePayload;
return $this->ajaxResponsePayload;
} }
$this->set('metaGroup', 'ContactDB'); $this->set('metaGroup', 'ContactDB');
$this->render('add'); $this->render('add');
@ -59,8 +66,9 @@ class IndividualsController extends AppController
public function delete($id) public function delete($id)
{ {
$this->CRUD->delete($id); $this->CRUD->delete($id);
if ($this->ParamHandler->isRest()) { $responsePayload = $this->CRUD->getResponsePayload();
return $this->restResponsePayload; if (!empty($responsePayload)) {
return $responsePayload;
} }
$this->set('metaGroup', 'ContactDB'); $this->set('metaGroup', 'ContactDB');
} }

View File

@ -23,12 +23,9 @@ class UsersController extends AppController
public function add() public function add()
{ {
$this->CRUD->add(); $this->CRUD->add();
if ($this->ParamHandler->isRest()) { $responsePayload = $this->CRUD->getResponsePayload();
return $this->restResponsePayload; if (!empty($responsePayload)) {
} else if ($this->ParamHandler->isAjax() && $this->request->is(['post', 'put'])) { return $responsePayload;
if (empty($this->isFailResponse) || empty($this->ajax_with_html_on_failure)) {
return $this->ajaxResponsePayload;
}
} }
$dropdownData = [ $dropdownData = [
'role' => $this->Users->Roles->find('list', [ 'role' => $this->Users->Roles->find('list', [
@ -78,8 +75,9 @@ class UsersController extends AppController
$params['fields'][] = 'role_id'; $params['fields'][] = 'role_id';
} }
$this->CRUD->edit($id, $params); $this->CRUD->edit($id, $params);
if ($this->ParamHandler->isRest()) { $responsePayload = $this->CRUD->getResponsePayload();
return $this->restResponsePayload; if (!empty($responsePayload)) {
return $responsePayload;
} }
$dropdownData = [ $dropdownData = [
'role' => $this->Users->Roles->find('list', [ 'role' => $this->Users->Roles->find('list', [
@ -94,11 +92,21 @@ class UsersController extends AppController
$this->render('add'); $this->render('add');
} }
public function toggle($id, $fieldName = 'disabled')
{
$this->CRUD->toggle($id, $fieldName);
$responsePayload = $this->CRUD->getResponsePayload();
if (!empty($responsePayload)) {
return $responsePayload;
}
}
public function delete($id) public function delete($id)
{ {
$this->CRUD->delete($id); $this->CRUD->delete($id);
if ($this->ParamHandler->isRest()) { $responsePayload = $this->CRUD->getResponsePayload();
return $this->restResponsePayload; if (!empty($responsePayload)) {
return $responsePayload;
} }
$this->set('metaGroup', $this->isAdmin ? 'Administration' : 'Cerebrate'); $this->set('metaGroup', $this->isAdmin ? 'Administration' : 'Cerebrate');
} }

View File

@ -15,6 +15,10 @@ echo $this->element('genericElements/IndexTable/index_table', [
] ]
] ]
], ],
[
'type' => 'context_filters',
'context_filters' => $filteringContexts
],
[ [
'type' => 'search', 'type' => 'search',
'button' => __('Filter'), 'button' => __('Filter'),
@ -68,12 +72,12 @@ echo $this->element('genericElements/IndexTable/index_table', [
'icon' => 'eye' 'icon' => 'eye'
], ],
[ [
'onclick' => 'populateAndLoadModal(\'/individuals/edit/[onclick_params_data_path]\');', 'onclick' => 'openModalFromURL(\'/individuals/edit/[onclick_params_data_path]\');',
'onclick_params_data_path' => 'id', 'onclick_params_data_path' => 'id',
'icon' => 'edit' 'icon' => 'edit'
], ],
[ [
'onclick' => 'populateAndLoadModal(\'/individuals/delete/[onclick_params_data_path]\');', 'onclick' => 'openModalFromURL(\'/individuals/delete/[onclick_params_data_path]\');',
'onclick_params_data_path' => 'id', 'onclick_params_data_path' => 'id',
'icon' => 'trash' 'icon' => 'trash'
] ]

View File

@ -3,7 +3,6 @@ echo $this->element('genericElements/IndexTable/index_table', [
'data' => [ 'data' => [
'data' => $data, 'data' => $data,
'top_bar' => [ 'top_bar' => [
'pull' => 'right',
'children' => [ 'children' => [
[ [
'type' => 'simple', 'type' => 'simple',
@ -31,6 +30,22 @@ echo $this->element('genericElements/IndexTable/index_table', [
'sort' => 'id', 'sort' => 'id',
'data_path' => 'id', 'data_path' => 'id',
], ],
[
'name' => __('Disabled'),
'sort' => 'disabled',
'data_path' => 'disabled',
'element' => 'toggle',
'url' => '/users/toggle/{{0}}',
'url_params_vars' => ['id'],
'toggle_data' => [
'editRequirement' => [
'function' => function($row, $options) {
return true;
},
],
'skip_full_reload' => true
]
],
[ [
'name' => __('Username'), 'name' => __('Username'),
'sort' => 'username', 'sort' => 'username',
@ -71,12 +86,12 @@ echo $this->element('genericElements/IndexTable/index_table', [
'icon' => 'eye' 'icon' => 'eye'
], ],
[ [
'onclick' => 'populateAndLoadModal(\'/users/edit/[onclick_params_data_path]\');', 'onclick' => 'openModalFromURL(\'/users/edit/[onclick_params_data_path]\');',
'onclick_params_data_path' => 'id', 'onclick_params_data_path' => 'id',
'icon' => 'edit' 'icon' => 'edit'
], ],
[ [
'onclick' => 'populateAndLoadModal(\'/users/delete/[onclick_params_data_path]\');', 'onclick' => 'openModalFromURL(\'/users/delete/[onclick_params_data_path]\');',
'onclick_params_data_path' => 'id', 'onclick_params_data_path' => 'id',
'icon' => 'trash' 'icon' => 'trash'
] ]

View File

@ -75,9 +75,6 @@
...correctOptions, ...correctOptions,
APIConfirm: (tmpApi) => { APIConfirm: (tmpApi) => {
return submitForm(tmpApi, url) return submitForm(tmpApi, url)
.catch(e => {
// Provide feedback inside modal?
})
}, },
} }
UI.modal(modalOptions) UI.modal(modalOptions)

View File

@ -32,7 +32,7 @@
empty($data['value']) ? '' : h($data['value']) empty($data['value']) ? '' : h($data['value'])
); );
echo sprintf( echo sprintf(
'<div class="input-group" data-table-random-value="%s">%s%s</div>', '<div class="input-group" data-table-random-value="%s" style="margin-left: auto;">%s%s</div>',
h($tableRandomValue), h($tableRandomValue),
$input, $input,
$button $button

View File

@ -1,10 +1,12 @@
<?php <?php
$groups = ''; $groups = '';
$hasGroupSearch = false;
foreach ($data['children'] as $group) { foreach ($data['children'] as $group) {
$groups .= $this->element('/genericElements/ListTopBar/group_' . (empty($group['type']) ? 'simple' : h($group['type'])), array('data' => $group, 'tableRandomValue' => $tableRandomValue)); $groups .= $this->element('/genericElements/ListTopBar/group_' . (empty($group['type']) ? 'simple' : h($group['type'])), array('data' => $group, 'tableRandomValue' => $tableRandomValue));
$hasGroupSearch = $hasGroupSearch || (!empty($group['type']) && $group['type'] == 'search');
} }
$tempClass = "btn-toolbar"; $tempClass = "btn-toolbar";
if (count($data['children']) > 1) { if (count($data['children']) > 1 && !$hasGroupSearch) {
$tempClass .= ' justify-content-between'; $tempClass .= ' justify-content-between';
} else if (!empty($data['pull'])) { } else if (!empty($data['pull'])) {
$tempClass .= ' float-' . h($data['pull']); $tempClass .= ' float-' . h($data['pull']);

View File

@ -35,7 +35,7 @@ if (isset($menu[$metaGroup])) {
} }
$active = ($scope === $this->request->getParam('controller') && $action === $this->request->getParam('action')); $active = ($scope === $this->request->getParam('controller') && $action === $this->request->getParam('action'));
if (!empty($data['popup'])) { if (!empty($data['popup'])) {
$link_template = '<a href="#" onClick="populateAndLoadModal(\'%s\')" class="list-group-item list-group-item-action %s %s pl-3 border-0 %s">%s</a>'; $link_template = '<a href="#" onClick="openModalFromURL(\'%s\')" class="list-group-item list-group-item-action %s %s pl-3 border-0 %s">%s</a>';
} else { } else {
$link_template = '<a href="%s" class="list-group-item list-group-item-action %s %s pl-3 border-0 %s">%s</a>'; $link_template = '<a href="%s" class="list-group-item list-group-item-action %s %s pl-3 border-0 %s">%s</a>';
} }

View File

@ -13,7 +13,7 @@
<?= $this->Form->postLink( <?= $this->Form->postLink(
'Delete', 'Delete',
['action' => 'delete', $id], ['action' => 'delete', $id],
['class' => 'btn btn-primary button-execute'] ['class' => 'btn btn-primary button-execute', 'id' => 'submitButton']
) )
?> ?>
<button type="button" class="btn btn-secondary cancel-button" data-dismiss="modal"><?= __('Cancel') ?></button> <button type="button" class="btn btn-secondary cancel-button" data-dismiss="modal"><?= __('Cancel') ?></button>

View File

@ -114,3 +114,8 @@
color: inherit; color: inherit;
text-decoration: inherit; text-decoration: inherit;
} }
.btn-group > .btn:last-of-type:not(.dropdown-toggle), .btn-group > .btn-group:not(:last-of-type) > .btn {
border-top-right-radius: 0.2rem;
border-bottom-right-radius: 0.2rem;
}

View File

@ -142,7 +142,7 @@ class AJAXApi {
try { try {
const response = await fetch(url, AJAXApi.genericRequestConfigGET); const response = await fetch(url, AJAXApi.genericRequestConfigGET);
if (!response.ok) { if (!response.ok) {
throw new Error('Network response was not ok') throw new Error(`Network response was not ok. \`${response.statusText}\``)
} }
const dataHtml = await response.text(); const dataHtml = await response.text();
this.provideFeedback({ this.provideFeedback({
@ -179,7 +179,7 @@ class AJAXApi {
try { try {
const response = await fetch(url, AJAXApi.genericRequestConfigGET); const response = await fetch(url, AJAXApi.genericRequestConfigGET);
if (!response.ok) { if (!response.ok) {
throw new Error('Network response was not ok') throw new Error(`Network response was not ok. \`${response.statusText}\``)
} }
const formHtml = await response.text(); const formHtml = await response.text();
let tmpNode = document.createElement("div"); let tmpNode = document.createElement("div");
@ -231,7 +231,7 @@ class AJAXApi {
}; };
const response = await fetch(form.action, options); const response = await fetch(form.action, options);
if (!response.ok) { if (!response.ok) {
throw new Error('Network response was not ok') throw new Error(`Network response was not ok. \`${response.statusText}\``)
} }
const clonedResponse = response.clone() const clonedResponse = response.clone()
try { try {
@ -268,7 +268,7 @@ class AJAXApi {
}, true, feedbackShown); }, true, feedbackShown);
toReturn = Promise.reject(error); toReturn = Promise.reject(error);
} }
} catch (error) { } catch (error) { // -> probably not useful
toReturn = Promise.reject(error); toReturn = Promise.reject(error);
} finally { } finally {
if (!skipRequestHooks) { if (!skipRequestHooks) {

View File

@ -503,7 +503,7 @@ class ModalFactory {
} }
/** Attach the submission click listener for modals that have been generated by raw HTML */ /** Attach the submission click listener for modals that have been generated by raw HTML */
findSubmitButtonAndAddListener() { findSubmitButtonAndAddListener(clearOnclick=true) {
const $submitButton = this.$modal.find('.modal-footer #submitButton') const $submitButton = this.$modal.find('.modal-footer #submitButton')
const formID = $submitButton.data('form-id') const formID = $submitButton.data('form-id')
let $form let $form
@ -512,6 +512,9 @@ class ModalFactory {
} else { } else {
$form = this.$modal.find('form') $form = this.$modal.find('form')
} }
if (clearOnclick) {
$submitButton[0].removeAttribute('onclick')
}
this.options.APIConfirm = (tmpApi) => { this.options.APIConfirm = (tmpApi) => {
tmpApi.mergeOptions({forceHTMLOnValidationFailure: true}) tmpApi.mergeOptions({forceHTMLOnValidationFailure: true})