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 variable to a template #1

Closed
GildedHonour opened this issue Mar 20, 2017 · 13 comments
Closed

passing variable to a template #1

GildedHonour opened this issue Mar 20, 2017 · 13 comments

Comments

@GildedHonour
Copy link

from the documentation:

<!DOCTYPE html>
<html>
    <head>
        <title>{{ title }}</title>
    </head>
    {# This is a comment. Comments are removed from the output. #}
    <body>
        <menu id="nav-main">
        {% for item in navigation %}
            <li><a href="{{ item.url }}">{{ item.label }}</a></li>
        {% endfor %}
        </menu>
        <div class="layout-content-main">
            <h1>{{ title }}</h1>
            {{ body }}
        </div>
    </body>
</html>

how am I supposed to pass these variables -- title, naviagation, body -- to this template?

@tdammers
Copy link
Owner

This happens on the Haskell side of things; you need to pass context variables in when calling runGinger. http://hackage.haskell.org/package/ginger-0.5.1.2/docs/Text-Ginger-Run.html explains things in some more detail; however, essentially what you need to do is write a context lookup function which returns a suitable GVal value for each variable name, then you use one of the makeContext functions to turn it into a GingerContext, and then you pass that to the runGinger(M) function. E.g.:

let contextLookup varName =
           case varName of
               "username" -> toGVal username
               "imageURL" -> toGVal imageURL
               _ -> def -- def for GVal is equivalent to a NULL value
    context = makeContext contextLookup
htmlSource $ runGinger context template

Alternatively, you can write your context object as any dictionary-like data type that has a ToGVal instance, and use the easyRender(M) API.

@tdammers
Copy link
Owner

This should probably be fixed in the documentation.

@GildedHonour
Copy link
Author

Is there any way to create a parent/child template?

@tdammers
Copy link
Owner

Sure. You have two options here: {% include %} and {% extend %}/{% block %}.

{% include %} is just a dumb inclusion mechanism; it picks up the file you reference and injects it at the current position, similar to how #include works in C.

{% extends %} and {% block %} implement "template inheritance". It works as follows:

  • First, you write a parent template. This is just a regular template, but you can wrap blocks that you want to be able to override in {% block block_name %} / {% endblock %}.
  • Then you write the child template; this is not a regular template, it starts with an {% extends %} statement, and after that, only {% block %} statements are allowed, which override blocks of the same name from the parent template.

For example, you might write a parent template like so:

<!DOCTYPE html>
<html>
    <head>
        <title>{% block title %}My Super Fun Site{% endblock %}</title>
    </head>
    <body>
        <header>My Super Fun Site</header>
        <main>
            <h1>{% block main_header %}Hello!{% endblock %}</h1>
            {% block content %}
            {% endblock %}
        </main>
        <footer>
            Copyright (c) 2017 myself. All Rights Reserved.
        </footer>
    </body>
</html>

And then you can have a child template that overrides some of these blocks:

{% extends 'master.html' %}

{%- block title %}This is a different title.{% endblock -%}

{%- block content %}
Lorem ipsum dolor sit amet adipiscing elit. And so on.
{% endblock -%}

This will render a page using the same structure as the master template, but overriding the title and content blocks.

In order for this to work correctly, you need to tell the template loader how to load the parent template; the CLI contained in the main repo demonstrates this; you need to pass an IncludeResolver to parseGinger (or parseGingerFile), which is essentially a function that takes a file name and monadically return source code. The actual type is SourceName -> m (Maybe Source), with Monad m; however both Source and SourceName are aliases for String, and m is usually IO or something similar.

Hope that clears things up.

@GildedHonour
Copy link
Author

thanks, I'll try.

@GildedHonour
Copy link
Author

GildedHonour commented Mar 23, 2017

how is your library better compared to https://github.com/sourrust/karver ?

@GildedHonour
Copy link
Author

@tdammers up

@tdammers
Copy link
Owner

how is your library better compared to https://github.com/sourrust/karver ?

Well, the most important differences should be obvious (ish) from the API and its types; anyway, from what I can gather from Karver's documentation, here's a (probably incomplete) list of what Ginger does differently (though whether that is necessarily better is debatable and depends on priorities and context):

  • Effectful templates. This was a big one for me, because I want template to be able to trigger effects such as loading additional data from the backend, getting the current time, etc. At the same time, I still wanted to keep things sandboxable, so Ginger doesn't just come with a truckload of I/O primitives, instead it allows you to parametrize your template loaders and runners over arbitrary monads, and then you can pass your effectful functions in as context variables.
  • Multi-tagged unitype context variables. For lack of a better word. Karver's context object is a hashmap of Text to Text; Ginger has a more sophisticated unitype that can express functions, lists, hashmaps, numbers, booleans, null, strings, and encoded HTML. The last one is especially important, because being able to tag HTML as "already encoded" means you hardly ever have to worry about XSS anymore. It also allows us to do things like write a |markdown filter that takes a raw string, interprets it as markdown, and spits out pre-encoded HTML - without the tag-level distinction between text and HTML, the template engine would HTML-encode the markdown output, which is not what you want.
  • Separate parsing and running steps. This is important for two reasons; one, you can cache parsed templates, avoiding a re-parse on future usages (and you can precompile all your templates at startup, reducing per-request overhead further, and catching template syntax errors early); and two, you can control effects for the parsing and running stages individually, that is, you can have effectful parses (for loading external templates, e.g. using {% include %}) but keep the running stage pure, or you can hook extra effects into the running stage that you don't need for parsing, or you can use an alternative loader mechanism (such as an in-memory hashmap of template names to template sources) for the parser while keeping the interpreter in plain IO.
  • More complete implementation of the Jinja2 language. Ginger implements most of the core Jinja2 syntax; I deviated only with features that don't make sense in a language like Haskell (the "blatant Pythonisms" mentioned elsewhere), or where I truly believe that making things simpler would be better (this is why filters, functions, and tests are all the same thing in Ginger - {{ foo|bar }} and {{ bar(foo) }} compile to exactly the same thing).
  • More programming features in the template language. Ginger is, in fact, a full-blown dynamic programming language; you can define your own functions, there's variables with scopes and all, there's a complete expression sublanguage (and it's used in every place you'd expect to put a variable, so you can, for example, loop over the output of a function that you call on a sublist of a context variable filtered through a function returned from another function call, if you want), you can even drop into "script mode" and write a Ginger flavor that's similar to a Python/JavaScript hybrid.

The only real problem I see with Karver is that it uses unsafePerformIO to load included templates from disk - IMO this is really really really not what you should use unsafePerformIO for.

Hope that clears things up a little.

@GildedHonour
Copy link
Author

thanks.

@GildedHonour
Copy link
Author

GildedHonour commented May 3, 2017

Is there a MVP of returning an html file to a client?

in this code, how is "index.html", assuming that's the name of an html file, supposed to be passed to it?

loadFile fn = openFile fn ReadMode >>= hGetContents

loadFileMay fn =
    tryIOError (loadFile fn) >>= \e ->
         case e of
            Right contents ->
                return (Just contents)
            Left err -> do
                print err -- remove this line if you want to fail silently
                return Nothing

@GildedHonour
Copy link
Author

that's not a library, but a pain. part of code here, part there, setup this, adjust that. how to run it after all? in the documentation -- nothing.

in https://github.com/sourrust/karver everything is comprehensible.

@tdammers
Copy link
Owner

tdammers commented May 3, 2017 via email

@tdammers
Copy link
Owner

tdammers commented May 4, 2017

Also, this has long gone beyond the scope of "how am I supposed to pass these variables -- title, naviagation, body -- to this template?", so I'm closing this.

@tdammers tdammers closed this as completed May 4, 2017
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

No branches or pull requests

2 participants