Jump to content

Templates, MVC, recursion, etc.


SteveB
 Share

Recommended Posts

I'm new here so have been reading a lot of forum posts and doing small experiments. This is about figuring out flexible ways of working with the PW's ways of delivering content. Sometimes having the PW template file echo out stuff from $page is just right but I wanted to figure out an approach that starts with that and adds complexity as needed.

So, here I'll show you the parts of my first working test with MVC, recursive rendering of child pages and a template engine.

The PW template file is used as a simple controller to delegate rendering to a view.

//By default we'll use PW's template as the view name.
$name = $page->template->name;
//Load the view file
require_once '../iop/views/'.$name.'.php';
//Create the view object
$view = new $name($options);	//<-- options passed to template are passed to view
//Hook it into the PW template object
$this->addHook('Page::renderView', $view, 'renderView');
//After page does "renderView" output will be in $view->output
$page->renderView();
//spew it out
echo $view->output;

Learned the hook from http://processwire.com/talk/topic/1510-templatedisplay-mode/

The view file:

/*
this is a view file:
$options passed to template are passed to view
$options['params'] would be parameters to use in rendering, such as a different template for rendering as child to another page
*/

require_once $config->paths->root.'/supp/StampTE.php';

class sb3d_mvc_test {
	
	var $templateName = null;
	var $output = null;
	var $kidOutput = null;
	var $options = null;
	var $depth = null;
	
	public function __construct($options = null){
		$this->options = $options;
		$this->templateName = get_class($this);	//default is to use same name for all the major parts. No extension.
		$this->depth = count($this->options['pageStack']);
	}

	//Prepares data for the template
	public function prepare($page){
		$msg = 'The '.$page->name.' page.';
		if(!empty($this->depth)) $msg .= ' The pageStack count = '.$this->depth;
		return array('msg'=>$msg);
	}

	//Renders child pages
	public function renderKids($page, $params){
		$kids = $page->children;
		if(!empty($kids)){
			$this->kidOutput = array();
			foreach($kids as $kid){
				$this->kidOutput[] = $kid->render(array('params' => $params));
			}
		}
	}
	
	//This view makes use of the StampTE template engine and makes some content from child pages
	public function renderView(HookEvent $event) {
		$page = $event->object;
		$data = $this->prepare($page);

		//Limit how far down rendering goes and set parameters for child page. Maybe use callbacks?
		if($this->depth==0){	//not rendering as a child page
			$raw = file_get_contents('../iop/templates/'.$this->templateName.'.html');
			$params = array('renderMode'=>'Sub');	//child pages use this as template name suffix 
			$this->renderKids($page, $params);
		}
		else{	//rendering as a child page
			$raw = file_get_contents('../iop/templates/'.$this->templateName.$this->options['params']['renderMode'].'.html');
		}

		//Now we play with the template engine. This one's interesting in the way it does cut and paste within a template 
		//	We could instead leave stuff in $this->output and let the PW template (our controller) build output with PHP/HTML
		$t = new StampTE($raw);
		$t->inject('title', $page->title);	
		$t->inject('msg', $data['msg']);
		if(!empty($this->kidOutput)){
			$kidLayout = $t->get('kid');	//extract little subtemplate ('cut') to use for each message
			foreach($this->kidOutput as $stuff){
				$b = $kidLayout->copy(); 	//each message is put in a copy of subtemplate
				$b->injectRaw('kidstuff',$stuff);
				$t->selfkid->add($b);	//completed subtemplate is pasted into master template
			}
		}
		$this->output = $t->getString();
	}
}//class

In this case we use StampTE to render the page with the sb3d_mvc_test.html and render the child pages with sb3d_mvc_testSub.html. It's setup to only render child pages from the top level but we could recurse more deeply (not tested) if there was a need to. More could be done. So far so good.

The render hook and the $options used by $page->render are key parts.

Lot's more to say/ask about PW but I'll dribble it out in future posts. Thanks.

  • Like 2
Link to comment
Share on other sites

Up until now i've never had the need to go beyond simply echo'ing out stuff directly on my template files but it's nice to see people experimenting with other approaches, although it's a bit over my head and i don't quite get this StampTE stuff (looking at https://github.com/gabordemooij/stamp didn't help much but that's just me).

Link to comment
Share on other sites

I agree, good to see experiments like this. Though without knowing exactly what the result is, it also looks like a lot of work and code. Which is to say, I don't totally understand what I'm looking at just yet, but am interested–please keep posting. For another MVC-style approach, also check out the blog profile

Link to comment
Share on other sites

It looks like overkill because it's just a "Hello world" kind of thing to make sure all the parts connect. That was an excercise in synthesizing a bunch of threads about pages and templates and rendering and adding a bit of my own baggage to prove to myself that I could start with the basic PW template file (PHP/HTML) and progessively add bells and whistles without losing any standard functionality.

My clients often ask for some simple thing which may stay simple or may grow. After feature creep an MVC approach becomes more appealing. The question of whether to use a template engine (and which one) depends on the situation. I use different ones (or none) for different reasons. In the test, the view uses a template engine but nothing outside of the view has any knowlege of that. Similarly nothing outside of the PW template file is aware that MVC is being used. The test had rendering of subpages only to demonstrate that those additions don't break normal PW trickery. Have your cake and eat it too. 

So, that's sufficient for getting started. Now I'm mulling over issues around categories and the admin page tree vs site structure vs information structure. I'll be working on catalogs and e-commerce which I've done before and consequently have opinions about and favored ways of doing things. I'm happy to see the Shop-for-ProcessWire work-in-progress but my first impressions (that's two disclaimers if you're counting) are that it leans a little towards depending on having anticipated everything. I think I'll go in a different direction for some things and try for compatibility with it in other areas.

Ryan, I'll check blog profile. Glad I found PW. Your code is a pleasure to read and the API-centric approach is a huge plus. Great ore in the forum too. It might be good to put a sticky thread in Getting Started about certain common nouns which are special in the PW context (Page, Template, Field...). There are plenty of good posts to draw from such as this one on pages. Thanks.

  • Like 1
Link to comment
Share on other sites

  • 1 month later...

Several weeks later...

I have a Version Two of this which I like better. It's using the hook and module approach in Apesia's MarkupRenderHelper.

The scenario I have is a bunch of pages in a tree, each of which uses a selector to select certain "item" pages which are all lurking under a single parent elsewhere in the tree. Any page can show any items. When rendering a page we recurse one level down to make little displays from the child pages, and we find the selected items and render little boxes for those too. Thus we have items displayed and informative navigation for drilling down.

For anything more than very basic output I like to use a template engine (as opposed to straight PHP/HTML). I used Smarty for a long time, but now I tend to use a three part setup with a simple cut/paste/search/replace template engine in the middle. Driving it is a PHP method with just ifs and foreaches and lines of code to work the template engine. The third piece is the HTML template files. Many of the day to day "little changes" clients ask for get isolated in those places which keeps the other code clean.

I'm using 2 modules. One is for configurable settings and some utilities. The renderer is in the other one, which is autoloaded (conditional autoload by template name soon). The PW template file gets an instance of the settings module, puts that in the $options array, and passes that to the renderer.

The renderer starts with a "prep" method, one for each kind of page, to get data ready for the layout and stash it in arrays. Some of that is just grabbing fields but there's navigation and comments to get ready, etc. If that preparation resulted in lists of subpages and items to render, another method does that and finally a layout method works the template system. We pass a few things back and forth through the Page objects. That's how we pass paramters to influence the secondary rendering and then recover the rendered oputput.

The filenames of (non-PW) templates are based on the PW template name. If we are recursing, the depth is added to that (we stop at 1 so that makes 4 different files). Finally, the pages and items have a "style" field which lets you specify an alternate set of these templates to use just for certain ones. That may be decided automatically too.

The gift of Blog Profile:

This is reaching the point where I show an early demo to a few people to fish for comments before I get too far along. What better way than to drop it into a Blog? So, I took the Blog Profile pretty much as is, put a fixed piece of content on the home page and messed with the commenting so it's off until you login. When you are logged in you only see comments that you or the admin posted. That makes it more of a one on one demo. If something comes up which needs to be shared I can write a blog postabout it. Eventually we end up with something like documentation. I'll let you know how it goes.

  • Like 2
Link to comment
Share on other sites

Thanks for the followup and details on how this is going SteveB. Glad to hear it's going well and sounds like you are having fun with it too. You mentioned using a template engine–which are you using, or are you using the TemplateFile class? Glad to hear you like (or will like) the new conditional autoload features. I think that passing your own variables through the $page object is a fine way to go. But wanted to mention that the $page->render() method now accepts an array of variables you want to pass to the template file that gets called, and it can be accessed in the template file via the name $options. Not sure if this is useful in your case or not, but wanted to mention it. 

Link to comment
Share on other sites

I use $options to pass settings TO the renderer (some of this is recursive) and I suppose I could pass things back (&$options?) but since the action of rendering is more about the Page it seems intuitive to pass things through the Page, using distinct names.

The render engine I'm using at the moment (not a final decision) is StampTE. If I change my mind about which one to use only one method in my class is affected (the templates too of course).

Right now I'm wondering how best to implement something with PageArrays where the pages in the array just need to be able to find each other. Unlike a tag, we aren't accesing it by name. The pages it connects wouldn't know what name to use. I'm calling this a "ring" and the idea is that when any one of the items in the ring is selected to be on a page it will be displayed with little subrenders (maybe just links) for other items in that ring. It's kind of like those "You might also like" suggestions you see on shopping sites. I know how to find whether a Page is in a PageArray but how do I find which of several PageArrays contain a given page?

Link to comment
Share on other sites

I use $options to pass settings TO the renderer (some of this is recursive) and I suppose I could pass things back (&$options?) but since the action of rendering is more about the Page it seems intuitive to pass things through the Page, using distinct names.

You can't currently pass things back through $options, so $page would definitely be the way way to go there. I actually take the same approach as you, passing things through $page. That's in part because most of the times I've done it were before $options was around, but also because it's simple, unambiguous and easy to remember how it works. 

Right now I'm wondering how best to implement something with PageArrays where the pages in the array just need to be able to find each other. Unlike a tag, we aren't accesing it by name. The pages it connects wouldn't know what name to use. I'm calling this a "ring" and the idea is that when any one of the items in the ring is selected to be on a page it will be displayed with little subrenders (maybe just links) for other items in that ring. It's kind of like those "You might also like" suggestions you see on shopping sites. I know how to find whether a Page is in a PageArray but how do I find which of several PageArrays contain a given page?

I'm not sure I understand all the details of the scenario you are talking about. But if you've got several PageArrays in memory and you want to determine which one contains a given page, I would think you'd just add a loop to the mix?

$pageArrays = array($somePageArray1, $somePageArray2, $somePageArray3); 

foreach($pageArrays as $pa) {
  if($pa->has($somePage)) {
    // you found it
  }
} 
Link to comment
Share on other sites

I'm not sure I understand all the details of the scenario you are talking about.

It was a off-topic of me to bring it up in the first place but I was thinking of some kind of anonymous tag or category, I just wanted the member pages of the group to be able to find each other. I've put that on hold and am doing it in a non-reciprocal way with each page having a Multiple Page Reference Field it can use to list pages it should link to. If I want that to be a reciprocal arrangement I imagine some code hooked to page saving could notice that A points to B and then make B point to A.

The use/case for anonymous group is:

Group some pages, p1, p2

Group some other pages, p6, p7, p8

Render page p8, and it can show links to the other pages in its group, p6 and p7.

Likewise the pages in the other group link to each other.

The groups should work without a lot of fuss over what they are called. I suppose it could be done like tags but they don't need titles so having pages for them seems like overkill.

The idea about hooking additional behavior onto the way I'm doing it now seems more appealing in terms of administration etc.

  • Like 1
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
 Share

  • Recently Browsing   0 members

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