thephpleague / plates

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

Section push not showing after section start #240

Closed brightboxtech closed 5 years ago

brightboxtech commented 5 years ago

Hope this makes since, first time posting. Using Plates 4 alpha. Please excuse bad coding, Im working on it. Ok so in the main template.php file I load initial sections through a include file like so:

<?=$view->insert('page-section')?>

Inside page-section.php has all the default content for each section I want to load in template.php. So in the header section of template.php I have:

<?=$view->section('header_codetop')?>

The problem is farther down the template.php I have some code to load side modules, like so:

foreach($side_modules AS $curr_module):

$view->insert('modules/' .$curr_module['modfile']);

endforeach;

In some of the module files I want to push data like needed JS or CSS code/includes to the header_codetop section, for example modules/site-links.php:

<?php $view->push('header_codetop') ?>
<script src="/test.js"></script>
<?php $view->stop() ?>

//HTML content goes here

But it doesnt work. It only works if the push happens before:

<?=$view->section('header_codetop')?>

So if I want to push content to a section in the footer, which is below the foreach loop, it works just fine. Is there a way to make the push happen later down the process so I can push data to a section at anytime on the page? Or maybe, hopefully, Im doing something wrong?

EDIT: Also I understand that when Im doing this:

<?=$view->section('header_codetop')?>

Its echoing out the value for section header_codetop. However I was hoping there was a different way of not echoing it right away, instead it can wait until everything is rendering and echo it all out at the end. Or maybe Im using plates all wrong and there is a better way of doing what Im trying to do?

lkraav commented 5 years ago

I've yet to use push() method myself, so hopefully @ragboyjr has an idea here.

brightboxtech commented 5 years ago

I've yet to use push() method myself, so hopefully @ragboyjr has an idea here.

Ok thank you. Yeah looking at the test scripts I also noticed a function called unshift. After using it, it looks like push appends content to a section and unshift prepends the content to a section. But if you never used push, wondering if Im using this all wrong lol Just started using it last week so still learning it. Probably as I use it more, will realize Im using it all wrong at the moment. But I still would like to know if its possible to use push later down the script when it renders instead of outputting the content right away. Because would be perfect to append/prepend includes/inline code throughout the template when needed.

Version 3 I read you could use something like fetch to assign to a string instead of directly outputting it. But fetch is not a valid function in v4 and I dont know if that will even resolve this issue. I also noticed section has its own namespace and functions, but dont know how to integrate that into the template, or if that is even needed to do what Im looking to do...

namespace League\Plates\Extension\LayoutSections;
/** A simple store for managing section content. This needs to be a mutable object
    so that references can be shared across templates. */
final class Sections
{
    private $sections;
    public function __construct(array $sections = []) {
        $this->sections = $sections;
    }
    public function add($name, $content) {
        $this->sections[$name] = $content;
    }
    public function append($name, $content) {
        $this->sections[$name] = ($this->get($name) ?: '') . $content;
    }
    public function prepend($name, $content) {
        $this->sections[$name] = $content . ($this->get($name) ?: '');
    }
    public function clear($name) {
        unset($this->sections[$name]);
    }
    public function get($name) {
        return array_key_exists($name, $this->sections)
            ? $this->sections[$name]
            : null;
    }
    public function merge(Sections $sections) {
        return new self(array_merge($this->sections, $sections->sections));
    }
}
ragboyjr commented 5 years ago

@brightboxtech I think you just need to restructure your code to use a layout.

Let me understand if this the structure of your templates:

// template.php
<head>
  <?=$view->section('header_codetop')?>
</head>
<body>
  <?=$view->insert('page-section')?>
</body>

And inside of page-section is where you further down that template tree you append the JS via push.

You're doing a top down approach to the templating, which is fine, but plates wasn't designed to support that in the context of sections.

What you should do is turn your template.php into a layout file:

// template.php
<head>
  <?=$view->section('header_codetop')?>
</head>
<body>
  <?=$view->section('content')?>
</body>
// page-section.php
<?php $view->layout('template.php') ?>
// the rest of page-section.php as you did before.

And when you go to render from your controller or whatever, don't render template.php, render page-section.php.

Internally what happens is that page-section is assigned to a template object which renders and then accumulates the Section data for each section that was defined. The layout template isn't loaded UNTIL after that process. So a layout is guaranteed to load after its child template loads which is what you are looking for.

brightboxtech commented 5 years ago

Thank you for your response! Let me share the exact code, should of done this from the get go. My apologizes. Here is template.php

<!doctype html>
<html lang="en">
  <head>
    <?=$view->insert('page-section')?>

    <?=$view->section('header_meta')?>

    <?=$view->section('header_codetop')?>

    <!-- CSS Main Site File -->
    <link rel="stylesheet" href="/css/site.css">

    <?=$view->section('header_codebot')?>
  </head>
  <body id="curr-page">
    <?=$menu_header?>
    <div class="container">
      <div id="curr-content" class="row">
        <div class="col-12">
          <?php $view->insert('page-header')?>
          <div class="row">
            <div class="col-12 col-lg-8 main-content">
            <?=$view->section('content')?>
            </div>
            <div class="col-12 col-lg-4 col-md-6 mx-auto mt-4 mt-lg-0 side-content">
            <?php

                if($side_modules) :

                    foreach($side_modules AS $curr_module):

                      $view->insert('pagemods::' .$curr_module['modfile']);

                    endforeach;

                endif;
            ?>
            </div>
          </div>
        </div>
      </div>
    </div>
    <?=$menu_footer?>

    <?=$view->section('footer_codetop')?>

    <!-- JS Main Site File -->
    <script src="/js/site.js"></script>

    <?=$view->section('footer_codebot')?>
  </body>
</html>

Here is page-section.php

<?php $view->start('header_meta') ?>
<!-- Site Meta Data -->
<title><?=$view->e($meta_title)?></title>
<meta name="description" content="<?=$view->e($meta_desc)?>">
<meta name="format-detection" content="telephone=no">

<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=0 shrink-to-fit=no">

<!-- Page Canonical Link -->
<link rel="canonical" href="<?=$view->e($meta_canonical)?>">

<!-- Site Favicon -->
<link rel="icon" href="data:;base64,iVBORw0KGgo=">
<?php $view->stop() ?>

<?php $view->start('header_codetop') ?>
<!-- CSS Site Files -->
<link rel="stylesheet" href="/js/library/bootstrap/dist/css/bootstrap.min.css">
<link rel="stylesheet" href="/js/library/fontawesome/css/all.css">
<link rel="stylesheet" href="/js/library/megamenu/files/css/navigation.css">
<link rel="stylesheet" href="/js/library/megamenu/files/css/navigation.skin.striped.css">
<link rel="stylesheet" href="/js/library/EasyAutocomplete/dist/easy-autocomplete.min.css">
<link rel="stylesheet" href="/css/fonts.css">
<?php $view->stop() ?>

<?php $view->start('footer_codetop') ?>
<!-- JS Site Files -->
<script src="/js/library/jquery/dist/jquery.min.js"></script>
<script src="/js/library/jquery/dist/jquery-migrate-3.0.0.min.js"></script>
<script src="/js/library/popper.js/dist/umd/popper.min.js"></script>
<script src="/js/library/bootstrap/dist/js/bootstrap.min.js"></script>
<script src="/js/library/megamenu/files/js/navigation.js"></script>
<script src="//s7.addthis.com/js/300/addthis_widget.js#pubid=ra-522c0ed93ddda486"></script>
<?php $view->stop() ?>

Now in template.php you see code down the page to load the side menu modules:

            <?php

                if($side_modules) :

                    foreach($side_modules AS $curr_module):

                      $view->insert('pagemods::' .$curr_module['modfile']);

                    endforeach;

                endif;
            ?>

Inside a module I want to push to a section that is inside the header. like so:

<?php $view->push('header_codetop') ?>
    <script type="text/javascript" id="main"></script>
<?php $view->end() ?>

But it wont work because Im doing the push after:

<?=$view->section('header_codetop')?>

Now if I did a push to:

<?=$view->section('footer_codetop')?>

It works because that section code is after the side module code.

Now in the controller, in this case index.php, I render the template like so:

//We render the final template to display for this page
echo $global_template->render('page-home');

In page-home.php is where I have the layout code specified, it also contains the content specific to the page Im currently on. So page-home.php would be content for index.php. If I went to another page like contact-us, the controller would render a page called contact-us.php which would have the same code below, but content specific for contact-us. Perhaps this is where Im using plates incorrectly.

<?php $view->layout('template') ?>

//Content I want to show for this page...
brightboxtech commented 5 years ago

@brightboxtech I think you just need to restructure your code to use a layout.

Let me understand if this the structure of your templates:

// template.php
<head>
  <?=$view->section('header_codetop')?>
</head>
<body>
  <?=$view->insert('page-section')?>
</body>

And inside of page-section is where you further down that template tree you append the JS via push.

You're doing a top down approach to the templating, which is fine, but plates wasn't designed to support that in the context of sections.

What you should do is turn your template.php into a layout file:

// template.php
<head>
  <?=$view->section('header_codetop')?>
</head>
<body>
  <?=$view->section('content')?>
</body>
// page-section.php
<?php $view->layout('template.php') ?>
// the rest of page-section.php as you did before.

And when you go to render from your controller or whatever, don't render template.php, render page-section.php.

Internally what happens is that page-section is assigned to a template object which renders and then accumulates the Section data for each section that was defined. The layout template isn't loaded UNTIL after that process. So a layout is guaranteed to load after its child template loads which is what you are looking for.

I just did a test like so inside template.php:

    <?=$view->section('header_meta')?>

<?php $view->push('header_meta') ?>
    <script type="text/javascript" id="main"></script>
<?php $view->end() ?>

the push doesnt work because its after

<?=$view->section('header_meta')?>

So if I want to push to any section inside the template it has to be before the section is outputted. I mean it makes sense, because its echoing out the value of section. I just thought there might be a different way to tell plates to load all the section push or unshift requests at the end instead of inline, if that makes sense.

ragboyjr commented 5 years ago

@brightboxtech y, so it looks like my assumption was correct. update to use layouts as mentioned, and that should be resolved.

brightboxtech commented 5 years ago

@brightboxtech y, so it looks like my assumption was correct. update to use layouts as mentioned, and that should be resolved.

Actually, it wont work as I want it to work. I removed page-section.php and put all the code into the template.php file. But that isnt the issue, the issue is I cannot push to a section inside a template after the section has already been echoed out. Again it makes sense, just thought there was something else I could do to force sections to load the push at the end instead of inline.

I want to be able to push to sections inside my modules that I include inside template.php. But as you were saying, it cant because its attaching the push to the section as its happening, so because I already echoed out the section header_codetop, there is no way to push to that section after the fact inside of template.php.

ragboyjr commented 5 years ago

@brightboxtech so, you need to use the $this->layout('template.php') from within the page-section.php, and then:

- $plates->render('template.php')
+ $plates->render('page-section.php')
brightboxtech commented 5 years ago

@brightboxtech so, you need to use the $this->layout('template.php') from within the page-section.php, and then:

- $plates->render('template.php')
+ $plates->render('page-section.php')

No, thats not my issue. My issue is with this code inside template.php:

 <?php

                if($side_modules) :

                    foreach($side_modules AS $curr_module):

                      $view->insert('pagemods::' .$curr_module['modfile']);

                    endforeach;

                endif;
            ?>

I am inserting module files on my side menu. Inside a module file I want to be able to push to a section in the header of template.php. But it appears I cant do that after the section has already been echoed out. So for example, the code above I want to insert a module called site-links.php.

Inside site-links.php I want to push a JS file to header_codetop inside template.php. But it never works, because the section for header_codetop has already been echoed out in template.php.

ragboyjr commented 5 years ago

@brightboxtech I believe it your issue:

Internally what happens is that page-section is assigned to a template object which renders and then accumulates the Section data for each section that was defined. The layout template isn't loaded UNTIL after that process. So a layout is guaranteed to load after its child template loads which is what you are looking for.

The only way you can utilize sections from your template.php file is if it's a layout. Please give that a try and let me know if you have any more issues with the layout approach.

As far as being able to use sections in the way you are doing it WITHOUT using layouts, that is not supported.

brightboxtech commented 5 years ago

@brightboxtech I believe it your issue:

Internally what happens is that page-section is assigned to a template object which renders and then accumulates the Section data for each section that was defined. The layout template isn't loaded UNTIL after that process. So a layout is guaranteed to load after its child template loads which is what you are looking for.

The only way you can utilize sections from your template.php file is if it's a layout. Please give that a try and let me know if you have any more issues with the layout approach.

As far as being able to use sections in the way you are doing it WITHOUT using layouts, that is not supported.

Right, you are spot on with the issue. The resolution though is, well there is no resolution as you stated. I have a class that I use that generates css/js includes or inline code and puts it into an array in the order I want then at the end right before rendering I can call each "section" in the array and put it into a variable and pass it to the template so I can properly put the needed js/css code in the header or footer as needed.

But when I saw your push method, that would of made it so much simpler. Oh well. Thanks for your help and explanation! Like I said, hopefully as I use plates more I will see a better/proper way of using it.

brightboxtech commented 5 years ago

@brightboxtech I believe it your issue:

Internally what happens is that page-section is assigned to a template object which renders and then accumulates the Section data for each section that was defined. The layout template isn't loaded UNTIL after that process. So a layout is guaranteed to load after its child template loads which is what you are looking for.

The only way you can utilize sections from your template.php file is if it's a layout. Please give that a try and let me know if you have any more issues with the layout approach.

As far as being able to use sections in the way you are doing it WITHOUT using layouts, that is not supported.

Can I ask one quick question? Looking through your test scripts I saw:

$v->component('alerts',[]);

Is there any documentation that tells me what component does?

ragboyjr commented 5 years ago

Right, you are spot on with the issue. The resolution though is, well there is no resolution as you stated.

So, that's a bit misguided. The resolution is to use the layouts feature. If you use layouts, you can do exactly what you are attempting to do.

Re components:

They are like page partials via $v->insert(), but allow you to pass in rendered contents as parameters.

So, you can do:

<?php $v->component('alert', []) ?>
<div>Content to pass into the `alert` partial as `$contents`</div>
<?php $v->end() ?>
brightboxtech commented 5 years ago

Right, you are spot on with the issue. The resolution though is, well there is no resolution as you stated.

So, that's a bit misguided. The resolution is to use the layouts feature. If you use layouts, you can do exactly what you are attempting to do.

Re components:

They are like page partials via $v->insert(), but allow you to pass in rendered contents as parameters.

So, you can do:

<?php $v->component('alert', []) ?>
<div>Content to pass into the `alert` partial as `$contents`</div>
<?php $v->end() ?>

Thank you!