ideas icon indicating copy to clipboard operation
ideas copied to clipboard

Blade: add view data support/helpers for blade layout components

Open stuartcusackie opened this issue 2 years ago • 6 comments

I've been trying to figure out the easiest way to get $page, $site and globalsets (e.g. $social) to all of my blade components.

View composers work pretty well, as outlined in this issue. The problem with this approach is that we are re-querying global sets and it seems to be adding to page load times. Ideally I would like to be able to access the variables that were already processed and provided to the view.

With this in mind, I came up with this solution. It's a simple package that extracts $page, $site and all global sets from the view's data and then passes them to a singleton on the service container. This way I can access all of these variables from a facade within any component without additional queries or having to remember to pass Statamic vars to my blade layout components.

I'm just wondering if we could get similar functionality added to Statamic itself? Perhaps some additional facade functions:

  • \Statamic::page()
  • \Statamic::site()
  • \Statamic::globalSet('handle')

UPDATE: Not only is this strategy handy for Blade Layouts, it is also gives Laravel Livewire easy access to all of the augmented data. Currently using my package for a Livewire infinite loading component.

stuartcusackie avatar Jul 04 '22 11:07 stuartcusackie

This would be a good PR, IMO.

edalzell avatar Jul 04 '22 16:07 edalzell

Just noticed that $__data in the template contains the augmented values, but $__data in the in the layout component does not. My collections have defined the layout component in their collection settings so I wonder why augmented values are not being passed? It's quite easy to do with a View Composer so I don't know why Statamic isn't passing it here too.

This solution wouldn't be great for Livewire though. I think binding a singleton in the app would be a more flexible solution.

stuartcusackie avatar Jul 05 '22 19:07 stuartcusackie

There's some good ideas in here. Renaming title to better reflect the purpose of this 👍

jesseleite avatar Jul 05 '22 22:07 jesseleite

Perhaps a new ViewData Facade that initialises and accesses a singleton class similar to the way my package does.

I think an ideal process would be:

  • Create the singleton with the below methods and initialise it at an appropriate point (not sure where is best in Statamic's codebase)
  • Initialise the singleton's view data once Statamic has done it's processing on $page, $site and $globals (again, I don't know where this happens)
  • Add a 'ViewData' Facade to interact with the singleton.

Then everything would be accessible from anywhere like so:

\ViewData::page()
\ViewData::site()
\ViewData::global('sethandle')
\ViewData::global('sethandle', 'fieldhandle')

stuartcusackie avatar Aug 10 '22 13:08 stuartcusackie

I solved something similar recently @stuartcusackie by doing this:

Cascade::hydrated(fn ($cascade) => $cascade->set('campaign', $campaign));

You could pass in whatever you want there. That is called whenever Statamic hydrates the cascade.

edalzell avatar Nov 07 '22 23:11 edalzell

You can inject the cascade into your components using a view composer.

use Facades\Statamic\View\Cascade;

View::composer('components.*', fn ($view) => $view->with('cascade', Cascade::instance()->toArray()));
{{ $cascade['title'] }}
{{ $cascade['myglobal']->field }}
etc

Or you could even inject them as separate variables.

View::composer('components.*', function ($view) {
    foreach (Cascade::instance()->toArray() as $key => $value) {
        $view->with($key, $value);
    }
});
{{ $title }}
{{ $myglobal->field }}
etc

jasonvarga avatar Nov 08 '22 04:11 jasonvarga

This could be perfect but there are a few concerns:

  • Does calling the cascade facade re-augment everything or is it all processed just the once? Just wondering about performance hits from calling the facade multiple times.
  • Are $site and $page available in the cascade, along with all the other stuff that Statamic provides to templates?
  • The SEO addon had a problem with getting the $context. That seems to be solved yesterday but I worry about similar problems from other addons.

View composers are okay but they don't provide the variables to the component class, just the view. Maybe something like this would be more flexible.

class AppLayout extends Component
{
  public $site;
  public $page;

  public function __construct() {
    $cascade = Cascade::instance()->toArray();
    $this->site = $cascade['site'];
    $this->page = $cascade['page'];
  }

If that works then I might write some Traits to add this stuff easily to each component. I'll run some experiments when I get a chance.

stuartcusackie avatar Nov 08 '22 10:11 stuartcusackie

Does calling the cascade facade re-augment everything or is it all processed just the once? Just wondering about performance hits from calling the facade multiple times.

A Cascade instance is populated with request/entry/term data whenever Statamic returns a view. We use the realtime facade in core, so that instance is stored in the service container as a singleton. If an addon tries to get the Facades\Statamic\View\Cascade::instance() using that realtime facade, you're accessing the same instance that was already populated, so performance should not be affected 👍

The SEO addon had a problem with getting the context. That seems to be solved yesterday but I worry about similar problems from other addons.

It's more a limitation with how data is passed and scoped when using blade layout components. It took a little while to find the right fix, but Cascade::instance()->toArray() makes it quite painless for addon devs, and can theoretically be accessed from anywhere. Maybe we can add an example to the docs.

jesseleite avatar Nov 08 '22 16:11 jesseleite

Confirmed. This seems perfect after my initial tests, and all the variables I require are available (page, site, globals).

I'll close this for now and come back to it if I come across any issues. I have added a note to my blade view data package to use this method instead.

stuartcusackie avatar Nov 17 '22 12:11 stuartcusackie