0

I'm using CakePHP 3.9

I'm trying to set css / script dependency block inside my view cell.

I want it to be fetched inside the head of my layout, but it just don't work. If I want it to work I must set the css dependency inside the view before/after calling the cell.

Is there any way to set the css dependency inside a View Cell?

My view:

<?= $this->cell('Post::post_input'); ?>
....

My view cell:

<?php echo $this->Html->css(['element/post-input'], ['block' => true]);  ?>
<div class="post-input">...</div>

My Layout:

<html>
<head>
...
    <?php
    echo $this->fetch('css');
    echo $this->fetch('script');
    ?>
...
</head>
<body>
    <?php echo $this->fetch('content'); ?>
</body>
</html>

3 Answers 3

1

It doesn't work because a cell is a closed environment that uses a separate view instance, so all blocks that you define in its templates, are going to reside in its internal view.

From the docs:

Cell templates have an isolated scope that does not share the same View instance as the one used to render template and layout for the current controller action or other cells. Hence they are unaware of any helper calls made or blocks set in the action’s template / layout and vice versa.

Cookbook > Views > View Cells > Implementing the Cell

If you don't want to manually add the CSS block whenever you use the cell, then I would suggest that you build a custom helper that can generate (or even directly echo) the cell and add the block(s), something along the lines of this:

<?php
// in src/View/Helper/PostHelper.php

namespace App\View\Helper;

use Cake\View\Helper;

/**
 * @property \Cake\View\Helper\HtmlHelper $Html
 */
class PostHelper extends Helper
{
    protected $helpers = [
        'Html'
    ];

    protected $wasPostInputCssAdded = false;

    public function postInput()
    {
        if (!$this->wasPostInputCssAdded) {
            $this->wasPostInputCssAdded = true;

            $this->Html->css(['element/post-input'], ['block' => true]);
        }

        return $this->_View->cell('Post::post_input');
    }
}

The following would then add the block and echo the cell:

<?= $this->Post->postInput() ?>

See also Cookbook > Views > Helpers > Creating Helpers

Sign up to request clarification or add additional context in comments.

1 Comment

Ahh thank you @ndm!!! You really helped me here! Your helper exemple put me in the direction! Thank you
1

Another option that doesn't require rewriting templates would be to create a custom Cell class that uses a CellView class. The view class then wraps the cell contents in a layout which can display blocks like you would normally expect in Cake view/layout templating.

<?php
declare(strict_types=1);

namespace App\View;

use Cake\View\Cell as BaseCell;
use Cake\View\View;

class Cell extends BaseCell
{
    /**
     * @inheritDoc
     */
    public function createView(?string $viewClass = null): View
    {
        // use custom CellView to for Cells
        return parent::createView(CellView::class);
    }
}

Then the CellView:

<?php
declare(strict_types=1);

namespace App\View;

class CellView extends AppView
{
    /**
     * @inheritDoc
     */
    public function render(?string $template = null, $layout = null): string
    {
        $cellContents = parent::render($template, $layout);

        // wrap cell contents in "cell" layout
        return $this->renderLayout($cellContents, 'cell');
    }
}

Now that all cells use a layout, all that is left is to create a basic layout used exclusively by cells:

/templates/layout/cell.php (CakePHP 3: /src/Template/Layout/cell.ctp)

<?php
/**
 * @var CellView $this
 */

use App\View\CellView;

echo $this->fetch('styles');
echo $this->fetch('content');
echo $this->fetch('scripts');

After this initial work, all App\View\Cells will use the "cell" layout. I've found this is a bit more intuitive in the long run if you use different blocks or postLinks with blocks.

Note: The code here is for CakePHP 4 but all you should need to do to make it CakePHP 3 compatible is match the method signatures

Comments

0

I've just had this same problem, but I was attempting to add things to the scriptBottom block of the main layout, sometimes from within a Cell.

Because each Cell has it's own View and Helper instances, we can't access the main View where the desired blocks live, however we do have access to the main View, as it's the first one instantiated, and therefore the first before.render event our helper receives is for the main view. So we can store that in a static variable, and then append things to that as required within helper called from within cells.

<?php

declare(strict_types=1);

namespace App\View\Helper;

use App\View\AppView;
use Cake\Core\Exception\Exception;
use Cake\Event\Event;
use Cake\Routing\Router;
use Cake\View\Helper;
use Cake\View\Helper\FormHelper;
use Cake\View\Helper\HtmlHelper;

use function assert;
use function func_get_args;
use function uniqid;

/**
 * @property HtmlHelper $Html
 * @property FormHelper $Form
 */
final class SecureDeleteHelper extends Helper
{
    private static AppView $mainView;


    public function beforeRender(Event $event): void
    {
        // Each cell has its own view, but the first one created is the main
        // one that we want to add scripts to, so we'll store it here…
        if (isset(self::$mainView)) {
            return;
        }

        $view = $event->getSubject();
        assert($view instanceof AppView);
        self::$mainView = $view;
    }


    /**
     * @throws Exception if $url is not valid.
     */
    public function doSomethingThatRequiresScriptTBeIncluded(): void
    {
        // …

        self::$mainView->helpers()->Html->script('my-script', ['block' => 'scrptBottom']);
    }
}

Hope this info is useful to someone.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.