1: <?php
2:
3: declare(strict_types=1);
4:
5: namespace Atk4\Ui;
6:
7: /**
8: * Virtual page normally does not render, yet it has it's own trigger and will respond
9: * to the trigger in a number of useful way depending on trigger's argument:.
10: *
11: * - cut = will only output HTML of this VirtualPage and it's sub-elements
12: * - popup = will add VirtualPage directly into body, ideal for pop-up windows
13: * - normal = will get rid of all the normal components inside Layout's content replacing them
14: * the render of this page. Will preserve menus and top bar but that's it.
15: */
16: class VirtualPage extends View
17: {
18: public $ui = 'container';
19:
20: /** @var Callback */
21: public $cb;
22:
23: /** @var string|null specify custom callback trigger for the URL (see Callback::$urlTrigger) */
24: protected $urlTrigger;
25:
26: #[\Override]
27: protected function init(): void
28: {
29: parent::init();
30:
31: $this->cb = Callback::addTo($this, ['urlTrigger' => $this->urlTrigger ?? $this->name]);
32: unset($this->{'urlTrigger'});
33: }
34:
35: /**
36: * Set callback function of virtual page.
37: *
38: * @param \Closure($this, mixed, mixed, mixed, mixed, mixed, mixed, mixed, mixed, mixed, mixed): void $fx
39: * @param array $fxArgs
40: */
41: #[\Override]
42: public function set($fx = null, $fxArgs = [])
43: {
44: if (!$fx instanceof \Closure) {
45: throw new \TypeError('$fx must be of type Closure');
46: }
47:
48: $this->cb->set($fx, [$this, ...$fxArgs]);
49:
50: return $this;
51: }
52:
53: /**
54: * Is virtual page active?
55: */
56: public function isTriggered(): bool
57: {
58: return $this->cb->isTriggered();
59: }
60:
61: /**
62: * Returns URL which you can load directly in the browser location, open in a new tab,
63: * new window or inside iframe. This URL will contain HTML for a new page.
64: */
65: public function getUrl(string $mode = 'callback'): string
66: {
67: return $this->cb->getUrl($mode);
68: }
69:
70: /**
71: * Return URL that is designed to be loaded from inside JavaScript and contain JSON code.
72: * This is useful for dynamically loaded Modal, Tabs or Loader.
73: */
74: public function getJsUrl(string $mode = 'callback'): string
75: {
76: return $this->cb->getJsUrl($mode);
77: }
78:
79: /**
80: * VirtualPage is not rendered normally. It's invisible. Only when
81: * it is triggered, it will exclusively output it's content.
82: */
83: #[\Override]
84: public function getHtml(): string
85: {
86: if (!$this->cb->isTriggered()) {
87: return '';
88: } elseif (!$this->cb->canTerminate()) {
89: return parent::getHtml();
90: }
91:
92: $mode = $this->cb->getTriggeredValue();
93: if ($mode) {
94: // special treatment for popup
95: if ($mode === 'popup') {
96: $this->getApp()->html->template->set('title', $this->getApp()->title);
97: $this->getApp()->html->template->dangerouslySetHtml('Content', parent::getHtml());
98: $this->getApp()->html->template->dangerouslyAppendHtml('Head', $this->getApp()->getTag('script', [], '$(function () { ' . $this->getJs() . '; });'));
99:
100: $this->getApp()->terminateHtml($this->getApp()->html->template);
101: }
102:
103: // render and terminate
104: if ($this->getApp()->hasRequestQueryParam('__atk_json')) {
105: $this->getApp()->terminateJson($this);
106: }
107:
108: if ($this->getApp()->hasRequestQueryParam('__atk_tab')) {
109: $this->getApp()->terminateHtml($this->renderToTab());
110: }
111:
112: // do not terminate if callback supplied (no cutting)
113: if ($mode !== 'callback') {
114: $this->getApp()->terminateHtml($this);
115: }
116: }
117:
118: // remove all elements from inside the Content
119: foreach ($this->getApp()->layout->elements as $key => $view) {
120: if ($view instanceof View && $view->region === 'Content') {
121: unset($this->getApp()->layout->elements[$key]);
122: }
123: }
124:
125: $this->getApp()->layout->template->dangerouslySetHtml('Content', parent::getHtml());
126:
127: // collect JS from everywhere
128: foreach ($this->_jsActions as $when => $actions) {
129: foreach ($actions as $action) {
130: $this->getApp()->layout->_jsActions[$when][] = $action;
131: }
132: }
133:
134: $this->getApp()->html->template->dangerouslySetHtml('Content', $this->getApp()->layout->template->renderToHtml());
135:
136: $this->getApp()->html->template->dangerouslyAppendHtml('Head', $this->getApp()->getTag('script', [], '$(function () {' . $this->getApp()->layout->getJs() . ';});'));
137:
138: $this->getApp()->terminateHtml($this->getApp()->html->template);
139: }
140: }
141: