Jump to content

A different way of using templates / delegate approach


Soma

Recommended Posts

I found (after 2-3 Projects using PW) that it's a good technique to use templates in a way I think hasn't been thought of yet really by some. (Although the CMS we use at work for year, works this way.) I'm sure I'm maybe wrong and someone else is already doing something similar. But I wanted to share this for everybody, just to show alternative way of using the brillant system that PW is.

Delegate Template approach

I tend to do a setup like this:

- I create a main.php with the main html markup, no includes. So the whole html structure is there.

- I then create page templates in PW without a file associated. I just name them let's say: basic-page, blog-entry, news-entry... but there's no basic-page.php actually.

- Then after creating the template I make it use the "main" as alternative under "Advanced" settings tab. So it's using the main.php as the template file.

- This allows to use all templates having the same php master template "main.php"

- Then I create a folder and call it something like "/site/templates/view/", in which I create the inc files for the different template types. So there would be a basic-page.inc, blog-entry.inc ...

- Then in the main.php template file I use following code to delegate what .inc should be included depending on the name of the template the page requested has. Using the TemplateFile functions you can use the render method, and assign variables to give to the inc explicitly, or you could also use just regular php include() technic.

<?php 
/*
*  template views depending on template name
       *  using TemplateFile method of PW
*/

// delegate render view template file 
// all page templates use "main.php" as alternative template file

if( $page->template ) {
	$t = new TemplateFile($config->paths->templates . "view/{$page->template}.inc");
	//$t->set("arr1", $somevar);
	echo $t->render();
}

<?php 
/*
*  template views depending on template name
       * using regular php include
*/
if( $page->template ) {
	include($config->paths->templates . "view/{$page->template}.inc");
}

I chosen this approach mainly because I hate splitting up the "main" template with head.inc and foot.inc etc. although I was also using this quite a lot, I like the delegate approach better. Having only one main.php which contains the complete html structure makes it easier for me to see/control whats going on.

Hope this will be useful to someone. Cheers

  • Like 27
Link to comment
Share on other sites

Thanks for posting Soma, this is an interesting approach and not one I've seen before, but it looks great. The underlying concept and result is similar to the approach I usually use. Since you posted a good description, I'll try to do the same for mine.

The only reason you see head/foot files in the default PW profile is because it seems to be simpler for new users to grasp. But I almost never use that approach in my own sites.

Like your system, I have a main.php file which is my main markup file. But unlike your system, main.php is included from all the other template files (rather than main.php including them).

The other template files focus on populating the key content areas of the site, specific to the needs of the template. Examples of key content areas might include "main" (for center column/bodycopy) and "side" (for sidebar/related info), though often includes several other identified areas. But I'll keep it simple in this case. Here's how it works:

basic-page.php

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

main.php

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

The benefit of this approach is that basic-page.php can setup whatever it wants in the key content areas ($main or $side) whether simple like in this example, or something much more complex.

I actually prefer for the variables representing the key content areas to be optional. In the scenario above, $outMain and $outSide would have to be defined by every template or they would end up as uninitialized variables in main.php. 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. For example:

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>

Final thing to point out here is that main.php is the only template actually outputting anything. Because basic-page.php (or any other template) is determining what's going to go in that output before it is actually sent, your template has the opportunity to modify stuff that you might not be able to with other methods. For instance, the <title> tag, what scripts and stylesheets are loaded, etc. Here's the example above carried further to demonstrate it:

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"); 
   $config->styles->add($config->urls->templates . "styles/gallery.css"); 

   $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>";
   }

   $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);

// 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); 

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

   <?php
   foreach($config->styles as $url) echo "<link rel='stylesheet' type='text/css' href='$url' />";
   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>

More than half the time, I'll actually just re-use page variables like $page->body and $page->sidebar rather than $page->outMain and $page->outSide. That way there's no need to consider defaults, since $page->body and $page->sidebar untouched technically are defaults.

<?php
$page->body = "<h2>{$page->subtitle}</h2>" . $page->body . $page->children->render(); 

But technically you've got a little more flexibility using your own self-assign anonymous variables like outMain and outSide, so figured I'd use that in the examples above. outMain and outSide are just example names I came up with for this example and you could of course name them whatever you want.

  • Like 22
Link to comment
Share on other sites

Nice discussion, thanks for posting guys. Our setup is pretty much similar to Ryan's (got good tips there when build it), although I have little bit of Soma's method baked in too :D

If I take Ryan's example, then our main.php would be something like this:

<html>
<head>
   <title><?php echo $page->title; ?></title>
</head>
<body>
   <h1><?php echo $page->title; ?></h1>
   <?php 
   if ($page->tpl) include("./tpl/{$page->tpl}.inc");
   else include("./tpl/default.inc");
   ?>
</body>
</html>

Then I would set that $page->tpl in actual template file (like product.php, event.php etc), if I need to. Of course it would also be possible to allow client to choose tpl on certain page templates (using basic page field selection), but I haven't had the need yet.

This way I can use same basic wrapper even though markup inside body tags would be totally different. This also allows to have $page->tpl as selection for page editors if that is needed (not yet the case). Most of the time it seems that default.inc works 90% of the time.

  • Like 3
Link to comment
Share on other sites

Great to see and know we kinda got same spirit here, you also seem to use similar approaches! Should have posted that earlier, I think it's pretty much needed this kind of threads sharing ideas. As with other parts of PW that still seem to be undiscovered for some people. (was surprised some people here didn't knew there were a tabfield they could use). Not that I think is a problem, but maybe we should start getting/pointing those things out more, even if it's just in threads like this.

Back to the template thing.

I also use main.php with basic html frame with head and body, and maybe a div wrapper, and the page template includes some structure that is different from page to page layout, though still it's enough flexible to extend and based on variables or template do different things like sidebars.

I'm also trying out to use it the other way round much like your's Ryan. Also having structure and data more separate can help a lot. While I'm still using the echo "<p>$somevar</p>" quite a lot now in PW, I think it's not really the best way to build. Mixing php and html can lead to messy code and cluttered, noisy, harder to maintain. I'm strictly going down that road now again and still thinking about different approaches. A template parser would be handy but can create some limits at best and it's slower at the end of the day. - The other day I was trying to code one simple module to use for blog/news type listing, just for fun. Mainly to try out coding a tag parser, and ended up with the plain php and the template parser approach right in front of me, and the knowledge creating this yet simple but hard to still programm little module. I came to the conclusion, that I still prefer pure php since it can do the same with a little more code but be alot more flexible to me. – But still I'm also thinking it could simplify some tasks, and maybe still be useful to the less experiences php newcomers.

It would be easy possible within PW to create markup-generating type of modules. Not sure If that should be done, as It's a little against PW's philosophie. But I really like pre-created profiles! As mentioned also in another thread, I think this could get possibly a big thing. I like the idea of having ready to go installs, and you Ryan already created the tool to export site profiles. Best part of it It can help some newcomer peoples so much, I think this should be considered quite important thing we should ignite as soon as possible. Well not trying to push things. I'm sometimes really impatient when something new excites me. :D

  • Like 1
Link to comment
Share on other sites

As with other parts of PW that still seem to be undiscovered for some people. (was surprised some people here didn't knew there were a tabfield they could use). Not that I think is a problem, but maybe we should start getting/pointing those things out more, even if it's just in threads like this.

I agree, though also think it's a delicate balance. I'd like people to perceive ProcessWire as something like Google (in terms of simplicity) – they can start using it without really having to know anything. They can discover the depth of it as their needs grow. If the depth is out in front, they might be overwhelmed. So I like to keep lots of surface simplicity and let PW grow with them.

I'm also trying out to use it the other way round much like your's Ryan. Also having structure and data more separate can help a lot. While I'm still using the echo "<p>$somevar</p>" quite a lot now in PW, I think it's not really the best way to build. Mixing php and html can lead to messy code and cluttered, noisy, harder to maintain.

Most of what you use PHP for in a template is no different than how you would use some other template engine, except in syntax.  So something like: <p>$somevar</p> is probably not a good example because there is no logic there, it's just pure output generation. You have to connect your variable to the markup at some point, and it doesn't get any simpler than that. I do think the point is valid when you get down into code that isn't geared towards output generation. But the nature of PW's API is that all the data is put at your fingertips so your template code can focus on output generation. The logic (or lack of it) that one uses in PHP at this level isn't really any different than that provided by something like Smarty, EE or another template engine. What is different is that PW puts all of the site's data at close reach, rather than some specific subset of it. That kind of resembles working in Javascript with the DOM. As you start working at a larger scale, using some discipline in how you do things can help you in the long run. I'm not talking so much about whether something is in a <p> or <li> tag, but about stuff that would create major obstacles if you wanted to completely re-skin your site tomorrow.

An example of something that does go beyond output generation a little is a search engine. This is one of the reasons why I like the approaches we've been talking about, because it does a good job of isolating logic where it matters. This particular example doesn't have any real logic of the sort I'm thinking of, but pretend for a moment that it was a much bigger/more complex search engine:

search.php

<?php
$q = $sanitizer->selectorValue($input->post->q);
$results = $pages->find("title|body|sidebar*=$q");  
$page->body .= $results->render();
include("./main.php"); 

Or using something like your guys method, where a separate view file generates output separately:

search.php

<?php
$q = $sanitizer->selectorValue($input->post->q);
$page->searchResults = $pages->find("title|body|sidebar*=$q");  
/* then the output file /includes/search.inc knows to output results from $page->searchResults */
include("./main.php"); 

Another thing I've found that works well is to put reusable output generation in modules and attach it to PageArrays, not unlike $results->render(). For example, on those villa sites, I have a PageArray::renderVillas hook that can be called anywhere in the site and it always renders villas the same way, provides a sort select at the top, and knows to handle pagination. Likewise, I added a Pages::villas() hook that only finds villas and automatically plugins in the proper limit=n for consistent site-wide pagination. So if I needed to re-skin the site tomorrow, I could update all the site's villa lists in one shot (and there are hundreds of them).

As time goes on, I think it'll be good for us to document best practices for large scale use. Like with PHP itself, I'm glad PW doesn't force us into one way of doing things. But I think it'll be helpful to lots of others if we outline what we've found helps to maximize scalability, longevity, etc. At the same time, when it comes to teaching people how to use ProcessWire with examples, I think some of these best practices can confuse the message. I wouldn't suggest that someone learning ProcessWire should use it in pure MVC mode until that is appropriate for their scale and need. It's only when you go bigger and more complex that your template code starts to become something more than a 'view', and it's at that point where  you need to start considering the long term.

A template parser would be handy but can create some limits at best and it's slower at the end of the day.

I think this is only worthwhile if you are dealing with some other group of people that is creating the output files, and that group refuses to get anywhere near PHP. Ultimately they just need to be told that there's no difference between <?=$somevar?> and {{$somevar}} except for a lot of unnecessary overhead for the {{$somevar}} version. More here: http://processwire.com/api/why-php-syntax/ :)

It would be easy possible within PW to create markup-generating type of modules. Not sure If that should be done, as It's a little against PW's philosophie.

Markup generating modules are fine I think, but I just don't want PW's core to generate any markup. I figure it should always be markup neutral. But markup generating modules can be very handy. Though my preference is for markup generating modules to provide options for customizing that markup.

But I really like pre-created profiles! As mentioned also in another thread, I think this could get possibly a big thing.

Me too. Though when providing these as something beyond a basic example, I think it invites an audience that is interested in turn-key solutions and not web development. Such things require good support. Thats why I think there is a good fit for paid solutions in this (like Maruchan brought up).

  • Like 3
Link to comment
Share on other sites

  • 1 month later...
  • 1 month later...

Great thread! I am still working out the best approach for my own site and you guys have definitely helped. I'm sure that I'll end up with a mixed approach.

Thanks again!

Link to comment
Share on other sites

I've only just come across this thread. This is a really good discussion with loads of good information - I wish I had seen this before.

My next PW site will probably be doing away with head.php and foot.php (though I do agree that this is an easier approach for beginners) for a preferred method above.

Are there any performance advantages with any particular approach over using head.php and foot.php?

Link to comment
Share on other sites

Are there any performance advantages with any particular approach over using head.php and foot.php?

I don't think so. Advantages are in how to keep code in large sites easily maintainable.

  • Like 1
Link to comment
Share on other sites

I don't think so. Advantages are in how to keep code in large sites easily maintainable.

Okay, thanks. I always wondered whether including head.php and foot.php was more inefficient in any way due to the extra include.

By the way - welcome to the ProcessWire forums Zach.

Link to comment
Share on other sites

Thanks for all suggestions. Following the Soma's template, I have created a master layout template (functionally identical with the default PW template), that looks like this:

<!DOCTYPE html>
<html lang="en">
<head>
<? include "blocks/htmlhead.inc" ?>
</head>
<body>

<p id='bgtitle'><?= $page->rootParent->title ?></p>

<div id="masthead" class="masthead">
	<div class="container">
		<? include "blocks/masthead.inc" ?>
	</div><!--/container-->
</div>

<div id="content" class="content">
	<div class="container">

		<div id="sidebar">
			<? include "blocks/sidebar.inc" ?>
		</div><!--/sidebar-->

		<div id="bodycopy">
			<? include "views/{$page->template}.inc" ?>
		</div><!--/bodycopy-->

	</div><!--/container-->
</div><!--/content-->

<div id="footer" class="footer">
	<div class="container">
		<? include "blocks/footer.inc" ?>
	</div><!--/container-->
</div><!--/footer-->

<? include "blocks/editpage.inc" ?>

</body>
</html>
  • Like 2
Link to comment
Share on other sites

Robert, I noticed that you used shorthand syntax on only one place of your template

<?= $page->rootParent->title; ?>

I would change that to the complete syntax, just in case the server doesn't support the previous.

Or, since in PHP 5.4, the shorthand syntax will always be activated, I would change all the others to

<? include "blocks/htmlhead.inc"; ?>
Link to comment
Share on other sites

Ya, I know!

But if the server doesn't support <?, it won't support <?= neither...

So, what I'm saying is: if you are going to use <?= in one statement, why not use <? in all the others also?

Or, if you are concerned that the server doesn't support them, don't use neither.

EDIT: Or am I wrong here? I thought they where both shorthands :)

Link to comment
Share on other sites

I love that PHP 5.4 makes short tags always on. But it's going to be a long time before the rest of the world is using PHP 5.4, so for anything "distributable" we probably still have to use traditional <?php tags for a couple more years. If I have control over the environment, I usually use the <?= short tag, but not the <? one. I have no idea why, perhaps I should.

Link to comment
Share on other sites

  • 4 months 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

  • Like 1
Link to comment
Share on other sites

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?

I'm not sure I understand right, but I would put them in main.php or include them like you would when using a head.inc.

Link to comment
Share on other sites

I'm not sure I understand right, but I would put them in main.php or include them like you would when using a head.inc.

My question is how you deal with functions that are potentially needed in every single page on the site. Putting them into main.php doesn't work in this example, because those functions are needed in teaser.php, which is called before main.php. One way would of course be to put include('./functions.php') into every template but I was wondering if there's a way to specify a file that is automatically included into every template.

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...