mirror of
https://github.com/django-components/django-components.git
synced 2025-08-04 06:18:17 +00:00
feat: add Media.extend (#890)
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
This commit is contained in:
parent
5e8770c720
commit
ab037f24b0
7 changed files with 727 additions and 61 deletions
|
@ -137,6 +137,9 @@ This `Media` class behaves similarly to
|
|||
- A [`SafeString`](https://docs.djangoproject.com/en/5.1/ref/utils/#django.utils.safestring.SafeString),
|
||||
or a function (with `__html__` method) is considered an already-formatted HTML tag, skipping both static file
|
||||
resolution and rendering with `media_class.render_js()` or `media_class.render_css()`.
|
||||
- You can set [`extend`](../../../reference/api#django_components.ComponentMediaInput.extend) to configure
|
||||
whether to inherit JS / CSS from parent components. See
|
||||
[Controlling Media Inheritance](../../fundamentals/defining_js_css_html_files/#controlling-media-inheritance).
|
||||
|
||||
However, there's a few differences from Django's Media class:
|
||||
|
||||
|
@ -145,8 +148,6 @@ However, there's a few differences from Django's Media class:
|
|||
2. Individual JS / CSS files can be any of `str`, `bytes`, `Path`,
|
||||
[`SafeString`](https://docs.djangoproject.com/en/5.1/ref/utils/#django.utils.safestring.SafeString), or a function
|
||||
(See [`ComponentMediaInputPath`](../../../reference/api#django_components.ComponentMediaInputPath)).
|
||||
3. Our Media class does NOT support
|
||||
[Django's `extend` keyword](https://docs.djangoproject.com/en/5.1/topics/forms/media/#extend)
|
||||
|
||||
```py
|
||||
class MyTable(Component):
|
||||
|
@ -402,8 +403,8 @@ print(Calendar.css)
|
|||
|
||||
## Accessing component's Media files
|
||||
|
||||
To access the files defined under [`Component.Media`](../../../reference/api#django_components.Component.Media),
|
||||
you can access [`Component.media`](../../reference/api.md#django_components.Component.media) (lowercase).
|
||||
To access the files that you defined under [`Component.Media`](../../../reference/api#django_components.Component.Media),
|
||||
use [`Component.media`](../../reference/api.md#django_components.Component.media) (lowercase).
|
||||
This is consistent behavior with
|
||||
[Django's Media class](https://docs.djangoproject.com/en/5.1/topics/forms/media/#assets-as-a-static-definition).
|
||||
|
||||
|
@ -419,6 +420,95 @@ print(MyComponent.media)
|
|||
# <link href="/static/path/to/style.css" media="all" rel="stylesheet">
|
||||
```
|
||||
|
||||
### `Component.Media` vs `Component.media`
|
||||
|
||||
When working with component media files, there are a few important concepts to understand:
|
||||
|
||||
- `Component.Media`
|
||||
|
||||
- Is the "raw" media definition, or the input, which holds only the component's **own** media definition
|
||||
- This class is NOT instantiated, it merely holds the JS / CSS files.
|
||||
|
||||
- `Component.media`
|
||||
- Returns all resolved media files, **including** those inherited from parent components
|
||||
- Is an instance of [`Component.media_class`](../../reference/api.md#django_components.Component.media_class)
|
||||
|
||||
```python
|
||||
class ParentComponent(Component):
|
||||
class Media:
|
||||
js = ["parent.js"]
|
||||
|
||||
class ChildComponent(ParentComponent):
|
||||
class Media:
|
||||
js = ["child.js"]
|
||||
|
||||
# Access only this component's media
|
||||
print(ChildComponent.Media.js) # ["child.js"]
|
||||
|
||||
# Access all inherited media
|
||||
print(ChildComponent.media._js) # ["parent.js", "child.js"]
|
||||
```
|
||||
|
||||
!!! note
|
||||
|
||||
You should **not** manually modify `Component.media` or `Component.Media` after the component has been resolved, as this may lead to unexpected behavior.
|
||||
|
||||
If you want to modify the class that is instantiated for [`Component.media`](../../reference/api.md#django_components.Component.media),
|
||||
you can configure [`Component.media_class`](../../reference/api.md#django_components.Component.media_class)
|
||||
([See example](#customize-how-paths-are-rendered-into-html-tags)).
|
||||
|
||||
## Controlling Media Inheritance
|
||||
|
||||
By default, the media files are inherited from the parent component.
|
||||
|
||||
```python
|
||||
class ParentComponent(Component):
|
||||
class Media:
|
||||
js = ["parent.js"]
|
||||
|
||||
class MyComponent(ParentComponent):
|
||||
class Media:
|
||||
js = ["script.js"]
|
||||
|
||||
print(MyComponent.media._js) # ["parent.js", "script.js"]
|
||||
```
|
||||
|
||||
You can set the component NOT to inherit from the parent component by setting the [`extend`](../../reference/api.md#django_components.ComponentMediaInput.extend) attribute to `False`:
|
||||
|
||||
```python
|
||||
class ParentComponent(Component):
|
||||
class Media:
|
||||
js = ["parent.js"]
|
||||
|
||||
class MyComponent(ParentComponent):
|
||||
class Media:
|
||||
extend = False # Don't inherit parent media
|
||||
js = ["script.js"]
|
||||
|
||||
print(MyComponent.media._js) # ["script.js"]
|
||||
```
|
||||
|
||||
Alternatively, you can specify which components to inherit from. In such case, the media files are inherited ONLY from the specified components, and NOT from the original parent components:
|
||||
|
||||
```python
|
||||
class ParentComponent(Component):
|
||||
class Media:
|
||||
js = ["parent.js"]
|
||||
|
||||
class MyComponent(ParentComponent):
|
||||
class Media:
|
||||
# Only inherit from these, ignoring the files from the parent
|
||||
extend = [OtherComponent1, OtherComponent2]
|
||||
|
||||
js = ["script.js"]
|
||||
|
||||
print(MyComponent.media._js) # ["script.js", "other1.js", "other2.js"]
|
||||
```
|
||||
|
||||
!!! info
|
||||
|
||||
The `extend` behaves consistently with
|
||||
[Django's Media class](https://docs.djangoproject.com/en/5.1/topics/forms/media/#extend),
|
||||
with one exception:
|
||||
|
||||
- When you set `extend` to a list, the list is expected to contain Component classes (or other classes that have a nested `Media` class).
|
||||
|
|
132
docs/concepts/fundamentals/subclassing_components.md
Normal file
132
docs/concepts/fundamentals/subclassing_components.md
Normal file
|
@ -0,0 +1,132 @@
|
|||
---
|
||||
title: Subclassing components
|
||||
weight: 11
|
||||
---
|
||||
|
||||
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](https://en.wikipedia.org/wiki/Don%27t_repeat_yourself).
|
||||
|
||||
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:
|
||||
|
||||
- [`Component.template`](../../reference/api.md#django_components.Component.template)/[`Component.template_file`](../../reference/api.md#django_components.Component.template_file)
|
||||
- [`Component.js`](../../reference/api.md#django_components.Component.js)/[`Component.js_file`](../../reference/api.md#django_components.Component.js_file)
|
||||
- [`Component.css`](../../reference/api.md#django_components.Component.css)/[`Component.css_file`](../../reference/api.md#django_components.Component.css_file)
|
||||
|
||||
inheritance follows these rules:
|
||||
|
||||
- If a child component class defines either member of a pair (e.g., either [`template`](../../reference/api.md#django_components.Component.template) or [`template_file`](../../reference/api.md#django_components.Component.template_file)), it takes precedence and the parent's definition is ignored completely.
|
||||
- For example, if a child component defines [`template_file`](../../reference/api.md#django_components.Component.template_file), the parent's [`template`](../../reference/api.md#django_components.Component.template) or [`template_file`](../../reference/api.md#django_components.Component.template_file) will be ignored.
|
||||
- This applies independently to each pair - you can inherit the JS while overriding the template, for instance.
|
||||
|
||||
For example:
|
||||
|
||||
```python
|
||||
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 Class Inheritance
|
||||
|
||||
The [`Component.Media`](../../reference/api.md#django_components.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`](../../reference/api.md#django_components.Component.Media.extend) attribute in the Media class, similar to Django's forms.
|
||||
Read more on this in [Controlling Media Inheritance](./defining_js_css_html_files.md#controlling-media-inheritance).
|
||||
|
||||
For example:
|
||||
|
||||
```python
|
||||
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
|
||||
```
|
||||
|
||||
### Regular Python Inheritance
|
||||
|
||||
All other attributes and methods (including the [`Component.View`](../../reference/api.md#django_components.ComponentView) class and its methods) follow standard Python inheritance rules.
|
||||
|
||||
For example:
|
||||
|
||||
```python
|
||||
class BaseForm(Component):
|
||||
template = """
|
||||
<form>
|
||||
{{ form_content }}
|
||||
<button type="submit">
|
||||
{{ submit_text }}
|
||||
</button>
|
||||
</form>
|
||||
"""
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
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_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
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>
|
||||
"""
|
||||
```
|
Loading…
Add table
Add a link
Reference in a new issue