yogthos / Selmer

A fast, Django inspired template system in Clojure.

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Include/extends + block interaction

mchughs opened this issue · comments

Let's say I have 3 html templates.

<!-- head.html -->
{% block foo %}
{% endblock %}
<!-- parent.html -->
{% include "head.html" %}
<!-- child.html -->
{% extends "parent.html" %}

{% block foo %}
  <div>bar</div>
{% endblock %}

I expect

(selmer.parser/render-file "child.html" {}) => "<div>bar</div>"

but I get

(selmer.parser/render-file "child.html" {}) => ""

My intuition of "includes" would be that any blocks written in the included html would be made available to children extending the parent.

The use case for this is imagine there is a main index file which includes a head tag containing all my scripts. I extend index into a few different page types, some of which may need to add additional head scripts specific to those pages.

Do you have any idea how Django handles this?

I'm not sure I follow the issue. I've made three files locally:

  • foo.html
{% block foo %}
<div>{{x}}</div>
{% endblock %}
  • bar.html
{% include "foo.html" %}
  • baz.html
{% extends "bar.html" %}

When I run (selmer.parser/render-file "baz.html" {:x "x"}) I see "\n<div>x</div>\n" which is what I'd expect. The block from foo.thml included in bar.html is being rendered when bar.html is extended by baz.html.

Permit me a bit more full bodied in my example.

<!-- head.html -->
<head>
  {% block page-specific-header-scripts %}
  {% endblock %}
  <script src="some/common/page/script" />
  <script src="another/common/page/script" />
</head>
<!-- parent.html -->
{% include "head.html" %}

<body>
  <div>my body</div>
</body>
<!-- child-foo.html -->
{% extends "parent.html" %}

{% block page-specific-header-scripts %}
  <script src="my/page/foo/script" />
{% endblock %}
<!-- child-bar.html -->
{% extends "parent.html" %}

{% block page-specific-header-scripts %}
  <script src="my/page/bar/script" />
{% endblock %}

The result of running of either

(s/render-file "child-foo.html" {})

or

(s/render-file "child-bar.html" {})

are both

<head>
  <!-- I expect my page-specific script to appear here but it doesn't-->
  <script src=\"some/common/page/script\" />
  <script src=\"another/common/page/script\" />
</head>
<body>
  <div>my body</div>
</body>

Since what I'm trying to substitute into the block is html I think it doesn't make sense to pass it as a variable in the argument map of render-file as you did with "x" in your example.

Maybe there is another construction which aims at getting me my desired state?
I can anticipate you may suggest to pass the src string as the variable for each page, but if I wanted each page foo and bar to pass totally different html, I think that would not work.

Without having looked closely at the source code, it appears like the block defined in head.html is hidden behind the include and therefor, pages extending parent cannot use this block as a substitution point.

Ah ok, I see your use case now. The problem is that the include block gets processed eagerly here, so the blocks won't be visible in the child.

I think it would be a good enhancement to handle the blocks from includes in the parent, but I'm not sure if I'll get a chance to look at this in the near future. If you'd be open to doing a PR I can help guide that.

Yeah, sure. Happy to take a crack at it.

Awesome!

Could you let me know if there is anything special in how you run your tests?

If I run a simple lein test at the root of the project folder I get

Ran 95 tests containing 373 assertions.
6 failures, 0 errors.
Tests failed.

And I see that those 6 failures are all related to the (filter-date)

FAIL in (filter-date) (core_test.clj:782)
expected: (= "2014 Mar 1" (render "{{d|date:longDate:en_US}}" {:d firstofmarch}))
  actual: (not (= "2014 Mar 1" "March 1, 2014"))

If you can take a look at #240 and let me know what you think.

It should just be lein test, maybe it's the version of the JVM that's having an issue with the date filter?

The PR looks great, and the tests pass locally for me. I just pushed out 1.12.29 with the fix.

Cool, I'll upgrade to the new version tomorrow and confirm I achieved the use case I wanted.

After Upgrading I'm getting a new error which I suspect is related to having an include tag nested in a block tag. Something like

{% block foo %}
{% include "bar.html" %}
{% endblock %}

Later today I'll try to find a minimally reproducible case and add it to the tests.

Ah this might be related to the order of things being injected. Since the includes are now being handled eagerly. With the previous approach all the blocks would be injected first, and then includes handled as the last pass over the synthesized template. What might be happening now is that a block gets injected and its include doesn't get parsed. So, likely the solution is to check for include in blocks as they're injected and handle it there.

Sorry about the PR spam. I'm not use to github's UI and thought I had time to add another commit. I just wanted to decouple the test templates so they are meaningful and targeted for the tests.

I'll take another look tomorrow if I looked over another edge case. Thanks for your patience on this.

No worries, the PRs look good and I'm good to push out a new release if everything looks good to you.

Sounds good to me. I'll keep the issue open until I can verify tomorrow that I haven't introduced any other loose ends.

Sounds good, and 1.12.30 is up on Clojars.