Catalog

The Catalog is the central manager for your components. It handles loading, caching, and rendering components from one or more folders.

Basic Setup

from jx import Catalog

catalog = Catalog("components/")

This creates a catalog that loads components from the components/ folder.

Constructor Options

catalog = Catalog(
    folder="components/",       # Optional initial folder
    jinja_env=None,             # Custom Jinja2 environment
    extensions=None,            # Extra Jinja2 extensions
    filters=None,               # Custom template filters
    tests=None,                 # Custom template tests
    auto_reload=True,           # Auto-detect file changes
    **globals                   # Global template variables
)

folder

Optional path to a component folder. Shortcut for calling add_folder():

# These are equivalent:
catalog = Catalog("components/")

catalog = Catalog()
catalog.add_folder("components/")

auto_reload

When True (default), jx checks if component files have changed and reloads them automatically. Great for development.

For production, set to False to skip file modification checks:

catalog = Catalog("components/", auto_reload=False)

globals

Variables available to all components:

catalog = Catalog(
    "components/",
    site_name="My App",
    current_year=2024,
    debug=True,
)
components/footer.jinja
<footer>
  © {{ current_year }} {{ site_name }}
</footer>

filters

Custom Jinja2 filters:

def format_price(value):
    return f"${value:,.2f}"

def pluralize(count, singular, plural=None):
    plural = plural or f"{singular}s"
    return singular if count == 1 else plural

catalog = Catalog(
    "components/",
    filters={
        "price": format_price,
        "pluralize": pluralize,
    }
)
<span>{{ product.price | price }}</span>
<span>{{ count }} {{ count | pluralize("item") }}</span>

tests

Custom Jinja2 tests:

def is_admin(user):
    return user.role == "admin"

catalog = Catalog(
    "components/",
    tests={"admin": is_admin}
)
{% if user is admin %}
  <a href="/admin">Admin Panel</a>
{% endif %}

extensions

Extra Jinja2 extensions to load:

catalog = Catalog(
    "components/",
    extensions=["jinja2.ext.i18n", "jinja2.ext.loopcontrols"]
)

Note: The jinja2.ext.do extension is always enabled (required for attrs manipulation).

jinja_env

Use an existing Jinja2 environment instead of creating a new one:

from jinja2 import Environment

env = Environment()
env.globals["my_func"] = my_function
env.filters["my_filter"] = my_filter

catalog = Catalog("components/", jinja_env=env)

This is useful when integrating with frameworks that provide their own Jinja environment.


Adding Folders

add_folder(path, prefix="", preload=True)

Add a folder of components to the catalog:

catalog = Catalog()
catalog.add_folder("components/")
catalog.add_folder("layouts/")

Components are imported by their path relative to the folder:

{#import "button.jinja" as Button #}
{#import "forms/input.jinja" as Input #}

Using Prefixes

Prefixes namespace components, useful for third-party libraries:

catalog.add_folder("components/")
catalog.add_folder("vendor/ui-kit/", prefix="ui")
catalog.add_folder("vendor/icons/", prefix="icons")

Import prefixed components with @prefix/:

{#import "button.jinja" as Button #}
{#import "@ui/modal.jinja" as Modal #}
{#import "@icons/check.jinja" as CheckIcon #}

Preloading

By default, preload=True parses all components when the folder is added. This makes the first render faster.

Set preload=False to defer parsing until each component is first used:

catalog.add_folder("components/", preload=False)

Multiple Folders, Same Prefix

If you add multiple folders with the same prefix (or no prefix), they're treated as one namespace. If both contain a component with the same path, the first one added wins:

catalog.add_folder("my-components/")      # Has button.jinja
catalog.add_folder("fallback-components/") # Also has button.jinja

# "button.jinja" resolves to my-components/button.jinja

Rendering

render(relpath, globals=None, **kwargs)

Render a component by its path:

html = catalog.render("page.jinja", title="Hello", user=current_user)

Arguments:

  • relpath - Path to the component (e.g., "pages/home.jinja")
  • globals - Dict of variables available to this component and all its imports
  • **kwargs - Arguments passed directly to the component
# Pass data as keyword arguments
html = catalog.render(
    "user-profile.jinja",
    user=user,
    posts=posts,
    show_email=True,
)

# Or use globals for values needed by child components too
html = catalog.render(
    "page.jinja",
    globals={"request": request, "csrf_token": token},
    title="Dashboard",
)

render_string(source, globals=None, **kwargs)

Render a component from a string (not a file):

source = """
{#def name #}
<h1>Hello, {{ name }}!</h1>
"""

html = catalog.render_string(source, name="World")
# <h1>Hello, World!</h1>

Useful for:

  • Testing components
  • Dynamic templates from a database
  • Simple one-off renders

Note: String components can use absolute imports but not relative imports (no file path to resolve from).


Framework Integration

Flask

from flask import Flask, render_template_string
from jx import Catalog

app = Flask(__name__)
catalog = Catalog(
    "components/",
    auto_reload=app.debug,
    url_for=flask.url_for,  # Make url_for available in components
)

@app.route("/")
def home():
    return catalog.render("pages/home.jinja", products=get_products())

FastAPI

from fastapi import FastAPI, Request
from fastapi.responses import HTMLResponse
from jx import Catalog

app = FastAPI()
catalog = Catalog("components/", auto_reload=True)

@app.get("/", response_class=HTMLResponse)
def home(request: Request):
    return catalog.render(
        "pages/home.jinja",
        globals={"request": request},
        products=get_products(),
    )

Sharing Jinja Environment

If your framework has its own Jinja environment with filters, globals, etc., pass it to the catalog:

# Flask example
from flask import Flask

app = Flask(__name__)
app.jinja_env.filters["my_filter"] = my_filter
app.jinja_env.globals["my_global"] = my_global

catalog = Catalog("components/", jinja_env=app.jinja_env)

Now your components have access to everything registered in Flask's environment.


Production Settings

For production, disable auto-reload.

import os

catalog = Catalog(
    "components/",
    auto_reload=os.environ.get("DEBUG", "false").lower() == "true",
)

Or based on your framework's debug setting:

# Flask
catalog = Catalog("components/", auto_reload=app.debug)

# FastAPI
catalog = Catalog("components/", auto_reload=settings.debug)