Jump to content
Wanze

Module: Twig for the TemplateEngineFactory

Recommended Posts

I mostly try to use FormBuilder which does all the work :) However, if I need special forms in terms of styling or functionality, I'm using the ProcessWire form API to render and validate/process the input. You also get free CSRF protection. Here's an example how roughly works (written in browser, not copy paste ready):

// Controller contact.php
// ------------------------
$form = $modules->get('InputfieldForm');
$select = $modules->get('InputfieldSelect');
$select->required = 1;
$select->setOptions(['blub', 'blub2']);
$form->append($select);

$csrf = $session->CSRF->renderInput();

$view->set('inputs', ['select' => $select', 'csrf' => $csrf]);

// Validate and process the input if submitted
if ($isSubmitted) {
  $form->processInput($input->post);
  // Handle errors and success
}

// View contact.twig
// ------------------------------
<form action="{{ page.url}} " method="POST">
  <label>Select</label>
  {{ form.select.render() }}
  {{ csrf }}
  <button type="submit">Submit me</button>
</form>

Cheers

  • Like 3

Share this post


Link to post
Share on other sites

Hi @Wanze,

I'm interested in somehow adding twig namespaces.  I'm not sure if it would be best to do this in a custom module via hooking initTwig, adding to this module, or the adding to the TwigExtensions module?

The reason I'm interested in this is because I would like to use PatternLab(http://patternlab.io/) with Twig and Processwire.  Basically I would like to use my PatternLab components created with Twig and include them within my Processwire Twig templates.  The benefit is that you can have a styleguide that is always in sync with the website's templates.

I would like to add something like @molecules, @organisms, and @atoms namespaces to specify what directory my Frontend twig components are located in.  The components usually live under the PatternLab's directory in your site/templates/Patternlab/source/_patterns folder.  This @styleguide namespace would allow me to easily change the paths to my components folders if they ever change.

The new symfony.com also uses a @icons namespace to easily include svg icons.  http://symfony.com/blog/refactoring-symfony-com-front-end

{# Twig namespaces create concise and beautiful templates #}
<i class="icon">{{ source('@icons/arrow.svg') }}</i>

For background reading of why to do this:

Implementation

I'm thinking I just need to add a textarea field that allows the user to enter a namespace and the path to the twig templates on a new line separated by a delimiter of some kind.  The markupSEO module doesn't this in it's config settings.

To add the namespaces to Twig:

According to https://twig.sensiolabs.org/doc/1.x/api.html, I think you can do the following:

$loader->addPath($templateDir, 'admin');

//adds an @admin namespace

I would love to hear anyone's thoughts on this.  I really like working with Processwire and Twig, it really makes code so much cleaner and easier to read.

Edited by gmclelland

Share this post


Link to post
Share on other sites

Hi Wanze,

Thanks again for this module, have been really enjoying using it. Have run into a slight issue on a new site where my layout file is getting read but the views files themselves (set in `templates/views` ) aren't outputting anything.

I have made sure that the factory module is using Twig and have tried deleting the cache but nothing is outputting from the views folder. I'm pretty sure my paths are correct. Do you have any good ways to debug? Thanks!

Share this post


Link to post
Share on other sites

@onjegolders

What do you mean with layout file, can you give an example of your setup?

Do you have a corresponding controller file (aka ProcessWire template in /site/templates) for the template of the current $page?

Cheers

Share this post


Link to post
Share on other sites

@onjegolders Maybe you can try templates/views/ with the trailing slash?  Also like Wanze is saying you need a php controller file in /site/templates/ and you need a .twig file to go with it.  Ex. calendar-list.php and calendar-list.twig.

In my .twig file I extend a base template with {% extends "./partials/one-column-layout.twig" %}.  I like to keep my php controller files in the same directory as my .twig files so that I don't have switch between directories so much.

@Wanze Did you happen to see my post above?

 

Share this post


Link to post
Share on other sites

Hi @Wanze, @gmclelland thanks for your replies. I had my layout file and my twig files in /site/templates/views and for each page I had an equivalent .php file but these were in /site/templates.

I should say that my layout.twig file was getting loaded fine but not the individual view files. Seems a strange one.

I will try again and play around with the paths.. Thanks

Share this post


Link to post
Share on other sites
17 hours ago, gmclelland said:

Did you happen to see my post above?

@gmclelland

I would suggest to create a new module which uses the TemplateEngineTwig::initTwig hook. Having your own module also allows to enter/store some config data to map the namespaces to a path etc.

Cheers

Share this post


Link to post
Share on other sites

This is strange I can't find myself to hooking TemplateEngineTwig:init no matter what i do nothing happens and in Symfony this is not a problem for me, am I missing anything

 

wire()->addHookAfter("TemplateEngineTwig::initTwig", function($event) {
	$twig = $event->arguments('twig');
	$instanceOfFunction = new Twig_Function('instanceof', function ($object,$evaluatedClass) {
		return ($object instanceof $evaluatedClass);
	});
	$twig->addFunction($instanceOfFunction);
  });

 

Share this post


Link to post
Share on other sites

I think this is limiting if I can't extend modules from templates and only from modules. :(

Share this post


Link to post
Share on other sites

Had no choice than to settle for modifying the TemplateEngineTwig, feels so hackish. If anyone can guide me on how you can hook to the TemplateEngine::initTwig from a template i'd be appreciative, a feature in the future could be a user-defined folder where extensions can be placed and used easily. 

Share this post


Link to post
Share on other sites
<?php

    /**
     *
     * TwigExtensions
     *
     * See README.md for usage instructions.
     *
     * @author Jens Martsch <jmartsch@gmail.com>
     * @version 1.0.0
     * @see http://www.processwire.com
     */

    /**
     * Class TwigExtensions
     */
    class FriendChipFunctionsForTwig extends WireData implements Module
    {

        /**
         * Retrieves module meta data
         * Implementation of the Module interface
         *
         * @return array
         * @see http://processwire.com/apigen/class-Module.html
         */
        public static function getModuleInfo()
        {
            return array(
                'title' => 'Functions for Twig',
                'summary' => 'Allows customizing twig, e.g. add extensions',
                'version' => 100,
                'singular' => true,
                'autoload' => true,
                'icon' => 'puzzle-piece',
                'requires' => array(
                    'TemplateEngineFactory',
                    'TemplateEngineTwig'
                )
            );
        }

        public function init()
        {
            
            if ($this->modules->get('TemplateEngineTwig')) {
                $this->addHookBefore('TemplateEngineTwig::initTwig', $this, 'addExtensions');
            }
        }

        /**
         * Hook add twig extensions
         *
         * @param HookEvent $event
         */
        public function addExtensions(HookEvent $event)
        {
            $this->twig = $event->arguments('twig');
            $this->addRelativeTimeStr();
            $this->asset_path('');
        }

        private function addRelativeTimeStr()
        {
            $twigWireRelativeTimeStr = new Twig_SimpleFunction('wireRelativeTimeStr', function ($date) {
                return wireRelativeTimeStr($date);
            });

            $this->twig->addFunction($twigWireRelativeTimeStr);
        }

        /**
         * Returns the revved filename if a rev-manifest exists
         * else it returns the $filename
         * @param  string $filename relative to the template/assets directory
         * @return string
         */
        private function asset_path($filename)
        {
            $assetPathFunction = new Twig_SimpleFunction('asset_path', function ($filename) {
                $manifest_path = $this->config->paths->templates . 'assets/rev-manifest.json';
                if (file_exists($manifest_path)) {
                    $manifest = json_decode(file_get_contents($manifest_path), TRUE);
                } else {
                    return $this->config->urls->templates . "assets/" . $filename;
                }

                if (array_key_exists($filename, $manifest)) {
                    $manifest_filename = \ProcessWire\wire()->config->urls->templates . "assets/public/" . $manifest[$filename];
                    return $manifest_filename;
                }
            });

            $this->twig->addFunction($assetPathFunction);
        }
    }

or take a look at https://github.com/justb3a/processwire-twigextensions

  • Like 2

Share this post


Link to post
Share on other sites

Has anybody had any trouble triggering a 404 page not found when accessing a non-valid paginated page with TemplateEngineTwig?

In my template calendar-listing.php I do the following:

throw new Wire404Exception();

but nothing happens.  The page is still displayed instead of being redirect and displaying the 404 page.

If I go to /calendar/page200 it shows the same page as /calendar instead of throwing the 404 page.  However, if you look at Chrome's console the document shows a 404.

notfound.thumb.jpg.a2a834069783f0d87464860f14a2629c.jpg

What's else is strange is that I can go to /some-fake-page and it will then redirect me and display the 404 error page just fine.

Note: allow page numbers is checked on this calendar-listing.php template.  I also use $config->prependTemplateFile = '_init.php';  calendar-listing.twig is used to output the html.

It sort of seems like something similar is happening on the processwire.com blog.  If I visit https://processwire.com/blog/page200 which doesn't exist, I would expect it to give me a 404 page not found error and redirect me to the 404 not found page, but instead it shows a 500 error and still displays the blog page.

The main reason I'm doing this is to try what is suggested here to improve my site's SEO:

https://processwire.com/blog/posts/processwire-2.6.18-updates-pagination-and-seo/

// Don't let search engines index non-existing pages
// see https://processwire.com/blog/posts/processwire-2.6.18-updates-pagination-and-seo/
if(!count($paginated_calendar_postings) && $input->pageNum > 1) {
  throw new Wire404Exception();
}

 

Share this post


Link to post
Share on other sites

I think I found my answer:

Basically with Processwire it is up to you on how you handle the 404.  So in my case I just need to do a $session->redirect to my 404 page.

 

Share this post


Link to post
Share on other sites

Big thanks for the module! I think it is very  useful for processwire.

I think it will be very practical for newcomers ( like me ) to include some basic examples to start with.
Suppose i have a blank profile installed and created only home page with home template consist of title and body

After installing TemplateEngineFactory module first, and TemplateEngineTwig second, and then setting engine to twig,
one need to
1 delete file site/templates/home.php
2 create file site/templates/home.twig
3 create file site/templates/views/home.html

please correct me if i wrong

//home.html
<!DOCTYPE html>
<html lang="en">
<head>
	<meta http-equiv="content-type" content="text/html; charset=utf-8" />
	<title>{{  page.title }}</title>
</head>
<body>
	<h1>{{ page.title }}</h1>
	{{page.body}}
	<hr>
	<p>myvar:{{myvar}}</p>
</body>
</html>
//home.twig   
<?php
    $view->set("myvar", "my test var");

Share this post


Link to post
Share on other sites

Hi @ak1001

Thanks! :)

1. Do not delete /site/templates/home.php. This file now acts as a "controller" and you will pass variables to your twig templates via $view.

2. You don't need this file, the corresponding twig file would be in /site/templates/views/home.html. Note that this path and the file extension depend on your module configuration.

3. Yes, this is actually your twig template.

The below code belongs to /site/templates/home.php

<?php $view->set("myvar", "my test var");

Think of this file as layer between ProcessWire and your twig template. So instead of echo stuff there, pass it to the twig template which takes care of the rendering.

Hope that helps!

Cheers

  • Like 2

Share this post


Link to post
Share on other sites

Hi @ak1001,

Welcome, I personally like to use home.php as my controller and home.twig as the view.  Using the .twig can provide Twig syntax highlighting in some text editors.  Make sure you set the .twig extension in the Template Engine Twig module settings > Template File Suffix

I prefer to keep those file in the same directory.  It's less context switching when your looking for files.  Template Engine Twig module settings > Path to templates = templates/

In your case, I would use the following:

site/templates/home.twig

<!DOCTYPE html>
<html lang="en">
<head>
	<meta http-equiv="content-type" content="text/html; charset=utf-8" />
	<title>{{  page.title }}</title>
</head>
<body>
	<h1>{{ page.title }}</h1>
	{{page.body}}
	<hr>
	<p>myvar:{{myvar}}</p>
</body>
</html>

site/templates/home.php

<?php
    $view->set("myvar", "my test var");

Hope that helps

  • Like 3

Share this post


Link to post
Share on other sites
On 8.12.2017 at 10:50 PM, gmclelland said:

Has anybody had any trouble triggering a 404 page not found when accessing a non-valid paginated page with TemplateEngineTwig?

In my template calendar-listing.php I do the following:


throw new Wire404Exception();

but nothing happens.  The page is still displayed instead of being redirect and displaying the 404 page.

@gmclelland

I noticed the same problem on a project of mine. I tried to fix this with the latest release of the TemplateEngineFactory module (1.1.2). If you throw a Wire404Exception() the module now should set the correct 404 page for twig. So the redirect should not be necessary now.

  • Thanks 1

Share this post


Link to post
Share on other sites

Thanks @Wanze

I tried to do next
1 site/templates/home.php ( with myvar variable)
2 site/templates/views/home.html (twig template)

and get error Notice: Undefined index: TemplateEngineNull in /home/WWW/pw/processwire/wire/core/Modules.php on line 3023

then i added empty home.twig :
1 site/templates/home.php (with myvar variable)
2 site/templates/views/home.html (twig template)
3 site/templates/home.twig (empty)

and it works  but not output myvar value! Only page.title and page.body

So working combination in my case only php code in home.twig and template in home.html.
home.php is  not working/needed
So i'm little confused what i do wrong.

Share this post


Link to post
Share on other sites

@ak1001,

Forget about home.html.  You only need to use home.php and home.twig.  All of your PHP stuff goes into home.php and your html stuff goes into home.twig.

Example:

in home.php

<?php namespace ProcessWire;
$view->set('mytitle', $page->title);

In home.twig

<h1>{{ mytitle }}</h1>
<div class="body-content">{{ page.body }}</div>

This code assumes your Processwire home template has a title and body field.

  • Like 2

Share this post


Link to post
Share on other sites
2 hours ago, gmclelland said:

You only need to use home.php and home.twig

Thanks a lot, i found my mistake. I had tried to install Template Twig Replace module
it have a
$config->templateExtension = 'twig'; 
in site/config.php
after i deleted this string your example works! And thanks for suggesting to keep twig and php in one folder.

Share this post


Link to post
Share on other sites

SOLVED: For every newbie like me:
Read the docs more carefully and use "{{ page.title }}" instead of "{{ page->title }}" within a twig-template

I am new to Twig and want to implement into my default PW installation and get the following error:
What did I do wrong?
PW Verson is 3.0.98

Error: Exception: Unexpected token "operator" of value ">". (in /Applications/MAMP/htdocs/_PROCESSWIRE-BOILERPLATE/site/modules/TemplateEngineTwig/TemplateEngineTwig.module line 94)

#0 /Applications/MAMP/htdocs/_PROCESSWIRE-BOILERPLATE/site/modules/TemplateEngineFactory/TemplateEngineFactory.module.php(121): TemplateEngineTwig->render()
#1 /Applications/MAMP/htdocs/_PROCESSWIRE-BOILERPLATE/wire/core/WireHooks.php(822): TemplateEngineFactory->hookRender(Object(ProcessWire\HookEvent))
#2 /Applications/MAMP/htdocs/_PROCESSWIRE-BOILERPLATE/wire/core/Wire.php(442): ProcessWire\WireHooks->runHooks(Object(ProcessWire\Page), 'render', Array)
#3 /Applications/MAMP/htdocs/_PROCESSWIRE-BOILERPLATE/wire/modules/Process/ProcessPageView.module(205): ProcessWire\Wire->__call('render', Array)
#4 /Applications/MAMP/htdocs/_PROCESSWIRE-BOILERPLATE/wire/modules/Process/ProcessPageView.module(205): ProcessWire\Page->render()
#5 /Applications/MAMP/htdocs/_PROCESSWIRE-BOILERP

This error message was shown because: you are logged in as a Superuser. Error has been logged.

Thanks

Share this post


Link to post
Share on other sites
8 hours ago, ocr_b said:

Read the docs more carefully and use "{{ page.title }}" instead of "{{ page->title }}" within a twig-template

 

Because of that, I use Smarty as my template engine now, because it is closer to the ProcessWire syntax. But in one project I still have to use twig 😞

Also twig often throws errors, when you try to access a property/variable that isn't defined, but then you can use page["myvar"] instead without getting an error.

Share this post


Link to post
Share on other sites

Hello,
I have some difficulties to understand extends.

How send variables through extends ? like here, may i send  {{ page.title }} or {{langue_str}} to base.twig ? Base.twig is my main template with headers, footers... that extends each page.

{% extends 'base.twig' %}
{% block content %}

<div id="titres">
<div id="titre">{{ page.title }} {{ lang }}</div>
</div>
 <div class="hr"></div>
<br>
{{ include('engagement.twig') }}
{{ include('lettre.twig') }}
{{ intro_derniers_textes }}<br /><ul>
{% for texte in derniers_textes %}
<li><a href="{{ texte.url }}{{langue_str}}/">{{ texte.title }}</a></li>
{% endfor %}
</ul>
{% endblock %}


Thank you

Share this post


Link to post
Share on other sites

You can't send variables through extends. I don't use this module, but you can include variables like this:

{% include('engagement.twig') with: {'title': 'your title here'} %}

In your engagement.twig you have access to "title".

When you want variables on a higher level you can create another block in your base.twig. Read more here.

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.

×
×
  • Create New...