Jump to content
marco

Template Data Providers

Recommended Posts

Hi all,
 
I just released my first module on github called Template Data Providers.
 
This module lets you create simple data provider classes for templates and chunks (a.k.a. partials, blocks, includes, ...) to gather and prepare data for templates and/or handling form data and other actions outside of the templates ("separation of concerns").
 
But even if you've installed the module the usage of the new functionality is merely optional. You may define your simple templates and chunks as usuals, adding custom classes (PageDataProvider for pages/templates, ChunkDataProvider for chunks) for more complex data handling on demand.
 
I provided detailed instructions in the README.md and encourage you to read it for further information.

Just some sample code here:

Defining a data provider for a home template:

class HomePage extends \nw\DataProviders\PageDataProvider {
    
    public function populate() {

        $this->foo = 'bar';         // definevariable $foo to use within the page's template
        $this->page->foo = 'baz';   // provides page member $page->foo to use within the page's template
    }
}

Calling a chunk from within a template:

$page->renderChunk('path/to/primary-navigation.php'); // relative to site/templates/

Calling a chunk providing contextual data:

$news = $pages->get('template=news');
foreach ($news as $newsItem) {
    $page->renderChunk('path/to/news-item.php', $newsItem); 
    // additional arguments provided will be avaiable within the chunk in a closed scope
}

Defining an example chunk data provider:

class ExampleChunk extends \nw\DataProviders\ChunkDataProvider {
   
    public function setContext(array $context = array()) {

        // store first context argument in $this->foo
        $this->foo = isset($context[0]) ? $context[0] : null;

        // store second context argument in $this->bar if instance of \Page
        $this->bar = null;
        if (isset($context[1]) && $context[1] instanceof \Page) {
            $this->bar = $context[1];
        }
    }
    
    public function populate() {

        $this->foo = 'bar'; // provides variable $foo to use within the chunk
    }
}

Please leave your questions, remarks, error reports here in the forum or at github and I'll try to answer as soon as possible.

This module is a preparation for another module coming soon. This will be an alternative Twig template engine module that will interact with Template Data Providers or could be used as stonde-alone module.

Regards from Hanover/Germany,

Marco

  • Like 3

Share this post


Link to post
Share on other sites

Please answer to this post instead.

Edited by diogo
removed the link to the other post because it was deleted

Share this post


Link to post
Share on other sites

The post seems to have been deleted. I will restore it.

edit: even better, the post was removed because the content was exactly the same. I moved this one to the forum "modules/plugins" instead of restoring the other.

Marco I edited your post to remove the link.

Share this post


Link to post
Share on other sites

Hi marco,

unfortunately the example for a home page template, mentioned above, doesn't work in my case.

I installed the module, left the default setting dataproviders, created this very folder in site/templates/ (checked its permissions), placed a HomePage.php with your example code in it, opened home.php and tried to echo $foo. With no result. Am I misunderstanding the concept, or did I forget a neccessary step to make this work?

Thanks in advance!

marcus

PS: PW 2.4, Module version 1.0.2

  • Like 1

Share this post


Link to post
Share on other sites

Hey Marcus,

as Marco is currently not available i'll try to answer your questions. Normally what you did should be correct.

Your structure should now look like this:

templates

--- | dataproviders

--- | ---HomePage.php

home.php

If this is the case you can try to debug if the dataprovider is loaded correctly. If you just place a "die('test')" inside it's populate method this should be the only output that is displayed on the homepage as the populat method is executed before rendering the actual template.

If the template renders correctly instead of just displaying the text "test" there is a problem with loading the dataprovider. This (from my experience) is mostly the case if either the classname of the dataprovider is misspelled or (as stated above) there is a problem with a case sensitive filename.

If this doesn't help just contact me here or via skype and we'll debug your problem together. 

Best Regards

Felix

Share this post


Link to post
Share on other sites

Hi Felix,

greetings to Hannover and thanks for your answer. die(test) works and proves the dataprovider is loaded at all. But the value of $foo still isn't provided to home. See my code below in both files:

home.php: 

<?php 

/**
 * Home template
 *
 */

echo "This should be the value of foo: " . $foo;

HomePage.php:

<?php

class HomePage extends \nw\DataProviders\PageDataProvider {

	public function populate() {

		$this->foo = 'bar';
		$this->page->foo = 'baz';
		//die('test');
	} 
}

But the output is just "This should be the value of foo:", without any 'bar' added.

Overall, its just testing a different way to populate/architect my templates, so no show stopper at all - but thanks for the offer via Skype!

Marcus

Share this post


Link to post
Share on other sites

Hi marcus,

greetings to berlin! ;)

Well: This is sort of strange. Your examples seem to be absolutely correct.

To debug let me ask you some questions regarding your setup:

  • Which PHP Version are you using?
  • Do you have any modules installed that might conflict with TemplateDataProviders (something that installs hooks before rendering templates)?
  • Have you tried setting some different variables (i.e. clearing the "foo" example and setting something else)? 

Felix 

Share this post


Link to post
Share on other sites
  • The testing environment is an out-of-the-box MAMP 3 virtual host, meaning PHP 5.5.10 - but I could try to downgrade, since it will really take some time until this version hits my customers web hosters ;)
  • Just MarkupSimpleNavigation, ManageFiles, FieldtypeCropImage -  just installed, none of them initiated in any form - apart from that the installation is totally empty / unconfigured
  • Yeah, different variable name, integer instead of string, but same effect

edit: 5.4.25, 5.3.28 - no difference

Edited by marcus

Share this post


Link to post
Share on other sites

Hi Marcus,

PHP 5.5 shouldn't be a problem. I'll hack together a working site-profile that you can download and check against your code today. I'm pretty sure both of us are just missing/overseeing something important. We're using this module in pretty large projects and didn't find any bugs yet. :)

Share this post


Link to post
Share on other sites

I tested it with a fresh setup and got the same result you did. We're always using this module combined with TemplateTwigReplace so I wasn't aware that this happens when using it with "plain php" :). If you'd install TemplateTwigReplace and echo the variable using {{ bar }} everything would work as expected. But I'm sure not everyone likes using twig as much as we do. Also this shouldn't be a dependency. 

I'll ask Marco about this tomorrow and keep you updated. 

Share this post


Link to post
Share on other sites

Hey marcus. It seems like there really is an issue which (most propably) is caused by a changed behaviour when doing object overloading since the 2.4 update (at least that's what marco said - I'm not that much of a "PHP Pro" :) ).

We didn't noticed this yet due to the reasons already stated. There will presumably be a fix for this next week.

Share this post


Link to post
Share on other sites

Hi felix, thanks for the updates on the issue! I haven't found time in the last days to move any further in this project - which is just a learning installation anyway - but will look into TemplateTwigReplace on the weekend. I better be learning twig sooner or later, if not for PW then for Drupal 8 ;)

Share this post


Link to post
Share on other sites

For me this Module is the best approach for organizing logic and views without being too much in your face with restrictions.

I tested it yesterday, and like it very much and discovered the same problem as @marcus without knowing it has been posted here.

I created an issue: https://github.com/marcostoll/processwire-template-data-providers/issues/2

What's the status of this module? Will it be maintained further?

  • Like 1

Share this post


Link to post
Share on other sites

Hey owzim. I'm glad you like the module. We're still using it in all of our projects (but as I've already mentioned combined with TemplateTwigReplace) so it has to be maintained further even if it's only for our own projects.

I'll look into this issue and try to do my best in the next days. Sadly marco (who is a great software engineer - a lot better than me) isn't working for neuwaerts anymore but if I can't resolve this issue by myself I'm sure he'll help us fix it.

  • Like 3

Share this post


Link to post
Share on other sites

Actually beforePageRender doesn't change any output or replaces vars. It just adds data to the page object. So imo there should be no need to replace the default render method.

I tracked the problem down to the Core-File "PageRender.module" (line 305) where output = $page->output(true); gets called. $page->output(true) returns a new, uncached TemplateFile object which is filled with a fresh "Page" Object via $fuel = self::getAllFuel(). So if there would be a way to permanently store the variables set inside the hook (I've tried using setFuel('page',$page) but that didn't work) this would be fixed. Any Ideas?

Share this post


Link to post
Share on other sites

I don't but any of the PW hackers here should.

What irritated me, is that with the Twig replace module everything works as expected, for which a reason should exist, so I looked into the code, and not only is the Twig replace module hooking into PageRender::renderPage instead of Page::render (as suggested in my GitHub issue) but also the vars are explicitly collected and passed, see https://github.com/marcostoll/processwire-template-twig-replace/blob/master/TemplateTwigReplace.module#L216

$twigVars = $this->collectVariables($page->output);
$output = $this->getTwig()->render($page->template->name . '.' . wire('config')->templateExtension, $twigVars);

So perhaps the right path would be to actually replace the render method with the same hook as above and load the respective template file and the TemplateFile class. The downside of it (and I think it's the same with Twig replace module) that the render method must be held in sync with the core version (appending, and prepending files like _init.php and such, and also the new template specific append/prepend functionality since 2.5).

I am not too much into the topic so I might just be guessing here. Dear PW hackers come one, come all.

PS/OT: this forum should support MarkDown, post authoring/editing in here is a bloody mess. >:(

  • Like 2

Share this post


Link to post
Share on other sites

Hey owzim,

this is exactly the reason why i don't want to completely replace the render method (plus i honestly don't know what would happen if the method is replaced not only by one but several modules).

I'll start a thread asking if it's possible to make the variables persistent in the module development board.

  • Like 1

Share this post


Link to post
Share on other sites

I just revisited the issue and now I understand what you mean with the output thingy, found that too.
 
This makes it work, but setting data directly to the fuel leaves a bad taste in my mouth.

/**
 * Provides direct reference access to variables in $this->page->output
 *
 * Overwrites \WireData::__set
 *
 * @param string $key
 * @param mixed $value
 */
public function __set($key, $value) {
    $this->wire($key, $value);
    $this->page->output->$key = $value;
}

Just browsed through the core code to find info about the fuel, and found that in the docs of setFuel:
 

Fuel is an internal-only keyword. Unless static needed, use $this->wire($name, $value) instead.

The data sharing via output is great because all fuel data gets merged into the output, so a potential overwrite of fuel data is not possible.

setting data to the fuel directly entails the risk of overwriting stuff, which can lead to very unwanted behavior, e.g. modules that hook into after page render (or something similar) might get a messed up fuel.

Am I right? I might not be ... :rolleyes:

Sadly one cannot hook into that specific part of the page render process. Only via replacing the whole method via hook, which might lead to a crap fest as well :mellow:

Edit: I just tested it, you cannot set most of the fuel vars, like session, fields, or templates, they are locked and throw an Exception, great stuff.

You can however overwrite say, page or db (I guess they are supposed to be dynamic, because they are set by the system itself and change).

So the fuel way could work after all?

 

I created a pull request: https://github.com/marcostoll/processwire-template-data-providers/pull/6

Share this post


Link to post
Share on other sites

First of all: Thanks for your help!

I've just merged the pull request and altered some stuff. Basically I removed the part where you overwrite the fuel every time something is set. If it would have stayed like this you wouldn't have been able to overwrite fuel variables once they are set (no matter if they are proteced or not).

Now all "system" keys are stored during module initialization and the setter method checks against those. Furthermore I replaced the isset() check within the getter with is_null as isset always returned false.

  • Like 1

Share this post


Link to post
Share on other sites

Tything to get the processwire version 3 of the module working correctly.

Admin:
Data Providers Name Space :  "pwire\DataProviders "
Data Provider Base Path : "site/templates/dataproviders/"

site/template/home.php (template)
site/template/dataproviders/pwire/HomePageDataProvider.php (data provider)

namespace pwire;
class HomePage extends \nw\DataProviders\PageDataProvider {
    public function populate() {
		echo "you made it here "; die();
    }
}


Do I also need to any something to my composer file to get the class to autoload, or just put it in the correct directory?

Share this post


Link to post
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.

  • Similar Content

    • By CachePuzzles
      Hi there,
      I'm probably missing something simple, but I'm just setting up my first site so I guess that's bound to happen.
      I've installed the LoginRegistration module and at first I was thinking that the confirmation emails weren't being sent. However I now see that new user confirmation emails are taking 20+ minutes to arrive. Is there a way to speed this up?
      Also the email is coming from processwire@mydomain - I couldn't found a place to change this.
      Any advice or help would be great!
      Brian
    • By fruid
      Hi friends,
      just 7 months ago, I was "just" a designer and a rather semi-developer, still struggling with WordPress sites and more often than not delegating that work to pros. ProcessWire really helped me realising that it's not that hard after all (well it's still hard but it's doable) and you learn a lot just by doing it which has to be true for all developers anyway. So I started coding myself, I might also have to thank the 2020 pandemic to help me focus a bit more than usual, I created a bunch of websites using PW already – one of which I can't wait to put on showcases but it's still not live yet. That is one big personal milestone for me. The next milestone would be to give something back, and so I'd like to take on contributing some code I wrote, namely a event calendar for anyone to use. It's my first attempt doing this so please don't eat me alive. It's not a PW module yet (that's the plan however) just a git but I'm positive it will be useful once it works – well it works but it still needs more work – because if I had found a module that came close I wouldn't have started coding it myself :D
      With that, I will appreciate all input, be it how to improve the .js (probably especially), or the templates, or guidance how to proceed turning this into a PW-module, and of course, in that last process, no pun intended, suggestions to make it more versatile for different needs.
      git: github.com/bbblgmsp/ProcessEventCalendar
      demo: http://foobar.roofaccess.org/events/
      Thank you!
    • By Lmwt
      Hi there,
      I am new to PW but already very convinced. I need to build a dropdown button to browse in a collection of authors by country and language. The dropdown should be editable in the BACK and displayed on the same as the authors collection. I tried my luck with "PageField Creator" but I don't know how to display the dropdown, I simply used: 
      "$content .=$page->Land;" (the name of the select field)
      but nothing is displayed...
      I see there is a 2nd module out there to achieve this : "Select Options Fieldtype". I am not sure what is the difference, anyone has a tip?
      And a clear tutorial with the necessary steps to build this? I am a bit confused which should be the parent template or page, if it should have a file or not...
      Thanks a lot in advance!
    • By Noel Boss
      👋 PW Pros…
      I have some hooks that I need to bind at the init phase (or even __construct) and I was wondering, and I couldn't find a good and simple way to determine if I'm in the admin. Would be nice if there is a reliable short option to do so, but I can't seem to find one… Is there a coherent way to tell this no matter where I am?
      Right now, I use the following method inside one of my modules:
      public function isAdmin($page = null) { if ( strpos($this->input->url, $this->urls->admin) !== false || $this->process instanceof ProcessPageList || $this->process instanceof ProcessPageEdit || ($page instanceof Page && $page->rootParent->id == $this->config->adminRootPageID) ) { return true; } return false; } @ryan wouldn't it be nice to have something like wire()->isAdmin(); like wire()->user->isLoggedin(); to tell if we are in admin – very early on (probably even in __construct() phase of modules?
×
×
  • Create New...