Can you create components in Flask/Jinja to insert in various templates

Question:

Let’s say I make a really cool search box layout that I’d like to reuse

eg.

<div class='someClass'>
    <input class='fancyInput'>
</div>

Is it possible to reuse that snippet in other templates in the same way I can extend upon a template, but instead “import” a snippet so to speak. Like the reserve of `{% extend %}

I’d like to have html blocks that I can reuse but insert into different areas depending on the page.

Currently every time I want to use that block of HTML I have to hard code it in.

here’s a pseudo html/jinja example

The snippet

{% component fancyInput %} # not real jinja

<div class='someClass'>
    <input class='fancyInput'>
</div>

{% endcomponent %}

Then lets say on a random page somewhere

<html>
<body>
    <div class='container'><p>Some text!</p></div>
    {% import component fancyInput}
</body>
</html>

The rendered HTML would be

<html>
<body>
    <div class='container'>
        <p>Some text!</p>
    </div>
    <div class='someClass'>
        <input class='fancyInput'>
    </div>
</body>
</html>
Asked By: Ari

||

Answers:

Jinja2 uses macros. Once a Macro is defined, it can be called on to render elements.

So if you define a macro in a template like:

  {% macro newComponent(text) -%}
      <div class='container'><p>{{text}}</p></div>
  {%- endmacro %}

Then it can be called on in any file with

{{ newComponent('Insert Text') }}

Here is a link to the documentation

Also Stack Overflow post on macros
Parameterized reusable blocks with Jinja2 (Flask) templating engine

Answered By: Alex G

Use include to include whole template file in your current template, docs

Example from documentation:

{% include 'header.html' %}
    Body
{% include 'footer.html' %}
Answered By: Adrian Krupa

Macros are the standard way to do this (as answered before), but as our project grew bigger, it started to be bit messy.
I was trying different approaches to this, and finally I went with following:

Inspired by github ViewComponent library (for Rails), I started working with different "architecture" for reusable HTML code (can be named TemplateComponents as template in Flask == view elsewhere):

Components are classes, in my case located in app/components/.
Component itself does some logic and then renders jinja template (with minimum logic from app/templates/components/
They are used by helper functions (defined in same place), these helpers are registered by flask to be globally accessible, eg.(application.add_template_global(icon))

What are the benefits for me?

  • components as classes are easily testable
  • I can harness power of OOP.
  • logic in Python is (to me) more readable than in jinja2

Here’s example how it can be set up:

# app/__init__.py


def create_app():
    (...)
    from app.components import register_all_components

    register_all_components(application)
    (...)
# app/components/__init__.py

from .links import link_to, link_to_edit


def register_all_components(application):
    application.add_template_global(link_to)
    application.add_template_global(link_to_edit)
# app/components/links.py
from app.components import Component


# core
class Link(Component):
    def __init__(self, path, value, **kwargs):
        super(Link, self).__init__(**kwargs)
        self.path = path
        self.value = value


# helpers
def link_to(obj, **kwargs):
    path = obj.path_to_show()
    value = kwargs.pop("value", obj.default_value_link)

    return Link(path=path, value=value, **kwargs).render()


def link_to_edit(obj, **kwargs):
    from app.components import icon
    path = obj.path_to_edit()
    value = kwargs.pop("value", icon("edit"))

    return Link(path=path, value=value, **kwargs).render()
# app/components/base.py

from flask import render_template as render
from flask import Markup


class Component:
    def __init__(self, **kwargs):
        self.kwargs = kwargs
        self.kwargs["data"] = self.kwargs.pop("data", {})

    def render(self):
        return Markup(render(self.template, **self.attributes))

    @property
    def template(self):
        folder = getattr(self, "folder", f"{self.__class__.__name__.lower()}s")
        file = getattr(self, "file", f"{self.__class__.__name__.lower()}")

        return f"components/{folder}/{file}.html.j2"

    @property
    def attributes(self):
        # All public variables of the view are passed to template
        class_attributes = self.__class__.__dict__
        view_attributes = self.__dict__
        all_attributes = class_attributes | view_attributes
        public_attributes = {
            k: all_attributes[k] for k in all_attributes if not k.startswith("_")
        }

        # kwargs has higher priority, therefore rewrites public attributes
        merged_values = {**public_attributes, **self.kwargs}
        return merged_values
# app/templates/components/links/link.html.j2

<a
    href="{{ path }}"
    {% if class %} class="{{ class }}" {% endif %}
>{{ value }}</a>

This is super-early stage of how I manage this, just wanted to share it as I was for a long time thinking and changing how we manage this sustainably in our project.

Hope it gives some inspiration!

Answered By: janpeterka
Categories: questions Tags: ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.