chg: Added support of displayOnSuccess, non-dismissable modals and some

house cleaning
pull/37/head
mokaddem 2021-01-14 11:33:51 +01:00
parent 41ca17ef36
commit 691032551b
7 changed files with 80 additions and 36 deletions

View File

@ -17,7 +17,7 @@ class AuthKeysController extends AppController
public function index()
{
$this->CRUD->index([
'filters' => ['users.username', 'authkey', 'comment', 'users.id'],
'filters' => ['Users.username', 'authkey', 'comment', 'Users.id'],
'quickFilters' => ['authkey', 'comment'],
'contain' => ['Users'],
'exclude_fields' => ['authkey']
@ -45,7 +45,9 @@ class AuthKeysController extends AppController
$this->CRUD->add([
'displayOnSuccess' => 'authkey_display'
]);
$responsePayload = $this->CRUD->getResponsePayload();
$responsePayload = $this->CRUD->getResponsePayload([
'displayOnSuccess' => 'authkey_display'
]);
if (!empty($responsePayload)) {
return $responsePayload;
}

View File

@ -6,6 +6,7 @@ use Cake\Controller\Component;
use Cake\Error\Debugger;
use Cake\Utility\Hash;
use Cake\Utility\Inflector;
use Cake\View\ViewBuilder;
class CRUDComponent extends Component
{
@ -108,17 +109,16 @@ class CRUDComponent extends Component
$this->saveMetaFields($data->id, $input);
}
if ($this->Controller->ParamHandler->isRest()) {
$this->Controller->restResponsePayload = $this->RestResponse->viewData($data, 'json');
$this->Controller->restResponsePayload = $this->RestResponse->viewData($savedData, 'json');
} else if ($this->Controller->ParamHandler->isAjax()) {
if (!empty($params['displayOnSuccess'])) {
$displayOnSuccess = $this->renderViewInVariable($params['displayOnSuccess'], ['entity' => $data]);
$this->Controller->ajaxResponsePayload = $this->Controller->RestResponse->ajaxSuccessResponse($this->ObjectAlias, 'add', $savedData, $message, ['displayOnSuccess' => $displayOnSuccess]);
} else {
$this->Controller->ajaxResponsePayload = $this->Controller->RestResponse->ajaxSuccessResponse($this->ObjectAlias, 'add', $savedData, $message);
}
} else {
$this->Controller->Flash->success($message);
if (!empty($params['displayOnSuccess'])) {
$this->Controller->set('entity', $data);
$this->Controller->set('referer', $this->Controller->referer());
$this->Controller->render($params['displayOnSuccess']);
return;
}
if (empty($params['redirect'])) {
$this->Controller->redirect(['action' => 'view', $data->id]);
} else {
@ -503,4 +503,12 @@ class CRUDComponent extends Component
return $this->Table->find()->distinct([$field])->all()->extract($field)->toList();
}
}
private function renderViewInVariable($templateRelativeName, $data)
{
$builder = new ViewBuilder();
$builder->disableAutoLayout()->setTemplate("{$this->TableAlias}/{$templateRelativeName}");
$view = $builder->build($data);
return $view->render();
}
}

View File

@ -420,7 +420,7 @@ class RestResponseComponent extends Component
return $this->__sendResponse($response, 200, $format);
}
public function ajaxSuccessResponse($ObjectAlias, $action, $entity, $message)
public function ajaxSuccessResponse($ObjectAlias, $action, $entity, $message, $additionalData=[])
{
$action = $this->__dissectAdminRouting($action);
$response = [
@ -429,6 +429,9 @@ class RestResponseComponent extends Component
'data' => $entity->toArray(),
'url' => $this->__generateURL($action, $ObjectAlias, $entity->id)
];
if (!empty($additionalData)) {
$response['additionalData'] = $additionalData;
}
return $this->viewData($response);
}

View File

@ -1,5 +1,13 @@
<h4><?= __('Authkey created'); ?></h4>
<p><?= __('Please make sure that you note down the authkey below, this is the only time the authkey is shown in plain text, so make sure you save it. If you lose the key, simply remove the entry and generate a new one.'); ?></p>
<p><?=__('Cerebrate will use the first and the last 4 digit for identification purposes.')?></p>
<p><?= sprintf('%s: <span class="text-weight-bold">%s</span>', __('Authkey'), h($entity->authkey_raw)) ?></p>
<a href="<?= $referer ?>" class="btn btn-primary"><?= __('I have noted down my key, take me back now') ?></a>
<?php
echo $this->element('genericElements/genericModal', [
'title' => __('Authkey created'),
'body' => sprintf(
'<p>%s</p><p>%s</p><p>%s</p>',
__('Please make sure that you note down the authkey below, this is the only time the authkey is shown in plain text, so make sure you save it. If you lose the key, simply remove the entry and generate a new one.'),
__('Cerebrate will use the first and the last 4 digit for identification purposes.'),
sprintf('%s: <span class="font-weight-bold">%s</span>', __('Authkey'), h($entity->authkey_raw))
),
'actionButton' => sprintf('<button" class="btn btn-primary" data-dismiss="modal">%s</button>', __('I have noted down my key, take me back now')),
'noCancel' => true,
'staticBackdrop' => true,
]);

View File

@ -10,7 +10,6 @@ echo $this->element('genericElements/IndexTable/index_table', [
'data' => [
'type' => 'simple',
'text' => __('Add authentication key'),
'class' => 'btn btn-primary',
'popover_url' => '/authKeys/add'
]
]

View File

@ -1,4 +1,4 @@
<div class="modal-dialog <?= empty($class) ? '' : h($class) ?>" role="document">
<div class="modal-dialog <?= empty($class) ? '' : h($class) ?>" <?= !empty($staticBackdrop) ? 'data-backdrop="static"' : ''?> role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title"><?= h($title) ?></h5>
@ -10,7 +10,9 @@
<?= $body ?>
</div>
<div class="modal-footer">
<?php if (empty($noCancel)): ?>
<button type="button" class="btn btn-secondary cancel-button" data-dismiss="modal"><?= __('Cancel') ?></button>
<?php endif; ?>
<?= $actionButton ?>
</div>
</div>

View File

@ -34,7 +34,7 @@ class UIFactory {
modalFromURL(url, POSTSuccessCallback, POSTFailCallback) {
return AJAXApi.quickFetchURL(url).then((modalHTML) => {
const theModal = new ModalFactory({
rawHTML: modalHTML,
rawHtml: modalHTML,
POSTSuccessCallback: POSTSuccessCallback !== undefined ? POSTSuccessCallback : () => {},
POSTFailCallback: POSTFailCallback !== undefined ? POSTFailCallback : (errorMessage) => {},
});
@ -46,31 +46,43 @@ class UIFactory {
}
/**
* Create and display a modal where the modal's content is fetched from the provided URL. Reload the table after a successful operation
* Creates and displays a modal where the modal's content is fetched from the provided URL. Reloads the table after a successful operation and handles displayOnSuccess option
* @param {string} url - The URL from which the modal's content should be fetched
* @param {string} tableId - The table ID which should be reloaded on success
* @return {Promise<Object>} Promise object resolving to the ModalFactory object
*/
openModalFromURL(url, reloadUrl=false, tableId=false) {
return UI.modalFromURL(url, () => {
return UI.modalFromURL(url, (data) => {
let reloaded = false
if (reloadUrl === false || tableId === false) { // Try to get information from the DOM
let $elligibleTable = $('table.table')
let currentModel = location.pathname.split('/')[1]
if ($elligibleTable.length == 1 && currentModel.length > 0) {
let $container = $elligibleTable.closest('div[id^="table-container-"]')
if ($container.length == 1) {
return UI.reload(`/${currentModel}/index`, $container, $elligibleTable)
UI.reload(`/${currentModel}/index`, $container, $elligibleTable)
reloaded = true
} else {
$container = $elligibleTable.closest('div[id^="single-view-table-container-"]')
if ($container.length == 1) {
return UI.reload(location.pathname, $container, $elligibleTable)
UI.reload(location.pathname, $container, $elligibleTable)
reloaded = true
}
}
}
} else {
return UI.reload(reloadUrl, $(`#table-container-${tableId}`), $(`#table-container-${tableId} table.table`))
UI.reload(reloadUrl, $(`#table-container-${tableId}`), $(`#table-container-${tableId} table.table`))
reloaded = true
}
if (data.additionalData !== undefined && data.additionalData.displayOnSuccess !== undefined) {
UI.modal({
rawHtml: data.additionalData.displayOnSuccess
})
} else {
if (!reloaded) {
location.reload()
}
}
})
}
@ -247,7 +259,7 @@ class ModalFactory {
*/
constructor(options) {
this.options = Object.assign({}, ModalFactory.defaultOptions, options)
if (this.options.rawHTML) {
if (this.options.rawHtml && options.POSTSuccessCallback !== undefined) {
this.attachSubmitButtonListener = true
}
if (options.type === undefined && options.cancel !== undefined) {
@ -256,6 +268,9 @@ class ModalFactory {
this.bsModalOptions = {
show: true
}
if (this.options.backdropStatic) {
this.bsModalOptions['backdrop'] = 'static'
}
}
/**
@ -305,11 +320,12 @@ class ModalFactory {
* @property {string=('sm'|'lg'|'xl'|'')} size - The size of the modal
* @property {boolean} centered - Should the modal be vertically centered
* @property {boolean} scrollable - Should the modal be scrollable
* @property {boolean} backdropStatic - When set, the modal will not close when clicking outside it.
* @property {string} title - The title's content of the modal
* @property {string} titleHtml - The raw HTML title's content of the modal
* @property {string} body - The body's content of the modal
* @property {string} bodyHtml - The raw HTML body's content of the modal
* @property {string} rawHTML - The raw HTML of the whole modal. If provided, will override any other content
* @property {string} rawHtml - The raw HTML of the whole modal. If provided, will override any other content
* @property {string=('primary'|'secondary'|'success'|'danger'|'warning'|'info'|'light'|'dark'|'white'|'transparent')} variant - The variant of the modal
* @property {string} modalClass - Classes to be added to the modal's container
* @property {string} headerClass - Classes to be added to the modal's header
@ -334,11 +350,12 @@ class ModalFactory {
size: 'md',
centered: false,
scrollable: false,
backdropStatic: false,
title: '',
titleHtml: false,
body: false,
bodyHtml: false,
rawHTML: false,
rawHtml: false,
variant: '',
modalClass: '',
headerClass: '',
@ -375,6 +392,8 @@ class ModalFactory {
if (this.isValid()) {
this.$modal = this.buildModal()
$('#mainModalContainer').append(this.$modal)
} else {
console.log('Modal not valid')
}
}
@ -393,6 +412,8 @@ class ModalFactory {
that.findSubmitButtonAndAddListener()
}
})
} else {
console.log('Modal not valid')
}
}
@ -413,7 +434,7 @@ class ModalFactory {
isValid() {
return this.options.title !== false || this.options.titleHtml !== false ||
this.options.body !== false || this.options.bodyHtml !== false ||
this.options.rawHTML !== false
this.options.rawHtml !== false
}
/**
@ -430,8 +451,11 @@ class ModalFactory {
$modal.addClass(this.options.modalClass)
}
let $modalDialog
if (this.options.rawHTML) {
$modalDialog = $(this.options.rawHTML)
if (this.options.rawHtml) {
$modalDialog = $(this.options.rawHtml)
if ($modalDialog.data('backdrop') == 'static') {
this.bsModalOptions['backdrop'] = 'static'
}
} else {
$modalDialog = $('<div class="modal-dialog"/>')
const $modalContent = $('<div class="modal-content"/>')
@ -564,14 +588,12 @@ class ModalFactory {
}
this.options.APIConfirm = (tmpApi) => {
tmpApi.mergeOptions({forceHTMLOnValidationFailure: true})
return tmpApi.postForm($form[0])
.then((data) => {
if (data.success) {
this.options.POSTSuccessCallback(data)
} else { // Validation error, replace modal content with new html
this.$modal.html(data.html)
this.findSubmitButtonAndAddListener()
} else { // Validation error
this.injectFormValidationFeedback(form, data.errors)
return Promise.reject('Validation error');
}
})