ProcessWire 3.0.49

Part of our 2017 roadmap is to introduce new site profile(s) to our core distribution. One of the challenges in building site profiles for this purpose is finding the right balance between keeping things simple, and showing off the flexibility and power of the system. Currently we accomplish this by having separate "beginner" (direct output) and "intermediate" (delayed output) profiles, plus a multi-language version of the intermediate profile.

What if we had an approach that was as simple (or simpler) than our "beginner" approach, but as powerful as our intermediate approach? And what if someone new to PW didn't have to learn what direct or delayed output was in order to understand how the template files work?

This week, we've got something added to the core that I think builds on the simplicity of direct output, and has the power of delayed output. It doesn't intend to replace either of those strategies (or whatever other strategy you may be using) but provides an alternative that may be very attractive to many, especially those coming from the front-end development side.

This new template file/output strategy is similar to delayed output in that you don't have to split up your main HTML template into separate header/footer sections, or the like. And you can use a $config->appendTemplateFile (i.e. _main.php file) with it. Or you could include() your document markup on your own.

The new strategy is similar to direct output in that you directly output (or echo) your markup from your template files. No concatenating of variables that will be output somewhere else. You can treat your template files as plain HTML, PHP, or any combination of either.

This strategy does not need an _init.php file ($config->prependTemplateFile), since it's not necessary to define or concatenate any variables like you might do with delayed output.

Settings things up

Since this is a new and experimental feature in PW 3.0.49, it's not enabled by default. To enable it, set $config->useMarkupRegions = true; in your /site/config.php file. Don't worry, this is safe and shouldn't interfere with anything on an existing installation. For this reason, we may have it enabled by default in the future. But since I don't know all the things you might be using template files for, I figured we'd play it safe for now, just in case. So if you try out this new template strategy, just remember to add the config setting mentioned here.

While not required for this template strategy, if you want to follow along with our examples here, you'll also want to add $config->appendTemplateFile = '_main.php'; to your /site/config.php file as well.

Let's say that I'm using a _main.php file that serves as my primary document markup, like used in our current default site profile. It contains the main document markup, like this:

<!DOCTYPE html>
<html>
  <head>
    <title><?=$page->title?></title>
  </head>
  <body>
    <div id='content'>
      <h1 id='headline'><?=$page->title?></h1>
      <div id='bodycopy'>
        <?=$page->body?>
      </div>
    </div>
    <aside id='sidebar'>
      <p>Welcome!</p>
    </aside>
    <footer id='footer'>
      <p>Copyright 2017</p>
    </footer>
  </body>
</html>

Note the various regions above identified by HTML id attributes:

  • content
  • headline
  • bodycopy
  • sidebar
  • footer

Our _main.php file outputs default values for these, which we might pull from the current $page, or leave some static default value in there (like <p>Welcome!</p> in our sidebar), or we could just leave anything blank.

Now lets consider the output for our homepage (file: /site/templates/home.php). If we simply left the file blank, we'd get the default output that we see in the _main.php file above. But lets say that on our homepage, we want to display a $page->intro field in our #headline <h1> tag, and we want to display a photo in the #sidebar. Here's what our home.php file would look like:

<h1 id='headline'><?=$page->intro?></h1>
<aside id='sidebar'>
  <img src='<?=$page->image->url?>'>
</aside> 

That's the entirety of our home.php file. It simply outputs whatever regions (identified by id attributes) that it wants to modify. Those regions automatically replace the ones from our _main.php file at output time. In this manner, anything output in your HTML that has an "id" attribute becomes a region that you can modify from your template files (or views, or any other file you are rendering/including from them). The strategy is entirely markup based, not unlike what you might use with a CSS framework.

What's already been mentioned above may be all you need. But you can do a lot more by using pw- prefix classes on elements. We'll cover these in the sections below.

Going further: prepend and append

Lets say that we've changed our mind a bit about that homepage, and now we want to append the image in the sidebar, rather than replacing it. Meaning, we want it to show the <p>Welcome!</p> from the _main.php file (or whatever happens to be there), and have the image appear underneath it, rather than replace it. Here's how we'd do that:

<aside id='sidebar' class='pw-append'>
  <img src='<?=$page->image->url?>'>
</aside>

Note that class attribute that we added: pw-append. That indicates that you want the content to append whatever is in that #sidebar rather than replace it. Another way to do the same thing is to output this instead:

<img class='pw-append-sidebar' src='<=$page->image->url?>'>

When just appending one element like this, the shorter syntax above may be preferable. If we were appending a group of elements, or wanting to add new attributes or classes to the <aside>, then the earlier syntax would be preferable.

In the same manner, lets say that we want to prepend some big hero image to the #bodycopy div:

<img class='pw-prepend-bodycopy' src='/img/hero.png'>

When viewing the page, now you'd have a big hero image appearing directly within the #bodycopy div, before the $page->body text.

Modifying <head>

Now lets say that we want to append two new <link> elements to add new stylesheets to our document head. But these stylesheets are only needed on the homepage, so we don't have them in our _main.php file. We can add them from our home.php template file, but as a prerequisite, we need our <head> tag in our main document markup to have a unique id attribute, since that is how ProcessWire identifies manipulatable markup regions. So we'd update our head tag in _main.php to be <head id='html-head'>, or choose your own id. Then we'd add this HTML below to our home.php file, to add our new stylesheets:

<head id="html-head" class="pw-append">
  <link rel="stylesheet" type="text/css" href="/path/to/file.css">
  <link rel="stylesheet" type="text/css" href="/another/file.css">
</head>

Inserting new elements before/after other elements

We've covered prepend and append, but what about inserting new elements before or after existing elements? Lets say we want to insert a subhead after the #headline:

<h2 class='pw-after-headline'><?=$page->subhead?></h2>

Or lets say we want to insert a Twitter feed above our #footer:

<div class='twitter pw-before-footer'>
  <?=$modules->get('MarkupTwitterFeed')->render();?>
</div>

Removing elements

We've continued to re-think our homepage and realized we don't need a sidebar at all, and instead want a full-width #bodycopy with the hero image in it. We also want to add the class "no-sidebar" to our wrapping #content div. Here's how we might do that:

<aside id='sidebar' class='pw-remove'></aside>
<div id='content' class='pw-append no-sidebar'></div>

The above removes the #sidebar <aside> completely from the document markup, and adds a "no-sidebar" class to the wrapping #content div.

Working with attributes

When you use a tag that references an id attribute, like <aside id='sidebar'>, any attributes you add to that tag get merged with any other appearances of it. Meaning if the sidebar appears as-is in the _main.php file, and like below in my home.php template file…

<aside id='sidebar' title='Hello world' class='uk-card pw-append'>

…then the resulting #sidebar would have the new title and class attributes. Note that the "pw-" prefix classes do not appear in the markup that gets output to the user.

If that #sidebar already had a populated class attribute in the _main.php file, then the classes present there would remain, and that "uk-card" class above would be added to the existing class attribute.

Does any of this depend on using a _main.php file?

No it doesn't. I'm just using that as an example here because it's something that I think readers may be familiar with. You could just as easily include("./markup.inc") from your home.php template file, and everything would work the same.

By that token, the elements that you manipulate don't even have to be part of the main document markup. This template strategy can be nested and go recursive. Meaning you can manipulate something you added from a template file or some other include(), which could be useful in certain cases where you might want it.

How does it work technically?

It's actually really simple. When ProcessWire renders a page, it now asks the question: does any markup appear before the doctype or <html> element? And if it does, does any of it reference "id" attributes or have "pw-" prefix classes? If so, it processes them as markup regions, letting you do all that's mentioned in this post.

So really it's a combination of direct output and delayed output. Anything that appears in the direct output is considered markup that will be populated somewhere within the delayed output, dynamically.

Quick reference

Below are some examples to serve as a quick reference. Note that “yo” is used as an example “id” attribute of an element that appears in the main document markup, and the examples below focus on manipulating it. The examples assume there is a <div id=yo> in the _main.php file (or wherever the final markup comes from), and the lines in the examples would be output from a template file, which manipulates what would ultimately be output when the page is rendered.

Replacing and removing elements

<div id=yo>Replaces #yo and merges attributes</div>
<div id=yo class=pw-replace>Same as above</div>
<div id=yo class=pw-remove>Removes #yo</div>

Prepending and appending elements

<div id=yo class=pw-prepend><p>Prepends #yo with this</p></div>
<div id=yo class='pw-prepend bar'>Prepends #yo, adds bar class</div>
<div id=yo class=pw-append><p>Appends #yo with this p tag</p></div>
<div id=yo class='pw-append foo'>Appends #yo, adds foo class</div>
<div id=yo class=pw-append title=hello>Appends #yo, adds title attr</div>
<div id=yo class='pw-append -baz'>Appends #yo, removes baz class</div>

Inserting new elements

<h2 class=pw-before-yo>Adds H2 headline with this text before #yo</h2>
<footer class=pw-after-yo>Adds this footer after #yo</p></footer>
<div class='pw-append-yo foo'>Appends this div.foo to #yo</div>
<div class='pw-prepend-yo bar'>Prepends this div.bar to #yo</div>

Summary of class attributes

  • pw-replace replaces element (default assumption)
  • pw-prepend prepends markup to element
  • pw-append appends markup to element
  • pw-before places markup before element
  • pw-after places markup after element
  • pw-before-id places this tag and markup before element with "id"
  • pw-after-id places this tag and markup after element with "id"
  • pw-remove removes element from document completely
  • foo adds class "foo" to element (can be any class name)
  • -bar removes class "bar" from element (can be any class name)

Note: after being processed, the “pw-” classes above are removed from the HTML tags automatically by ProcessWire.

Benefits and drawbacks

While you can perform some fairly powerful manipulations with this approach, the reality is that it's also unusually simple. Just use your template files to directly output the elements you want to replace or update in the document markup.

Any markup element with an "id" attribute becomes a region that you can modify, insert before, insert after, or remove, dynamically.

No concatenation of string variables is necessary, and there's no need to have an _init.php file predefining variables for that purpose. Just start outputting markup and let ProcessWire match up where it goes in the document via it's “id” attribute.

This strategy is based entirely on HTML markup, and doesn't rely on PHP as our other strategies do. For this reason, I think it will be useful in attracting more front-end developers, and even beginners, to ProcessWire. But unlike other simple template strategies (like direct output) there are no sacrifices that I can think of.

Like I said at the beginning of this post, I was really thinking about what I want both beginners and seasoned professionals [new to ProcessWire] to see in those new site profiles we develop. I think this template strategy will make ProcessWire accessible to a broader audience, since it's based almost entirely around front-end terminology that's already “home” to most, like a really simple CSS framework.

It may be obvious but just want to be clear this is not a template system or something like twig. It is a strategy for communicating markup between template files, not unlike the recently introduced region() function. So if you need to do any kind of logic or API calls, that's PHP – nothing changes there when you use this strategy. What this strategy saves you from is having to shuffle variables containing markup, or include() calls across multiple files.

This template file strategy does not replace other strategies, it's just an alternative. For instance, my preferred strategy of using delayed output and region() calls is likely something I'll keep doing. But it's nice to have alternatives.

If your current strategy already involves using template files (or separate views) to generate HTML (as most do), there really aren't may drawbacks that I can think of so far. While there is some overhead in having ProcessWire perform these markup manipulations, the code around it has really been written to be quite fast. For instance, many things I'd usually use regular expressions for have been hand-coded the more verbose and harder way, to optimize them for speed.

I'm not necessarily sure if this is a drawback, but this strategy won't work correctly if there's something wrong with your HTML. While a browser might forgive a missing closing tag for some markup element, it's bound to confuse ProcessWire when populating your regions.

Perhaps there are other drawbacks that will surface as we explore this further. But for now it seems like a good experiment, and this is all very preliminary. I welcome your feedback and am interested to hear what the benefits and drawbacks are in your experience after using it. I'd also like to hear your thoughts on what additions or changes would be helpful to you. For instance, one thought is having an alternative to the “id” attribute for region identification, like a “pwid” attribute that gets stripped from the final markup.

Thanks for reading and I hope that you have a great weekend and week ahead. Be sure to enjoy reading the fantastic ProcessWire Weekly tomorrow too.


Comments

  • HC

    HC 6 months ago 80

    Simply fantastic. Over my career, the simple things that get the job done have so much shelf life. Thanks Ryan and team!

  • Joe Regan

    Joe Regan 6 months ago 40

    Very interesting!

    Kind of reminds me of laravel but with a twist. I would need to use it to decide if i liked it or not though for sure.

    I am also working on a profile for beginners but it uses an easy form of delayed output! I hope to launch it in a few months. I think it will be very easy to use!

  • John Faulds

    John Faulds 6 months ago 20

    Wow, this looks awesome!

  • thetuningspoon

    thetuningspoon 6 months ago 10

    Very interesting. I really like the simplicity of this.

    Ryan, can you elaborate a bit on the part about nesting? I didn't understand what you meant about being able to manipulate markup that was not part of the main document markup, since elsewhere you said it determines what can be edited based on whether it is before or after the doctype.

    At our company I've been developing a template strategy that involves breaking up the interface into modular blocks. Each block has a view file (powered by pw's TemplateFile class), a controller class for handling input and preparing data for output to the view, and an optional css and js file. All are grouped into a folder for that block under a subdirectory of the templates folder. The block controllers can be called from within any other controller, can have their properties manipulated, and then have their output sent to the parent view. So they are nestable.

    This strategy has a learning curve (I keep working on ways to simplify it) but it has proven to be a very powerful and flexible approach. I wonder if a strategy like this might be appropriate to include in the core as an advanced strategy at some point. Or maybe what I'm describing is also possible with this new method?

    • ryan

      ryan 6 months ago 10

      By nesting, I mean that you can do this (pretend this is HTML)

      [div id=photo class=pw-append-sidebar]
      [img src='...']
      [/div]

      [caption class='pw-append-photo']
      Caption added to photo div
      [/caption]

      That #photo div added above doesn't have to appear in the original markup. Basically, you can manipulate things that have been added by the same or other template files. You aren't limited to only append/prepend/before/after of elements in your _main.php file. Though I imagine most of the time people wouldn't need this nested ability, though think it'll be nice to have for certain rare instances.

  • kixe

    kixe 6 months ago 20

    Interesting stuff. I recommend to use valid attributes for identification, even if they are removed (for now). What do you think about 'data-pw' instead oft 'pwid'?

    • ryan

      ryan 6 months ago 60

      It will be a configurable option what attributes people want to use. Personally, I like using the id and class attributes as a way to introduce the concept here, and because everyone is already familiar with them (less to remember). I also like the id attribute because it's already enforced as a unique in an HTML document, and that's consistent with what a markup region needs to be. (useful in the IDE). Also, the fewer ways to refer to the same thing across front-end and back-end, the better, IMO. I don't like to have to remember lots of things. :) For people that want to use other attributes, we'll be supporting other attributes, both data and non-data attributes. Since the attributes are removed from the markup, it doesn't really matter whether you use a "data-" prefix or not, but we'll support it for those with IDE's that might complain about an unrecognized attribute.

  • Marcello Bacos Moreno

    Marcello Bacos Moreno 6 months ago 20

    Ryan, just perfect !!
    Thank you !!!

  • Okeowo Aderemi

    Okeowo Aderemi 6 months ago 11

    It's not bad but i don't feel quite comfy with this, maybe i'd like to have explicit control over how layout and control should go, but will obviously make templating a breeze. I guess i don't want to give up that control am used to

    • ryan

      ryan 6 months ago 30

      There's no requirement to use it, you can keep doing what you've always done. But this update will be very useful for some, and also make it easier for new users to learn PW. I think it will also be attractive to experienced front-end developers, since it's already familiar.

  • tpr

    tpr 6 months ago 20

    I have mixed feelings and had to read the strategy several times to get its benefits over my current template engine (Latte). The conclusion is that it substitutes its layout in part which is good because it does it without extra modules. Cons are the missing advanced stuff that a mature template engine offers, but for beginners or intermediate users it'll be just enough. I think I'll try this strategy on a smaller site to see if it suits me or not.

    • ryan

      ryan 6 months ago 20

      What's described in this post is not a template engine, and not even related to a template engine. Actually it should work quite nicely alongside a template engine if you are using one. What markup regions do is essentially the same thing that the existing PW region() function does, but with some added flexibility and simplicity.

  • Ben

    Ben 6 months ago 22

    Seems a bit strange and verbose too me, but will give it a go. Not sure it's easier for beginners as its not something I've seen before, maybe if they're unfamiliar with PHP it would be good.

    The best thing about processwire for me is that it has LESS structure so you simply leverage the API and PHP instead of getting bogged down by layers of complexity (I'm thinking Drupal here). This looks like a confusing addition for me.

  • Lenz

    Lenz 6 months ago 20

    That is a really well thought out and very useful feature.

    In my case the advantage of not having to store strings of markup into variables is huge. Also i don't need to split my template-files anymore (like $content=_content-partial.php; or do painful string concatenations, which leads to less template files and a better overview over my code.

    Imho templating is significantly more straight forward with this feature.

  • Joe Regan

    Joe Regan 6 months ago 00

    I dont think storing markup into a variable is really a bad thing. You can add one line of code in the area that sets and adds to the variable, and print out comments of everything to see what the order was and what file each came from. Great for trouble shooting where this you just see the before and after of the file you are on.

    I like to have one public snippets array or object, and store top, head, body variables in there.

    In my framework html, I have [[top]], [[head]], and [[body]] tags placed where I want my snippets to print.

    I put a snippets page at my root or under my theme page that stores my snippets for that theme, sorted by type. That way all my top snippets are together, and my head and body snippets are together and I can find what I need.

    If I check sitewide on the snippet page, it will print on every page, for say stats snippets. Or I can tie a snippet to just one page only using a drop down on that page.

    Works well for beginners because they can write the snippet in 5 to 10 lines with the cms using maybe ace text editor and get code highlighting, and tie it to a page or sitewide without needing software, and can use any computer and not lose changes.

    We even have change history support as a module that works great.

    • Joe Regan

      Joe Regan 6 months ago 00

      The snippets template would also need a weight dropdown so you can control the order they print. If you want your javascript at the bottom of the body area, make it 100 and leave everything else 50.

  • microcipcip

    microcipcip 6 months ago 00

    Is this feature similar to Twig extends/block template?

    • Ryan

      Ryan 5 months ago 00

      I'm not sure what extends/block template means, but this template file strategy doesn't have anything to do with a template engine like Twig. If you liked to use a template engine like Twig, it would still be perfectly fine to continue using it with this template file strategy.

  • Joel

    Joel 5 months ago 00

    Does Procache work seamlessly with this new strategy?

  • Stevie

    Stevie 5 months ago 00

    I probably won't use it myself - but there is something really beautiful about it.

Post a Comment

Your e-mail is kept confidential and not included with your comment. Website is optional.