src/Panel/Right.php

Summary

Maintainability
A
0 mins
Test Coverage
<?php

declare(strict_types=1);

namespace Atk4\Ui\Panel;

use Atk4\Core\Factory;
use Atk4\Ui\Button;
use Atk4\Ui\Js\Jquery;
use Atk4\Ui\Js\JsChain;
use Atk4\Ui\Js\JsExpressionable;
use Atk4\Ui\Modal;
use Atk4\Ui\View;

/**
 * Right Panel implementation.
 * Opening, closing and loading Panel content is managed
 * via the JS panel service.
 *
 * Content is loaded via a LoadableContent View.
 * This view must implement a callback for content to be added via the callback function.
 */
class Right extends View implements Loadable
{
    public array $class = ['atk-right-panel'];
    public $defaultTemplate = 'panel/right.html';

    /** @var Modal|null */
    public $closeModal;
    /** @var array<mixed> Confirmation Modal default */
    public $defaultModal = [Modal::class, 'class' => ['mini']];

    /** @var View|null The content to display inside flyout */
    protected $dynamicContent;

    /** @var bool can be closed by clicking outside panel. */
    protected $hasClickAway = true;

    /** @var bool can be closed via esc key. */
    protected $hasEscAway = true;

    /** @var array<mixed> The default content seed. */
    public $dynamic = [Content::class];

    /** @var string The CSS selector on where to add close panel event triggering for closing it. */
    public $closeSelector = '.atk-panel-close';

    /** @var string a CSS selector where warning trigger class will be applied. */
    public $warningSelector = '.atk-panel-warning';

    /** @var string the CSS class name to apply to element set by warning selector. */
    public $warningTrigger = 'atk-visible';

    /** @var string the warning icon class */
    public $warningIcon = 'exclamation circle';

    /** @var string the close icon class */
    public $closeIcon = 'times';

    #[\Override]
    protected function init(): void
    {
        parent::init();

        if ($this->dynamic) {
            $this->addDynamicContent(Factory::factory($this->dynamic));
        }
    }

    #[\Override]
    public function addDynamicContent(LoadableContent $content): void
    {
        $this->dynamicContent = Content::addTo($this, [], ['LoadContent']);
    }

    #[\Override]
    public function getDynamicContent(): LoadableContent
    {
        return $this->dynamicContent;
    }

    /**
     * Return JS expression in order to retrieve panelService.
     */
    public function jsService(): JsChain
    {
        return new JsChain('atk.panelService');
    }

    /**
     * Return JS expression need to open panel via JS panelService.
     *
     * @param array<string, string> $urlArgs       the argument to include when dynamic content panel open
     * @param list<string>          $dataAttribute the data attribute name to include in reload from the triggering element
     * @param string|null           $activeCss     the CSS class name to apply on triggering element when panel is open
     * @param JsExpressionable      $jsTrigger     JS expression that trigger panel to open. Default = $(this).
     */
    public function jsOpen(array $urlArgs = [], array $dataAttribute = [], ?string $activeCss = null, ?JsExpressionable $jsTrigger = null): JsExpressionable
    {
        return $this->jsService()->openPanel([
            'triggered' => $jsTrigger ?? new Jquery(),
            'reloadArgs' => $dataAttribute,
            'urlArgs' => $urlArgs,
            'openId' => $this->name,
            'activeCSS' => $activeCss,
        ]);
    }

    /**
     * Will reload panel passing args as Get param via JS flyoutService.
     *
     * @param array<string, string> $args
     */
    public function jsPanelReload(array $args = []): JsExpressionable
    {
        return $this->jsService()->reloadPanel($this->name, $args);
    }

    /**
     * Return JS expression need to close panel via JS panelService.
     */
    public function jsClose(): JsExpressionable
    {
        return $this->jsService()->closePanel($this->name);
    }

    /**
     * Attach confirmation modal view to display.
     * JS flyoutService will prevent closing of Flyout if a confirmation modal
     * is attached to it and flyoutService detect that the current open flyoutContent has warning on.
     */
    public function addConfirmation(string $msg, string $title = 'Closing panel!', ?string $okButton = null, ?string $cancelButton = null): void
    {
        if (!$okButton) {
            $okButton = (new Button(['Ok']))->addClass('ok');
        }

        if (!$cancelButton) {
            $cancelButton = (new Button(['Cancel']))->addClass('cancel');
        }
        $this->closeModal = $this->getApp()->add(array_merge($this->defaultModal, ['title' => $title]));
        $this->closeModal->add([View::class, $msg, 'element' => 'p']);
        $this->closeModal->addButtonAction(Factory::factory($okButton));
        $this->closeModal->addButtonAction(Factory::factory($cancelButton));

        $this->closeModal->notClosable();
    }

    /**
     * Callback to execute when panel open if dynamic content is set.
     * Differ the callback execution to the FlyoutContent.
     *
     * @param \Closure<T of LoadableContent>(T): void $fx
     */
    public function onOpen(\Closure $fx): void
    {
        $this->getDynamicContent()->onLoad($fx);
    }

    /**
     * Display or not a Warning sign in Panel.
     *
     * @return Jquery
     */
    public function jsDisplayWarning(bool $state = true): JsExpressionable
    {
        $chain = new Jquery('#' . $this->name . ' ' . $this->warningSelector);

        return $state ? $chain->addClass($this->warningTrigger) : $chain->removeClass($this->warningTrigger);
    }

    /**
     * Toggle warning sign.
     *
     * @return Jquery
     */
    public function jsToggleWarning(): JsExpressionable
    {
        return (new Jquery('#' . $this->name . ' ' . $this->warningSelector))->toggleClass($this->warningTrigger);
    }

    /**
     * @return array<string, mixed>
     */
    public function getPanelOptions(): array
    {
        $res = [
            'id' => $this->name,
            'loader' => ['selector' => '.ui.loader', 'trigger' => 'active'], // the CSS selector and trigger class to activate loader
            'modal' => $this->closeModal,
            'warning' => ['selector' => $this->warningSelector, 'trigger' => $this->warningTrigger],
            'visible' => 'atk-visible', // the triggering CSS class that will make this panel visible
            'closeSelector' => $this->closeSelector, // the CSS selector to close this flyout
            'hasClickAway' => $this->hasClickAway,
            'hasEscAway' => $this->hasEscAway,
        ];

        if ($this->dynamicContent) {
            $res['url'] = $this->getDynamicContent()->getCallbackUrl();
            $res['clearable'] = $this->getDynamicContent()->getClearSelector();
        }

        return $res;
    }

    #[\Override]
    protected function renderView(): void
    {
        $this->template->trySet('WarningIcon', $this->warningIcon);
        $this->template->trySet('CloseIcon', $this->closeIcon);

        parent::renderView();

        $this->js(true, $this->jsService()->addPanel($this->getPanelOptions()));
    }
}