fruid 25 Posted February 8 I have different types of content throughout my site that I want to display on one page. For each type of content I have different markup. I'm trying to create a very generic template so that it is easily expandable, maybe someone can help me with ideas how to approach this. Right now I loop through the items and have one function with dozens of if/elseif/else-conditions. Depending on each item's template it renders different markup. Not very pretty. Another idea would be to create individual functions for each different markup and, depending on the template, call the according function. I was thinking I could given the function the same name as the template and in case I add new content with a new template I would only need to add a function with that template's name. So in order to make this work I would need to store each function in a variable with the template's name and then do something like… $sometemplate = $item->template; $somefunction = $sometemplate; call_user_func($somefunction, $item); right? I wonder if there's a even smarter way, what's ProcessWire's approach to that? Share this post Link to post Share on other sites
psy 815 Posted February 8 It's a premium module but I believe Repeater Matrix will do exactly what you need. It's one of my 'must haves' on every site I create. It's part of the ProFields pack and I use other features on every site too, like Functional Fields . I'll be using the new Combo field as well. The time it saves me is well worth the investment not to mention the great support in the forum from other ProField users & Ryan himself. https://processwire.com/store/pro-fields/ 1 Share this post Link to post Share on other sites
LMD 53 Posted February 8 Without needing to buy a Pro module (although they are excellent!), there is indeed a ProcessWire way for this. You can use $files->render()/wireRenderFile() to achieve what you want:https://processwire.com/api/ref/wire-file-tools/render/https://processwire.com/api/ref/functions/wire-render-file/ The advantage of using this over your own functions is that it will automatically keep access to all of PW's API variables (no need to pass them in). Here is a simple example based on your use-case description: /** * In your generic template file. * Note: it assumes there is a directory under 'templates' called 'layouts' (it could be called anything). * Just create your layouts in the 'layouts' dir and name them the same as the '$item->template' names you are using. */ // Get the content of the specified layout into a variable. // This example looks for '/site/templates/layouts/name-of-template.php' (the .php extension is assumed) $pageContent = $file->render('layouts/' . $item->template); // Just echo it out wherever you need it. echo $pageContent; Read the docs on it, there is more you can do with this method/function, including passing custom variables, and some other options to make it more flexible. There is also a $files->include() method which is the same except it directly echos out the content instead of returning it to a variable. 5 Share this post Link to post Share on other sites
Zeka 970 Posted February 8 There is also additional options for field rendering https://processwire.com/blog/posts/more-repeaters-repeater-matrix-and-new-field-rendering/ https://processwire.com/blog/posts/processwire-3.0.7-expands-field-rendering-page-path-history-and-more/#field-rendering-with-template-files 1 Share this post Link to post Share on other sites
MoritzLost 819 Posted February 8 I'll just leave this here: How to set up Twig as a flexible view layer for ProcessWire and/or Create flexible content modules using Repeater Matrix fields The feature you're looking for is template inheritance – the ability to have a base template which is extended by child templates. The child templates can then override any block they want to. If you go down the path of wireRenderFile or MarkupRegions you'll always notice you're fundamentally lacking the ability to overwrite blocks in a parent template. With pure PHP templates, you'll always end up with a system that either needs to import a bunch of partials in every new template, just for the ability to leave out or change some of them for one particular template. Or your base template will need to be aware of every possible permutation and arrangement of partials and mixins in all your templates, resulting in messy and unmaintainable code which grows linearly with every template you add. Using a template system with template inheritance solves that for you and makes your template system infinitely scalable (besides other benefits like autoescaping and better readability). 2 Share this post Link to post Share on other sites
fruid 25 Posted February 16 hm… maybe I didn't make myself too clear. So let's say I have an PageArray $items that should get a specific markup aka layout. Let's say the markup is something like… <li> <h3><?=$item->title?></h3> <p><strong>price: </strong><span><?=$item->price?></span> <p><strong>category: </strong><span><?=$item->category?></span> <p><?=$item->body?></p> </li> would I loop through the $items? $filename = "somelayout.php"; foreach ($items as $item) : $out .= $item->render($filename); endforeach; echo $out; or just do: $out = $items->render($filename); echo $out; Don't know why I ask this because both doesn't work but maybe this gives you an idea of my thinking. Share this post Link to post Share on other sites
Jan Romero 459 Posted February 16 In somelayout.php the current Page is in the $page variable! It doesn’t know $item. Otherwise your loop should work. PageArray doesn’t have a render method, although I guess you could do $items->each(fn($item) => echo $item->render('somelayout.php')); (or something like that) 1 Share this post Link to post Share on other sites
fruid 25 Posted February 16 @Jan Romero so it doesn't work with PageArrays or WireArrays? just with regular php arrays? that's unfortunate… your code returns syntax error, unexpected '=>' (T_DOUBLE_ARROW), expecting ')' Share this post Link to post Share on other sites
Jan Romero 459 Posted February 16 Actually, I was wrong, PageArray does have a render method, but it comes through the default module MarkupPageArray. Not sure what you mean about the arrays? My code was supposed to use an arrow function. Maybe this works for you: $out .= $items->each(function($item) => return $item->render('somelayout.php')); But really, just do this foreach ($items as $item) $out .= $item->render('somelayout.php') and make sure you use $page instead of $item in somelayout.php. Share this post Link to post Share on other sites
fruid 25 Posted February 16 // somelayout.php echo $page->title; // somewhere else $items = $pages("template=posts, limit=10"); $filename = 'somelayout.php'; // inside /site/template/ $out = ''; $out .= $items->renderPageArray($filename); // module is installed echo $out; // result: nothing whatsoever. Share this post Link to post Share on other sites
Jan Romero 459 Posted February 16 1 hour ago, fruid said: $out .= $items->renderPageArray($filename); // module is installed Dude, at what point did I tell you to try that 😅 If you want to use that module, you need to familiarize yourself with it. Unfortunately there doesn’t seem to be any documentation apart from what’s in the code itself. What it does is add a method called render() to the PageArray class. Internally that calls MarkupPageArray::renderPageArray(), but you still need to write $items->render(). Now you need to figure out what arguments to pass. The information is in the code I linked. It takes an associative array of options, none of which are an actual template file like what you want to do. You could do something like this if you really wanted to: $items->render(array( 'listMarkup' => '<ul>{out}</ul>', 'itemMarkup' => "<li> <h3>{title}</h3> <p><strong>price: </strong><span>{price}</span> <p><strong>category: </strong><span>{category}</span> <p>{body}</p> </li>" )); But really, just use the foreach. Sorry for posting more confusing alternatives than anything. Do the foreach, it’s exactly what you want. You can write it in a single line if it feels cleaner. I haven’t tested any of this, I’m just writing it into the comment box. 1 Share this post Link to post Share on other sites
fruid 25 Posted February 16 I tried with foreach ($items as $item) $out .= $item->render('somelayout.php') as well, to no effect. Correct me if I'm wrong. This the one that uses a simple array. So I turned to MarkupPageArray, which is the name of the module, ->renderPageArray() is its method that works for PageArrays (as the module name claims). None of the above work for me but completely mess up my layout. Just use foreach like you propose is not as simple as it sounds and the workaround that is needed to make the method that is build around normal php arrays work for wire arrays as well defeats the purpose of why I'm even here. I have a working solution with dozens of ifs and elses, just thought I could find a more elegant, scalable and straightforward solution instead. Share this post Link to post Share on other sites
Jan Romero 459 Posted February 16 Right, so coming back to your original post, the items you want to render are ProcessWire Pages and they’re all in one PageArray, right? Now you want to render all of them but dynamically pick a markup file based on each item’s template. You can do this similar to the way LMD suggested, by creating a file for each template and naming it accordingly. foreach ($items as $item) { $out .= $item->render($item->template->name . '_listitem.php'); } echo $out; I mean, this is basically the same as what you originally proposed, only using template files instead of functions, but the call_user_func way should work, too. If your items are only ever viewed this way, you could even just use render() without any arguments. Can you tell us more about the specific logic that goes into rendering each item? Share this post Link to post Share on other sites
fruid 25 Posted February 16 I know it looks and sounds simple but it doesn't work. As soon as I echo what I ->render(); the entire page breaks. It returns the entire page inside the <content> tag and inside that one's <content> tag it renders the entire page and so on. I'm using markup regions, I guess that's an issue… Share this post Link to post Share on other sites
Jan Romero 459 Posted February 17 11 hours ago, fruid said: I'm using markup regions, I guess that's an issue… Good point. The render method will render the page according to all the usual settings, so for this use case you’ll need to disable any prepend/append files. If you have template caching enabled, you need to turn that off, too. You can call render with options like so: foreach ($items as $item) { $out .= $item->render($item->template->name . '_listitem.php', ['prependFile' => null, 'appendFile' => null, 'allowCache' => false]); } echo $out; 1 Share this post Link to post Share on other sites
fruid 25 Posted February 17 now it works, thanks @Jan Romero May I ask how you know that? Share this post Link to post Share on other sites
Jan Romero 459 Posted February 17 Excellent question, because the documentation on it is a little stupid. The information is here: https://processwire.com/api/ref/page-render/render-page/. Although if you really want to see what’s going on, you can look at the code directly: https://github.com/processwire/processwire/blob/master/wire/modules/PageRender.module#L399. One would expect this to show up in the documentation for the Page class, but there isn’t even a link… The reason it’s a little convoluted is that the render method is added by a Module as a Hook, so I guess it doesn’t technically belong to the Page class. Maybe it’s not really supposed to be used that way. You wouldn’t have all that trouble with append files and cache using LMD’s method. However, you would need to pass your $item to it somehow (for example $files->render('yourfile.php', ['item' => $item]);). 1 Share this post Link to post Share on other sites
fruid 25 Posted February 17 actually the following would help me a lot if it worked, don't know why I didn't consider it, it's simple enough as it is. https://www.w3schools.com/php/php_callback_functions.asp But weirdly, if I use the code of the last example (user defined function) in PW, I get an error: Call to undefined function exclaim() Share this post Link to post Share on other sites
Jan Romero 459 Posted February 17 You’re in ProcessWire’s namespace, so if you paste the code you linked, those functions will actually be ProcessWire\exclaim() and ProcessWire\ask(). You can call them like this: printFormatted("Hello world", 'Processwire\exclaim'); printFormatted("Hello world", __NAMESPACE__.'\ask'); Or alternatively, you can add the namespace in printFormatted: function printFormatted($str, $format) { // Calling the $format callback function echo (__NAMESPACE__ . '\\' . $format)($str); } Or you could declare exclaim() and ask() differently. For example by putting them in a class or in an include file with a different/global namespace. 1 Share this post Link to post Share on other sites
fruid 25 Posted February 17 thanks @Jan Romero works like a charm. I wonder if that is best practice to jump in and out of namespaces, but I guess it doesn't matter. Also curious why that namespace declaration is necessary, cause I have created my own functions before, no problem. And, again, wondering how you know this and how I could know something like this without having to ask. Share this post Link to post Share on other sites
Jan Romero 459 Posted February 17 You’re not jumping in an out of namespaces in that sense (you could do that if you wanted to get really freaky). As long as you’re in the ProcessWire namespace, any function you declare belongs to that namespace. Since you’re inside the namespace anyway, you can call them without specifying ProcessWire\myFunction(), but apparently PHP’s dynamic features like call_user_func() need you to be more specific. I don’t know this per se, it’s a combination of googling and talking out of my bum bum, lol, so I’m probably getting a good amount of technicalities wrong. Here’s a link to the PHP docs about it https://www.php.net/manual/en/language.namespaces.dynamic.php. 1 Share this post Link to post Share on other sites