mirror of
https://github.com/django-components/django-components.git
synced 2025-08-31 11:17:21 +00:00
feat: allow to set main JS and CSS from files + lazy-load component m… (#870)
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
This commit is contained in:
parent
8fcb84c002
commit
715bf7d447
20 changed files with 1014 additions and 248 deletions
|
@ -3,7 +3,7 @@ title: Defining HTML / JS / CSS files
|
|||
weight: 8
|
||||
---
|
||||
|
||||
django_component's management of files builds on top of [Django's `Media` class](https://docs.djangoproject.com/en/5.0/topics/forms/media/).
|
||||
django_component's management of files is inspired by [Django's `Media` class](https://docs.djangoproject.com/en/5.0/topics/forms/media/).
|
||||
|
||||
To be familiar with how Django handles static files, we recommend reading also:
|
||||
|
||||
|
@ -12,7 +12,7 @@ To be familiar with how Django handles static files, we recommend reading also:
|
|||
## Defining file paths relative to component or static dirs
|
||||
|
||||
As seen in the [getting started example](#create-your-first-component), to associate HTML/JS/CSS
|
||||
files with a component, you set them as `template_name`, `Media.js` and `Media.css` respectively:
|
||||
files with a component, you set them as `template_name`, `js_file` and `css_file` respectively:
|
||||
|
||||
```py
|
||||
# In a file [project root]/components/calendar/calendar.py
|
||||
|
@ -21,10 +21,8 @@ from django_components import Component, register
|
|||
@register("calendar")
|
||||
class Calendar(Component):
|
||||
template_name = "template.html"
|
||||
|
||||
class Media:
|
||||
css = "style.css"
|
||||
js = "script.js"
|
||||
css_file = "style.css"
|
||||
js_file = "script.js"
|
||||
```
|
||||
|
||||
In the example above, the files are defined relative to the directory where `component.py` is.
|
||||
|
@ -40,17 +38,24 @@ from django_components import Component, register
|
|||
@register("calendar")
|
||||
class Calendar(Component):
|
||||
template_name = "calendar/template.html"
|
||||
|
||||
class Media:
|
||||
css = "calendar/style.css"
|
||||
js = "calendar/script.js"
|
||||
css_file = "calendar/style.css"
|
||||
js_file = "calendar/script.js"
|
||||
```
|
||||
|
||||
NOTE: In case of conflict, the preference goes to resolving the files relative to the component's directory.
|
||||
|
||||
## Defining multiple paths
|
||||
|
||||
Each component can have only a single template. However, you can define as many JS or CSS files as you want using a list.
|
||||
Each component can have only a single template, and single main JS and CSS. However, you can define additional JS or CSS
|
||||
using the nested [`Media` class](../../../reference/api#django_components.Component.Media).
|
||||
|
||||
This `Media` class behaves similarly to [Django's Media class](https://docs.djangoproject.com/en/5.1/topics/forms/media/#assets-as-a-static-definition),
|
||||
with a few differences:
|
||||
|
||||
1. Our Media class accepts various formats for the JS and CSS files: either a single file, a list, or (CSS-only) a dictonary (See below)
|
||||
2. Individual JS / CSS files can be any of `str`, `bytes`, `Path`, [`SafeString`](https://dev.to/doridoro/django-safestring-afj), or a function.
|
||||
3. Our Media class does NOT support [Django's `extend` keyword](https://docs.djangoproject.com/en/5.1/topics/forms/media/#extend)
|
||||
|
||||
|
||||
```py
|
||||
class MyComponent(Component):
|
||||
|
@ -106,14 +111,14 @@ from django.utils.safestring import mark_safe
|
|||
class SimpleComponent(Component):
|
||||
class Media:
|
||||
css = [
|
||||
mark_safe('<link href="/static/calendar/style.css" rel="stylesheet" />'),
|
||||
mark_safe('<link href="/static/calendar/style1.css" rel="stylesheet" />'),
|
||||
Path("calendar/style1.css"),
|
||||
"calendar/style2.css",
|
||||
b"calendar/style3.css",
|
||||
lambda: "calendar/style4.css",
|
||||
]
|
||||
js = [
|
||||
mark_safe('<script src="/static/calendar/script.js"></script>'),
|
||||
mark_safe('<script src="/static/calendar/script1.js"></script>'),
|
||||
Path("calendar/script1.js"),
|
||||
"calendar/script2.js",
|
||||
b"calendar/script3.js",
|
||||
|
@ -152,7 +157,7 @@ class Calendar(Component):
|
|||
}
|
||||
|
||||
class Media:
|
||||
css = "calendar/style.css"
|
||||
css = "calendar/style1.css"
|
||||
js = [
|
||||
# <script> tag constructed by Media class
|
||||
"calendar/script1.js",
|
||||
|
@ -191,10 +196,12 @@ class MyMedia(Media):
|
|||
@register("calendar")
|
||||
class Calendar(Component):
|
||||
template_name = "calendar/template.html"
|
||||
css_file = "calendar/style.css"
|
||||
js_file = "calendar/script.js"
|
||||
|
||||
class Media:
|
||||
css = "calendar/style.css"
|
||||
js = "calendar/script.js"
|
||||
css = "calendar/style1.css"
|
||||
js = "calendar/script2.js"
|
||||
|
||||
# Override the behavior of Media class
|
||||
media_class = MyMedia
|
||||
|
|
|
@ -3,7 +3,7 @@ title: Single-file components
|
|||
weight: 1
|
||||
---
|
||||
|
||||
Components can also be defined in a single file, which is useful for small components. To do this, you can use the `template`, `js`, and `css` class attributes instead of the `template_name` and `Media`. For example, here's the calendar component from above, defined in a single file:
|
||||
Components can be defined in a single file, which is useful for small components. To do this, you can use the `template`, `js`, and `css` class attributes instead of the `template_name` and `Media`. For example, here's the calendar component from above, defined in a single file:
|
||||
|
||||
```python title="[project root]/components/calendar.py"
|
||||
# In a file called [project root]/components/calendar.py
|
||||
|
@ -35,3 +35,5 @@ class Calendar(Component):
|
|||
```
|
||||
|
||||
This makes it easy to create small components without having to create a separate template, CSS, and JS file.
|
||||
|
||||
To add syntax highlighting to these snippets, head over to [Syntax highlighting](../../guides/setup/syntax_highlight.md).
|
||||
|
|
|
@ -177,18 +177,16 @@ So in your HTML, you may see something like this:
|
|||
|
||||
Finally, we return to our Python component in `calendar.py` to tie this together.
|
||||
|
||||
To link JS and CSS defined in other files, use the `Media` nested class
|
||||
([Learn more about using Media](../fundamentals/defining_js_css_html_files.md)).
|
||||
To link JS and CSS defined in other files, use [`js_file`](../../../reference/api#django_components.Component.js_file)
|
||||
and [`css_file`](../../../reference/api#django_components.Component.css_file) attributes:
|
||||
|
||||
```python title="[project root]/components/calendar/calendar.py"
|
||||
from django_components import Component
|
||||
|
||||
class Calendar(Component):
|
||||
template_name = "calendar.html"
|
||||
|
||||
class Media: # <--- new
|
||||
js = "calendar.js"
|
||||
css = "calendar.css"
|
||||
js_file = "calendar.js" # <--- new
|
||||
css_file = "calendar.css" # <--- new
|
||||
|
||||
def get_context_data(self):
|
||||
return {
|
||||
|
@ -196,6 +194,86 @@ class Calendar(Component):
|
|||
}
|
||||
```
|
||||
|
||||
And that's it! If you were to embed this component in an HTML, django-components will
|
||||
automatically embed the associated JS and CSS.
|
||||
|
||||
!!! note
|
||||
|
||||
Similarly to the template file, the JS and CSS file paths can be either:
|
||||
|
||||
1. Relative to the Python component file (as seen above),
|
||||
2. Relative to any of the component directories as defined by
|
||||
[`COMPONENTS.dirs`](../../reference/settings.md#django_components.app_settings.ComponentsSettings.dirs)
|
||||
and/or [`COMPONENTS.app_dirs`](../../reference/settings.md#django_components.app_settings.ComponentsSettings.app_dirs)
|
||||
(e.g. `[your apps]/components` dir and `[project root]/components`)
|
||||
3. Relative to any of the directories defined by `STATICFILES_DIRS`.
|
||||
|
||||
|
||||
<!-- TODO: UPDATE AFTER AT LEAST ONE IMPLEMENTED
|
||||
!!! info
|
||||
|
||||
Special role of `css` and `js`:
|
||||
|
||||
The "primary" JS and CSS you that specify via `js/css` and `js_file/css_file` have special role in many of django-components'
|
||||
features:
|
||||
- CSS scoping [a la Vue](https://vuejs.org/api/sfc-css-features.html#scoped-css)
|
||||
- CSS variables from Python are available
|
||||
- JS variables from Python are available
|
||||
- JS can pass a callback to special JS method `$onLoad()`, which will be called every time
|
||||
a component is rendered on the page.
|
||||
|
||||
This is not true for JS and CSS defined in `Media.js/css`, where the linked JS / CSS are rendered as is.
|
||||
-->
|
||||
|
||||
### 5. Link additional JS and CSS to a component
|
||||
|
||||
Your components may depend on third-party packages or styling, or other shared logic.
|
||||
To load these additional dependencies, you can use a nested [`Media` class](../../../reference/api#django_components.Component.Media).
|
||||
|
||||
This `Media` class behaves similarly to [Django's Media class](https://docs.djangoproject.com/en/5.1/topics/forms/media/#assets-as-a-static-definition),
|
||||
with a few differences:
|
||||
|
||||
1. Our Media class accepts various formats for the JS and CSS files: either a single file, a list, or (CSS-only) a dictonary (see below).
|
||||
2. Individual JS / CSS files can be any of `str`, `bytes`, `Path`, [`SafeString`](https://dev.to/doridoro/django-safestring-afj), or a function.
|
||||
3. Our Media class does NOT support [Django's `extend` keyword](https://docs.djangoproject.com/en/5.1/topics/forms/media/#extend).
|
||||
|
||||
[Learn more](../fundamentals/defining_js_css_html_files.md) about using Media.
|
||||
|
||||
```python title="[project root]/components/calendar/calendar.py"
|
||||
from django_components import Component
|
||||
|
||||
class Calendar(Component):
|
||||
template_name = "calendar.html"
|
||||
js_file = "calendar.js"
|
||||
css_file = "calendar.css"
|
||||
|
||||
class Media: # <--- new
|
||||
js = [
|
||||
"path/to/shared.js",
|
||||
"https://unpkg.com/alpinejs@3.14.7/dist/cdn.min.js", # AlpineJS
|
||||
]
|
||||
css = [
|
||||
"path/to/shared.css",
|
||||
"https://unpkg.com/tailwindcss@^2/dist/tailwind.min.css", # Tailwind
|
||||
]
|
||||
|
||||
def get_context_data(self):
|
||||
return {
|
||||
"date": "1970-01-01",
|
||||
}
|
||||
```
|
||||
|
||||
!!! note
|
||||
|
||||
Same as with the "primary" JS and CSS, the file paths files can be either:
|
||||
|
||||
1. Relative to the Python component file (as seen above),
|
||||
2. Relative to any of the component directories as defined by
|
||||
[`COMPONENTS.dirs`](../../reference/settings.md#django_components.app_settings.ComponentsSettings.dirs)
|
||||
and/or [`COMPONENTS.app_dirs`](../../reference/settings.md#django_components.app_settings.ComponentsSettings.app_dirs)
|
||||
(e.g. `[your apps]/components` dir and `[project root]/components`)
|
||||
|
||||
|
||||
!!! info
|
||||
|
||||
The `Media` nested class is shaped based on [Django's Media class](https://docs.djangoproject.com/en/5.1/topics/forms/media/).
|
||||
|
@ -223,18 +301,57 @@ class Calendar(Component):
|
|||
|
||||
If you define a list of JS files, they will be executed one-by-one, left-to-right.
|
||||
|
||||
!!! note
|
||||
#### Rules of execution of scripts in `Media.js`
|
||||
|
||||
Same as with the template file, the file paths for the JS and CSS files can be either:
|
||||
The scripts defined in `Media.js` still follow the rules outlined above:
|
||||
|
||||
1. Relative to the Python component file (as seen above),
|
||||
2. Relative to any of the component directories as defined by
|
||||
[`COMPONENTS.dirs`](../../reference/settings.md#django_components.app_settings.ComponentsSettings.dirs)
|
||||
and/or [`COMPONENTS.app_dirs`](../../reference/settings.md#django_components.app_settings.ComponentsSettings.app_dirs)
|
||||
(e.g. `[your apps]/components` dir and `[project root]/components`)
|
||||
1. JS is executed in the order in which the components are found in the HTML.
|
||||
2. JS will be executed only once, even if there is multiple instances of the same component.
|
||||
|
||||
Additionally to `Media.js` applies that:
|
||||
|
||||
And that's it! If you were to embed this component in an HTML, django-components will
|
||||
automatically embed the associated JS and CSS.
|
||||
1. JS in `Media.js` is executed **before** the component's primary JS.
|
||||
2. JS in `Media.js` is executed **in the same order** as it was defined.
|
||||
3. If there is multiple components that specify the same JS path or URL in `Media.js`,
|
||||
this JS will be still loaded and executed only once.
|
||||
|
||||
Putting all of this together, our `Calendar` component above would render HTML like so:
|
||||
|
||||
```html
|
||||
<html>
|
||||
<head>
|
||||
...
|
||||
<!-- CSS from Media.css -->
|
||||
<link href="/static/path/to/shared.css" media="all" rel="stylesheet">
|
||||
<link href="https://unpkg.com/tailwindcss@^2/dist/tailwind.min.css" media="all" rel="stylesheet">
|
||||
<!-- CSS from Component.css_file -->
|
||||
<style>
|
||||
.calendar {
|
||||
width: 200px;
|
||||
background: pink;
|
||||
}
|
||||
.calendar span {
|
||||
font-weight: bold;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
...
|
||||
<!-- JS from Media.js -->
|
||||
<script src="/static/path/to/shared.js"></script>
|
||||
<script src="https://unpkg.com/alpinejs@3.14.7/dist/cdn.min.js"></script>
|
||||
<!-- JS from Component.js_file -->
|
||||
<script>
|
||||
(function () {
|
||||
document.querySelector(".calendar").onclick = () => {
|
||||
alert("Clicked calendar!");
|
||||
};
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
Now that we have a fully-defined component, [next let's use it in a Django template ➡️](./components_in_templates.md).
|
||||
|
|
|
@ -32,10 +32,8 @@ from django_components import Component, register # <--- new
|
|||
@register("calendar") # <--- new
|
||||
class Calendar(Component):
|
||||
template_name = "calendar.html"
|
||||
|
||||
class Media:
|
||||
js = "calendar.js"
|
||||
css = "calendar.css"
|
||||
js_file = "calendar.js"
|
||||
css_file = "calendar.css"
|
||||
|
||||
def get_context_data(self):
|
||||
return {
|
||||
|
@ -48,7 +46,7 @@ by calling `{% load component_tags %}` inside the template.
|
|||
|
||||
!!! info
|
||||
|
||||
Why do we have to register components?
|
||||
**Why do we have to register components?**
|
||||
|
||||
We want to use our component as a template tag (`{% ... %}`) in Django template.
|
||||
|
||||
|
@ -170,7 +168,7 @@ and keeping your CSS and Javascript in the static directory.
|
|||
|
||||
!!! info
|
||||
|
||||
How does django-components pick up registered components?
|
||||
**How does django-components pick up registered components?**
|
||||
|
||||
Notice that it was enough to add [`@register`](../../../reference/api#django_components.register) to the component.
|
||||
We didn't need to import the component file anywhere to execute it.
|
||||
|
@ -191,6 +189,9 @@ and keeping your CSS and Javascript in the static directory.
|
|||
...
|
||||
```
|
||||
|
||||
You can now render the components! But our component will render the same content now matter where
|
||||
and how many times we use it. [Let's parametrise some of its state, so that our Calendar component
|
||||
You can now render the components in templates!
|
||||
|
||||
---
|
||||
|
||||
Currently our component always renders the same content. [Let's parametrise it, so that our Calendar component
|
||||
is configurable from within the template ➡️](./parametrising_components.md)
|
||||
|
|
|
@ -222,4 +222,6 @@ the parametrized version of the component:
|
|||
</div>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
Next, you will learn [how to use slots give your components even more flexibility ➡️](./adding_slots.md)
|
||||
|
|
|
@ -5,19 +5,51 @@ weight: 1
|
|||
|
||||
A component in django-components can be as simple as a Django template and Python code to declare the component:
|
||||
|
||||
```py
|
||||
```htmldjango title="calendar.html"
|
||||
<div class="calendar">
|
||||
Today's date is <span>{{ date }}</span>
|
||||
</div>
|
||||
```
|
||||
|
||||
```py title="calendar.py"
|
||||
from django_components import Component
|
||||
|
||||
class Calendar(Component):
|
||||
template = """
|
||||
<div class="calendar">
|
||||
Today's date is <span>{{ date }}</span>
|
||||
</div>
|
||||
"""
|
||||
template_name = "calendar.html"
|
||||
```
|
||||
|
||||
Or a combination of Django template, Python, CSS, and Javascript:
|
||||
|
||||
```htmldjango title="calendar.html"
|
||||
<div class="calendar">
|
||||
Today's date is <span>{{ date }}</span>
|
||||
</div>
|
||||
```
|
||||
|
||||
```css title="calendar.css"
|
||||
.calendar {
|
||||
width: 200px;
|
||||
background: pink;
|
||||
}
|
||||
```
|
||||
|
||||
```js title="calendar.js"
|
||||
document.querySelector(".calendar").onclick = function () {
|
||||
alert("Clicked calendar!");
|
||||
};
|
||||
```
|
||||
|
||||
```py title="calendar.py"
|
||||
from django_components import Component
|
||||
|
||||
class Calendar(Component):
|
||||
template_name = "calendar.html"
|
||||
js_file = "calendar.js"
|
||||
css_file = "calendar.css"
|
||||
```
|
||||
|
||||
Alternatively, you can "inline" HTML, JS, and CSS right into the component class:
|
||||
|
||||
```py
|
||||
from django_components import Component
|
||||
|
||||
|
@ -44,14 +76,9 @@ class Calendar(Component):
|
|||
|
||||
!!! note
|
||||
|
||||
With django-components, you can "inline" the HTML, JS and CSS code into the Python class,
|
||||
as seen above.
|
||||
|
||||
You can set up [syntax highlighting](../../guides/setup/syntax_highlight.md),
|
||||
but autocompletion / intellisense does not yet work.
|
||||
|
||||
So, in the example below we define the Django template in a separate file, `calendar.html`,
|
||||
to allow our IDEs to interpret the file as HTML / Django file.
|
||||
If you "inline" the HTML, JS and CSS code into the Python class, you can set up
|
||||
[syntax highlighting](../../guides/setup/syntax_highlight.md) for better experience.
|
||||
However, autocompletion / intellisense does not work with syntax highlighting.
|
||||
|
||||
|
||||
We'll start by creating a component that defines only a Django template:
|
||||
|
@ -154,4 +181,6 @@ It will output
|
|||
|
||||
And voilá!! We've created our first component.
|
||||
|
||||
---
|
||||
|
||||
Next, [let's add JS and CSS to this component ➡️](./adding_js_and_css.md).
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue