django-components/docs/concepts/fundamentals/subclassing_components.md
Juro Oravec 09cb8714cc
refactor: don't inherit media if child set to None (#1224)
* refactor: don't inherit media if child set to None

* refactor: fix typing errors

* refactor: more type fixes
2025-06-02 16:24:27 +02:00

5.9 KiB

In larger projects, you might need to write multiple components with similar behavior. In such cases, you can extract shared behavior into a standalone component class to keep things DRY.

When subclassing a component, there's a couple of things to keep in mind:

Template, JS, and CSS inheritance

When it comes to the pairs:

inheritance follows these rules:

  • If a child component class defines either member of a pair (e.g., either template or template_file), it takes precedence and the parent's definition is ignored completely.
  • For example, if a child component defines template_file, the parent's template or template_file will be ignored.
  • This applies independently to each pair - you can inherit the JS while overriding the template, for instance.

For example:

class BaseCard(Component):
    template = """
        <div class="card">
            <div class="card-content">{{ content }}</div>
        </div>
    """
    css = """
        .card {
            border: 1px solid gray;
        }
    """
    js = """console.log('Base card loaded');"""

# This class overrides parent's template, but inherits CSS and JS
class SpecialCard(BaseCard):
    template = """
        <div class="card special">
            <div class="card-content">✨ {{ content }} ✨</div>
        </div>
    """

# This class overrides parent's template and CSS, but inherits JS
class CustomCard(BaseCard):
    template_file = "custom_card.html"
    css = """
        .card {
            border: 2px solid gold;
        }
    """

Media inheritance

The Component.Media nested class follows Django's media inheritance rules:

  • If both parent and child define a Media class, the child's media will automatically include both its own and the parent's JS and CSS files.
  • This behavior can be configured using the extend attribute in the Media class, similar to Django's forms. Read more on this in Media inheritance.

For example:

class BaseModal(Component):
    template = "<div>Modal content</div>"

    class Media:
        css = ["base_modal.css"]
        js = ["base_modal.js"]  # Contains core modal functionality

class FancyModal(BaseModal):
    class Media:
        # Will include both base_modal.css/js AND fancy_modal.css/js
        css = ["fancy_modal.css"]  # Additional styling
        js = ["fancy_modal.js"]    # Additional animations

class SimpleModal(BaseModal):
    class Media:
        extend = False  # Don't inherit parent's media
        css = ["simple_modal.css"]  # Only this CSS will be included
        js = ["simple_modal.js"]    # Only this JS will be included

Opt out of inheritance

For the following media attributes, when you don't want to inherit from the parent, but you also don't need to set the template / JS / CSS to any specific value, you can set these attributes to None.

For example:

class BaseForm(Component):
    template = "..."
    css = "..."
    js = "..."

    class Media:
        js = ["form.js"]

# Use parent's template and CSS, but no JS
class ContactForm(BaseForm):
    js = None
    Media = None

Regular Python inheritance

All other attributes and methods (including the Component.View class and its methods) follow standard Python inheritance rules.

For example:

class BaseForm(Component):
    template = """
        <form>
            {{ form_content }}
            <button type="submit">
                {{ submit_text }}
            </button>
        </form>
    """

    def get_template_data(self, args, kwargs, slots, context):
        return {
            "form_content": self.get_form_content(),
            "submit_text": "Submit"
        }

    def get_form_content(self):
        return "<input type='text' name='data'>"

class ContactForm(BaseForm):
    # Extend parent's "context"
    # but override "submit_text"
    def get_template_data(self, args, kwargs, slots, context):
        context = super().get_template_data(args, kwargs, slots, context)
        context["submit_text"] = "Send Message"  
        return context

    # Completely override parent's get_form_content
    def get_form_content(self):
        return """
            <input type='text' name='name' placeholder='Your Name'>
            <input type='email' name='email' placeholder='Your Email'>
            <textarea name='message' placeholder='Your Message'></textarea>
        """