Delayed Output

Also known as the main.inc strategy

This strategy focuses on generating the output for all the various regions of our final document ahead of time, and temporarily storing them in placeholders (called variables). Rather than including a head.inc and foot.inc file, we instead just include a main.inc file at the end. That main.inc file knows where to output all the placeholders.

The placement of content matters little to our template files, which can focus purely on populating those placeholders. Should the site later be re-designed and have everything get moved around, our template files don't need to change. Only our main.inc file and CSS files are likely to need adjustment to support the new design.

What does this strategy look like?

When using delayed output with variables, your template files focus on populating variables rather than outputting them. This leads to template files that are quite a bit simpler, and occasionally even blank! Here's an example of what our template file might look like:

/site/templates/basic-page.php

<?php
$headline = $page->get("headline|title");
$bodycopy = $page->body;
$sidebar = $page->sidebar;
include("./main.inc"); 

As you can see above, our template file is simply populating 3 predefined regions by using placeholder variables: $headline, $bodycopy, and $sidebar. These variables are populated with values that we want to be output in main.inc. Then we end by just including the main.inc file. What does this mysteries main.inc file look like?

/site/templates/main.inc

<html>
  <head>
    <title><?php echo $headline; ?></title>
  </head>
  <body>
    <div id='bodycopy'>
      <h1><?php echo $headline; ?></h1>
      <?php echo $bodycopy; ?>
    </div>
    <div id='sidebar'>
      <?php echo $sidebar; ?>
    </div>
  </body>
</html>

What this example isn't showing is that your $bodycopy and $sidebar may consist of a lot more than just $page->body and $page->sidebar. Which leads us to…

Populating placeholders (variables) with more

Lets say that you want your $bodycopy to include a list of comments at the end, and you want your $sidebar to include sub-navigation to child pages. Here's how that might look:

/site/templates/basic-page.php

<?php
$headline = $page->get("headline|title");

// bodycopy is body text plus comments
$bodycopy = $page->body . $page->comments->render();
$sidebar = $page->sidebar;

// check if this page has any children
if(count($page->children)) {
  // render sub-navigation in sidebar
  $sidebar .= "<ul class='nav'>";
  foreach($page->children as $child) {
    $sidebar .= "<li><a href='$child->url'>$child->title</a></li>";
  }
  $sidebar .= "</ul>";
}

include("./main.inc"); 

As you can see, we can put whatever we want into our predefined placeholders/variables very easily. We've got a lot of flexibility here.

Defining new placeholders to represent new regions

Lets say that we wanted to let our main.inc file decide where the sub-navigation should appear, rather than us appending it to the sidebar. Whether you need that or not depends, but lets look at how we might approach it. We will give our main.inc full control over where that <ul class='nav'>…</ul> sub-navigation lives. We could do that by setting up another placeholder variable, which we will name $subnav. Rather than populating $sidebar, we would populate $subnav. Then our main.inc file would determine where that $subnav gets displayed.

Using the placeholder to hold pages rather than markup
In this particular case, we would have to repeat ourselves with writing the foreach() code to generate the <ul class='nav'>…</ul> in every template file where we wanted sub-navigation. It would make more sense to let our main.inc file handle that markup generation, and let our template file just pass it the list of pages that should appear in that sub-navigation. So we will use our $subnav placeholder variable to hold a group of pages rather than a string of markup. Here's how that might look:

/site/templates/basic-page.php

<?php
$headline = $page->get("headline|title");
$bodycopy = $page->body . $page->comments->render();
$sidebar = $page->sidebar;
$subnav = $page->children;
include("./main.inc"); 

And here's how the main.inc file might look:

/site/templates/main.inc

<html>
  <head>
    <title><?php echo $headline; ?></title>
  </head>
  <body>
    <div id='bodycopy'>
      <h1><?php echo $headline; ?></h1>
      <?php echo $bodycopy; ?>
    </div>
    <div id='sidebar'>
      <?php
      echo $sidebar
      if(count($subnav)) {
        echo "<ul class='nav'>";
        foreach($subnav as $child) {
          echo "<li><a href='$child->url'>$child->title</a></li>";
        }
        echo "</ul>";
      }
      ?>
    </div>
  </body>
</html>

Adding an init.inc file to the mix (a best practice)

One of the problems with the above examples is that each template file must populate every single placeholder variable output by the main.inc file. If you decide to add a new placeholder for a new region, you'd have to go back and edit all your template files to account for it. Otherwise you'd end up with "undefined variable" notices. In addition, maybe a lot of our template files simply don't need $bodycopy to be anything more than $page->body and don't need $sidebar to be anything more than $page->sidebar. We could think of those as our default values. Wouldn't it be nice if we didn't have to even mention them in our template files when we didn't need them to be different from the default?

This is an easy task to accomplish. Simply include an init.inc file at the top of each template file. Like the name implies, the init.inc file initializes all the placeholder variables that will get output in main.inc by establishing default values to them. Here's how our init.inc file might look:

/site/templates/init.inc

<?php
$headline = $page->get("headline|title");
$bodycopy = $page->body;
$sidebar = $page->sidebar;
$subnav = $page->children;

Now all of our placeholders in main.inc are accounted for with default values. Our template files can focus only on modifying the placeholders that they want to be different from the defaults. Our basic-page.php only needs to append comments to the $bodycopy, so it now looks like this:

/site/templates/basic-page.php

<?php
include("./init.inc");
$bodycopy .= $page->comments->render();
include("./main.inc");  

Automatic inclusions

We now have a scalable and sustainable strategy for our template files, but it's a little annoying to have to repeat the include("./init.inc"); and include("./main.inc"); in every single one of our template files. Thankfully, there are two $config options that can solve this for us:

/site/config.php

$config->prependTemplateFile = 'init.inc';
$config->appendTemplateFile = 'main.inc';

The $config->prependTemplateFile specifies the file that you want ProcessWire to load before your template file, and the $config->appendTemplateFile specifies the file you want ProcessWire to load after your template file. These files are assumed to live in /site/templates/. They act as if they had literally been prepended or appended to your template file, sharing the same variable space as your main template file. This is what enables you to share variables between them. Meaning, a variable set in your init.inc file (like $bodycopy) will still be present ("in scope") in your template file, and will still be present in the main.inc file as well.

After editing our /site/config.php file to add those two options, our basic-page.php template file may be reduced to just this:

/site/templates/basic-page.php

<?php
$bodycopy .= $page->comments->render();

When to use automatic inclusions
Whether or not you should use automatic inclusions kind of depends on the situation. If you are working with a site that has various output needs, some direct and some delayed, then automatic inclusions may get in the way. Likewise, if you have more than one main.inc file (perhaps representing different layouts) then it might be preferable to include() whatever output file you desire at the end of each template file. You may find in some cases that you'll use $config->prependTemplateFile, but not $config->appendTemplateFile. Again, it really just depends on the situation. However, now that you know about automatic inclusions, we think you'll find them very useful in many instances.

Next: More Strategies »


  1. Introduction
  2. Direct Output
  3. Direct Output with Includes
  4. Delayed Output
  5. More Strategies

Comments

  • Kristoffer

    Kristoffer 3 years ago 42

    If you are having trouble loading the $config->prependTemplateFile and $config->appendTemplateFile variables, make sure that your FTP user has write permissions to the ProcessWire Configuration File. When config.php is write protected ("chmod 444 config.php", or similar) on the FTP server, you may not notice that changes to config.php are not uploaded to the server.

  • Bernhard

    Bernhard 3 years ago 64

    really great tutorial! thank you ryan!

  • pwired

    pwired 3 years ago 52

    Thanks to the forum I arrived here after the release
    of pw-2.5.0. Pages 3, 4 and 5 explain very nice how
    header and footer.inc have been evolved in pw-2.5.0

  • adrian

    adrian 2 years ago 32

    With the example shown on the above documentation, the output website page layout is fixed in $config->appendTemplateFile in config.php. If I have a page with a different layout, how to deal with it ?

    • ryan

      ryan 2 years ago 22

      There are any number of ways you could do it, but simplest would be just to go to your template settings (Setup > Templates > your-template > Files [tab]), and specify a different append/prepend file for that template.

  • James

    James 2 years ago 42

    Hi Ryan

    I am new to PW, having come from building many sites previously with Concrete5. With the Delayed Output approach, and the use of main.inc, does this not leave you with one file solely responsible for the structure of every page on the site? For example, what if I have a home page that is full width. I might also have some pages with sub nav, and hence the need for a sidebar. Maybe on my blog page I might want a three column approach with nav on one side and categories on the other.

    Is main.inc responsible for having to structure each of these pages? Or, would you approach this differently by having different versions of main.inc? ie there might be a sidenav-page.inc, full-width.inc, blog-entry.inc etc

    Regards

    James

    • ryan

      ryan 2 years ago 43

      James, the main.inc can be swapped out with whatever you want on a template-by-template basis (see my reply to Adrian below). However, the reality is that there's often going to be as much similar as there is different, regardless of layout. And you want to avoid repeating yourself. So you might choose to make your main.inc include different layout (.inc) files, or simply have something like if($sidebar) then output a sidebar column, etc. The default site profile has an example of this. Another way is to define a $layout variable (or whatever you want to call it) in your init.inc that indicates a default layout file. Then any of the template files can choose to change that. Then the main.inc file can take that $layout into account to render or include accordingly.

  • Alex

    Alex 2 months ago 10

    Suppose hat I have to create a set of new pages which uses a template(say template_2)different than the rest of the website. In my site/config, I have
    $config->prependTemplateFile = '_init.php';
    $config->appendTemplateFile = '_main.php';

    However, I do not want the sidebar and navigation I had defined in the main.php for the template_2. I want to have different footer,header and sidebar. Any suggestions how I can add an exception such that when I load template_2, I should not include the original _init.php and _main.php and should load from some _init2.php and _main2.php?

    • Pavel

      Pavel 4 weeks ago 00

      Hello Alex, look at this post.
      https://processwire.com/talk/topic/8767-regarding-delayed-output/?do=findComment&comment=84604

Post a Comment

Your e-mail is kept confidential and not included with your comment. Website is optional.