344 lines
13 KiB
PHP
344 lines
13 KiB
PHP
<?php
|
|
|
|
namespace App\View\Helper\BootstrapElements;
|
|
|
|
use App\View\Helper\BootstrapGeneric;
|
|
|
|
/**
|
|
* Creates a bootstrap modal based on the given options
|
|
*
|
|
* # Options
|
|
* - size: Control the horizontal size of the modal. Valid values: 'sm', 'lg', 'xl'
|
|
* - centered, scrollable, backdropStatic: Default bootstrap behavior
|
|
* - show: Immediately instantiate the modal and show it
|
|
* - header-variant, body-variant, footer-variant: Default bootstrap variant to be applied to these modal sections
|
|
* - title: The title of the modal
|
|
* - titleHtml: The HTML title of the modal
|
|
* - body: The body of the modal
|
|
* - bodyHtml: The HTML body of the modal
|
|
* - footerHtml: The HTML footer of the modal. Override the $type option
|
|
* - modalClass, headerClass, footerClass: Classes to be applied to these modal sections
|
|
* - type: Control the type of actions available.
|
|
* Valid values: 'ok-only', 'confirm', 'custom'
|
|
* - The `ok-only` Displays a single 'Ok' button
|
|
* - The `confirm` Displays a 'Confirm' and 'Cancel' buttons
|
|
* - `confirmButton` and `cancelButton`: Can be used to pass a BootstrapElements/BootstrapButton configuration
|
|
* - The `custom` Display a list of button defined in the $footerButtons parameter
|
|
* - confirmFunction: The function to be called when clicking the "confirm" button
|
|
* - This options only works if the option $show is enabled or if the modal is loaded with the UI ModalFactory function (e.g. `UI.submissionModal()` or `UI.modal()`)
|
|
* - cancelOnclick: The function to be called once the "cancel" button trigger the `onclick` event
|
|
* - footerButtons: A list of configuration to be passed to BootstrapElements/BootstrapButton
|
|
* - The option `clickFunction` can be used to set the function to be called when clicking the button. Behavior similar to "confirmFunction"
|
|
*
|
|
* # Click functions behaviors:
|
|
* - *-Onclick functions have the same behavior as the 'onclick' HTML parameter
|
|
* - `confirmFunction` and `clickFunction` are called with additional 2 additional arguments:
|
|
* - modalObject: The instantiated ModalFactory object
|
|
* - tmpApi: An instantiated AJAXApi object linked with the modal button
|
|
* - If no functions are provided, Submit the form in place or close the modal
|
|
*
|
|
*
|
|
* # Usage:
|
|
*
|
|
* ## Simple styled modal that is displayed automatically when the HTML is attached to the page
|
|
* $this->Bootstrap->modal([
|
|
* 'title' => 'Modal title',
|
|
* 'size' => 'lg',
|
|
* 'type' => 'ok-only',
|
|
* 'body' => '<b>Body content</b>',
|
|
* 'header-variant' => 'dark',
|
|
* 'body-variant' => 'light',
|
|
* 'footer-variant' => 'warning',
|
|
* 'show' => true,
|
|
* ]);
|
|
|
|
* ## Modal with custom onclick handler
|
|
* $this->Bootstrap->modal([
|
|
* 'type' => 'confirm',
|
|
* 'bodyHtml' => '<b>Body content</b>',
|
|
* 'confirmButton' => [
|
|
* 'text' => 'Show modal',
|
|
* 'icon' => 'eye',
|
|
* 'onclick' => 'UI.toast({"title": "confirmed!"})',
|
|
* ],
|
|
* 'cancelOnclick' => 'UI.toast({"title": "cancelled"})',
|
|
* 'show' => true,
|
|
* ]);
|
|
*
|
|
* ## Modal with a onclick handler with prepared arguments bound to the confirm button
|
|
* $this->Bootstrap->modal([
|
|
* 'type' => 'confirm',
|
|
* 'confirmButton' => [
|
|
* 'text' => 'Confirm',
|
|
* 'icon' => 'check',
|
|
* ],
|
|
* 'confirmFunction' => 'myConfirmFunction', // myConfirmFunction is called with the $modalObject and $tmpApi intialized
|
|
* 'show' => true,
|
|
* ]);
|
|
*
|
|
* /*
|
|
* Example of confirm function
|
|
* - case 1: If void is returned the modal close automatically regardless of the result
|
|
* - case 2: If a promise is returned, the modal close automatically if the promise is a success
|
|
* A success is defined as follow:
|
|
* - No exceptions
|
|
* - No data returned
|
|
* - Object returned with key `success` evaluting to true
|
|
* - case 3: The modal can be closed manually with: `modalObject.hide()`
|
|
*
|
|
* function myConfirmFunction(modalObject, tmpApi) {
|
|
* const $form = modalObject.$modal.find('form')
|
|
* const postPromise = $form.length == 1 ?
|
|
* tmpApi.postForm($form[0]) :
|
|
* tmpApi.fetchJSON('/users/view/', false, true)
|
|
* .then((result) => {
|
|
* console.log(result)
|
|
* constToReturn = {
|
|
* success: true, // will close the modal automatically
|
|
* }
|
|
* return constToReturn
|
|
* })
|
|
* .catch((errors) => {
|
|
* console.log(errors)
|
|
* })
|
|
*
|
|
* return postPromise
|
|
* }
|
|
|
|
* ## Modal with custom footer made of buttons
|
|
* $this->Bootstrap->modal([
|
|
* 'type' => 'custom',
|
|
* 'footerButtons' => [
|
|
* [
|
|
* 'text' => 'Confirm',
|
|
* 'icon' => 'check',
|
|
* 'variant' => 'danger',
|
|
* 'clickFunction' => 'testapi',
|
|
* ],
|
|
* [
|
|
* 'text' => 'Cancel',
|
|
* 'onclick' => 'UI.toast({"title": "confirmed!"})',
|
|
* ],
|
|
* ],
|
|
* 'show' => true,
|
|
* ]);
|
|
*/
|
|
class BootstrapModal extends BootstrapGeneric
|
|
{
|
|
private $defaultOptions = [
|
|
'size' => '',
|
|
'centered' => true,
|
|
'scrollable' => true,
|
|
'backdropStatic' => false,
|
|
'show' => false,
|
|
'header-variant' => '',
|
|
'body-variant' => '',
|
|
'footer-variant' => '',
|
|
'title' => '',
|
|
'titleHtml' => null,
|
|
'body' => '',
|
|
'bodyHtml' => null,
|
|
'footerHtml' => null,
|
|
'modalClass' => [''],
|
|
'headerClass' => [''],
|
|
'bodyClass' => [''],
|
|
'footerClass' => [''],
|
|
'confirmButton' => [
|
|
'text' => 'Confirm',
|
|
],
|
|
'cancelButton' => [
|
|
'text' => 'Cancel',
|
|
],
|
|
'type' => 'ok-only',
|
|
'footerButtons' => [],
|
|
'confirmFunction' => '', // Will be called with the following arguments confirmFunction(modalObject, tmpApi)
|
|
'cancelOnclick' => ''
|
|
];
|
|
|
|
function __construct($options)
|
|
{
|
|
$this->allowedOptionValues = [
|
|
'size' => ['sm', 'lg', 'xl', ''],
|
|
'type' => ['ok-only', 'confirm', 'custom'],
|
|
'header-variant' => array_merge(BootstrapGeneric::$variants, ['']),
|
|
'body-variant' => array_merge(BootstrapGeneric::$variants, ['']),
|
|
'footer-variant' => array_merge(BootstrapGeneric::$variants, ['']),
|
|
];
|
|
$this->processOptions($options);
|
|
}
|
|
|
|
private function processOptions($options)
|
|
{
|
|
$this->options = array_merge($this->defaultOptions, $options);
|
|
$this->checkOptionValidity();
|
|
$this->options['modalClass'] = $this->convertToArrayIfNeeded($this->options['modalClass']);
|
|
$this->options['headerClass'] = $this->convertToArrayIfNeeded($this->options['headerClass']);
|
|
$this->options['bodyClass'] = $this->convertToArrayIfNeeded($this->options['bodyClass']);
|
|
$this->options['footerClass'] = $this->convertToArrayIfNeeded($this->options['footerClass']);
|
|
|
|
$possiblVariants = ['header-variant', 'body-variant', 'footer-variant'];
|
|
foreach ($possiblVariants as $possiblVariant) {
|
|
if (!empty($this->options[$possiblVariant])) {
|
|
$this->options[sprintf('%sClass', substr($possiblVariant, 0, -8))][] = self::getBGAndTextClassForVariant($this->options[$possiblVariant]);
|
|
}
|
|
}
|
|
|
|
if (!empty($options['confirmFunction']) && !empty($options['confirmButton']['onclick'])) {
|
|
throw new \InvalidArgumentException(__('Option `{0}` can not be used in conjuction with `{1}` for the confirm button', 'confirmFunction', 'onclick'));
|
|
}
|
|
}
|
|
|
|
public function modal()
|
|
{
|
|
$modal = $this->genModal();
|
|
if ($this->options['show']) {
|
|
return $this->encapsulateWithUIHelper($modal);
|
|
}
|
|
return $modal;
|
|
}
|
|
|
|
private function encapsulateWithUIHelper(string $modal): string
|
|
{
|
|
return $this->node('script', [], sprintf(
|
|
"$(document).ready(function() {
|
|
setTimeout(() => {
|
|
UI.modal({
|
|
rawHtml: \"%s\"
|
|
})
|
|
}, 1);
|
|
})",
|
|
str_replace('"', '\"', $modal)
|
|
));
|
|
}
|
|
|
|
private function genModal()
|
|
{
|
|
$dialog = $this->nodeOpen('div', [
|
|
'class' => array_merge(
|
|
['modal-dialog', (!empty($this->options['size'])) ? "modal-{$this->options['size']}" : ''],
|
|
$this->options['modalClass']
|
|
),
|
|
]);
|
|
$content = $this->nodeOpen('div', [
|
|
'class' => ['modal-content'],
|
|
]);
|
|
$header = $this->genHeader();
|
|
$body = $this->genBody();
|
|
$footer = $this->genFooter();
|
|
$closedDiv = $this->nodeClose('div');
|
|
|
|
$html = "{$dialog}{$content}{$header}{$body}{$footer}{$closedDiv}{$closedDiv}";
|
|
return $html;
|
|
}
|
|
|
|
private function genHeader()
|
|
{
|
|
$header = $this->nodeOpen('div', ['class' => array_merge(['modal-header'], $this->options['headerClass'])]);
|
|
$header .= $this->options['titleHtml'] ?? $this->node('h5', ['class' => ['modal-title']], h($this->options['title']));
|
|
if (empty($this->options['backdropStatic'])) {
|
|
$header .= $this->genericCloseButton('modal');
|
|
}
|
|
$header .= $this->nodeClose('div');
|
|
return $header;
|
|
}
|
|
|
|
private function genBody()
|
|
{
|
|
$body = $this->nodeOpen('div', ['class' => array_merge(['modal-body'], $this->options['bodyClass'])]);
|
|
$body .= $this->options['bodyHtml'] ?? h($this->options['body']);
|
|
$body .= $this->nodeClose('div');
|
|
return $body;
|
|
}
|
|
|
|
private function genFooter()
|
|
{
|
|
$footer = $this->nodeOpen('div', [
|
|
'class' => array_merge(['modal-footer'], $this->options['footerClass']),
|
|
'data-custom-footer' => $this->options['type'] == 'custom'
|
|
]);
|
|
$footer .= $this->options['footerHtml'] ?? $this->getFooterBasedOnType();
|
|
$footer .= $this->nodeClose('div');
|
|
return $footer;
|
|
}
|
|
|
|
private function getFooterBasedOnType()
|
|
{
|
|
if ($this->options['type'] == 'ok-only') {
|
|
return $this->getFooterOkOnly();
|
|
} else if (str_contains($this->options['type'], 'confirm')) {
|
|
return $this->getFooterConfirm();
|
|
} else if ($this->options['type'] == 'custom') {
|
|
return $this->getFooterCustom();
|
|
} else {
|
|
return $this->getFooterOkOnly();
|
|
}
|
|
}
|
|
|
|
private function getFooterOkOnly()
|
|
{
|
|
return (new BootstrapButton([
|
|
'variant' => 'primary',
|
|
'text' => __('Ok'),
|
|
'onclick' => $this->options['confirmOnclick'],
|
|
'attrs' => [
|
|
'data-bs-dismiss' => $this->options['confirmOnclick'] ?? 'modal',
|
|
],
|
|
]))->button();
|
|
}
|
|
|
|
private function getFooterConfirm()
|
|
{
|
|
$buttonCancelConfig = array_merge(
|
|
[
|
|
'variant' => 'secondary',
|
|
'attrs' => [
|
|
'data-bs-dismiss' => 'modal',
|
|
'onclick' => $this->options['cancelOnclick']
|
|
]
|
|
],
|
|
$this->options['cancelButton'],
|
|
);
|
|
$buttonCancel = (new BootstrapButton($buttonCancelConfig))->button();
|
|
|
|
$defaultConfig = [
|
|
'variant' => 'primary',
|
|
'class' => 'modal-confirm-button',
|
|
];
|
|
if (!empty($this->options['confirmOnclick'])) {
|
|
$defaultConfig['onclick'] = $this->options['confirmOnclick'];
|
|
}
|
|
if (!empty($this->options['confirmFunction'])) {
|
|
$defaultConfig['attrs']['data-confirmFunction'] = $this->options['confirmFunction'];
|
|
}
|
|
$buttonConfirmConfig = array_merge(
|
|
$defaultConfig,
|
|
$this->options['confirmButton'],
|
|
);
|
|
$buttonConfirm = (new BootstrapButton($buttonConfirmConfig))->button();
|
|
return $buttonCancel . $buttonConfirm;
|
|
}
|
|
|
|
private function getFooterCustom()
|
|
{
|
|
$buttons = [];
|
|
foreach ($this->options['footerButtons'] as $buttonConfig) {
|
|
$defaultConfig = [
|
|
'variant' => 'primary',
|
|
'class' => 'modal-confirm-button',
|
|
'attrs' => [
|
|
'data-bs-dismiss' => !empty($buttonConfig['clickFunction']) ? '' : 'modal',
|
|
]
|
|
];
|
|
if (!empty($buttonConfig['clickFunction'])) {
|
|
$defaultConfig['attrs']['data-clickFunction'] = $buttonConfig['clickFunction'];
|
|
}
|
|
$buttonConfig = array_merge(
|
|
$defaultConfig,
|
|
$buttonConfig,
|
|
);
|
|
$buttons[] = (new BootstrapButton($buttonConfig))->button();
|
|
}
|
|
return implode('', $buttons);
|
|
}
|
|
}
|