chg: [generic] Added Modal from URL support
- Support Form submission - Success / Fail callbacks - Modal reloading in case of validation errorspull/37/head
parent
ae0272a62c
commit
f9bf1c6f55
|
@ -110,6 +110,9 @@ class AppController extends Controller
|
||||||
$this->ACL->checkAccess();
|
$this->ACL->checkAccess();
|
||||||
$this->set('menu', $this->ACL->getMenu());
|
$this->set('menu', $this->ACL->getMenu());
|
||||||
$this->set('ajax', $this->request->is('ajax'));
|
$this->set('ajax', $this->request->is('ajax'));
|
||||||
|
if (!empty($this->request->getHeader('X-Request-Html-On-Failure'))) {
|
||||||
|
$this->ajax_with_html_on_failure = true;
|
||||||
|
}
|
||||||
$this->request->getParam('prefix');
|
$this->request->getParam('prefix');
|
||||||
$this->set('darkMode', !empty(Configure::read('Cerebrate.dark')));
|
$this->set('darkMode', !empty(Configure::read('Cerebrate.dark')));
|
||||||
}
|
}
|
||||||
|
|
|
@ -81,13 +81,16 @@ class CRUDComponent extends Component
|
||||||
$patchEntityParams['fields'] = $params['fields'];
|
$patchEntityParams['fields'] = $params['fields'];
|
||||||
}
|
}
|
||||||
$data = $this->Table->patchEntity($data, $input, $patchEntityParams);
|
$data = $this->Table->patchEntity($data, $input, $patchEntityParams);
|
||||||
if ($this->Table->save($data)) {
|
$savedData = $this->Table->save($data);
|
||||||
|
if ($savedData !== false) {
|
||||||
$message = __('{0} added.', $this->ObjectAlias);
|
$message = __('{0} added.', $this->ObjectAlias);
|
||||||
if (!empty($input['metaFields'])) {
|
if (!empty($input['metaFields'])) {
|
||||||
$this->saveMetaFields($data->id, $input);
|
$this->saveMetaFields($data->id, $input);
|
||||||
}
|
}
|
||||||
if ($this->Controller->ParamHandler->isRest()) {
|
if ($this->Controller->ParamHandler->isRest()) {
|
||||||
$this->Controller->restResponsePayload = $this->RestResponse->viewData($data, 'json');
|
$this->Controller->restResponsePayload = $this->RestResponse->viewData($data, 'json');
|
||||||
|
} else if ($this->Controller->ParamHandler->isAjax()) {
|
||||||
|
$this->Controller->ajaxResponsePayload = $this->Controller->RestResponse->ajaxSuccessResponse($this->ObjectAlias, 'add', $savedData, $message);
|
||||||
} else {
|
} else {
|
||||||
$this->Controller->Flash->success($message);
|
$this->Controller->Flash->success($message);
|
||||||
if (!empty($params['displayOnSuccess'])) {
|
if (!empty($params['displayOnSuccess'])) {
|
||||||
|
@ -103,6 +106,7 @@ class CRUDComponent extends Component
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
$this->Controller->isFailResponse = true;
|
||||||
$validationMessage = $this->prepareValidationError($data);
|
$validationMessage = $this->prepareValidationError($data);
|
||||||
$message = __(
|
$message = __(
|
||||||
'{0} could not be added.{1}',
|
'{0} could not be added.{1}',
|
||||||
|
@ -110,7 +114,8 @@ class CRUDComponent extends Component
|
||||||
empty($validationMessage) ? '' : ' ' . __('Reason:{0}', $validationMessage)
|
empty($validationMessage) ? '' : ' ' . __('Reason:{0}', $validationMessage)
|
||||||
);
|
);
|
||||||
if ($this->Controller->ParamHandler->isRest()) {
|
if ($this->Controller->ParamHandler->isRest()) {
|
||||||
|
} else if ($this->Controller->ParamHandler->isAjax()) {
|
||||||
|
$this->Controller->ajaxResponsePayload = $this->Controller->RestResponse->ajaxFailResponse($this->ObjectAlias, 'add', $data, $message, $validationMessage);
|
||||||
} else {
|
} else {
|
||||||
$this->Controller->Flash->error($message);
|
$this->Controller->Flash->error($message);
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,6 +25,10 @@ class UsersController extends AppController
|
||||||
$this->CRUD->add();
|
$this->CRUD->add();
|
||||||
if ($this->ParamHandler->isRest()) {
|
if ($this->ParamHandler->isRest()) {
|
||||||
return $this->restResponsePayload;
|
return $this->restResponsePayload;
|
||||||
|
} else if ($this->ParamHandler->isAjax() && $this->request->is(['post', 'put'])) {
|
||||||
|
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', [
|
||||||
|
|
|
@ -12,6 +12,7 @@
|
||||||
- use these to define dynamic form fields, or anything that will feed into the regular fields via JS population
|
- use these to define dynamic form fields, or anything that will feed into the regular fields via JS population
|
||||||
* - submit: The submit button itself. By default it will simply submit to the form as defined via the 'model' field
|
* - submit: The submit button itself. By default it will simply submit to the form as defined via the 'model' field
|
||||||
*/
|
*/
|
||||||
|
$this->Form->setConfig('errorClass', 'is-invalid');
|
||||||
$modelForForm = empty($data['model']) ?
|
$modelForForm = empty($data['model']) ?
|
||||||
h(\Cake\Utility\Inflector::singularize(\Cake\Utility\Inflector::classify($this->request->getParam('controller')))) :
|
h(\Cake\Utility\Inflector::singularize(\Cake\Utility\Inflector::classify($this->request->getParam('controller')))) :
|
||||||
h($data['model']);
|
h($data['model']);
|
||||||
|
@ -35,11 +36,14 @@
|
||||||
'select' => '<select name="{{name}}" {{attrs}}>{{content}}</select>',
|
'select' => '<select name="{{name}}" {{attrs}}>{{content}}</select>',
|
||||||
'checkbox' => '<input type="checkbox" name="{{name}}" value="{{value}}"{{attrs}}>',
|
'checkbox' => '<input type="checkbox" name="{{name}}" value="{{value}}"{{attrs}}>',
|
||||||
'checkboxFormGroup' => '{{label}}',
|
'checkboxFormGroup' => '{{label}}',
|
||||||
'formGroup' => '<div class="col-sm-2 col-form-label" {{attrs}}>{{label}}</div><div class="col-sm-10">{{input}}</div>',
|
'formGroup' => '<div class="col-sm-2 col-form-label" {{attrs}}>{{label}}</div><div class="col-sm-10">{{input}}{{error}}</div>',
|
||||||
'nestingLabel' => '{{hidden}}<div class="col-sm-2 col-form-label">{{text}}</div><div class="col-sm-10">{{input}}</div>',
|
'nestingLabel' => '{{hidden}}<div class="col-sm-2 col-form-label">{{text}}</div><div class="col-sm-10">{{input}}</div>',
|
||||||
'option' => '<option value="{{value}}"{{attrs}}>{{text}}</option>',
|
'option' => '<option value="{{value}}"{{attrs}}>{{text}}</option>',
|
||||||
'optgroup' => '<optgroup label="{{label}}"{{attrs}}>{{content}}</optgroup>',
|
'optgroup' => '<optgroup label="{{label}}"{{attrs}}>{{content}}</optgroup>',
|
||||||
'select' => '<select name="{{name}}"{{attrs}}>{{content}}</select>'
|
'select' => '<select name="{{name}}"{{attrs}}>{{content}}</select>',
|
||||||
|
'error' => '<div class="error-message invalid-feedback d-block">{{content}}</div>',
|
||||||
|
'errorList' => '<ul>{{content}}</ul>',
|
||||||
|
'errorItem' => '<li>{{text}}</li>',
|
||||||
];
|
];
|
||||||
if (!empty($data['fields'])) {
|
if (!empty($data['fields'])) {
|
||||||
foreach ($data['fields'] as $fieldData) {
|
foreach ($data['fields'] as $fieldData) {
|
||||||
|
@ -49,6 +53,7 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// we reset the template each iteration as individual fields might override the defaults.
|
// we reset the template each iteration as individual fields might override the defaults.
|
||||||
|
$this->Form->setConfig($default_template);
|
||||||
$this->Form->setTemplates($default_template);
|
$this->Form->setTemplates($default_template);
|
||||||
if (isset($fieldData['requirements']) && !$fieldData['requirements']) {
|
if (isset($fieldData['requirements']) && !$fieldData['requirements']) {
|
||||||
continue;
|
continue;
|
||||||
|
|
|
@ -3,8 +3,8 @@
|
||||||
echo sprintf(
|
echo sprintf(
|
||||||
'%s',
|
'%s',
|
||||||
sprintf(
|
sprintf(
|
||||||
'<button id="submitButton" class="btn btn-primary" onClick="%s" autofocus>%s</button>',
|
'<button id="submitButton" class="btn btn-primary" data-form-id="%s" autofocus>%s</button>',
|
||||||
"$('#form-" . h($formRandomValue) . "').submit()",
|
'#form-' . h($formRandomValue),
|
||||||
__('Submit')
|
__('Submit')
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
if (!isset($data['requirement']) || $data['requirement']) {
|
if (!isset($data['requirement']) || $data['requirement']) {
|
||||||
if (!empty($data['popover_url'])) {
|
if (!empty($data['popover_url'])) {
|
||||||
$onClick = sprintf(
|
$onClick = sprintf(
|
||||||
'onClick="populateAndLoadModal(%s)"',
|
'onClick="openModalFromURL(%s)"',
|
||||||
sprintf("'%s'", h($data['popover_url']))
|
sprintf("'%s'", h($data['popover_url']))
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -67,3 +67,11 @@
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
?>
|
?>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
function openModalFromURL(url) {
|
||||||
|
UI.modalFromURL(url, (data) => {
|
||||||
|
UI.reload('<?= $this->Url->build(['action' => 'index']); ?>', $('#table-container-<?= $tableRandomValue ?>'), $('#table-container-<?= $tableRandomValue ?> table.table'))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
</script>
|
|
@ -2,7 +2,7 @@
|
||||||
if (!isset($data['requirement']) || $data['requirement']) {
|
if (!isset($data['requirement']) || $data['requirement']) {
|
||||||
$elements = '';
|
$elements = '';
|
||||||
foreach ($data['children'] as $element) {
|
foreach ($data['children'] as $element) {
|
||||||
$elements .= $this->element('/genericElements/ListTopBar/element_' . (empty($element['type']) ? 'simple' : h($element['type'])), array('data' => $element));
|
$elements .= $this->element('/genericElements/ListTopBar/element_' . (empty($element['type']) ? 'simple' : h($element['type'])), array('data' => $element, 'tableRandomValue' => $tableRandomValue));
|
||||||
}
|
}
|
||||||
echo sprintf(
|
echo sprintf(
|
||||||
'<div %s class="btn-group mr-2" role="group" aria-label="button-group">%s</div>',
|
'<div %s class="btn-group mr-2" role="group" aria-label="button-group">%s</div>',
|
||||||
|
|
|
@ -66,7 +66,7 @@ $cakeDescription = 'Cerebrate';
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
<div id="mainModal" class="modal fade" tabindex="-1" role="dialog" aria-labelledby="mediumModalLabel" aria-hidden="true"></div>
|
<div id="mainModal" class="modal fade" tabindex="-1" role="dialog" aria-labelledby="mediumModalLabel" aria-hidden="true"></div>
|
||||||
<div id="mainToastContainer" style="position: absolute; top: 15px; right: 15px;"></div>
|
<div id="mainToastContainer" style="position: absolute; top: 15px; right: 15px; z-index: 1080"></div>
|
||||||
<div id="mainModalContainer"></div>
|
<div id="mainModalContainer"></div>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
@ -1,19 +1,27 @@
|
||||||
class AJAXApi {
|
class AJAXApi {
|
||||||
static genericRequestHeaders = new Headers({
|
static genericRequestHeaders = {
|
||||||
'X-Requested-With': 'XMLHttpRequest'
|
'X-Requested-With': 'XMLHttpRequest'
|
||||||
});
|
};
|
||||||
static genericRequestConfigGET = {
|
static genericRequestConfigGET = {
|
||||||
headers: AJAXApi.genericRequestHeaders
|
headers: new Headers(Object.assign({}, AJAXApi.genericRequestHeaders))
|
||||||
}
|
}
|
||||||
static genericRequestConfigPOST = {
|
static genericRequestConfigPOST = {
|
||||||
headers: AJAXApi.genericRequestHeaders,
|
headers: new Headers(Object.assign({}, AJAXApi.genericRequestHeaders)),
|
||||||
redirect: 'manual',
|
redirect: 'manual',
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
}
|
}
|
||||||
|
static renderHTMLOnFailureHeader = {
|
||||||
|
name: 'X-Request-HTML-On-Failure',
|
||||||
|
value: '1'
|
||||||
|
}
|
||||||
|
|
||||||
static defaultOptions = {
|
static defaultOptions = {
|
||||||
provideFeedback: true,
|
provideFeedback: true,
|
||||||
statusNode: false,
|
statusNode: false,
|
||||||
|
renderedHTMLOnFailureRequested: false,
|
||||||
|
errorToast: {
|
||||||
|
delay: 10000
|
||||||
|
}
|
||||||
}
|
}
|
||||||
options = {}
|
options = {}
|
||||||
loadingOverlay = false
|
loadingOverlay = false
|
||||||
|
@ -23,12 +31,15 @@ class AJAXApi {
|
||||||
this.mergeOptions(options)
|
this.mergeOptions(options)
|
||||||
}
|
}
|
||||||
|
|
||||||
provideFeedback(options, isError=false) {
|
provideFeedback(toastOptions, isError=false, skip=false) {
|
||||||
|
const alteredToastOptions = isError ? Object.assign({}, AJAXApi.defaultOptions.errorToast, toastOptions) : toastOptions
|
||||||
|
if (!skip) {
|
||||||
if (this.options.provideFeedback) {
|
if (this.options.provideFeedback) {
|
||||||
UI.toast(options)
|
UI.toast(alteredToastOptions)
|
||||||
} else {
|
} else {
|
||||||
if (isError) {
|
if (isError) {
|
||||||
console.error(options.body)
|
console.error(alteredToastOptions.body)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -56,13 +67,19 @@ class AJAXApi {
|
||||||
return tmpApi.fetchForm(url, constAlteredOptions.skipRequestHooks)
|
return tmpApi.fetchForm(url, constAlteredOptions.skipRequestHooks)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static async quickPostForm(form, dataToMerge={}, options={}) {
|
||||||
|
const constAlteredOptions = Object.assign({}, {}, options)
|
||||||
|
const tmpApi = new AJAXApi(constAlteredOptions)
|
||||||
|
return tmpApi.postForm(form, dataToMerge, constAlteredOptions.skipRequestHooks)
|
||||||
|
}
|
||||||
|
|
||||||
static async quickFetchAndPostForm(url, dataToMerge={}, options={}) {
|
static async quickFetchAndPostForm(url, dataToMerge={}, options={}) {
|
||||||
const constAlteredOptions = Object.assign({}, {}, options)
|
const constAlteredOptions = Object.assign({}, {}, options)
|
||||||
const tmpApi = new AJAXApi(constAlteredOptions)
|
const tmpApi = new AJAXApi(constAlteredOptions)
|
||||||
return tmpApi.fetchAndPostForm(url, dataToMerge, constAlteredOptions.skipRequestHooks)
|
return tmpApi.fetchAndPostForm(url, dataToMerge, constAlteredOptions.skipRequestHooks)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fetchURL(url, skipRequestHooks=false) {
|
async fetchURL(url, skipRequestHooks=false, skipFeedback=false) {
|
||||||
if (!skipRequestHooks) {
|
if (!skipRequestHooks) {
|
||||||
this.beforeRequest()
|
this.beforeRequest()
|
||||||
}
|
}
|
||||||
|
@ -76,14 +93,14 @@ class AJAXApi {
|
||||||
this.provideFeedback({
|
this.provideFeedback({
|
||||||
variant: 'success',
|
variant: 'success',
|
||||||
title: 'URL fetched',
|
title: 'URL fetched',
|
||||||
});
|
}, false, skipFeedback);
|
||||||
toReturn = data;
|
toReturn = data;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.provideFeedback({
|
this.provideFeedback({
|
||||||
variant: 'danger',
|
variant: 'danger',
|
||||||
title: 'There has been a problem with the operation',
|
title: 'There has been a problem with the operation',
|
||||||
body: error
|
body: error
|
||||||
}, true);
|
}, true, skipFeedback);
|
||||||
toReturn = Promise.reject(error);
|
toReturn = Promise.reject(error);
|
||||||
} finally {
|
} finally {
|
||||||
if (!skipRequestHooks) {
|
if (!skipRequestHooks) {
|
||||||
|
@ -93,7 +110,7 @@ class AJAXApi {
|
||||||
return toReturn
|
return toReturn
|
||||||
}
|
}
|
||||||
|
|
||||||
async fetchForm(url, skipRequestHooks=false) {
|
async fetchForm(url, skipRequestHooks=false, skipFeedback=false) {
|
||||||
if (!skipRequestHooks) {
|
if (!skipRequestHooks) {
|
||||||
this.beforeRequest()
|
this.beforeRequest()
|
||||||
}
|
}
|
||||||
|
@ -116,7 +133,74 @@ class AJAXApi {
|
||||||
variant: 'danger',
|
variant: 'danger',
|
||||||
title: 'There has been a problem with the operation',
|
title: 'There has been a problem with the operation',
|
||||||
body: error
|
body: error
|
||||||
}, true);
|
}, true, skipFeedback);
|
||||||
|
toReturn = Promise.reject(error);
|
||||||
|
} finally {
|
||||||
|
if (!skipRequestHooks) {
|
||||||
|
this.afterRequest()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return toReturn
|
||||||
|
}
|
||||||
|
|
||||||
|
async postForm(form, dataToMerge={}, skipRequestHooks=false, skipFeedback=false) {
|
||||||
|
if (!skipRequestHooks) {
|
||||||
|
this.beforeRequest()
|
||||||
|
}
|
||||||
|
let toReturn
|
||||||
|
let feedbackShown = false
|
||||||
|
try {
|
||||||
|
try {
|
||||||
|
let formData = new FormData(form)
|
||||||
|
formData = AJAXApi.mergeFormData(formData, dataToMerge)
|
||||||
|
let requestConfig = AJAXApi.genericRequestConfigPOST
|
||||||
|
if (this.options.renderedHTMLOnFailureRequested) {
|
||||||
|
requestConfig.headers.append(AJAXApi.renderHTMLOnFailureHeader.name, AJAXApi.renderHTMLOnFailureHeader.value)
|
||||||
|
}
|
||||||
|
let options = {
|
||||||
|
...requestConfig,
|
||||||
|
body: formData,
|
||||||
|
};
|
||||||
|
const response = await fetch(form.action, options);
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error('Network response was not ok')
|
||||||
|
}
|
||||||
|
const clonedResponse = response.clone()
|
||||||
|
try {
|
||||||
|
const data = await response.json()
|
||||||
|
if (data.success) {
|
||||||
|
this.provideFeedback({
|
||||||
|
variant: 'success',
|
||||||
|
body: data.message
|
||||||
|
}, false, skipFeedback);
|
||||||
|
toReturn = data;
|
||||||
|
} else {
|
||||||
|
this.provideFeedback({
|
||||||
|
variant: 'danger',
|
||||||
|
title: 'There has been a problem with the operation',
|
||||||
|
body: data.errors
|
||||||
|
}, true, skipFeedback);
|
||||||
|
feedbackShown = true
|
||||||
|
toReturn = Promise.reject(data.errors);
|
||||||
|
}
|
||||||
|
} catch (error) { // could not parse JSON
|
||||||
|
if (this.options.renderedHTMLOnFailureRequested) {
|
||||||
|
const data = await clonedResponse.text();
|
||||||
|
toReturn = {
|
||||||
|
success: 0,
|
||||||
|
html: data,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
this.provideFeedback({
|
||||||
|
variant: 'danger',
|
||||||
|
title: 'There has been a problem with the operation',
|
||||||
|
body: error
|
||||||
|
}, true, feedbackShown);
|
||||||
|
toReturn = Promise.reject(error);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
toReturn = Promise.reject(error);
|
toReturn = Promise.reject(error);
|
||||||
} finally {
|
} finally {
|
||||||
if (!skipRequestHooks) {
|
if (!skipRequestHooks) {
|
||||||
|
@ -132,41 +216,8 @@ class AJAXApi {
|
||||||
}
|
}
|
||||||
let toReturn
|
let toReturn
|
||||||
try {
|
try {
|
||||||
const form = await this.fetchForm(url, true);
|
const form = await this.fetchForm(url, true, true);
|
||||||
try {
|
toReturn = await this.postForm(form, dataToMerge, true, true)
|
||||||
let formData = new FormData(form)
|
|
||||||
formData = AJAXApi.mergeFormData(formData, dataToMerge)
|
|
||||||
let options = {
|
|
||||||
...AJAXApi.genericRequestConfigPOST,
|
|
||||||
body: formData,
|
|
||||||
};
|
|
||||||
const response = await fetch(form.action, options);
|
|
||||||
if (!response.ok) {
|
|
||||||
throw new Error('Network response was not ok')
|
|
||||||
}
|
|
||||||
const data = await response.json();
|
|
||||||
if (data.success) {
|
|
||||||
this.provideFeedback({
|
|
||||||
variant: 'success',
|
|
||||||
body: data.message
|
|
||||||
});
|
|
||||||
toReturn = data;
|
|
||||||
} else {
|
|
||||||
this.provideFeedback({
|
|
||||||
variant: 'danger',
|
|
||||||
title: 'There has been a problem with the operation',
|
|
||||||
body: data.errors
|
|
||||||
}, true);
|
|
||||||
toReturn = Promise.reject(error);
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
this.provideFeedback({
|
|
||||||
variant: 'danger',
|
|
||||||
title: 'There has been a problem with the operation',
|
|
||||||
body: error
|
|
||||||
}, true);
|
|
||||||
toReturn = Promise.reject(error);
|
|
||||||
}
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
toReturn = Promise.reject(error);
|
toReturn = Promise.reject(error);
|
||||||
} finally {
|
} finally {
|
||||||
|
|
|
@ -16,6 +16,22 @@ class UIFactory {
|
||||||
return theModal
|
return theModal
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Display a modal based on provided options */
|
||||||
|
modalFromURL (url, successCallback, failCallback) {
|
||||||
|
return AJAXApi.quickFetchURL(url).then((modalHTML) => {
|
||||||
|
const theModal = new ModalFactory({
|
||||||
|
rawHTML: modalHTML,
|
||||||
|
replaceFormSubmissionByAjax: true,
|
||||||
|
successCallback: successCallback !== undefined ? successCallback : () => {},
|
||||||
|
failCallback: failCallback !== undefined ? failCallback : (errorMessage) => {},
|
||||||
|
});
|
||||||
|
theModal.makeModal(modalHTML)
|
||||||
|
theModal.show()
|
||||||
|
theModal.$modal.data('modalObject', theModal)
|
||||||
|
return theModal
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
/* Fetch HTML from the provided URL and override content of $container. $statusNode allows to specify another HTML node to display the loading */
|
/* Fetch HTML from the provided URL and override content of $container. $statusNode allows to specify another HTML node to display the loading */
|
||||||
reload (url, $container, $statusNode=null) {
|
reload (url, $container, $statusNode=null) {
|
||||||
$container = $($container)
|
$container = $($container)
|
||||||
|
@ -26,7 +42,7 @@ class UIFactory {
|
||||||
AJAXApi.quickFetchURL(url, {
|
AJAXApi.quickFetchURL(url, {
|
||||||
statusNode: $statusNode[0]
|
statusNode: $statusNode[0]
|
||||||
}).then((data) => {
|
}).then((data) => {
|
||||||
$container[0].outerHTML = data
|
$container.replaceWith(data)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -143,6 +159,7 @@ class ModalFactory {
|
||||||
titleHtml: false,
|
titleHtml: false,
|
||||||
body: false,
|
body: false,
|
||||||
bodyHtml: false,
|
bodyHtml: false,
|
||||||
|
rawHTML: false,
|
||||||
variant: '',
|
variant: '',
|
||||||
modalClass: [],
|
modalClass: [],
|
||||||
headerClass: [],
|
headerClass: [],
|
||||||
|
@ -160,6 +177,8 @@ class ModalFactory {
|
||||||
error: function() {},
|
error: function() {},
|
||||||
shownCallback: function() {},
|
shownCallback: function() {},
|
||||||
hiddenCallback: function() {},
|
hiddenCallback: function() {},
|
||||||
|
successCallback: function() {},
|
||||||
|
replaceFormSubmissionByAjax: false
|
||||||
}
|
}
|
||||||
|
|
||||||
static availableType = [
|
static availableType = [
|
||||||
|
@ -190,6 +209,9 @@ class ModalFactory {
|
||||||
})
|
})
|
||||||
.on('shown.bs.modal', function () {
|
.on('shown.bs.modal', function () {
|
||||||
that.options.shownCallback()
|
that.options.shownCallback()
|
||||||
|
if (that.options.replaceFormSubmissionByAjax) {
|
||||||
|
that.replaceFormSubmissionByAjax()
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -203,11 +225,11 @@ class ModalFactory {
|
||||||
}
|
}
|
||||||
|
|
||||||
isValid() {
|
isValid() {
|
||||||
return this.options.title !== false || this.options.body !== false || this.options.titleHtml !== false || this.options.bodyHtml !== false
|
return this.options.title !== false || this.options.body !== false || this.options.titleHtml !== false || this.options.bodyHtml !== false || this.options.rawHTML !== false
|
||||||
}
|
}
|
||||||
|
|
||||||
buildModal() {
|
buildModal() {
|
||||||
var $modal = $('<div class="modal fade" tabindex="-1" aria-hidden="true"/>')
|
const $modal = $('<div class="modal fade" tabindex="-1" aria-hidden="true"/>')
|
||||||
if (this.options.id !== false) {
|
if (this.options.id !== false) {
|
||||||
$modal.attr('id', this.options.id)
|
$modal.attr('id', this.options.id)
|
||||||
$modal.attr('aria-labelledby', this.options.id)
|
$modal.attr('aria-labelledby', this.options.id)
|
||||||
|
@ -215,11 +237,15 @@ class ModalFactory {
|
||||||
if (this.options.modalClass !== false) {
|
if (this.options.modalClass !== false) {
|
||||||
$modal.addClass(this.options.modalClass)
|
$modal.addClass(this.options.modalClass)
|
||||||
}
|
}
|
||||||
var $modalDialog = $('<div class="modal-dialog"/>')
|
let $modalDialog
|
||||||
var $modalContent = $('<div class="modal-content"/>')
|
if (this.options.rawHTML) {
|
||||||
|
$modalDialog = $(this.options.rawHTML)
|
||||||
|
} else {
|
||||||
|
$modalDialog = $('<div class="modal-dialog"/>')
|
||||||
|
const $modalContent = $('<div class="modal-content"/>')
|
||||||
if (this.options.title !== false || this.options.titleHtml !== false) {
|
if (this.options.title !== false || this.options.titleHtml !== false) {
|
||||||
var $modalHeader = $('<div class="modal-header"/>')
|
const $modalHeader = $('<div class="modal-header"/>')
|
||||||
var $modalHeaderText
|
let $modalHeaderText
|
||||||
if (this.options.titleHtml !== false) {
|
if (this.options.titleHtml !== false) {
|
||||||
$modalHeaderText = $('<div/>').html(this.options.titleHtml);
|
$modalHeaderText = $('<div/>').html(this.options.titleHtml);
|
||||||
} else {
|
} else {
|
||||||
|
@ -230,8 +256,8 @@ class ModalFactory {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.options.body !== false || this.options.bodyHtml !== false) {
|
if (this.options.body !== false || this.options.bodyHtml !== false) {
|
||||||
var $modalBody = $('<div class="modal-body"/>')
|
const $modalBody = $('<div class="modal-body"/>')
|
||||||
var $modalBodyText
|
let $modalBodyText
|
||||||
if (this.options.bodyHtml !== false) {
|
if (this.options.bodyHtml !== false) {
|
||||||
$modalBodyText = $('<div/>').html(this.options.bodyHtml);
|
$modalBodyText = $('<div/>').html(this.options.bodyHtml);
|
||||||
} else {
|
} else {
|
||||||
|
@ -241,11 +267,12 @@ class ModalFactory {
|
||||||
$modalContent.append($modalBody)
|
$modalContent.append($modalBody)
|
||||||
}
|
}
|
||||||
|
|
||||||
var $modalFooter = $('<div class="modal-footer"/>')
|
const $modalFooter = $('<div class="modal-footer"/>')
|
||||||
$modalFooter.append(this.getFooterBasedOnType())
|
$modalFooter.append(this.getFooterBasedOnType())
|
||||||
$modalContent.append($modalFooter)
|
$modalContent.append($modalFooter)
|
||||||
|
|
||||||
$modalDialog.append($modalContent)
|
$modalDialog.append($modalContent)
|
||||||
|
}
|
||||||
$modal.append($modalDialog)
|
$modal.append($modalDialog)
|
||||||
return $modal
|
return $modal
|
||||||
}
|
}
|
||||||
|
@ -270,20 +297,29 @@ class ModalFactory {
|
||||||
getFooterConfirm() {
|
getFooterConfirm() {
|
||||||
let variant = this.options.type.split('-')[1]
|
let variant = this.options.type.split('-')[1]
|
||||||
variant = variant !== undefined ? variant : 'primary'
|
variant = variant !== undefined ? variant : 'primary'
|
||||||
return [
|
const $buttonCancel = $('<button type="button" class="btn btn-secondary" data-dismiss="modal"></button>')
|
||||||
$('<button type="button" class="btn btn-secondary" data-dismiss="modal"></button>')
|
|
||||||
.text(this.options.cancelText)
|
.text(this.options.cancelText)
|
||||||
.click(
|
.click(
|
||||||
(evt) => {
|
(evt) => {
|
||||||
this.options.cancel(() => { this.hide() }, this, evt)
|
this.options.cancel(() => { this.hide() }, this, evt)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
.attr('data-dismiss', (this.options.closeManually || !this.options.closeOnSuccess) ? '' : 'modal'),
|
.attr('data-dismiss', (this.options.closeManually || !this.options.closeOnSuccess) ? '' : 'modal')
|
||||||
$('<button type="button" class="btn"></button>')
|
|
||||||
|
const $buttonConfirm = $('<button type="button" class="btn"></button>')
|
||||||
.addClass('btn-' + variant)
|
.addClass('btn-' + variant)
|
||||||
.text(this.options.confirmText)
|
.text(this.options.confirmText)
|
||||||
.click(
|
.click(this.getConfirmationHandlerFunction())
|
||||||
(evt) => {
|
.attr('data-dismiss', (this.options.closeManually || this.options.closeOnSuccess) ? '' : 'modal')
|
||||||
|
return [$buttonCancel, $buttonConfirm]
|
||||||
|
}
|
||||||
|
|
||||||
|
static getCloseButton() {
|
||||||
|
return $(ModalFactory.closeButtonHtml)
|
||||||
|
}
|
||||||
|
|
||||||
|
getConfirmationHandlerFunction() {
|
||||||
|
return (evt) => {
|
||||||
let confirmFunction = this.options.confirm
|
let confirmFunction = this.options.confirm
|
||||||
if (this.options.APIConfirm) {
|
if (this.options.APIConfirm) {
|
||||||
const tmpApi = new AJAXApi({
|
const tmpApi = new AJAXApi({
|
||||||
|
@ -295,7 +331,7 @@ class ModalFactory {
|
||||||
if (confirmResult === undefined) {
|
if (confirmResult === undefined) {
|
||||||
this.hide()
|
this.hide()
|
||||||
} else {
|
} else {
|
||||||
confirmResult.then(() => {
|
confirmResult.then((data) => {
|
||||||
if (this.options.closeOnSuccess) {
|
if (this.options.closeOnSuccess) {
|
||||||
this.hide()
|
this.hide()
|
||||||
}
|
}
|
||||||
|
@ -305,13 +341,36 @@ class ModalFactory {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
|
||||||
.attr('data-dismiss', (this.options.closeManually || this.options.closeOnSuccess) ? '' : 'modal')
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static getCloseButton() {
|
replaceFormSubmissionByAjax() {
|
||||||
return $(ModalFactory.closeButtonHtml)
|
const $submitButton = this.$modal.find('.modal-footer #submitButton')
|
||||||
|
const formID = $submitButton.data('form-id')
|
||||||
|
let $form
|
||||||
|
if (formID) {
|
||||||
|
$form = $(formID)
|
||||||
|
} else {
|
||||||
|
$form = this.$modal.find('form')
|
||||||
|
}
|
||||||
|
|
||||||
|
this.options.APIConfirm = (tmpApi) => {
|
||||||
|
tmpApi.mergeOptions({renderedHTMLOnFailureRequested: true})
|
||||||
|
return tmpApi.postForm($form[0])
|
||||||
|
.then((data) => {
|
||||||
|
if (data.success) {
|
||||||
|
this.options.successCallback(data)
|
||||||
|
} else { // Validation error, replace modal content with new html
|
||||||
|
this.$modal.html(data.html)
|
||||||
|
this.replaceFormSubmissionByAjax()
|
||||||
|
return Promise.reject('Validation error');
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch((errorMessage, response) => {
|
||||||
|
this.options.failCallback(errorMessage)
|
||||||
|
return Promise.reject(errorMessage);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
$submitButton.click(this.getConfirmationHandlerFunction())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue