Flask Macros
In this tutorial, you'll learn about Flask macros - a powerful feature of Jinja2 templates that allows you to create reusable code blocks in your templates.
Introduction to Macros
Have you ever found yourself copying and pasting the same HTML code across multiple templates? Perhaps you have a form input, a card component, or a navigation element that gets used repeatedly. This is where macros come to the rescue.
Macros in Flask (powered by Jinja2) are similar to functions in programming languages. They allow you to define reusable template code blocks that can be called multiple times with different parameters. This promotes DRY (Don't Repeat Yourself) principles in your template code.
Basic Syntax of Macros
Here's the basic syntax for defining and using macros in Flask templates:
{% macro name(parameters) %}
<!-- Template code -->
{% endmacro %}
To call a macro:
{{ name(arguments) }}
Creating Your First Macro
Let's start with a simple example - a macro that generates a form input field:
{% macro input_field(name, label, type="text", value="", required=False) %}
<div class="form-group">
<label for="{{ name }}">{{ label }}</label>
<input type="{{ type }}"
id="{{ name }}"
name="{{ name }}"
value="{{ value }}"
class="form-control"
{% if required %}required{% endif %}>
</div>
{% endmacro %}
This macro creates a standardized form input field with a label. Notice how we've defined parameters with default values, making some of them optional.
Using the Macro
To use this macro in your template:
<form method="post">
{{ input_field('username', 'Username', required=True) }}
{{ input_field('password', 'Password', type='password', required=True) }}
{{ input_field('email', 'Email Address', type='email') }}
<button type="submit" class="btn btn-primary">Submit</button>
</form>
This will generate:
<form method="post">
<div class="form-group">
<label for="username">Username</label>
<input type="text" id="username" name="username" value="" class="form-control" required>
</div>
<div class="form-group">
<label for="password">Password</label>
<input type="password" id="password" name="password" value="" class="form-control" required>
</div>
<div class="form-group">
<label for="email">Email Address</label>
<input type="email" id="email" name="email" value="" class="form-control">
</div>
<button type="submit" class="btn btn-primary">Submit</button>
</form>
Storing Macros in Separate Files
For better organization, you can store your macros in dedicated files. Create a file named macros.html
in your templates folder:
{# macros.html #}
{% macro input_field(name, label, type="text", value="", required=False) %}
<div class="form-group">
<label for="{{ name }}">{{ label }}</label>
<input type="{{ type }}"
id="{{ name }}"
name="{{ name }}"
value="{{ value }}"
class="form-control"
{% if required %}required{% endif %}>
</div>
{% endmacro %}
{% macro alert(message, type="info") %}
<div class="alert alert-{{ type }}">
{{ message }}
</div>
{% endmacro %}
Then import and use these macros in your templates:
{# form.html #}
{% import 'macros.html' as forms %}
<form method="post">
{{ forms.input_field('username', 'Username', required=True) }}
{{ forms.input_field('password', 'Password', type='password', required=True) }}
{{ forms.alert('Please fill out all required fields', 'warning') }}
<button type="submit">Submit</button>
</form>
Advanced Macro Techniques
Macros with Content Blocks
You can create macros that accept content blocks using the caller()
function:
{% macro card(title) %}
<div class="card">
<div class="card-header">
<h3>{{ title }}</h3>
</div>
<div class="card-body">
{{ caller() }}
</div>
</div>
{% endmacro %}
Use it with the call
block:
{% import 'macros.html' as components %}
{% call components.card('User Profile') %}
<p>Username: johndoe</p>
<p>Email: [email protected]</p>
<button class="btn">Edit Profile</button>
{% endcall %}
Output:
<div class="card">
<div class="card-header">
<h3>User Profile</h3>
</div>
<div class="card-body">
<p>Username: johndoe</p>
<p>Email: [email protected]</p>
<button class="btn">Edit Profile</button>
</div>
</div>
Macros with Keyword Arguments
You can use kwargs
to pass additional attributes to HTML elements in your macros:
{% macro button(text, type="button", **kwargs) %}
<button type="{{ type }}" {% for key, value in kwargs.items() %}{{ key }}="{{ value }}"{% endfor %}>
{{ text }}
</button>
{% endmacro %}
Usage:
{{ button('Save', type='submit', class='btn btn-primary', data_toggle='modal') }}
Output:
<button type="submit" class="btn btn-primary" data_toggle="modal">
Save
</button>
Real-World Example: A Complete UI Component Library
Let's create a more comprehensive example - a UI component library with macros for common frontend elements.
Create a file named components.html
:
{# components.html #}
{# Navigation bar macro #}
{% macro navbar(items, active_item="", brand="My Website") %}
<nav class="navbar navbar-expand-lg navbar-light bg-light">
<a class="navbar-brand" href="/">{{ brand }}</a>
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarNav">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarNav">
<ul class="navbar-nav">
{% for item in items %}
<li class="nav-item {{ 'active' if item.name == active_item else '' }}">
<a class="nav-link" href="{{ item.url }}">{{ item.name }}</a>
</li>
{% endfor %}
</ul>
</div>
</nav>
{% endmacro %}
{# Card component macro #}
{% macro card(title, content="", footer="", image="") %}
<div class="card" style="width: 18rem;">
{% if image %}
<img src="{{ image }}" class="card-img-top" alt="{{ title }}">
{% endif %}
<div class="card-body">
<h5 class="card-title">{{ title }}</h5>
<p class="card-text">{{ content }}</p>
</div>
{% if footer %}
<div class="card-footer text-muted">
{{ footer }}
</div>
{% endif %}
</div>
{% endmacro %}
{# Pagination macro #}
{% macro pagination(current_page, total_pages, url_for_page) %}
<nav>
<ul class="pagination">
<li class="page-item {{ 'disabled' if current_page == 1 else '' }}">
<a class="page-link" href="{{ url_for_page(current_page - 1) if current_page > 1 else '#' }}">Previous</a>
</li>
{% for page in range(1, total_pages + 1) %}
<li class="page-item {{ 'active' if page == current_page else '' }}">
<a class="page-link" href="{{ url_for_page(page) }}">{{ page }}</a>
</li>
{% endfor %}
<li class="page-item {{ 'disabled' if current_page == total_pages else '' }}">
<a class="page-link" href="{{ url_for_page(current_page + 1) if current_page < total_pages else '#' }}">Next</a>
</li>
</ul>
</nav>
{% endmacro %}
Now you can use these components in your templates:
{% import 'components.html' as ui %}
<!DOCTYPE html>
<html>
<head>
<title>My Flask App</title>
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css">
</head>
<body>
{{ ui.navbar([
{'name': 'Home', 'url': '/'},
{'name': 'About', 'url': '/about'},
{'name': 'Contact', 'url': '/contact'}
], active_item='Home') }}
<div class="container mt-4">
<div class="row">
<div class="col-md-4">
{{ ui.card(
title='Getting Started',
content='Learn how to use our product with this quick tutorial.',
image='/static/images/tutorial.jpg'
) }}
</div>
<div class="col-md-4">
{{ ui.card(
title='Documentation',
content='Browse our comprehensive documentation.'
) }}
</div>
<div class="col-md-4">
{{ ui.card(
title='Community',
content='Join our thriving community of developers.',
footer='Last updated: May 2023'
) }}
</div>
</div>
<div class="mt-4">
{{ ui.pagination(2, 5, lambda page: '/posts?page=' + page|string) }}
</div>
</div>
<script src="https://code.jquery.com/jquery-3.5.1.slim.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.bundle.min.js"></script>
</body>
</html>
Benefits of Using Macros
- Code Reusability: Write once, use many times.
- Consistency: Ensure UI elements maintain the same look and behavior.
- Maintainability: Update a component in one place, and changes apply everywhere.
- Cleaner Templates: Your templates become more concise and easier to read.
- Separation of Concerns: Organize your UI components separately from the content.
Common Use Cases for Macros
- Form elements (inputs, checkboxes, selects)
- UI components (cards, alerts, modals)
- Navigation elements (menus, breadcrumbs)
- Pagination controls
- Table generation
- Social media sharing buttons
- Comment or review components
Summary
Macros in Flask templates provide a powerful way to create reusable components in your web application. By defining UI elements as macros, you can maintain consistent styling, reduce redundancy, and make your templates more maintainable. Think of macros as your template-level component library.
Key points to remember:
- Macros work like functions for your templates
- They can accept parameters and have default values
- Macros can be stored in separate files and imported when needed
- Advanced macros can include content blocks and accept keyword arguments
By incorporating macros into your Flask templates, you'll speed up development and create more maintainable applications.
Additional Resources
- Jinja2 Template Designer Documentation
- Flask Documentation
- Bootstrap Documentation (for the component examples)
Exercises
- Create a macro for a form select dropdown that takes an array of options and marks one as selected.
- Design a macro for a file upload input with preview functionality.
- Build a complete form macro that generates a form with validation messages.
- Create a macro for a tabbed interface that accepts multiple content blocks.
- Design a responsive image gallery macro that arranges images in a grid.
If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)