Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Passing data to includes #539

Open
sjelfull opened this issue Oct 2, 2015 · 38 comments
Open

Passing data to includes #539

sjelfull opened this issue Oct 2, 2015 · 38 comments
Assignees

Comments

@sjelfull
Copy link

sjelfull commented Oct 2, 2015

Twig supports sending data to includes, like so:

{% include 'include.twig' with {
    name: 'whatever'
} %}

This is really useful in many cases, like re-using a block in different contexts, with different behaviour / data. Have anyone looked into adding support for this?

@ricordisamoa
Copy link
Contributor

#522

@carljm
Copy link
Contributor

carljm commented Oct 2, 2015

Reopening to track this feature request. Some discussion in #522.

I'm not opposed to this feature, but since it's not in Jinja2 I'm also uninterested in implementing it or reviewing or merging the implementation. Anyone interested in the feature is welcome to submit a pull request, it may be that one of the other maintainers is interested in reviewing and merging it.

@carljm carljm reopened this Oct 2, 2015
@sbussard
Copy link

it would be great to be able to pass blocks into includes to make it easier to create one-off versions

@carljm
Copy link
Contributor

carljm commented Oct 29, 2015

I don't know what "pass blocks into includes" means, concretely speaking.

@sbussard
Copy link

Here's the use case I'm concerned about:

I have an include for a site banner.
The banner has links in it, but the links need to change based on the page.
The banner include is used in multiple templates.

On a page I would like to define a block with the links to be used, then be able to pass that block to the banner include to replace the default markup for that section.

@carljm
Copy link
Contributor

carljm commented Oct 29, 2015

The "nunjucks way" to approach that use case is to make the banner-display a macro, which takes arguments (perhaps with default values) for the various links that may need to change.

I still don't understand how your proposed solution would work. If there are multiple links that need to be changed, wouldn't you need multiple blocks? Why are blocks better here than simple variables or macro arguments?

@sbussard
Copy link

sbussard commented Nov 9, 2015

In that particular case all the links are side by side. The nunjucks way is good.

@keelii
Copy link

keelii commented Nov 17, 2015

+1

@sbussard
Copy link

Following up on this: I was able to do much of what I needed to do when I realized that variables are globally scoped (but you don't have to worry about leaking to sibling pages). For more complicated situations, it's good to know that includes have their own inheritance tree. If that's not enough, macros come to the rescue. In my opinion it's not a matter of being able to do something with nunjucks, it's a matter of how intuitive it will be to do.

@keelii I'd be interested in hearing your use case to test this hypothesis.

@carljm
Copy link
Contributor

carljm commented Nov 17, 2015

To be clear, the original feature request here is to pass simple extra context data to include, using syntax like {% include "somefile.html" with {'foo': 'bar'} %} This ticket is open because I don't have a problem with that feature in theory if someone wants to implement it, though as I've said it's not a high priority for me personally.

The ticket has since been clouded with discussion of some not-entirely-clear-to-me additional hypothetical feature involving "passing blocks into includes." If that's still something you want to pursue or discuss, please open a new separate issue for it, as IMO it's quite a different thing from the feature proposed in this issue, and I don't think anything will be gained by confusing them in a single issue.

Thanks!

@sbussard
Copy link

@carljm right, that is not what I'm trying to do. That's why I posted my follow up. I should have been more clear.

@keelii
Copy link

keelii commented Nov 25, 2015

@sbussard
What i want to do is :

{% component name="foo" data="{bar: 1, orz: '2'}" %}

and i convert this component tag to nunjucks syntax in my program, then use nunjucks compile to html file. looks ugly,but it works for me.

{% for i in range(0, 1)  %}
    {% set bar = 1 %}
    {% set orz = '2' %}
    {% include 'path/to/my/component_template'  %}
{% endfor %}

@carljm
Copy link
Contributor

carljm commented Nov 25, 2015

@keelii Why not just write {% component %} tag as a nunjucks extension?

@devoidfury
Copy link
Contributor

@keelii You might be able to use a macro:

{% macro component(bar, orz) %}
 // template here. this can also be included and then called. args can also be objects
{% endmacro %}

{% for i in range(0, 1)  %}
    {{ component(1, '2') }}
{% endfor %}

@keelii
Copy link

keelii commented Nov 26, 2015

@carljm The component tag contains a sub-template, but i want pass some variable to this sub-template. these variable works for component private scope, and share some parent's variables. just like The JavaScript closure.

I've tried to use nunjucks extension, but it is hard to understand for me.

checkout this:

https://github.com/keelii/wo/blob/master/boilerplate/app/views/index.html#L26
https://github.com/keelii/wo/blob/master/boilerplate/app/components/module/module.html

@devoidfury macro content can not be maintained in a separate file.

@elcontraption
Copy link

@keelii here's an example component tag. I'm new to this API but it seems to work:

// nunjucks-component.js

// I'm using gulp-nunjucks-render, so I have to grab the nunjucks object:
import {nunjucks as nunjucks} from 'gulp-nunjucks-render';

/**
 * {% component 'name', {title: 'Example', subtitle: 'An example component'} %}
 */
var ComponentTag = function() {
    this.tags = ['component'];

    this.parse = function(parser, nodes, lexer) {
        var token = parser.nextToken();

        var args = parser.parseSignature(null, true);
        parser.advanceAfterBlockEnd(token.value);

        return new nodes.CallExtension(this, 'run', args);
    };

    this.run = function(context, name, data) {
        // My components are organized in this manner: components/componentName/componentName.html, but this is easily changed.
        // Note that the component is only given the data object, and the context is not exposed.
        return nunjucks.render('components/' + name + '/' + name + '.html', data);
    };

};

module.exports = new ComponentTag();
// Enabling the component tag extension
import nunjucksComponent from 'nunjucks-component';

nunjucks.addExtension('component', nunjucksComponent);

I hope that helps!

@keelii
Copy link

keelii commented Dec 15, 2015

@elcontraption Awesome! it looks sexy, I will try.

@neysimoes
Copy link

Not the best solution, but works for me.

Input:

{% set sectionHeader = { title: 'Title 1', subtitle: 'Subtitle 1' } %}
{% include "_partials/section-header.html" %}

{% set sectionHeader = { title: 'Title 2', subtitle: 'Subtitle 2' } %}
{% include "_partials/section-header.html" %}

_partials/section-header.html

<header class="section-header">
    <h3 class="section-title">{{sectionHeader.title}}</h3>
    <p class="section-subtitle">{{sectionHeader.subtitle}}</p>
</header>

Output:

<header class="section-header">
    <h3 class="section-title">Title 1</h3>
    <p class="section-subtitle">Subtitle 1</p>
</header>

<header class="section-header">
    <h3 class="section-title">Title 2</h3>
    <p class="section-subtitle">Subtitle 2</p>
</header>

@ettoredn
Copy link

Any news on this one? Handlebars already allows passing custom data to the included template and it's incredibly useful.

@fdintino
Copy link
Collaborator

As the new maintainer I'm slowly making my way through old tickets. This seems like something worth adding.

@fdintino fdintino self-assigned this May 27, 2017
@dreki
Copy link

dreki commented Jun 14, 2017

Thank you @fdintino! Right now we can use macros but jinja2 gives parent context to partials, which would be wonderful. You're the best!

@ArmorDarks
Copy link

@dreki You can import macros with context too.

@misscs
Copy link

misscs commented Jun 16, 2017

In theory not a bad add, but I feel like this feature can be accomplished with macros and/or custom components.

@fdintino
Copy link
Collaborator

@misscs In some cases that is true, but there have been arguments put forward elsewhere about the utility of this feature and how it would differ from a macro implementation.

There is actually a two-year-old pull request for this feature on jinja2, pallets/jinja#512. That PR hasn't been closed, but it has this bizarre response from the repo maintainer:

I really hate the include tag and wish I would never have added it. Because of that I'm not so sure if I want to extend it at this point. It's so buggy :(

It is the rare case where the django templating engine is actually more featured than jinja2.

@ArmorDarks
Copy link

ArmorDarks commented Jun 16, 2017

In some cases that is true, but there have been arguments put forward elsewhere about the utility of this feature and how it would differ from a macro implementation.

Hm, that seems to be strange argument. We're using solely macros, and they all stored as Components, each macro in standalone file: example. Doesn't seem to be much different from storing standalone templates which later will be included with specific context.

There is actually a two-year-old pull request for this feature on jinja2, pallets/jinja#512. That PR hasn't been closed, but it has this bizarre response from the repo maintainer:

Well, I actually share his opinion. We've used includes in the beginning too, but after it always started to rise debates "should this component be include or macro", we've ended up using only macros. And after that usefulness of includes became completely unclear for me. Just my two cents.

@fdintino
Copy link
Collaborator

Yeah, that is a strange point. It would be nice if there was something like an ES6 "default export" for macros, though I suppose a naming convention can address that. I can definitely see the advantage to having the context variables all listed explicitly in a file. Maybe I'm just biased due to familiarity—we're in the middle of transitioning our templates from django to jinja.

@ArmorDarks
Copy link

ArmorDarks commented Jun 16, 2017

It would be nice if there was something like an ES6 "default export" for macros,

Well, in Jinja all macros are like "exported by default", so you can import all ({% import '_components/_Link.nj' as linksComponents with context %}, or what you need ({% from '_components/_Link.nj' import Link, ActiveLink with context %}), or nothing...

So, in some sense, explicit exporting isn't necessary.

I can definitely see the advantage to having the context variables all listed explicitly in a file. Maybe I'm just biased due to familiarity—we're in the middle of transitioning our templates from django to jinja.

To be honest, didn't get that part :) Hard to gasp without example

@fdintino
Copy link
Collaborator

Oh, that was an argument in favor of macros over includes.

@ArmorDarks
Copy link

ArmorDarks commented Jun 16, 2017

Ah, now I get. Actually, it never occurred to me that includes with context also have this issue when you really don't know which context they can accept... Good point.

@misscs
Copy link

misscs commented Jun 16, 2017

Well, I actually share his opinion. We've used includes in the beginning too, but after it always started to rise debates "should this component be include or macro", we've ended up using only macros. And after that usefulness of includes became completely unclear for me. Just my two cents

I'm with @ArmorDarks (and creator of Jinja): macros cover most all use cases. I believe what folks are trying to accomplish can be done with what the language already provides.

@KayakinKoder
Copy link

KayakinKoder commented Jul 24, 2017

An alternative to @neysimoes solution with macros. I have used his solution a lot but over time found macros to be better. With macros, you avoid globals e.g. "sectionHeader.Title", and can easily implement default values. In this example we set a custom color in the first instance but easily revert to the default value in the second (which is not possible if you use include; you would need to re-set color before the second instance). Both of those benefits can be big time and error-savers if you have complex templates with a lot of variables.

Input:

{% import '_partials/section-header.html' as header %}

{% set sh1 = { title: 'Title 1', subtitle: 'Subtitle 1' } %}
{% set color = 'red' %}
{{ header.h(sh1, color) }}

<!-- now we want to revert to the default color in the macro, blue; no action is 
required on our part. if we were using include instead of a macro, we would 
need to remember to do a set color = blue here -->

{% set sh2 = { title: 'Title 2', subtitle: 'Subtitle 2' } %}
{{ header.h(sh2) }}

_partials/section-header.html

{% macro h(sectionHeader, color="blue") %}
<header class="section-header">
    <h3 class="section-title">{{sectionHeader.title}}</h3>
    <p class="section-subtitle">{{sectionHeader.subtitle}}</p>
    <p>{{color}}</p>
</header>
{% endmacro %}

Output:

<header class="section-header">
    <h3 class="section-title">Title 1</h3>
    <p class="section-subtitle">Subtitle 1</p>
    <p>red</p>
</header>

<header class="section-header">
    <h3 class="section-title">Title 2</h3>
    <p class="section-subtitle">Subtitle 2</p>
    <p>blue</p>
</header>

@sney-js
Copy link

sney-js commented Mar 26, 2018

I've found a simple way to achieve this. Wrote a method to merge two JSONs. Then we pass this merged json to our templates.
add in your filter.js:

  const mergeJSON = function (target, add) {
    function isObject(obj) {
      if (typeof obj == "object") {
        for (var key in obj) {
          if (obj.hasOwnProperty(key)) {
            return true; // search for first object prop
          }
        }
      }
      return false;
    }

    for (var key in add) {
      if (add.hasOwnProperty(key)) {
        if (target[key] && isObject(target[key]) && isObject(add[key])) {
          this.mergeJSON(target[key], add[key]);
        } else {
          target[key] = add[key];
        }
      }
    }
    return target;
  };

  env.addFilter('mergeJSON', function (original, appendJson) {
    return mergeJSON(Object.assign({}, original, {}), appendJson);
  });

then inside your template html files, use like:

{% set item = item | mergeJSON({small:true}) %}
{% include "../" + contentType + "/" + contentType + ".html" ignore missing %}

Then access inside the partial access by: item.small

renatodeleao added a commit to renatodeleao/sassdoc-theme-default that referenced this issue Feb 23, 2019
- remove `with variable` declarations. You cannot use them in nunjucks.
We can replace by macros, but for the sake of this first refactor we’ll
pass all context to includes
- More:
mozilla/nunjucks#539 (comment)

- updated include paths
- yup just this 🙌
renatodeleao added a commit to renatodeleao/sassdoc-theme-default that referenced this issue Feb 23, 2019
- remove `with variable` declarations. You cannot use them in nunjucks.
We can replace by macros, but for the sake of this first refactor we’ll
pass all context to includes
- More:
mozilla/nunjucks#539 (comment)

- updated include paths
- yup just this 🙌
renatodeleao added a commit to renatodeleao/sassdoc-theme-default that referenced this issue Mar 3, 2019
- remove `with variable` declarations. You cannot use them in nunjucks.
We can replace by macros, but for the sake of this first refactor we’ll
pass all context to includes
- More:
mozilla/nunjucks#539 (comment)

- updated include paths
- yup just this 🙌
renatodeleao added a commit to renatodeleao/sassdoc-theme-default that referenced this issue Apr 10, 2019
- remove `with variable` declarations. You cannot use them in nunjucks.
We can replace by macros, but for the sake of this first refactor we’ll
pass all context to includes
- More:
mozilla/nunjucks#539 (comment)

- updated include paths
- yup just this 🙌
renatodeleao added a commit to renatodeleao/sassdoc-theme-default that referenced this issue Apr 14, 2019
- remove `with variable` declarations. You cannot use them in nunjucks.
We can replace by macros, but for the sake of this first refactor we’ll
pass all context to includes
- More:
mozilla/nunjucks#539 (comment)

- updated include paths
- yup just this 🙌
@lodi-g
Copy link

lodi-g commented May 1, 2019

I've picked nunjucks randomly to change from my usual template engine. It seems incredible to me that nunjucks doesn't allow this simple and intuitive behavior.

I'll use the following syntax:

{% set barChartData = ...%}
{% include 'reveal/barchart.njk' %}

{% set barChartData = ... %}
{% include 'reveal/barchart.njk' %}

But that's a bit sad tbh.


Edit:
Nvm I used macros and import. Works perfectly. Thanks & sorry.

@FCO
Copy link

FCO commented Jul 6, 2020

I'd like this feature to use WITH macros. Something like:

{% macro HandleOn(on = {}) %}
   {% for event in required %}
      {{ throwError(event + " is required") if not in(event, on) }}
   {% endfor %}
   {% for key, value in on %}
      on{{ key }}="{{ value }}"
   {% endfor %}
{% endmacro %}

{% macro DocumentationPiece() %}
   Handles `on` events. The following events are required: {{ requires | join(", ") }}
{% endmacro %}

and on the other template I use it, I could say:

{{ from "handles_on.njk" import HandleOn, DocumentationPiece with { requires: ["click", "hover"] } }}

And I could use HandleOn and DocumentationPiece without passing the events they require.

(that's a simplified version of a code I'm really using, throwError and in are global functions)

@dbwodlf3
Copy link

dbwodlf3 commented Nov 7, 2020

It works for me.

test.njk

{{ tagName }}

main.njk

{% set tagName = someVariable %}
{% include 'test.njk' %}

someVariable is just variable. it can be represented {{someVariable}} in njk.

@janielMartell
Copy link

The solution proposed by @dbwodlf3 worked for me, but I would still like to have a way of passing arguments/having parameters in my includes.

@edwardhorsford
Copy link

I've been using a similar approach to @dbwodlf3 for those cases where I have an include rather than a macro. It makes me a little wary as it relies on the current context / changing something on the current context - which presumably for safety you might set in the line proceeding the include.

I like the 'neatness' / readability of directly passing the data to use, just like you can with a macro.


Includes can actually be really powerful when used with macros - one nice thing with them (though I love macros) is that the included file doesn't have to have any Nunjucks syntax in it. It can be pure text, html, whatever. If it were a macro, that content would need to be wrapped in the macro block (unless I'm wrong).

The UK Government uses this to good effect for their macros. The actual html is stored as html in a file, and then the macro includes that. This enables other things to make use of the html / compose with it (tests, rendering of examples for the deisgn system), without the macro block syntax getting in the way.

I've also used this as a way of mass-importing macros as a workaround for no default export or easy macro file management. I can all the source code for them in separate files, and then have a single macros.njk file that defines a bunch of macros, each just importing the relevant file.

example:

{% macro govukButton(params) %}
  {% include "govuk/components/button/template.njk" %}
{% endmacro %}

{% macro govukCharacterCount(params) %}
  {% include "govuk/components/character-count/template.njk" %}
{% endmacro %}

{% macro govukCheckboxes(params) %}
  {% include "govuk/components/checkboxes/template.njk" %}
{% endmacro %}

{% macro govukDateInput(params) %}
  {% include "govuk/components/date-input/template.njk" %}
{% endmacro %}

@ryanolee
Copy link

ryanolee commented Sep 28, 2021

Just as a heads up we did a POC for this a while back in https://github.com/Financial-Times/nunjucks-loader.
This is not really supported anymore but can be used as a reference if anyone want to look to get it implemented properly.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests