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

Using blocks more than once? #158

Closed
NocturnalSolutions opened this issue Dec 15, 2017 · 8 comments · Fixed by #182
Closed

Using blocks more than once? #158

NocturnalSolutions opened this issue Dec 15, 2017 · 8 comments · Fixed by #182
Milestone

Comments

@NocturnalSolutions
Copy link

NocturnalSolutions commented Dec 15, 2017

layout.stencil:

<!doctype html>
<html>
  <head>
    <title>My Music Collection: {% block pageTitle %}{% endblock %}</title>
  </head>
  <body>
    <h1>My Music Collection</h1>
    <h2>{% block pageTitle %}{% endblock %}</h2>
    {% block pageContent %}{% endblock %}
  </body>
</html>

hello.stencil:

{% extends "layout.stencil" %}

{% block pageTitle %}Hello!{% endblock %}

{% block pageContent %}
<p>
  Hello, {{ name|default:"World" }}!
</p>
{% endblock %}

So my ideal result would be:

<!doctype html>
<html>
  <head>
    <title>My Music Collection: Hello!</title>
  </head>
  <body>
    <h1>My Music Collection</h1>
    <h2>Hello!</h2>
    <p>
      Hello, World!
    </p>
  </body>
</html>

I think this is a natural way to have a "subtitle" that appears both in the page content and in the <title> tag, but it doesn't seem to work - the second pageTitle block isn't filled in. So the result looks like:

<h1>My Music Collection</h1>
<h2></h2>

Not sure if this counts as a bug. If not, please consider it a feature request.

@ilyapuchka
Copy link
Collaborator

ilyapuchka commented Dec 22, 2017

@NocturnalSolutions I have a solution for that but I'm not sure it's a right way to use blocks. In your example imagine that you will change {% block pageTitle %}Hello!{% endblock %} to {% block pageTitle %}{{ block.super }}Hello!{% endblock %}. This should render the content of this block from layout.stencil, but there are two of them and they may be different, what we should pick?

If I understand correctly in your case you are using blocks as a placeholders for something that is defined in extended templates. But I think blocks were originally designed to support another use case - extending their content in extended templates.

@kylef what do you think about that? Here is the change that will fix this issue. Here I'm pushing back already rendered node from extended template so that it can be used again by base template. All tests are passing but maybe we are missing some edge case where it can lead to infinite recursion.

diff --git a/Sources/Inheritence.swift b/Sources/Inheritence.swift
index b9bf87a..4f37022 100644
--- a/Sources/Inheritence.swift
+++ b/Sources/Inheritence.swift
@@ -129,6 +129,7 @@ class BlockNode : NodeType {
 
   func render(_ context: Context) throws -> String {
     if let blockContext = context[BlockContext.contextKey] as? BlockContext, let node = blockContext.pop(name) {
+      defer { blockContext.push(node, forKey: name) }
       let newContext: [String: Any] = [
         BlockContext.contextKey: blockContext,
         "block": ["super": try self.render(context)]

@ilyapuchka
Copy link
Collaborator

Actually tests do fail with this change, so I'm even less sure about it.

@ilyapuchka
Copy link
Collaborator

Jinja2 solves this this way:

If you want to print a block multiple times, you can, however, use the special self variable and call the block with that name:

<title>{% block title %}{% endblock %}</title>
<h1>{{ self.title() }}</h1>
{% block body %}{% endblock %}

We can do something similar, i.e. by storing all blocks in blocks context variable and make them resolvable. @kylef what do you think?

@NocturnalSolutions
Copy link
Author

NocturnalSolutions commented Dec 27, 2017

Requiring new syntax just for printing a block a second time, while better than nothing, still strikes me as odd, though. Why can't I just use the same syntax in both places? Am I really that odd for wanting/expecting this?

Aside from the example I give in the OP of repeating a title both in <title> and in a page header, I can think of another example where you might want to use a block twice on a page; you have a pager ("< Prev 1 2 3 4 Next >") that you want to display both at the top and bottom of a very large page.

Yes, I can work around this by using variables, but there are cases where that would clearly break the concept of code/design separation I'm supposed to gain by using a template engine.

Perhaps there are other workarounds I could use that I haven't discovered yet, but what I'm hoping for in the OP seems to me to be the simplest solution from the perspective of the template author.

@ilyapuchka
Copy link
Collaborator

ilyapuchka commented Dec 27, 2017

@NocturnalSolutions I tried to explain in my previous comment why the same syntax can't be used in the way you want it, it creates ambiguity in inheritance. The syntax that is used to define a block can't be the same as syntax used to call this block later, the same way as syntax for defining and calling functions are different in programming languages.
I didn't look at it close enough yet, just came across it going through Jinja2 docs, but it seems that using separate syntax is a good solution for this problem. If @kylef will agree I'll be glad to try to implement this in one or another way.

@svanimpe
Copy link
Member

svanimpe commented Jan 3, 2018

@NocturnalSolutions Doesn't a variable make more sense here?

<!doctype html>
<html>
  <head>
    <title>My Music Collection: {{ pageTitle }}</title>
  </head>
  <body>
    <h1>My Music Collection</h1>
    <h2>{{ pageTitle }}</h2>
    {% block pageContent %}{% endblock %}
  </body>
</html>

The only difference is that you specify the value in your rendering context, not on the page itself. I do something like that in https:/svanimpe/swift-blog.

@NocturnalSolutions
Copy link
Author

Sure, that works in that case. But what about the more complex example I put in my OP of a pager you want to appear at both the top and bottom of a page?

I suppose as a workaround you could render a template to a string that you then use as a variable. I still think just being able to reuse blocks would be a more pleasant solution, though.

@svanimpe
Copy link
Member

In your OP, the content of block pageTitle is just some text, which is why I suggested a variable.
If you want an entire block of HTML, you could try an include tag? I think that's the only way you can include the same snippet multiple times.

I view blocks as something similar to method overrides with subclassing. That is: you define a default in the parent, and optionally override it in a child. So not simply reusable blocks. I think that's what the include tag is for.

@djbe djbe added this to the 0.15.0 milestone Jul 28, 2022
@djbe djbe closed this as completed in #182 Jul 28, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

4 participants