Friday, April 29, 2016

Include a twig template as an object to be passed into another template?

Leave a Comment

Im using gulp-twig: https://github.com/zimmen/gulp-twig

I have a twig file for my container component:

{# container.twig #} <div class="container">   {% for item in items %}     <div class="container__item">        {{ item }}      </div>   {% endfor %} </div> 

I also have a snippet file:

{# snippet.twig #} <div class="snippet">   <h2>{{ title }}</h2> </div> 

Im demoing these in page.twig. I need to render the snippet as the {{ item }} within the container. So when viewing page.twig this should be the output:

<div class="container">     <div class="container__item">        <div class="snippet">         <h2>title</h2>       </div>     </div>    <div class="container__item">       <div class="snippet">        <h2>title</h2>      </div>     </div>    <div class="container__item">       <div class="snippet">        <h2>title</h2>      </div>     </div> </div> 

Now here is where it gets tricky. container.twig and snippet.twig are being pulled into another application. As such {{ item }} within container.twig cant be changed to something like {{ itemRenderer(item) }}.

However page.twig is not being used anywhere else so I can edit it however I like. Is there a way in page.twig to render container.twig with snippet.twig as it's item, without modifying container.twig or snippet.twig?

This is my gulp task:

var gulp    = require('gulp'),   config    = require('../config'),   utilities     = require('../build-utilities'),   src       = config.path.src,   dest      = config.path.dest,   opts      = config.pluginOptions,   env       = utils.getEnv(),   plugins   = require('gulp-load-plugins')(opts.load);  var compile = function() {   var notProdOrTest = env.deploy && !env.prod && !env.test,     deployPath    = env.deployPath,     sources = (env.deploy) ? ((env.styleguide) ? src.twig.styleguide: src.twig.testing): src.twig.all;   return gulp.src(sources, {base: 'src/'})     .pipe(plugins.twig({       data: {         component: utils.getDirectories('src/component/'),         deploy    : env.deploy,         test      : env.test,         prod      : env.prod       }     }))     .pipe(plugins.htmlmin(opts.htmlmin))     .pipe(plugins.tap(function(file){       file.path = file.path.replace('testing/', '');     }))     .pipe((notProdOrTest) ? plugins.replace(/src="\//g, 'src="/' + deployPath.root + '/'): plugins.gutil.noop())     .pipe((notProdOrTest) ? plugins.replace(/href="\//g, 'href="/' + deployPath.root + '/'): plugins.gutil.noop())     .pipe((notProdOrTest) ? plugins.replace(/srcset="\//g, 'srcset="/' + deployPath.root + '/'): plugins.gutil.noop())     .pipe((notProdOrTest) ? plugins.replace(/url\('\//g, 'url(\'/' + deployPath.root + '/'): plugins.gutil.noop())     .pipe(gulp.dest((env.deploy) ? deployPath.markup: dest.markup)); },   watch = function() {     gulp.watch(src.twig.watch, ['twig:compile']);   };  module.exports = {   compile: compile,   watch  : watch }; 

2 Answers

Answers 1

This could be done with macros:

{# macros.html.twig #} {% macro thisItem(item) %}     <div class="this-snippet">         <h2>{{ item.title }}</h2>     </div> {% endmacro %}  {% macro thatItem(item) %}     <div class="other-snippet">         <h2>{{ item.title }}</h2>     </div> {% endmacro %}  {% macro container(itemRenderer, items) %}     <div class="container">         {% for item in items %}             <div class="container__item">                 {{ itemRenderer(item) }}             </div>         {% endfor %}     </div> {% endmacro %} 

And then in the template:

{# template.html.twig #} {% from "macros.html.twig" import thisItem as itemRenderer, container %}  {% container(itemRenderer, items) %} 

And in another template:

{# template2.html.twig #} {% from "macros.html.twig" import thatItem as itemRenderer, container %}  {% container(itemRenderer, items) %} 

The same thing can be achieved with regular includes, although both offer the same possibilities, I think the macro solution is cleaner.

{# snippet.html.twig #} <div class="this-snippet">     <h2>{{ item.title }}</h2> </div>  {# container.html.twig #} <div class="container">     {% for item in items %}         <div class="container__item">             {% include snippetTmpl with { 'item': item } only %}         </div>     {% endfor %} </div>  {# page.html.twig #} {% include "container.html.twig" with { 'snippetTmpl': 'snippet.html.twig', 'items': items } only %} 

Answers 2

I don't see how it could be possible without modifying container.html.twig, since you are trying to render {{ item }}, which is intended to be HTML, without the raw filter, which is mandatory to mark the content of {{ item }} as HTML-safe.

If you are the owner of the container.html.twig file origin (not sure what you meant by

container.twig and snippet.twig are being pulled into another application

), maybe you could change {{ item }} to {{ item|raw }}. Then you would just need to be sure the items parameter passed to container.html.twig contains HTML generated by a renderView of snippet.html.twig. Then just be careful container.html.twig is not used somewhere else with HTML-unsafe items.

If you really don't have your hands on it, you may also try to render your template with a Twig environment that has autoescape disabled.

Hope this helps!

EDIT: Since you must do this using gulp-twig, what about something like this:

var titles = ['First snippet', 'second snippet']; var i, items; for (i = 0; i < titles.length; i++) {     gulp.src('/path/to/snippet.html.twig')         .pipe(plugins.twig({             data: {                 title: titles[i]             }         }))         .pipe(plugins.intercept(function(file){               items[i] = file.contents.toString();               return file;         })); }  gulp.src('/path/to/container.html.twig')     .pipe(plugins.twig({             data: {                 items: items             }         }))      .dest('/path/to/dest.html'); 
If You Enjoyed This, Take 5 Seconds To Share It

0 comments:

Post a Comment