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>
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
Use include
to include whole template file in your current template, docs
Example from documentation:
{% include 'header.html' %}
Body
{% include 'footer.html' %}
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!
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>
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
Use include
to include whole template file in your current template, docs
Example from documentation:
{% include 'header.html' %}
Body
{% include 'footer.html' %}
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!