Macros ====== When the same block of HTML appears in several places you can extract it into a *macro* - a reusable template fragment that you call like a function. This page shows how to define, call and organize them. **Key topics** * Defining a macro with ``macro`` / ``endmacro``. * Calling a macro and passing arguments. * Content macros vs attribute macros. * Practical examples for calendar tables and day styling. * Where to place macros in your template. Defining a macro ---------------- A macro starts with ``%% macro name(parameters)`` and ends with ``%% endmacro``. Everything between them is the macro body. **Template:** .. code-block:: html+jinja %% macro greeting(name)

Hello, {{ name }}!

%% endmacro {{ greeting("Alice") }} {{ greeting("Bob") }} **Output:** .. code-block:: html

Hello, Alice!

Hello, Bob!

The macro is called with ``{{ greeting("Alice") }}``. The argument ``"Alice"`` is assigned to the parameter ``name`` inside the body. Multiple parameters ~~~~~~~~~~~~~~~~~~~ Macros can accept more than one parameter: **Template:** .. code-block:: html+jinja %% macro badge(label, color) {{ label }} %% endmacro {{ badge("Weekend", "red") }} {{ badge("Workday", "black") }} **Output:** .. code-block:: html Weekend Workday Content macros vs attribute macros ---------------------------------- A *content macro* returns a chunk of HTML that you insert into the page: .. code-block:: html+jinja %% macro month_table(month) %% for week in month.table %% for day in week %% if day is not none {{ day }} %% else %% endif %% endfor %% endfor %% endmacro You call it where you want the rows to appear: .. code-block:: html+jinja {{ month_table(january) }}
An *attribute macro* returns a small fragment meant to be placed inside an HTML tag - for example a ``style`` attribute: .. code-block:: html+jinja %% macro day_style(day) {% if day.is_off_day %} style="color: #C00000"{% endif %} %% endmacro You call it inside the opening tag with a special ``{{-`` syntax that strips the whitespace before the output: .. code-block:: html+jinja {{ day.short_name }} **Output for a weekday:** .. code-block:: html Mon **Output for Saturday:** .. code-block:: html Sat The ``-`` after ``{{`` removes the space that would otherwise appear before ``style``. Without it you would get ````. .. tip:: Use ``{{-`` (with the dash) when inserting a macro result directly into an HTML tag to avoid extra spaces. Combining macros ---------------- Macros can call other macros. For example, a ``month_table`` macro can use ``day_style`` to color off-days: .. code-block:: html+jinja %% macro day_style(day) {% if day.is_off_day %} style="color: #C00000"{% endif %} %% endmacro %% macro month_table(month) %% for week in month.table %% for day in week %% if day is not none {{ day }} %% else %% endif %% endfor %% endfor %% endmacro If you later want to change how off-days look you only edit ``day_style`` once and every table that calls it picks up the change. Where to place macros --------------------- Define all macros before the ```` line: **Do:** .. code-block:: html+jinja %% macro day_style(day) {% if day.is_off_day %} style="color: #C00000"{% endif %} %% endmacro %% macro month_table(month) ... %% endmacro ... **Don't** - define macros in the middle of the HTML: .. code-block:: html+jinja %% macro day_style(day) ... %% endmacro ... Keeping macros at the top makes them easy to find and ensures they are defined before they are called. Do and don't summary -------------------- .. list-table:: :header-rows: 1 :widths: 50 50 * - Do - Don't * - Place macros before ````. - Scatter macros throughout the body. * - Use macros to avoid copy-pasting repeated HTML. - Duplicate the same table code in five places. * - Use ``{{-`` when inserting into an HTML tag. - Use ``{{`` inside a tag (leaves extra whitespace). * - Close every macro with ``%% endmacro``. - Forget ``endmacro`` - the template will not render. Update the Demo Planner ----------------------- Refactor the month calendar from the previous page. Move the off-day styling and the table rows into macros. Your ``planners/demo/demo.html`` should now look like this: .. code-block:: html+jinja %% set year = calendar.year(2026) %% set month = year.months[0] %% macro day_color(day) {% if day.is_off_day %}#C00000{% else %}#000000{% endif %} %% endmacro %% macro month_table(month) %% for week in month.table %% for day in week %% if day is not none {{ day }} %% else %% endif %% endfor %% endfor %% endmacro ## -- Cover page --

Demo Planner - {{ month }} {{ year }}

## -- Month calendar --

{{ month }}

{{ year }}

%% for wd in calendar.weekdays %% endfor {{ month_table(month) }}
{{ wd.short_name }}
Regenerate:: pyplanner planners/demo The output should look the same as before, but the template is now shorter and easier to maintain. The ``day_color`` macro can be reused on day pages later. What is next ------------ Continue to :doc:`data-reference` for a complete list of every object and property available in your templates.