slippers

repository·main·Indexed 20 days ago

https://github.com/mixxorz/slippers

A Django extension for building reusable UI components using only the Django Template Language (DTL). Slippers enables a component-driven workflow with a custom tag-based syntax, allowing developers to define reusable components in templates and register them via a components.yaml file or programmatically, reducing the need for custom Python logic for interface building.

Tokens
8.1K
Snippets
34
Records
39
Agent score
58%

What's inside slippers

  1. What is Slippers?

    main

    Slippers is an augmentation for the Django Template Language (DTL) designed to make building interfaces more comfortable. It provides additional template tags and filters, but its primary feature is the ability to create reusable components using a simplified syntax. This allows developers to avoid the verbosity of standard {% include %} tags and enables passing HTML content directly into components, a limitation in standard DTL.

    {% #Button variant="primary" %}See how it works{% /Button %}
  2. Build reusable components in Django

    main

    Slippers allows you to define components using a block-based syntax in your Django templates. You can wrap content within a component tag and pass arguments to customize its behavior. This enables a component-driven workflow similar to modern frontend frameworks but stays entirely within the Django template layer.

    {% #Button variant="primary" %}See how it works{% /Button %}
  3. Understand component context and variable scoping

    main

    Component template tags in Slippers do not automatically pass the current parent context to the child component. All variables must be passed explicitly as keyword arguments.

    Key Scoping Rules:

    1. No Context Leakage: Any variable defined inside a component template (e.g., using {% var ... %}) is local to that component and does not leak into the global context.
    2. Explicit Passing: If a child component needs a variable from the parent, you must pass it: {% Component var=parent_var %}.
    3. Automatic Request Passing: If a request object exists in the parent context, it is automatically injected into the component as {{ request }}. You can override this by passing request explicitly: {% MyComponent request=other_request %}.
  4. Define component types and defaults with front matter

    main

    For complex component logic, you can use a Python front matter block (delimited by ---) at the start of the component file. This block allows you to define types, defaults, and custom logic using the props object (an instance of slippers.props.Props).

    1. Type Checking with props.types

    Define a dictionary mapping prop names to Python types. If a passed prop does not match the type, a PropError is raised. The typing module is automatically available in the front matter.

    2. Default Values with props.defaults

    Define a dictionary of default values. If a prop is passed as None or omitted, it will fall back to these values.

    3. Prop Mapping and Logic

    You can manipulate the props object directly using props['name'] = value to create new variables or modify existing ones. Since the front matter is standard Python, you can also perform imports.

    ---
    props.types = {
        'required_string': str,
        'optional_number': Optional[int],
    }
    props.defaults = {
        'default_number': 10,
    }
    
    props['new_number'] = props['default_number'] * 2
    ---
    
    {{ required_string }}
    {{ new_number }}
    ---
    props.types = {
        'required_string': str,
        'optional_number': Optional[int],
        'default_number': int,
    }
    props.defaults = {
        'default_number': 10,
    }
    
    props['new_number'] = props['default_number'] * 2
    ---
    
    Required string: {{ required_string }}
    Optional number: {{ optional_number }}
    Default number: {{ default_number }}
    New number: {{ new_number }}
  5. Make Slippers template tags available globally

    main

    By default, you must use {% load slippers %} at the top of every template where you want to use Slippers components. To avoid this, you can add slippers.templatetags.slippers to the builtins list within your TEMPLATES configuration in settings.py.

    TEMPLATES = [
        {
            "BACKEND": "django.template.backends.django.DjangoTemplates",
            "DIRS": [BASE_DIR / "templates"],
            "APP_DIRS": True,
            "OPTIONS": {
                "context_processors": [
                    "django.template.context_processors.debug",
                    "django.template.context_processors.request",
                    "django.contrib.auth.context_processors.auth",
                    "django.contrib.messages.context_processors.messages",
                ],
                "builtins": ["slippers.templatetags.slippers"],
            },
        },
    ]
  6. Configure Slippers in Django settings

    main

    After installing the package, you must add 'slippers' to your INSTALLED_APPS list in your Django settings.py file to enable its template tags and functionality.

    INSTALLED_APPS = [
        ...
        'slippers',
        ...
    ]
  7. Register components using components.yaml

    main

    To make your templates available as components, create a components.yaml file in your root template folder. This file maps a component name (which becomes the template tag name) to its corresponding template path. This file also acts as a central directory for all components in your application.

    components:
      Card: "myapp/Card.html"
  8. Create a Slippers component template

    main

    Slippers components are written as standard Django templates. To allow a component to accept nested content, use the {{ children }} placeholder within your template. This placeholder defines where the content wrapped between the component's opening and closing tags will be rendered.

    <div class="card">
      <h1 class="card__header">{{ heading }}</h1>
      <div class="card__body">
        {{ children }}
      </div>
    </div>
  9. Use block and inline component syntax

    main

    Slippers components can be used in two ways depending on whether they need to wrap content or not.

    Block Syntax

    Use block syntax when you need to pass content into the component via the {{ children }} variable. The opening tag is prefixed with # and the closing tag is prefixed with /.

    {% #ComponentName prop="value" %}Content goes here{% /ComponentName %}

    Inline Syntax

    Use inline syntax for components that do not wrap content or do not use the {{ children }} variable. This uses the plain component name without # or /.

    {% ComponentName prop="value" %}
    {# Block syntax #}
    {% #IconButton icon="star" %}Favorite{% /IconButton %}
    
    {# Inline syntax #}
    {% IconButton icon="heart" %}
  10. Register components to a custom tag register

    main

    By default, register_components targets the slippers tag register, requiring {% load slippers %} in your templates. To build a reusable library with its own namespace, pass a custom Django template.Library() instance as the second argument to register_components.

    from django import template
    from slippers.templatetags.slippers import register_components
    
    register = template.Library()
    
    register_components({
      "Card": "my_library/Card.html",
      "Button": "my_library/Button.html",
    }, register)

    Then, use the library in your templates by loading its specific tag name:

    {% load my_components %}
    
    {% #Button %}My button{% /Button %}
  11. Preprocess and extend component context with front matter

    main

    Use the front matter block to define internal component data that shouldn't be part of the public component interface (props). This is ideal for static lists, configuration, or importing constants.

    By assigning values to props['var_name'], these variables become available in the component's template context.

    ---
    from my_app.constants.icons import icon_bars_3
    
    props.types = {
        'title_level': Optional[int],
    }
    props.defaults = {
        'title_level': 3,
    }
    
    props['items'] = [
      ('Link 1', '/path1'),
      ('Link 2', '/path2'),
    ]
    
    props['title_tag'] = f'h{props["title_level"]}'
    ---
    
    <div class="menu">
      {{ icon_bars_3 }}
      <{{ title_tag }}>Menu</{{ title_tag }}>
      {% for name, href in items %}
        <a href="{{ href }}">{{ name }}</a>
      {% endfor %}
    </div>
    ---
    from my_app.constants.icons import icon_bars_3
    
    props.types = {
        'title_level': Optional[int],
    }
    props.defaults = {
        'title_level': 3,
    }
    
    props['items'] = [
      ('Link 1', '/path1'),
      ('Link 2', '/path2'),
      ('Link 3', '/path3')
    ]
    
    props['title_tag'] = f'h{props["title_level"]}'
    ---
    <div class="menu">
      {{ icon_bars_3 }}
      <{{ title_tag }}>Menu</{{ title_tag }}>
      <div class="menu-item">
        {% for name, href in items %}
        <a href="{{ href }}">{{ name }}</a>
        {% endfor %}
      </div>
    </div>