enthus1ast / nimja

typed and compiled template engine inspired by jinja2, twig and onionhammer/nim-templates for Nim.
MIT License
185 stars 11 forks source link

Look at template fragments #82

Closed enthus1ast closed 1 month ago

enthus1ast commented 2 years ago

https://htmx.org/essays/template-fragments/

greenersharp commented 8 months ago

Any plans to add fragments to Nimja ?

enthus1ast commented 8 months ago

I think you can use the self variable for this: https://github.com/enthus1ast/nimja?tab=readme-ov-file#self-variable

greenersharp commented 1 month ago

Circling back to this, is there any chance you can show an example of how Nimja can compile a partial template? I read the docs but I still can't figure out how to render just a partial/fragment of a larger Nimja template.

I am currently using Python for this, with the flask partial library, but I would love to move everything over to Nim/Nimja if possible.

Thank you!

enthus1ast commented 1 month ago

@greenersharp can you show an example what you want to archive?

Maybe even with code from your jinja2 code.

enthus1ast commented 1 month ago

When i look at the fragment library i think you can do the same with nimja.

You currently cannot bind a context to importnimja (you can with compileTemplateFile). But you can just define new variables in a scope (which is like a nim block). And since child templates have access to the outer variables (in fact the same scoping rules like in nim apply) this should just do it.

So for example:

foo
{% for entry in entries %}
  {% scope %}
    {% let user = entry.user %}
    {% let another = entry.another %}
    {% importnimja "templates/partials/_oneUser.nimja" %}
  {% end %}
{% endfor %}
baa

The benefit of using scope is, that you do not pollute the outer template with vars. In the above example it would not make much sense, because the iteration body leaves scope any how. But in general using scope works good for this and i use it quite often.

_oneUser.nimja

{# renders one user #}
{{ user.name }}
{{ another }}

And if you want to create a "one user" page:

oneUser.nimja

{% extends some/base.nimja %}
{% block content %}
    {# get the user somehow #}
    {% let user = getUserFromDb() %}
    {% let another = getOtherFromDb() %}
    {% importnimja "templates/partials/_oneUser.nimja" %}
{% endblock %}

Is this what you need?

greenersharp commented 1 month ago

Thanks for the reply!

Here is the python library I was referring to: https://github.com/sponsfreixes/jinja2-fragments

It's commonly used alongside HTMX to render small pieces of a larger template. The link above gives a simple example of using the render_block method to render one small piece of a larger template.

It really helps cut down the number of template files in a project.

enthus1ast commented 1 month ago

@greenersharp have you seen my other message? Is that what you need?

enthus1ast commented 1 month ago

ah i see: return render_block("page.html.jinja2", "content", magic_number=42)

yeah i think this could actually be nice.

greenersharp commented 1 month ago

Awesome! The feature would pair really well with HTMX.

enthus1ast commented 1 month ago

I am not 100% sure how it should behave though, but i'll have a closer look.

enthus1ast commented 1 month ago

hey @greenersharp if you like you can test the change.

edit: templateFragment branch https://github.com/enthus1ast/nimja/tree/templateFragment

I'm still in the process of writing tests for it ( https://github.com/enthus1ast/nimja/blob/d9b7682d91e021608c5353392849d16538dd48ac/tests/basic/test_fragments.nim )

the usage:

you call compileTemplateStr or compileTemplateFile like normal, but now you can specify blockToRender then it should just render this one block.

    proc foo(blockToRender: static string): string =
      compileTemplateStr("""
        BUG
        {% block first %}first block{% endblock %}
        {% block second %}second block{% endblock %}
        BUG
      """, blockToRender = blockToRender)

    check "first block" == foo("first")
    check "second block" == foo("second")
enthus1ast commented 1 month ago

Ah btw, if it works for you, are you interested to write an "real world" example for this? For the example folder? Maybe even with htmx (if you can condense the stuff in a small self contained example).

greenersharp commented 1 month ago

Great, thank you so much, I'll definitely try and get a simple working example going

enthus1ast commented 1 month ago

While doing this PR i also played with htmx (for the first time). And i like it.

greenersharp commented 1 month ago

If you write even a very simple example SPA, like a to-do app in vuejs or react, you end up with thousands of lines of boilerplate code.

With HTMX all that boilerplate code is gone. It's really refreshing.

And the nimja/htmx combo might very well be the fastest out there.

I'm traveling at the moment but I'll definitely work on the parital example !

On Mon, Sep 16, 2024, 8:32 PM David Krause @.***> wrote:

While doing this PR i also played with htmx (for the first time). And i like it.

— Reply to this email directly, view it on GitHub https://github.com/enthus1ast/nimja/issues/82#issuecomment-2353628614, or unsubscribe https://github.com/notifications/unsubscribe-auth/AZABNLWOQVR4FY4ZSUSXXKDZW4P5XAVCNFSM6AAAAAAQME5FPGVHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMZDGNJTGYZDQNRRGQ . You are receiving this because you were mentioned.Message ID: @.***>

greenersharp commented 1 month ago

It works amazingly well. Thank you so much for adding this feature!

I'm new to nim and still learning, but does 'blocktorender' need to be a static string? it would be great if it could be mutable if possible.

Imagine a use case where the rendered block (if any) is conditional on a url query parameter, something like this:

let url = request.uri.parseUrl
var block = ""
if "block" in url.query:
    block = url.query["block"]
request.respond(200, headers, tmplf("index.nimja",baseDir = getScriptDir(),blocktorender=block))

This way I only need to write request.respond once, and I either leave blocktorender empty if I want the whole page rendered, or I set it to a string if I need a partial.

Right now my code looks like this:

if block:
   request.respond(200, headers, tmplf("index.nimja",baseDir = getScriptDir(),blocktorender=block))
else:
   request.respond(200, headers, tmplf("index.nimja",baseDir = getScriptDir()))

Thank you

enthus1ast commented 1 month ago

Yes it's a static string, I don't think there is a way around this unfortunately. It needs to compile a fresh proc for you when you change the parameters. Then on runtime it's fast.

Nevertheless, I'll look into this problem.

I can imagine that a case statement could work, where you list all the routes explicitly.

Edit: and/or you could create a macro or template, that create the different routes for you.