thephpleague / plates

Native PHP template system
https://platesphp.com
MIT License
1.48k stars 180 forks source link

Setting Layout Once Per Controller #123

Closed KnightYoshi closed 6 years ago

KnightYoshi commented 8 years ago

So, as I'm looking through the docs I don't see way to set a global layout. Is there? What I mean: setting a layout in the main controller that all other controllers use and/or extend then each template would use that layout instead of defining it in each and every template explicitly.

Changing the layout of a particular set of templates I'd have to go through and edit all of those files. This doesn't seem ideal. Or even just passing a side bar data for a particular layout has to be done by passing the side bar data to each template and from each template to the layout. This seems very redundant. Or am I missing something?

funivan commented 7 years ago

You can write some wrapper function to do this. For example:

class MyTemplating {
 public $layoutName = '';
 public function render($path, $data){
   $engine = new (Plates\Engine());
   $content = $engine->fetch($path, $data);
   return $engine->render($this->layoutPath, ['content'=>$contnet]);
 }
}
#Content of the layout.html
<html>

<div class="my-layout">
<?= $content ?>
</div>

</html>
  1. Render template (sub view)
  2. Render layout with content if the previous rendered template
ragboyjr commented 7 years ago

@Knight-Yoshi You can assign actually assign global data to the templates. Check this out

In regards to the specific layout file, i'm not sure of anyway of setting a default layout file. Can you give an example of another Template engine or project that would allow you to set the default layout like you are referring to?

KnightYoshi commented 7 years ago

@ragboyjr Yeah, I found that page and figured out how to set global data. Still no good way to set a global template though.

CI-template engine by Phil sturgeon. It's far more simplified than this library, which is why I'm replacing it with Plates; template nesting and whatnot. Just that this library doesn't allow setting the template once. It seems that it has to be defined in each view. That doesn't follow the DRY pattern. Albeit I understand that each view might have it's own template and that's why its set there, but if there are a group of views that use the same template... That's where the problem comes in

https://github.com/philsturgeon/codeigniter-template So I set the template in CI in MY_Controller and then when I need to change it for a class I change it in the controller's constructor, this way it's always only set in one place.

ragboyjr commented 7 years ago

@Knight-Yoshi

Thanks for the example.

Personally, this has never been an issue for me because I've always passed data into the layout like the Page title, so there was always a reason include the layout. How are you getting around things like that?

KnightYoshi commented 7 years ago

@ragboyjr I pass data to the templates and layouts as well. So for instance setting the page title, after I found out how to set the global data, I set the page title in the global data and use it in the main layout. I have a class property for things like that

I'll note that I extend Plates in a library file that I load in with CI's library loader class Plates extends League\Plates\Engine

// In top-level MY_Controller constructor
$this->data['page_title'] = 'My Site |';

Then in the methods of the controller that extends MY_Controller

// this does not include setting specific view data, just data that would get shared
$this->data['page_title'] .= ' Page Name';
$this->template->addData($this->data);

// $data is not necessarily global view data.
echo $this->template->render('path/to/view', $data);

Then in the view, render('path/to/view', $data);, I would load the main site layout. In one example have a collection of views that require a different, but nested, layout that has a sidebar for all, and only, those views. So the home page and many other views would load the main layout directly so I'd like to set that layout globally so that <?php $this->layout('_layouts/main') ?> doesn't have to be done in every single view, something like $template->layout('path/to/layout') in MY_Controller. Then in the extending controller that changes the main layout $template->layout('path/to/diff_layout'), but be able to still set nested layouts from there when setting the layout like in the example of stacked layouts, http://platesphp.com/templates/layouts/.

So that would make if it's set $template->layout() the outer most / last layout to be used.

I hope I explained that well enough.

ragboyjr commented 7 years ago

@Knight-Yoshi thanks man, I have a feeling your use case is more fringe, and probably won't every be available in the core. However, I'm thinking maybe we could make Plates more extendable to where you can make an extension or something and configure that.

ragboyjr commented 7 years ago

Because right now, there is NO way to do that unless you make extend the engine maybe which is a hack, but if we could alter the extensions to be even more flexible, so that you can do this, i think that'd be best of both worlds.

KnightYoshi commented 7 years ago

I don't think it's necessarily fringe to allow being able to set the [outer most / last] layout from a controller.

That said I do extend the library to easily load it within the context of CI. Is there a relatively easy to build that functionality in or would I have to completely replace a bunch of functions?

reinink commented 7 years ago

So Plates follows the way other major template libraries works in this regard. Libraries like Twig and Blade also don't allow you to assign a "global" base layout template. One of the reasons for this is because you could have multiple layers of inheritance. For example, you could have a home page that extends a "logged in user" layout, and that layout actually extends a base page layout.

I think that in general, it's good to have your templates explicitly state which layouts they extend.

With that said, I think a feature like this could be possible. You could set your base layout at the Engine level, and it would automatically apply that layout to all templates except nested templates.

One problem I see with this is when you have a template that you don't want to extend the base template. Consider something like an HTML email. We'd need a way to disable the default layout. Here are a couple ideas:

// In the controller
echo $templates->renderWithoutLayout('email', ['name' => 'Jonathan']);
// in the template itself
<?php $this->withoutLayout() ?>

<h1>Your new account</h1>
<p>Hello, <?=$this->e($name)?></p>

We're starting to create a list of ideas for a 4.0 release. I'll add this to that list. Please feel free to share any additional thoughts you have.

KnightYoshi commented 7 years ago

You talk about inherited layouts. That's the same as nested layouts, right? That's why I suggested they nest as they currently do, but then the layout set in the controller would be the last layout.

So taking the example from http://platesphp.com/templates/layouts/

Main site layout

// this would be called in the controller via something like $template->base_layout(...)
<html>
<head>
    <title><?=$this->e($title)?></title>
</head>
<body>

<?=$this->section('content')?>

</body>
</html>

Where the base layout can be changed freely. So using CI's structure as an example again the main layout would be set in MY_Controller then an Admin controller would extend MY_Controller and can change the base layout to the admin panel base layout.

Then the Blog layout wouldn't need to define the layout that it uses.

// this doesn't define the main site layout to use because it's already set globally from a controller
<h1>The Blog</h1>

<section>
    <article>
        <?=$this->section('content')?>
    </article>
    <aside>
        <?=$this->insert('blog/sidebar')?>
    </aside>
</section>

But Blog Article could

<?php $this->layout('blog', ['title' => $article->title]) ?>

<h2><?=$this->e($article->title)?></h2>
<article>
    <?=$this->e($article->content)?>
</article>

So if the Blog Article layout didn't use the Blog layout (and no other layouts were used in between) it would just be wrapped with the main site layout.

If that clears up what I'm talking about? I think this has a fair amount of practical use.

I think the option of rendering without a layout would be good if a base layout was set.

ragboyjr commented 7 years ago

@Knight-Yoshi I guess that makes sense because within one controller, you might render several pages, and they all would likely use the same layout, so no need to set each one. Am I correct in my understanding?

KnightYoshi commented 7 years ago

Yes, exactly. :)

kamov commented 7 years ago

I am using template->addData() to set title of layout, example:

    $this->template->addData([
        'title' => 'Custom title'
    ], 'layout');
KnightYoshi commented 7 years ago

@kamov That's not what this is about. It's about setting an optional [final] layout itself from the controller for pages that use the same layout instead of explicitly setting it in multiple files.

ragboyjr commented 7 years ago

@Knight-Yoshi after re-reading through this, have you tried,

$this->template->addData(['page_layout' => 'layouts/admin'])

Then in all of your template files, just do:

$this->layout($page_layout)

ragboyjr commented 6 years ago

Any word @Knight-Yoshi

KnightYoshi commented 6 years ago

The point was to be able to specify in a single location a layout that would be used by all of the template views loaded by a single controller without having to individually specify a layout for each one or that a layout specified for each one would then be wrapped by the layout specified in the controller; if specified.

Yes, the example you posted is one way to do it, but then you still have to set it in each view. It comes down to if layouts should be able to be specified outside of the view itself. I think it would be useful.

ragboyjr commented 6 years ago

Understood, I don't think this will be a feature supported out of the core of plates, but when we rewrite for 4.0, i'll keep this in mind and see if there would be a "better" way to implement what you want, but this definitely doesn't feel like a feature that would be consistent with Plates semantics.

ragboyjr commented 6 years ago

For documentation sake, another way to implement this would be to just have a base template that will extend from the same layout.

So if you had a controller for Blog, that loaded templates from the blog directory, you could have something like this:

  blog/view.phtml
  blog/list.phtml
  blog/category.phtml
  blog/layout.phtml

Then all of your files but the layout.phtml will just use the blog/layout.phtml as the layout, and then blog/layout.phtml will then just use a parent layout or whatever.

So this would allow changing the layout for multiple templates in just one place.

ragboyjr commented 6 years ago

I think for 4.0, we could possibly add an extension that will do the following:

If a template doesn't specify a layout, then look in the current directory for a file named _layout.phtml, this will be the default layout used.