Hi
_First of all, I may be very wrong on my further assumptions. So, please, take my apologies in case I understand inheritance in wrong way and just wasting you time. Thanks in advance!_
Just let's take for example:
We have following structure:
|_ _layout.nj
|_ index.nj // extends `_layout.nj`
|_ extendsIndex.nj // extends `index.nj`
and following content:
// _layout.nj
<!DOCTYPE html>
<html>
<head></head>
<body>
{% set layoutVar = 'Test var from <b>layout</b>' %}
<p>TestVar: {{ testVar }}</p>
<p>layoutVar: {{ layoutVar }}</p>
<p>MainBlock: {% block main %}{% endblock %}</p>
</body>
</html>
// index.nj
{% extends "_layout.nj" %}
{# --------- #}
{% set testVar = "Test var from <b>index</b>" %}
{% set layoutVar = "Test var from <b>index</b>" %}
{# --------- #}
{% block main %}Test block from <b>index</b>{% endblock %}
// extendsIndex.nj
{% extends "_layout.nj" %}
{# --------- #}
{% extends "index.nj" %}
{# --------- #}
{% set testVar = "Test var from <b>extendsIndex</b>" %}
{% set layoutVar = "Test var from <b>extendsIndex</b>" %}
{# --------- #}
{% block main %}Test block from <b>extendsIndex</b>{% endblock %}
What I'm expecting to see in output:
// index.html
TestVar: Test var from index
layoutVar: Test var from index
MainBlock: Test block from index
// extendsIndex.html
TestVar: Test var from extendsIndex
layoutVar: Test var from extendsIndex
MainBlock: Test block from extendsIndex
What we will see:
// index.html
TestVar: Test var from index
layoutVar: Test var from layout // I assume it had to be overridden by `index.nj` variables, isn't it?
MainBlock: Test block from index
// extendsIndex.html
TestVar: Test var from index // I assume it had to be overridden by `extendsIndex.nj` variables
layoutVar: Test var from layout // I assume it had to be overridden by `extendsIndex.nj` variables
MainBlock: Test block from extendsIndex
Once again, maybe I'm simply getting it wrong and child template shouldn't override parent variables. But it seems to me like it should, since it's defined as last, thus must be applied on top of all.
How in the world is you extendsIndex.nj working? You cannot extend multiple times, and if I try it I get an error: Template render error: compileExtends: cannot extend multiple times
extends executes templates from bottom to top. Meaning child templates are executed first, and then parent templates. This is because we need the blocks from the child templates, and we need the output of the parent templates.
This code:
{% set layoutVar = 'Test var from <b>layout</b>' %}
<p>TestVar: {{ testVar }}</p>
<p>layoutVar: {{ layoutVar }}</p>
Think of it in an imperative language: these statements are executed sequentially so you're going to see the result of this set no matter what. It would be bizarre if this code did not print the output of this immediate set, as that would make all order of operations undefined.
Hi
Believe it or not, but extends multiple time works for me! This isn't empty words, I'm using that technique in about 5 projects (all in production), where we have global, main layout, which extended by all other, and using more specific layouts, which extends global layout, for internal sections. Each internal section of that section extends that specific section layout.
Sounds kinda weird... Just for sake of illustration, here is one of projects, where multiple extnends works:
https://github.com/FAS/tgs/tree/master/source/layouts
Here is global layout:
https://github.com/FAS/tgs/blob/master/source/layouts/_layout.nj
Here is section-specific layout, which used as base for all it's childs, and which extends global layout:
https://github.com/FAS/tgs/blob/master/source/layouts/%D1%81%D0%B5%D1%80%D0%B2%D0%B8%D1%81/_entry.nj
And, at last, here is one of entries page, which extends that specific layout:
Those examples are quite simple, since they don't feature any complex markup and someone may say that extends are not mandatory here, but for complex static websites lack of multiple extends is 'no-way-to-go. It could be bypassed by partials and some macroses, but they are unable to carry global variables from parent to child — that's is wereextends` are much more useful and powerful.
I'm very surprised that actually it shouldn't work, while actually it works. I guess, it somehow related to the fact, that those projects using Grunt task for generating static pages. I'm must be lucky that I didn't need to use precompiled templates yet, which, as I understand, won't work in such case. From my point of view, it's quite great lose, especially when it comes to good sectioning of website.
Maybe there is a chances that it will be considered as one of features?
extends executes templates from bottom to top. Meaning child templates are executed first, and then parent templates. This is because we need the blocks from the child templates, and we need the output of the parent templates.
Think of it in an imperative language: these statements are executed sequentially so you're going to see the result of this set no matter what. It would be bizarre if this code did not print the output of this immediate set, as that would make all order of operations undefined.
Thanks a lot for detailed explanations. Though, it's sounds like a letdown too in terms of flexibility.
I guess that no matter what I will say, we eventually anyway will end up by following Jinja's specifications. Though, I'm unsure is Jinja's way of doing that is same.
I think that problem could be workarounded, by reapplying context of the child, so that it will override parent. It would give much better flexibility, since for now variables, defined in parent, are seems like static for childs, you can't do anything anymore with them except output, while in any other language, let's say, JavaScript, you would expect, that child should create scope, in which you would be able to modify that variable, but only in it's scope.
I landed multiple extends support while working in 2.0, but you filed this issue before that, and the compiler definitely used to throw when trying to extend multiple times. I really don't know how that ever worked. https://github.com/mozilla/nunjucks/blob/1d70879ead9552b9041e48d10c54b675282bfc5c/src/compiler.js#L981
I'll read through this again soon though.
Thank you for your time!
Well, we're using multiple extends in all our projects for about a year, I think. I remember I've started to use it approximately when macro started to inclusion in other macros.
So, I'm exploiting some kind of bug? Kinda weird that in such case it works as expected (except that childs can't override parent's variables, which, despite being limiting feature, seems to be according to specs).
@jlongster maybe nothing important, but I've forget to mention that this actually won't work and will throw cannot extend multiple times, which you were getting:
{% extends "_layout.nj" %}
{% extends "_index2.nj" %}
However, described chaining here https://github.com/mozilla/nunjucks/issues/406#issuecomment-107005866 works. At least, with mentioned Grunt task...
Oh, sure, you can definitely extend multiple times by creating multiple templates that each extend each other. You just cannot extend multiple times in the same template. In your original comment you had:
// extendsIndex.nj
{% extends "_layout.nj" %}
{# --------- #}
{% extends "index.nj" %}
{# --------- #}
{% set testVar = "Test var from <b>extendsIndex</b>" %}
{% set layoutVar = "Test var from <b>extendsIndex</b>" %}
{# --------- #}
{% block main %}Test block from <b>extendsIndex</b>{% endblock %}
which would throw that error, but I suspect you were putting them in separate files, which is fine.
Yeah, I now see what you mean in the original comment. Reading through it again now.
Ok, I see what you mean. A couple things going on.
First, it isn't really defined which order things are executed in child templates outside of blocks. I wouldn't perform sets there. Child templates are executed _first_ to get the blocks, and then the parent is rendered with the blocks. So those global sets are actually happening before the base template is run. That's you main problem.
Secondly, I don't think this would work anyway. If I'm remembering correctly, each template has it's own scope, and variable scoping acts like Python. (I don't actually love this, but it's the most sane when you think about, otherwise variables would leak out all over the place).
In Python, you don't declare variables, but the scope of a variable is defined by the first time you set it to something. In JS, you declare them with var. In nunjucks, it acts like Python, where the first set _introduces_ the variable and it's bound to that scope. So even if the parent template executed first, I still don't think it would see the sets by the child templates, unless in the parent template you did something like {% set textVar = "" %}.
Your best bet is to create an init block in the base template that child templates can override and call set, if you need to do that. You will also need to predeclare all variables that can be set by child templates.
Hello!
I'm also looking for an easy way to override variables in parent template.
Of course I can use an "initialization" block as @jlongster suggested, but it's too cumbersome in my opinion.
I've managed to achieve the desired result the following way:
{# main.nj #}
{% set title = title | default('Hello World') %}
<!DOCTYPE html>
<html>
<head>
<title>{{ title }}</title>
</head>
</html>
{# index.nj #}
{% extends 'main.nj' %}
{% set title = 'Index!' %}
It works fine with strings, however, when I try to use booleans it's not working for some reason.
{# main.nj #}
{% set show_footer = show_footer | default(true) %}
...
{# index.nj #}
{% extends 'main.nj' %}
{% set show_footer = false %}
What could be the problem?
Also, I think the idea of overriding parent variables is so useful and required in almost any project, so we should provide a more convenient and short way to do it. For example we could define variables that way:
{% set default show_footer = true %}
So, variables marked as default could be overridden in the child templates.
In Twig we were using this type of statement:
{% if show_footer is not defined %}
{% set show_footer = true %}
{% endif %}
It's also too bulky, but it was working perfectly.
@jlongster Thanks for detailed explanation, now I understand root of the issue.
@slavafomin I agree that overriding of parents' variables is crucial feature and it needs better way
Can someone confirm that it doesn't work in plain form in Jinja too?
If it isn't, I'm afraind we're out of luck here, since Nunjucks trying to follow Jinja pretty close.
I confirmed that your example works the same in Jinja as in Nunjucks. I don't think there is any possibility to change this behavior in a backwards-incompatible way, so this issue is closed.
This is quite old issue, but I hope that information will be useful for those, who faced same issue.
We've been using mentioned by @slavafomin method here https://github.com/mozilla/nunjucks/issues/406#issuecomment-130742522, but I wasn't happy about all those |default.
So, inspired by Grunt's config method, we've found alternative way to declare extandable "globals" in Kotsu even despite Nunjucks templates rendered in "backwards".
Here is custom config global function for Nunjucks: code snippet
The main beauty of config() is that it allows not only to define default plain variables, but also define extendable configurational Objects.
Just in case I'll forget to update link, here is a snippet of code in CoffeScript:
###*
* Define config in context of current template. If config has been already
* defined on parent, it will extend it, unless `merge` set to false.
* If no `value` will be provided, it will return value of specified property.
* Works similar to `grunt.config()`
* @param {string|array} prop Prop name or path on which should be set `value`
* @param {*} value Value to be set on specified `prop`
* @param {bool} merge = true Should config extend already existing same prop or no
* @return {*} Value of `prop` if no `value` specified
###
env.addGlobal 'config', (prop, value, merge = true) ->
ctx = this.ctx
ctxValue = _.get(ctx, prop)
valueIsArray = Array.isArray(value)
valueIsObject = typeof value == 'object' and value and not valueIsArray
# Set if `value` provided
if value != undefined
if not merge or not ctxValue
_.set(ctx, prop, value)
else
value = if valueIsObject then _.merge(value, ctxValue) else if valueIsArray then _.union(value, ctxValue) else ctxValue
_.set(ctx, prop, value)
return
# Get if no `value` provided
else return ctxValue
Here is example of set on global base layout config: example
And how it can be extended by childs: example
Please, note that actually you can avoid getter of this function and call values directly, since config() just setting value to specified property of the context.
In examples below page.title and config('page').title doing basically same thing. It may vary depending on you needs and configuration, though.
I'm just asking for a way to replace bulky constructs like this: {% set show_footer = show_footer | default(true) %} with something more sane like this {% set default show_footer = true %}. Probably, it's just a syntactic sugar, I don't see why it should break the BC with Jinja.
And in general, I'm not quite sure that Jinja should prevent us from innovating and implementing new features.
Most helpful comment
Hello!
I'm also looking for an easy way to override variables in parent template.
Of course I can use an "initialization" block as @jlongster suggested, but it's too cumbersome in my opinion.
I've managed to achieve the desired result the following way:
It works fine with strings, however, when I try to use booleans it's not working for some reason.
What could be the problem?
Also, I think the idea of overriding parent variables is so useful and required in almost any project, so we should provide a more convenient and short way to do it. For example we could define variables that way:
So, variables marked as
defaultcould be overridden in the child templates.In Twig we were using this type of statement:
It's also too bulky, but it was working perfectly.