src/VirtualPage.php

Summary

Maintainability
A
0 mins
Test Coverage
<?php

declare(strict_types=1);

namespace Atk4\Ui;

use Atk4\Ui\Js\Jquery;
use Atk4\Ui\Js\JsFunction;

/**
 * Virtual page normally does not render, yet it has it's own trigger and will respond
 * to the trigger in a number of useful way depending on trigger's argument:.
 *
 *  - cut = will only output HTML of this VirtualPage and it's sub-elements
 *  - popup = will add VirtualPage directly into body, ideal for pop-up windows
 *  - normal = will get rid of all the normal components inside Layout's content replacing them
 *      the render of this page. Will preserve menus and top bar but that's it.
 */
class VirtualPage extends View
{
    public $ui = 'container';

    /** @var Callback */
    public $cb;

    /** @var string|null specify custom callback trigger for the URL (see Callback::$urlTrigger) */
    protected $urlTrigger;

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

        $this->cb = Callback::addTo($this, ['urlTrigger' => $this->urlTrigger ?? $this->name]);
        unset($this->{'urlTrigger'});
    }

    /**
     * Set callback function of virtual page.
     *
     * @param \Closure($this, mixed, mixed, mixed, mixed, mixed, mixed, mixed, mixed, mixed, mixed): void $fx
     * @param list<mixed>                                                                                 $fxArgs
     */
    #[\Override]
    public function set($fx = null, $fxArgs = [])
    {
        if (!$fx instanceof \Closure) {
            throw new \TypeError('$fx must be of type Closure');
        }

        $this->cb->set($fx, [$this, ...$fxArgs]);

        return $this;
    }

    /**
     * Is virtual page active?
     */
    public function isTriggered(): bool
    {
        return $this->cb->isTriggered();
    }

    /**
     * Returns URL which you can load directly in the browser location, open in a new tab,
     * new window or inside iframe. This URL will contain HTML for a new page.
     */
    public function getUrl(string $mode = 'callback'): string
    {
        return $this->cb->getUrl($mode);
    }

    /**
     * Return URL that is designed to be loaded from inside JavaScript and contain JSON code.
     * This is useful for dynamically loaded Modal, Tabs or Loader.
     */
    public function getJsUrl(string $mode = 'callback'): string
    {
        return $this->cb->getJsUrl($mode);
    }

    /**
     * VirtualPage is not rendered normally. It's invisible. Only when
     * it is triggered, it will exclusively output it's content.
     */
    #[\Override]
    public function getHtml(): string
    {
        if (!$this->cb->isTriggered()) {
            return '';
        } elseif (!$this->cb->canTerminate()) {
            return parent::getHtml();
        }

        $mode = $this->cb->getTriggeredValue();
        if ($mode) {
            // special treatment for popup
            if ($mode === 'popup') {
                $this->getApp()->html->template->set('title', $this->getApp()->title);
                $this->getApp()->html->template->dangerouslySetHtml('Content', parent::getHtml());
                $this->getApp()->html->template->dangerouslyAppendHtml(
                    'Head',
                    $this->getApp()->getTag('script', [], (new Jquery(new JsFunction([], $this->getJs())))->jsRender() . ';')
                );

                $this->getApp()->terminateHtml($this->getApp()->html->template);
            }

            // render and terminate
            if ($this->getApp()->hasRequestQueryParam('__atk_json')) {
                $this->getApp()->terminateJson($this);
            }

            // do not terminate if callback supplied (no cutting)
            if ($mode !== 'callback') {
                $this->getApp()->terminateHtml($this);
            }
        }

        // remove all elements from inside the Content
        foreach ($this->getApp()->layout->elements as $key => $view) {
            if ($view instanceof View && $view->region === 'Content') {
                unset($this->getApp()->layout->elements[$key]);
            }
        }

        $this->getApp()->layout->template->dangerouslySetHtml('Content', parent::getHtml());

        // collect JS from everywhere
        foreach ($this->_jsActions as $when => $actions) {
            foreach ($actions as $action) {
                $this->getApp()->layout->_jsActions[$when][] = $action;
            }
        }

        $this->getApp()->html->template->dangerouslySetHtml('Content', $this->getApp()->layout->template->renderToHtml());
        $this->getApp()->html->template->dangerouslyAppendHtml(
            'Head',
            $this->getApp()->getTag('script', [], (new Jquery(new JsFunction([], $this->getApp()->layout->getJs())))->jsRender() . ';')
        );

        $this->getApp()->terminateHtml($this->getApp()->html->template);
    }
}