Passed
Pull Request — master (#195)
by Sergei
02:33
created

WebView::getType()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 2

Importance

Changes 0
Metric Value
eloc 1
c 0
b 0
f 0
dl 0
loc 3
ccs 2
cts 2
cp 1
rs 10
cc 2
nc 2
nop 1
crap 2
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Yiisoft\View;
6
7
use InvalidArgumentException;
8
use Psr\EventDispatcher\EventDispatcherInterface;
9
use Psr\EventDispatcher\StoppableEventInterface;
10
use Yiisoft\Html\Html;
11
use Yiisoft\Html\Tag\Link;
12
use Yiisoft\Html\Tag\Meta;
13
use Yiisoft\Html\Tag\Script;
14
use Yiisoft\Html\Tag\Style;
15
use Yiisoft\View\Event\AfterRenderEventInterface;
16
use Yiisoft\View\Event\WebView\AfterRender;
17
use Yiisoft\View\Event\WebView\BeforeRender;
18
use Yiisoft\View\Event\WebView\BodyBegin;
19
use Yiisoft\View\Event\WebView\BodyEnd;
20
use Yiisoft\View\Event\WebView\Head;
21
use Yiisoft\View\Event\WebView\PageBegin;
22
use Yiisoft\View\Event\WebView\PageEnd;
23
use Yiisoft\View\State\WebViewState;
24
25
use function array_merge;
26
use function implode;
27
use function ob_get_clean;
28
use function ob_implicit_flush;
29
use function ob_start;
30
use function sprintf;
31
use function strtr;
32
33
/**
34
 * WebView represents an instance of a view for use in a WEB environment.
35
 *
36
 * WebView provides a set of methods (e.g. {@see WebView::render()}) for rendering purpose.
37
 */
38
final class WebView implements ViewInterface
39
{
40
    use ViewTrait;
41
42
    private WebViewState $state;
43
44
    /**
45
     * This means the location is in the head section.
46
     */
47
    public const POSITION_HEAD = 1;
48
49
    /**
50
     * This means the location is at the beginning of the body section.
51
     */
52
    public const POSITION_BEGIN = 2;
53
54
    /**
55
     * This means the location is at the end of the body section.
56
     */
57
    public const POSITION_END = 3;
58
59
    /**
60
     * This means the JavaScript code block will be executed when HTML document composition is ready.
61
     */
62
    public const POSITION_READY = 4;
63
64
    /**
65
     * This means the JavaScript code block will be executed when HTML page is completely loaded.
66
     */
67
    public const POSITION_LOAD = 5;
68
69
    /**
70
     * This is internally used as the placeholder for receiving the content registered for the head section.
71
     */
72
    private const PLACEHOLDER_HEAD = '<![CDATA[YII-BLOCK-HEAD-%s]]>';
73
74
    /**
75
     * This is internally used as the placeholder for receiving the content registered for the beginning of the body
76
     * section.
77
     */
78
    private const PLACEHOLDER_BODY_BEGIN = '<![CDATA[YII-BLOCK-BODY-BEGIN-%s]]>';
79
80
    /**
81
     * This is internally used as the placeholder for receiving the content registered for the end of the body section.
82
     */
83
    private const PLACEHOLDER_BODY_END = '<![CDATA[YII-BLOCK-BODY-END-%s]]>';
84
85
    /**
86
     * @param string $basePath The full path to the base directory of views.
87
     * @param EventDispatcherInterface $eventDispatcher The event dispatcher instance.
88
     */
89 82
    public function __construct(string $basePath, EventDispatcherInterface $eventDispatcher)
90
    {
91 82
        $this->basePath = $basePath;
92 82
        $this->state = new WebViewState();
93 82
        $this->eventDispatcher = $eventDispatcher;
94 82
        $this->setPlaceholderSalt(__DIR__);
95 82
    }
96
97
    /**
98
     * Marks the position of an HTML head section.
99
     */
100 47
    public function head(): void
101
    {
102 47
        echo sprintf(self::PLACEHOLDER_HEAD, $this->getPlaceholderSignature());
103 47
        $this->eventDispatcher->dispatch(new Head($this));
104 47
    }
105
106
    /**
107
     * Marks the beginning of an HTML body section.
108
     */
109 47
    public function beginBody(): void
110
    {
111 47
        echo sprintf(self::PLACEHOLDER_BODY_BEGIN, $this->getPlaceholderSignature());
112 47
        $this->eventDispatcher->dispatch(new BodyBegin($this));
113 47
    }
114
115
    /**
116
     * Marks the ending of an HTML body section.
117
     */
118 47
    public function endBody(): void
119
    {
120 47
        $this->eventDispatcher->dispatch(new BodyEnd($this));
121 47
        echo sprintf(self::PLACEHOLDER_BODY_END, $this->getPlaceholderSignature());
122 47
    }
123
124
    /**
125
     * Marks the beginning of an HTML page.
126
     */
127 47
    public function beginPage(): void
128
    {
129 47
        ob_start();
130
        /** @psalm-suppress InvalidArgument */
131 47
        PHP_VERSION_ID >= 80000 ? ob_implicit_flush(false) : ob_implicit_flush(0);
132
133 47
        $this->eventDispatcher->dispatch(new PageBegin($this));
134 47
    }
135
136
    /**
137
     * Marks the ending of an HTML page.
138
     *
139
     * @param bool $ajaxMode Whether the view is rendering in AJAX mode. If true, the JS scripts registered at
140
     * {@see POSITION_READY} and {@see POSITION_LOAD} positions will be rendered at the end of the view like
141
     * normal scripts.
142
     */
143 47
    public function endPage(bool $ajaxMode = false): void
144
    {
145 47
        $this->eventDispatcher->dispatch(new PageEnd($this));
146
147 47
        $content = ob_get_clean();
148
149 47
        echo strtr($content, [
150 47
            sprintf(self::PLACEHOLDER_HEAD, $this->getPlaceholderSignature()) => $this->renderHeadHtml(),
151 47
            sprintf(self::PLACEHOLDER_BODY_BEGIN, $this->getPlaceholderSignature()) => $this->renderBodyBeginHtml(),
152 47
            sprintf(self::PLACEHOLDER_BODY_END, $this->getPlaceholderSignature()) => $this->renderBodyEndHtml($ajaxMode),
153
        ]);
154
155 47
        $this->clear();
156 47
    }
157
158
    /**
159
     * Renders a view in response to an AJAX request.
160
     *
161
     * This method is similar to {@see render()} except that it will surround the view being rendered with the calls of
162
     * {@see beginPage()}, {@see head()}, {@see beginBody()}, {@see endBody()} and {@see endPage()}. By doing so, the
163
     * method is able to inject into the rendering result with JS/CSS scripts and files that are registered with the
164
     * view.
165
     *
166
     * @param string $view The view name. Please refer to {@see render()} on how to specify this parameter.
167
     * @param array $parameters The parameters (name-value pairs) that will be extracted and made available in the view
168
     * file.
169
     *
170
     * @return string The rendering result
171
     *
172
     * {@see render()}
173
     */
174 2
    public function renderAjax(string $view, array $parameters = []): string
175
    {
176 2
        $viewFile = $this->findTemplateFile($view);
177
178 2
        ob_start();
179
        /** @psalm-suppress InvalidArgument */
180 2
        PHP_VERSION_ID >= 80000 ? ob_implicit_flush(false) : ob_implicit_flush(0);
181
182 2
        $this->beginPage();
183 2
        $this->head();
184 2
        $this->beginBody();
185 2
        echo $this->renderFile($viewFile, $parameters);
186 2
        $this->endBody();
187 2
        $this->endPage(true);
188
189 2
        return ob_get_clean();
190
    }
191
192
    /**
193
     * Renders a string in response to an AJAX request.
194
     *
195
     * @param string $string The string.
196
     *
197
     * @return string The rendering result.
198
     */
199 1
    public function renderAjaxString(string $string): string
200
    {
201 1
        ob_start();
202
        /** @psalm-suppress InvalidArgument */
203 1
        PHP_VERSION_ID >= 80000 ? ob_implicit_flush(false) : ob_implicit_flush(0);
204
205 1
        $this->beginPage();
206 1
        $this->head();
207 1
        $this->beginBody();
208 1
        echo $string;
209 1
        $this->endBody();
210 1
        $this->endPage(true);
211
212 1
        return ob_get_clean();
213
    }
214
215
    /**
216
     * Get title in views.
217
     *
218
     * in Layout:
219
     *
220
     * ```php
221
     * <title><?= Html::encode($this->getTitle()) ?></title>
222
     * ```
223
     *
224
     * in Views:
225
     *
226
     * ```php
227
     * $this->setTitle('Web Application - Yii 3.0.');
228
     * ```
229
     *
230
     * @return string
231
     */
232 2
    public function getTitle(): string
233
    {
234 2
        return $this->state->getTitle();
235
    }
236
237
    /**
238
     * Set title in views.
239
     *
240
     * {@see getTitle()}
241
     *
242
     * @param string $value
243
     *
244
     * @return static
245
     */
246 3
    public function setTitle(string $value): self
247
    {
248 3
        $this->state->setTitle($value);
249 3
        return $this;
250
    }
251
252
    /**
253
     * Registers a meta tag.
254
     *
255
     * For example, a description meta tag can be added like the following:
256
     *
257
     * ```php
258
     * $view->registerMeta([
259
     *     'name' => 'description',
260
     *     'content' => 'This website is about funny raccoons.'
261
     * ]);
262
     * ```
263
     *
264
     * will result in the meta tag `<meta name="description" content="This website is about funny raccoons.">`.
265
     *
266
     * @param array $attributes The HTML attributes for the meta tag.
267
     * @param string|null $key The key that identifies the meta tag. If two meta tags are registered with the same key,
268
     * the latter will overwrite the former. If this is null, the new meta tag will be appended to the existing ones.
269
     */
270 1
    public function registerMeta(array $attributes, ?string $key = null): void
271
    {
272 1
        $this->state->registerMeta($attributes, $key);
273 1
    }
274
275
    /**
276
     * Registers a {@see Meta} tag.
277
     *
278
     * @see registerMeta()
279
     */
280 3
    public function registerMetaTag(Meta $meta, ?string $key = null): void
281
    {
282 3
        $this->state->registerMetaTag($meta, $key);
283 3
    }
284
285
    /**
286
     * Registers a link tag.
287
     *
288
     * For example, a link tag for a custom [favicon](http://www.w3.org/2005/10/howto-favicon) can be added like the
289
     * following:
290
     *
291
     * ```php
292
     * $view->registerLink(['rel' => 'icon', 'type' => 'image/png', 'href' => '/myicon.png']);
293
     * ```
294
     *
295
     * which will result in the following HTML: `<link rel="icon" type="image/png" href="/myicon.png">`.
296
     *
297
     * **Note:** To register link tags for CSS stylesheets, use {@see registerCssFile()]} instead, which has more
298
     * options for this kind of link tag.
299
     *
300
     * @param array $attributes The HTML attributes for the link tag.
301
     * @param int $position The position at which the link tag should be inserted in a page.
302
     * @param string|null $key The key that identifies the link tag. If two link tags are registered with the same
303
     * key, the latter will overwrite the former. If this is null, the new link tag will be appended
304
     * to the existing ones.
305
     */
306 3
    public function registerLink(array $attributes, int $position = self::POSITION_HEAD, ?string $key = null): void
307
    {
308 3
        $this->state->registerLink($attributes, $position, $key);
309 3
    }
310
311
    /**
312
     * Registers a {@see Link} tag.
313
     *
314
     * @see registerLink()
315
     */
316 5
    public function registerLinkTag(Link $link, int $position = self::POSITION_HEAD, ?string $key = null): void
317
    {
318 5
        $this->state->registerLinkTag($link, $position, $key);
319 5
    }
320
321
    /**
322
     * Registers a CSS code block.
323
     *
324
     * @param string $css The content of the CSS code block to be registered.
325
     * @param string|null $key The key that identifies the CSS code block. If null, it will use $css as the key.
326
     * If two CSS code blocks are registered with the same key, the latter will overwrite the former.
327
     * @param array $attributes The HTML attributes for the {@see Style} tag.
328
     */
329 5
    public function registerCss(
330
        string $css,
331
        int $position = self::POSITION_HEAD,
332
        array $attributes = [],
333
        ?string $key = null
334
    ): void {
335 5
        $this->state->registerCss($css, $position, $attributes, $key);
336 5
    }
337
338
    /**
339
     * Registers a CSS code block from file.
340
     *
341
     * @param string $path The path or URL to CSS file.
342
     *
343
     * @see registerCss()
344
     */
345 2
    public function registerCssFromFile(
346
        string $path,
347
        int $position = self::POSITION_HEAD,
348
        array $attributes = [],
349
        ?string $key = null
350
    ): void {
351 2
        $this->state->registerCssFromFile($path, $position, $attributes, $key);
352 1
    }
353
354
    /**
355
     * Register a {@see Style} tag.
356
     *
357
     * @see registerJs()
358
     */
359 1
    public function registerStyleTag(Style $style, int $position = self::POSITION_HEAD, ?string $key = null): void
360
    {
361 1
        $this->state->registerStyleTag($style, $position, $key);
362 1
    }
363
364
    /**
365
     * Registers a CSS file.
366
     *
367
     * This method should be used for simple registration of CSS files. If you want to use features of
368
     * {@see \Yiisoft\Assets\AssetManager} like appending timestamps to the URL and file publishing options, use
369
     * {@see \Yiisoft\Assets\AssetBundle}.
370
     *
371
     * @param string $url The CSS file to be registered.
372
     * @param array $options the HTML attributes for the link tag. Please refer to {@see \Yiisoft\Html\Html::cssFile()}
373
     * for the supported options.
374
     * @param string|null $key The key that identifies the CSS script file. If null, it will use $url as the key.
375
     * If two CSS files are registered with the same key, the latter will overwrite the former.
376
     */
377 11
    public function registerCssFile(
378
        string $url,
379
        int $position = self::POSITION_HEAD,
380
        array $options = [],
381
        string $key = null
382
    ): void {
383 11
        $this->state->registerCssFile($url, $position, $options, $key);
384 10
    }
385
386
    /**
387
     * It processes the CSS configuration generated by the asset manager and converts it into HTML code.
388
     *
389
     * @param array $cssFiles
390
     */
391 4
    public function addCssFiles(array $cssFiles): void
392
    {
393 4
        $this->state->addCssFiles($cssFiles);
394 1
    }
395
396
    /**
397
     * It processes the CSS strings generated by the asset manager.
398
     *
399
     * @param array $cssStrings
400
     */
401 7
    public function addCssStrings(array $cssStrings): void
402
    {
403 7
        $this->state->addCssStrings($cssStrings);
404 1
    }
405
406
    /**
407
     * Registers a JS code block.
408
     *
409
     * @param string $js the JS code block to be registered
410
     * @param int $position the position at which the JS script tag should be inserted in a page.
411
     *
412
     * The possible values are:
413
     *
414
     * - {@see POSITION_HEAD}: in the head section
415
     * - {@see POSITION_BEGIN}: at the beginning of the body section
416
     * - {@see POSITION_END}: at the end of the body section. This is the default value.
417
     * - {@see POSITION_LOAD}: executed when HTML page is completely loaded.
418
     * - {@see POSITION_READY}: executed when HTML document composition is ready.
419
     * @param string|null $key The key that identifies the JS code block. If null, it will use $js as the key.
420
     * If two JS code blocks are registered with the same key, the latter will overwrite the former.
421
     */
422 3
    public function registerJs(string $js, int $position = self::POSITION_END, ?string $key = null): void
423
    {
424 3
        $this->state->registerJs($js, $position, $key);
425 3
    }
426
427
    /**
428
     * Register a `script` tag
429
     *
430
     * @see registerJs()
431
     */
432 3
    public function registerScriptTag(Script $script, int $position = self::POSITION_END, ?string $key = null): void
433
    {
434 3
        $this->state->registerScriptTag($script, $position, $key);
435 3
    }
436
437
    /**
438
     * Registers a JS file.
439
     *
440
     * This method should be used for simple registration of JS files. If you want to use features of
441
     * {@see \Yiisoft\Assets\AssetManager} like appending timestamps to the URL and file publishing options, use
442
     * {@see \Yiisoft\Assets\AssetBundle}.
443
     *
444
     * @param string $url The JS file to be registered.
445
     * @param array $options The HTML attributes for the script tag. The following options are specially handled and
446
     * are not treated as HTML attributes:
447
     *
448
     * - `position`: specifies where the JS script tag should be inserted in a page. The possible values are:
449
     *     * {@see POSITION_HEAD}: in the head section
450
     *     * {@see POSITION_BEGIN}: at the beginning of the body section
451
     *     * {@see POSITION_END}: at the end of the body section. This is the default value.
452
     *
453
     * Please refer to {@see \Yiisoft\Html\Html::javaScriptFile()} for other supported options.
454
     * @param string|null $key The key that identifies the JS script file. If null, it will use $url as the key.
455
     * If two JS files are registered with the same key at the same position, the latter will overwrite the former.
456
     * Note that position option takes precedence, thus files registered with the same key, but different
457
     * position option will not override each other.
458
     */
459 11
    public function registerJsFile(
460
        string $url,
461
        int $position = self::POSITION_END,
462
        array $options = [],
463
        string $key = null
464
    ): void {
465 11
        $this->state->registerJsFile($url, $position, $options, $key);
466 10
    }
467
468
    /**
469
     * Registers a JS code block defining a variable. The name of variable will be used as key, preventing duplicated
470
     * variable names.
471
     *
472
     * @param string $name Name of the variable
473
     * @param mixed $value Value of the variable
474
     * @param int $position The position in a page at which the JavaScript variable should be inserted.
475
     *
476
     * The possible values are:
477
     *
478
     * - {@see POSITION_HEAD}: in the head section. This is the default value.
479
     * - {@see POSITION_BEGIN}: at the beginning of the body section.
480
     * - {@see POSITION_END}: at the end of the body section.
481
     * - {@see POSITION_LOAD}: enclosed within jQuery(window).load().
482
     *   Note that by using this position, the method will automatically register the jQuery js file.
483
     * - {@see POSITION_READY}: enclosed within jQuery(document).ready().
484
     *   Note that by using this position, the method will automatically register the jQuery js file.
485
     */
486 2
    public function registerJsVar(string $name, $value, int $position = self::POSITION_HEAD): void
487
    {
488 2
        $this->state->registerJsVar($name, $value, $position);
489 2
    }
490
491
    /**
492
     * It processes the JS configuration generated by the asset manager and converts it into HTML code.
493
     *
494
     * @param array $jsFiles
495
     */
496 4
    public function addJsFiles(array $jsFiles): void
497
    {
498 4
        $this->state->addJsFiles($jsFiles);
499 1
    }
500
501
    /**
502
     * It processes the JS strings generated by the asset manager.
503
     *
504
     * @param array $jsStrings
505
     *
506
     * @throws InvalidArgumentException
507
     */
508 7
    public function addJsStrings(array $jsStrings): void
509
    {
510 7
        $this->state->addJsStrings($jsStrings);
511 1
    }
512
513
    /**
514
     * It processes the JS variables generated by the asset manager and converts it into JS code.
515
     *
516
     * @param array $jsVars
517
     *
518
     * @throws InvalidArgumentException
519
     */
520 5
    public function addJsVars(array $jsVars): void
521
    {
522 5
        $this->state->addJsVars($jsVars);
523 1
    }
524
525 46
    protected function createBeforeRenderEvent(string $viewFile, array $parameters): StoppableEventInterface
526
    {
527 46
        return new BeforeRender($this, $viewFile, $parameters);
528
    }
529
530 46
    protected function createAfterRenderEvent(
531
        string $viewFile,
532
        array $parameters,
533
        string $result
534
    ): AfterRenderEventInterface {
535 46
        return new AfterRender($this, $viewFile, $parameters, $result);
536
    }
537
538
    /**
539
     * Renders the content to be inserted in the head section.
540
     *
541
     * The content is rendered using the registered meta tags, link tags, CSS/JS code blocks and files.
542
     *
543
     * @return string The rendered content
544
     */
545 47
    private function renderHeadHtml(): string
546
    {
547 47
        $lines = [];
548
549 47
        if (!empty($this->state->getMetaTags())) {
550 3
            $lines[] = implode("\n", $this->state->getMetaTags());
551
        }
552 47
        if (!empty($this->state->getLinkTags()[self::POSITION_HEAD])) {
553 3
            $lines[] = implode("\n", $this->state->getLinkTags()[self::POSITION_HEAD]);
554
        }
555 47
        if (!empty($this->state->getCssFiles()[self::POSITION_HEAD])) {
556 8
            $lines[] = implode("\n", $this->state->getCssFiles()[self::POSITION_HEAD]);
557
        }
558 47
        if (!empty($this->state->getCss()[self::POSITION_HEAD])) {
559 5
            $lines[] = $this->generateCss($this->state->getCss()[self::POSITION_HEAD]);
560
        }
561 47
        if (!empty($this->state->getJsFiles()[self::POSITION_HEAD])) {
562 1
            $lines[] = implode("\n", $this->state->getJsFiles()[self::POSITION_HEAD]);
563
        }
564 47
        if (!empty($this->state->getJs()[self::POSITION_HEAD])) {
565 4
            $lines[] = $this->generateJs($this->state->getJs()[self::POSITION_HEAD]);
566
        }
567
568 47
        return empty($lines) ? '' : implode("\n", $lines);
569
    }
570
571
    /**
572
     * Renders the content to be inserted at the beginning of the body section.
573
     *
574
     * The content is rendered using the registered JS code blocks and files.
575
     *
576
     * @return string The rendered content.
577
     */
578 47
    private function renderBodyBeginHtml(): string
579
    {
580 47
        $lines = [];
581
582 47
        if (!empty($this->state->getLinkTags()[self::POSITION_BEGIN])) {
583 2
            $lines[] = implode("\n", $this->state->getLinkTags()[self::POSITION_BEGIN]);
584
        }
585 47
        if (!empty($this->state->getCssFiles()[self::POSITION_BEGIN])) {
586 2
            $lines[] = implode("\n", $this->state->getCssFiles()[self::POSITION_BEGIN]);
587
        }
588 47
        if (!empty($this->state->getCss()[self::POSITION_BEGIN])) {
589 2
            $lines[] = $this->generateCss($this->state->getCss()[self::POSITION_BEGIN]);
590
        }
591 47
        if (!empty($this->state->getJsFiles()[self::POSITION_BEGIN])) {
592 2
            $lines[] = implode("\n", $this->state->getJsFiles()[self::POSITION_BEGIN]);
593
        }
594 47
        if (!empty($this->state->getJs()[self::POSITION_BEGIN])) {
595 1
            $lines[] = $this->generateJs($this->state->getJs()[self::POSITION_BEGIN]);
596
        }
597
598 47
        return empty($lines) ? '' : implode("\n", $lines);
599
    }
600
601
    /**
602
     * Renders the content to be inserted at the end of the body section.
603
     *
604
     * The content is rendered using the registered JS code blocks and files.
605
     *
606
     * @param bool $ajaxMode Whether the view is rendering in AJAX mode. If true, the JS scripts registered at
607
     * {@see POSITION_READY} and {@see POSITION_LOAD} positions will be rendered at the end of the view like normal
608
     * scripts.
609
     *
610
     * @return string The rendered content.
611
     */
612 47
    private function renderBodyEndHtml(bool $ajaxMode): string
613
    {
614 47
        $lines = [];
615
616 47
        if (!empty($this->state->getLinkTags()[self::POSITION_END])) {
617 2
            $lines[] = implode("\n", $this->state->getLinkTags()[self::POSITION_END]);
618
        }
619 47
        if (!empty($this->state->getCssFiles()[self::POSITION_END])) {
620 1
            $lines[] = implode("\n", $this->state->getCssFiles()[self::POSITION_END]);
621
        }
622 47
        if (!empty($this->state->getCss()[self::POSITION_END])) {
623 2
            $lines[] = $this->generateCss($this->state->getCss()[self::POSITION_END]);
624
        }
625 47
        if (!empty($this->state->getJsFiles()[self::POSITION_END])) {
626 8
            $lines[] = implode("\n", $this->state->getJsFiles()[self::POSITION_END]);
627
        }
628
629 47
        if ($ajaxMode) {
630 3
            $scripts = array_merge(
631 3
                $this->state->getJs()[self::POSITION_END] ?? [],
632 3
                $this->state->getJs()[self::POSITION_READY] ?? [],
633 3
                $this->state->getJs()[self::POSITION_LOAD] ?? [],
634
            );
635 3
            if (!empty($scripts)) {
636 3
                $lines[] = $this->generateJs($scripts);
637
            }
638
        } else {
639 44
            if (!empty($this->state->getJs()[self::POSITION_END])) {
640 4
                $lines[] = $this->generateJs($this->state->getJs()[self::POSITION_END]);
641
            }
642 44
            if (!empty($this->state->getJs()[self::POSITION_READY])) {
643
                $js = "document.addEventListener('DOMContentLoaded', function(event) {\n" .
644 1
                    $this->generateJsWithoutTag($this->state->getJs()[self::POSITION_READY]) .
645 1
                    "\n});";
646 1
                $lines[] = Html::script($js)->render();
647
            }
648 44
            if (!empty($this->state->getJs()[self::POSITION_LOAD])) {
649
                $js = "window.addEventListener('load', function(event) {\n" .
650 1
                    $this->generateJsWithoutTag($this->state->getJs()[self::POSITION_LOAD]) .
651 1
                    "\n});";
652 1
                $lines[] = Html::script($js)->render();
653
            }
654
        }
655
656 47
        return empty($lines) ? '' : implode("\n", $lines);
657
    }
658
659
    /**
660
     * @param string[]|Style[] $items
661
     */
662 7
    private function generateCss(array $items): string
663
    {
664 7
        $lines = [];
665
666 7
        $css = [];
667 7
        foreach ($items as $item) {
668 7
            if ($item instanceof Style) {
669 4
                if ($css !== []) {
670 1
                    $lines[] = Html::style(implode("\n", $css))->render();
671 1
                    $css = [];
672
                }
673 4
                $lines[] = $item->render();
674
            } else {
675 4
                $css[] = $item;
676
            }
677
        }
678 7
        if ($css !== []) {
679 4
            $lines[] = Html::style(implode("\n", $css))->render();
680
        }
681
682 7
        return implode("\n", $lines);
683
    }
684
685
    /**
686
     * @param Script[]|string[] $items
687
     */
688 7
    private function generateJs(array $items): string
689
    {
690 7
        $lines = [];
691
692 7
        $js = [];
693 7
        foreach ($items as $item) {
694 7
            if ($item instanceof Script) {
695 4
                if ($js !== []) {
696 3
                    $lines[] = Html::script(implode("\n", $js))->render();
697 3
                    $js = [];
698
                }
699 4
                $lines[] = $item->render();
700
            } else {
701 6
                $js[] = $item;
702
            }
703
        }
704 7
        if ($js !== []) {
705 5
            $lines[] = Html::script(implode("\n", $js))->render();
706
        }
707
708 7
        return implode("\n", $lines);
709
    }
710
711
    /**
712
     * @param Script[]|string[] $items
713
     */
714 1
    private function generateJsWithoutTag(array $items): string
715
    {
716 1
        $js = [];
717 1
        foreach ($items as $item) {
718 1
            $js[] = $item instanceof Script ? $item->getContent() : $item;
719
        }
720 1
        return implode("\n", $js);
721
    }
722
}
723