Setting Layout Once Per Controller #123

Open
Knight-Yoshi opened this Issue Aug 31, 2016 · 12 comments

Projects

None yet

4 participants

@Knight-Yoshi
Knight-Yoshi commented Aug 31, 2016 edited

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
funivan commented Nov 25, 2016

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
Contributor

@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?

@Knight-Yoshi

@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
Contributor
ragboyjr commented Dec 23, 2016 edited

@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?

@Knight-Yoshi
Knight-Yoshi commented Dec 23, 2016 edited

@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
Contributor

@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
Contributor

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.

@Knight-Yoshi
Knight-Yoshi commented Dec 24, 2016 edited

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
Member
reinink commented Dec 24, 2016

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.

@reinink reinink added the enhancement label Dec 24, 2016
@reinink reinink added this to the 4.0 milestone Dec 24, 2016
@Knight-Yoshi

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
Contributor

@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?

@Knight-Yoshi

Yes, exactly. :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment