Jump to content

A different way of using templates / delegate approach


Soma

Recommended Posts

Ah, yes then you would have to include the function to each template file. That's one reason I don't like Ryan's approach. :) Though no issue at all, just personal preference.

Edit: Yes a module would help here, that provides function for all templates.

Link to comment
Share on other sites

After Ryan's latest incarnation of Blog Profile I wanted to see if I could learn something from there to my own workflow. It turned out very nicely and this seems like a perfect template approach for me. Wanted to share it with you guys.

Folder structure under templates folder:

templates/markup/

templates/markup/helpers/

templates/markup/layouts/

templates/scripts/

templates/styles/

And it all begins from here:

templates/markup/index.php

-this is the "complete" html file, it has doctype, head and starting and ending body. There is very little "logic" here, it's more like a container. There is one very important code snippet there though:

<?php
if ($page->layout) {
 include("./markup/layouts/{$page->layout}.php");
} else {
 include("./markup/layouts/default.php");
}
?>

Code above goes between header and footer of your site, that will be the main content. I call it layout, but the better name would be "content layout" or "inner layout" or something like that.

Then the templates/markup/layouts/ folder will keep at least default.php file, but probably few others, like "threeColumns.php", "frontpage.php", "gallery.php" etc.. you got the idea.

Each of the actual pw template files are then purely "controllers" - no actual markup generated there. All markup are done in files inside templates/markup/ folder and it's subfolders. This is how template file templates/home.php on one site I am building right now looks like:

<?php

// Carousel items
$t = new TemplateFile(wire('config')->paths->templates . 'markup/helpers/carousel.php');
$t->set('carouselPages', $page->carousel);
$page->masthead = $t->render();

// Tour themes
$t = new TemplateFile(wire('config')->paths->templates . 'markup/helpers/items.php');
$t->set('title', "Get inspired from our <strong>tour themes</strong>");
$t->set('items', $page->featured_themes);
$t->set('description', $page->themes_description);
$t->set('url', $config->urls->root . "themes/");
$t->set('linkTitle', "All themes");
$page->main .= $t->render();

// National parks
$t = new TemplateFile(wire('config')->paths->templates . 'markup/helpers/items.php');
$t->set('title', "Seven beautiful <strong>national parks</strong>");
$t->set('items', $page->featured_parks);
$t->set('description', $page->parks_description);
$t->set('url', $config->urls->root . "national-parks/");
$t->set('linkTitle', "All national parks");
$page->main .= $t->render();

$page->layout = "frontpage";
include("./markup/index.php");

This uses few "helper" markup files from templates/markup/helpers/ folder (namely carousel.php and items.php). Here is the carousel.php for your reference:

<?php
/* Generates the markup for the frontpage carousel */
if (count($carouselPages) < 1) return;
$styles = '';
echo "<div id='carousel'><ul class='rslides'>";
foreach($carouselPages as $key => $p) {
 echo "<li class='c-item c-item-$key'>";
 echo "<img src='".$p->image->getThumb('carousel') ."' alt='' />";
 echo "<p>$p->summary</p>";
 echo "<a class='button' href='{$p->link->url}'>$p->headline</a>";
 echo "</li>";
}
echo "</ul></div>";

Then populates the $page->masthead and $page->main properties and then set's the inner layout to "frontpage". That templates/markup/layouts/frontpage.php file is very simple on this site, but could be much more complicated if needed:

<div id="masthead">
 <?= $page->masthead; ?>
</div>

<div id="main" class="wrap">
 <?= $page->main; ?>
</div>

Frontpage is rather unique and I could have done all the markup on the frontpage.php file also. But I do want to re-use those "carousel" and "items" components on other places as well. But if I do have totally unique stuff, where I do want to get "quick and dirty" this approach allows it. Then my template file would be something like this:

$page->layout = "campaign2012";
include("./markup/index.php");

And then all the markup would be in that templates/markup/layouts/campaign2012.php

Blog profile really gave me a good ideas (cleaner folder structure etc) and using TemplateFile class adds nice possibilities. This is of course just a one way to manage your templates, but hopefully someone of you finds this helpful when thinking about how you like to structure this stuff.

PS: If you are just getting started with PW, then I recommend using the head and foot includes method from demo install. There is nothing wrong with that method and only more complicated sites starts to benefit from these methods introduces in this topic.

  • Like 17
Link to comment
Share on other sites

Thanks for your detailed post! I'm studying the blog profile as well and I can't seem to wrap my head around the TemplateFile class. What does it do exactly? Do you create a temporary template? Or a template as a variable? Why and when do you use it?

Link to comment
Share on other sites

It is pretty simple and straightforward. It loads the file and then you can set variables for it. Then you can render the output (directly echo or save to a variable which get's echoed later on). It doesn't differ that much from having functions that return the output, but I think having them as separate files is much cleaner. And no need to passing variables as function parameters is nice also.

It is pretty close to plain simple includes, but it allows to save the output to a variable instead of directly outputting it. That is pretty much all it does (same as this: http://php.net/manual/en/function.include.php#example-140). This difference allows you to keep your actual template file purely as a controller if you want to (like in my example there is always last line include("./markup/index.php"); which starts the output).

Link to comment
Share on other sites

Thanks for the answer. It seems like a really clean and neat way to create templates. Would you be so kind to post the contents of items.php or carousel.php? So I can relate those as well?

Link to comment
Share on other sites

Sure, actually will add one of these into first example. But here are both:

Carousel.php

<?php
/* Generates the markup for the frontpage carousel */
if (count($carouselPages) < 1) return;
echo "<div id='carousel'><ul class='rslides'>";
foreach($carouselPages as $key => $p) {
 echo "<li class='c-item c-item-$key'>";
 echo "<img src='".$p->image->getThumb('carousel') ."' alt='' />";
 echo "<p>$p->summary</p>";
 echo "<a class='button' href='{$p->link->url}'>$p->headline</a>";
 echo "</li>";
}
echo "</ul></div>";

items.php

<?php
if (isset($title)) echo "<h2 class='items-title'>$title</h2>";

echo "<div class='items'>";
foreach($items as $key => $p) {
 $key++;
 if ($key > 3) {
$key = 1;
echo "<hr class='padding' />";
 }
 echo "<div class='col{$key}of3 item'>";
 echo "<img src='". $p->featured_image->getThumb('thumbnail') ."' alt='' />";
 echo "<h2 class='title'><a href='$p->url'>$p->title</a></h2>";
 echo "</div>";
}
echo "</div>";

And as additional bonus, items-with-description.php (it includes the items.php from above):

<?php
if (count($items) < 1) return;
$items = $items->slice(0,3);
include('./items.php');
echo "<div class='col1-2of3'><p>$description</p></div>";
echo "<div class='col3of3'><a class='button block' href='$url'>$linkTitle</a></div>";

Uh, just realized that my site has evolved a little. Those in my example actually use "items-with-description.php" instead of just "items.php".

There is also my "default-masthead.php":

<?php
$img = $image->width(304);
?>
<div class='col1-2of3'>
 <h1><?= $title ?></h1>
 <p class='summary'><?= $summary ?></p>
</div>
<div class='col3of3'>
 <img src='<?= $img->url ?>' alt='' />
</div>
  • Like 2
Link to comment
Share on other sites

Back to the Soma's template approach: I just realized that we would not need to use dirty hacks to force PW to render alternate template file, if PW would allow us to set template files for both layout and particular view.

Example:

We have a post template.

In post template we set

  • main.php for layout (default setting for the whole site) - renders the whole HTML page
  • post.php for view (default setting for this particular template - derived from the template name) - renders the content specific to the post template, nested inside the main.php layout

This approach is common in many web application frameworks.

Link to comment
Share on other sites

Robert, if I understood you correctly, that is exactly what my method does also. It doesn't do it automatically, since I want most of my templates to use same view (default.php), but it is possible to set any view just by saying:

$page->layout = "post";

I am pretty sure that similar thing is possible on Soma's approach also. No dirty hacks needed to get such an output.

Link to comment
Share on other sites

Back to the Soma's template approach: I just realized that we would not need to use dirty hacks to force PW to render alternate template file, if PW would allow us to set template files for both layout and particular view.

Example:

We have a post template.

In post template we set

  • main.php for layout (default setting for the whole site) - renders the whole HTML page
  • post.php for view (default setting for this particular template - derived from the template name) - renders the content specific to the post template, nested inside the main.php layout

This approach is common in many web application frameworks.

I'm not sure I understand correctly. "My" approach already does exactly this. The template in PW uses all "main" as alternative, but the template name still exists when main.php is called, so depending on the name of the template the right templatename.inc view include will be loaded.

Link to comment
Share on other sites

Yes, both approaches does exactly this using PHP. My suggestion is targeted to PW core developers to rethink the "Alternate Template Filename" option to take care of both layout and view, without the need of additional PHP code in the template files.

I still don't get what you mean. Reading you posts, there's no "dirty hack" needed, it's simple, flexible and straight forward with only 1 line of php code to include the view template. Without that how would your main template know where to include the view?

include($config->paths->templates . "view/{$page->template}.inc")

There's no need to go fancy here. I wouldn't like if PW would make assumptions on how to use it.

  • Like 1
Link to comment
Share on other sites

Yes, both approaches does exactly this using PHP. My suggestion is targeted to PW core developers to rethink the "Alternate Template Filename" option to take care of both layout and view, without the need of additional PHP code in the template files.

I am not sure but Adam's new module might do just that: http://processwire.com/talk/topic/1589-render-%E2%80%93-views-for-processwire/page__pid__14536#entry14536

I agree with Soma here: that is nowhere near dirty hack. Because of you are already editing template/markup files then having that setting inside template files instead of admin ui is much better in my opinion.

  • Like 1
Link to comment
Share on other sites

  • 6 months later...
  • 5 months later...

Apeisa,

Thanks for sharing your approach, I'm just now getting a chance to really dissect it.

I have been doing something kind of similar, but not quite as clean as your method.
It still relied heavily on a naming convention for pages/templates.

Setting the view via $page->layout allows for the flexibility I need, without having to worry about naming.

Cheers!

  • Like 1
Link to comment
Share on other sites

  • 1 month later...

Thanks for the interesting thread. If you (like me) don't like to put markup into variables, you can use the output buffer to use cleaner include files:

ob_start();
include('./teaser.php');
include('./pager.php');
$page->body = ob_get_clean();
include('./main.php');

That way your include files can be html with php elements and not $page->body="<p>something</p>"

I have one question to the main.php approach, though: I have a bunch of functions that are used by several templates. With the head/foot method I would simply put them into head.inc and make them available to every page. Where do you put this when using a main.php? Would that be a module?

Thanks,

thomas

I would use the config options in site/config.php for including functions (ver. 2.2.3+ i think, maybe it's 2.3+)...

/**
 * prependTemplateFile: PHP file in /site/templates/ that will be loaded before each page's template file
 *
 * Uncomment and edit to enable.
 *
 */
 $config->prependTemplateFile = '_in.php';

/**
 * appendTemplateFile: PHP file in /site/templates/ that will be loaded after each page's template file
 *
 * Uncomment and edit to enable.
 *
 */
 $config->appendTemplateFile = '_out.php';

In _in.php you can set up things that need to happen for each template. The nice thing about this option is not having to use an include statement on each of your template files. In my _in.php example below I'm initializing fredi and including functions. That's about all I'm using _in.php for.

$fredi = $modules->get("Fredi"); 
include("./includes/functions.php"); 
Your approach is a nice one. You could also wrap ob_start around all output and gzip it. PW may already do the gzipping, I don't know.
 
//compress the output for faster page loads
ob_start("ob_gzhandler");

//output from your includes needs to be sent back with a return
//e.g. return $html_string; or return $data as an array
$body[] = include './teaser.php';
$body[] = include './pager.php';
$page->body = implode("\n",$body);

//you can also use file_get_contents on a public url 
//(but you'll need to bootstrap PW if you need access to $page,$pages, etc.) 
//e.g. file_get_contents('http://www.mysite.com/my_php_include.php');

//depending on needs, maybe easier just to...
//include './teaser.php';
//include './pager.php';
//$page->body = ob_get_contents();
//ob_clean

include './main.php';
ob_get_clean();

Here's what the contents of teaser.php could look like...

//this gives you some flexibility to format data as you wish and is helpful if you want agnostic data 
//you can also use this approach to create your own template system. (e.g. regex replacement {{body}} with $page->body) 
switch ($input->urlSegment1) {
    case "csv":
		//format data for cvs, save to file, provide download link
        break;
    case "xml":
        //format data for xml
        break;
    default:
	//format data for html
	foreach ($page->children as $a) { 
		$data[] = "<li><a href=\"#{$a->$name}\">{$a->title}</a></li>"; 
	}		
        return "<ul>" . implode("\n",$data) . "</ul>";
}

And while you are at it, maybe the better approach is to put ob_start("ob_gzhandler"); in _in.php and ob_get_clean(); in _out.php. Really just depends on what you want to do, but you can see how damn cool PW is.

  • Like 4
Link to comment
Share on other sites

  • 3 months later...

So after reading again and again a lot of posts and examples I start to understand the way Its done. What should I look for to not harm the performance?

Are they some Main Rules and Main NoGo's for this way of using templates?

Link to comment
Share on other sites

There is so much mentioned above, that I'm not sure there is an answer without a more specific question or code snippets to respond to. This topic of this thread is not the approach that I use, but I think it's a good question so don't want to leave it unanswered. I think that for the most part, the same performance rules would apply here that would apply regardless of how you are approaching templates. You don't have much to think about if you are dealing with under a hundred pages. Once you start getting into hundreds and thousands of pages, you have to take care that you aren't loading hundreds and thousands of pages on every request by using things like pagination and calls like $page->children("limit=10") rather than $page->children(). If you'd like, tell us more about your context and details of the approach you are using we can give a better answer.   

Link to comment
Share on other sites

My current problem is that I'm not a coder. I read every free minute here in the forum. The more I read the more I see different examples for the same thing. Starting already at templating e.g. So I have to read simultaneously  PW Tutorials(Forum | HowTo's | FAQ's) , PHP Basics and look always to the Cheatsheet. I have to say Im really impressed more and more about PW's scope.

Up to this point, everything is clear.

basic-page.php

<?php
$page->outMain = "<h2>{$page->subtitle}</h2>" . $page->body;
if($page->numChildren) $page->outMain .= $page->children->render(); // list the children
// note: no $outSide specified
include("./main.php");
 

main.php

<?php

// setup defaults when none specified
if(empty($page->outMain)) $page->outMain = $page->body; 
if(empty($page->outSide)) $page->outSide = $page->sidebar; 

?>
<html>
<head>
   <title><?php echo $page->title; ?></title>
</head>
<body>
   <h1><?php echo $page->title; ?></h1>
   <div id='main'><?php echo $page->outMain; ?></div>
   <div id='side'><?php echo $page->outSide; ?></div>
</body>
</html>
 

and here it ends at the time  I WROTE SOME COMMENTS WHAT I DONT UNDERSTAND REALLY 

basic-page.php 

<?php

// make a custom <title> tag
$page->browserTitle = $page->rootParent->title . ": " . $page->title;

$page->outMain = "<h2>{$page->subtitle}</h2>" . $page->body;

if(count($page->images)) {
   // display a clickable lightbox gallery if this page has images on it

   $config->scripts->add($config->urls->templates . "scripts/lightbox.js");  //WHAT HAPPENS HERE AND WHERE GOES IT IN ?
   $config->styles->add($config->urls->templates . "styles/gallery.css");  //WHERE IS THE CONFIG BLOCK WITH THE CODE 

   $page->outMain .= "<ul id='gallery'>";

   foreach($page->images as $i) {
       $t = $i->size(100,100);
       $page->outMain .= "<li><a href='{$i->url}'><img src='{$t->url}' alt='{$t->description}' /></a></li>";  //WHAT HAPPENS TO THE BODYTEXT IN THE outMain PLACEHOLDER IF THERE ARE IMAGES ON THE SITE ?  THEN ONLY IMAGES SHOWN ON THE SITE 
   }

   $page->outMain .= "</ul>";

   // add a note to $page->title to say how many photos are in the gallery
   $page->title .= " (with " . count($page->images) . " photos!)";    
}

if($page->numChildren) $page->outMain .= $page->children->render(); // list the children

include("./main.php");
 

main.php 

<?php

// if current template has it's own custom CSS file, then include it
$file = "styles/{$page->template}.css";
if(is_file($config->paths->templates . $file)) $config->styles->add($config->urls->templates . $file);  // AGANIN THE $config placeholder ? Where goes it ? What happens real in the first two lines ?? 

// if current template has it's own custom JS file, then include it
$file = "scripts/{$page->template}.js"; 
if(is_file($config->paths->templates . $file)) $config->scripts->add($config->urls->templates . $file); 
// How do you check or where do you define the own custom JS file ? 

?>
<html>
<head>
   <title><?php echo $page->get('browserTitle|title'); // use browserTitle if there, otherwise title ?  WHERE IS BROWSER TITLE DEFINED ? ?></title>

   <?php
   foreach($config->styles as $url) echo "<link rel='stylesheet' type='text/css' href='$url' />";  // HAVE I TO DEFINE IN EVERY TPL FILE THE  $config placeholder ? 
   foreach($config->scripts as $url) echo "<script type='text/javascript' src='$url'></script>";
   ?>
</head>
<body>
   <h1><?php echo $page->title; ?></h1>
   <div id='main'><?php echo $page->get('outMain|body'); // use outMain if there, or body otherwise ?></div>
   <div id='side'><?php echo $page->get('outSide|sidebar'); // use outSide if there, or sidebar otherwise ?></div>
</body>
</html>
 

I try to find my workflow with the basic installation and some modules where I think i will always use in all projects. 

e.g. Simple Markup Navigation I change the example code in the installation 

head.inc

<?php  // Generate Menu 
            $treeMenu = $modules->get("MarkupSimpleNavigation"); 
            echo $treeMenu->render($options);  
            ?>
 

sitemap.php

$treeMenu = $modules->get("MarkupSimpleNavigation");
echo $treeMenu->render($optionsMap);
 

have a new file called functions.inc like in the Blog Profile example where i try to store all settings what I need. and include in in the head.inc

functions.inc

$homepage = $pages->get("/");
$browserTitle = $page->title;   // what appears in the <title> tag
$headline = $page->headline;   // primary h1 headline
$content = $page->body;     // bodycopy area

$options = array(
    'parent_class' => 'on',
    'levels' => false,
    'max_levels' => 1,
    'show_root' => true,
    'selector_field' => 'nav_selector',
    'outer_tpl' => '<ul id="topnav" >||</ul>'
);

$optionsMap = array(
    'parent_class' => 'on',
    'levels' => false,
    'max_levels' => 0,
    'show_root' => true,
    'selector_field' => 'nav_selector',
    'outer_tpl' => '<ul class="sitemap" >||</ul>'   
);
 

And I see in the examples that I have to youse always a placeholder ?? 

Why is it better ? 

e.g. 

 
$page->browserTitle = $page->rootParent->title . ": " . $page->title;
$page->outMain = "<h2>{$page->subtitle}</h2>" . $page->body;
 

It means that the $page placeholder is an Array ? an I have to define whats in there ? Like $browserTitle  $outeMain $sidBar $customThings  ?? 

is this the same Placeholder ? 

  • $page
    The $page variable is provided to every template, and it contains all the fields specific to the page being viewed. This includes both built-in fields, which are common to all pages, as well as the custom fields.

Because ryan wrote 

 As a result, I actually use $page as an anonymous placeholder for these variables (making sure they don't conflict with any existing field names) and then let main.php assign defaults if the calling template didn't specify one of them. 
Link to comment
Share on other sites

@creativ3minds,

My suggestions/questions:

  1. Maybe it will be easier to answer your questions if you divided them into separate new threads. I notice some of your questions are somewhat PHP related (e.g. concatenation). Others are about the PW API (both basic and advanced). Currently, your post is way too long IMHO. :-)
  2. Have you read up on the basics of PW? i.e. the API
Link to comment
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...