mirror of
https://github.com/django-components/django-components.git
synced 2025-08-03 22:08:17 +00:00
docs: fix links in README and "overview" section, add tutorial (#842)
This commit is contained in:
parent
6813c9d7aa
commit
789f3807aa
27 changed files with 1213 additions and 216 deletions
240
docs/concepts/getting_started/adding_js_and_css.md
Normal file
240
docs/concepts/getting_started/adding_js_and_css.md
Normal file
|
@ -0,0 +1,240 @@
|
|||
---
|
||||
title: Adding JS and CSS
|
||||
weight: 2
|
||||
---
|
||||
|
||||
Next we will add CSS and JavaScript to our template.
|
||||
|
||||
!!! info
|
||||
|
||||
In django-components, using JS and CSS is as simple as defining them on the Component class.
|
||||
You don't have to insert the `<script>` and `<link>` tags into the HTML manually.
|
||||
|
||||
Behind the scenes, django-components keeps track of which components use which JS and CSS
|
||||
files. Thus, when a component is rendered on the page, the page will contain only the JS
|
||||
and CSS used by the components, and nothing more!
|
||||
|
||||
### 1. Update project structure
|
||||
|
||||
Start by creating empty `calendar.js` and `calendar.css` files:
|
||||
|
||||
```
|
||||
sampleproject/
|
||||
├── calendarapp/
|
||||
├── components/
|
||||
│ └── calendar/
|
||||
│ ├── calendar.py
|
||||
│ ├── calendar.js 🆕
|
||||
│ ├── calendar.css 🆕
|
||||
│ └── calendar.html
|
||||
├── sampleproject/
|
||||
├── manage.py
|
||||
└── requirements.txt
|
||||
```
|
||||
|
||||
### 2. Write CSS
|
||||
|
||||
Inside `calendar.css`, write:
|
||||
|
||||
```css title="[project root]/components/calendar/calendar.css"
|
||||
.calendar {
|
||||
width: 200px;
|
||||
background: pink;
|
||||
}
|
||||
.calendar span {
|
||||
font-weight: bold;
|
||||
}
|
||||
```
|
||||
|
||||
Be sure to prefix your rules with unique CSS class like `calendar`, so the CSS doesn't clash with other rules.
|
||||
|
||||
<!-- TODO: UPDATE AFTER SCOPED CSS ADDED -->
|
||||
!!! note
|
||||
|
||||
Soon, django-components will automatically scope your CSS by default, so you won't have to worry
|
||||
about CSS class clashes.
|
||||
|
||||
This CSS will be inserted into the page as an inlined `<style>` tag, at the position defined by
|
||||
[`{% component_css_dependencies %}`](../../reference/template_tags.md#component_css_dependencies),
|
||||
or at the end of the inside the `<head>` tag (See [JS and CSS output locations](../../advanced/rendering_js_css/#js-and-css-output-locations)).
|
||||
|
||||
So in your HTML, you may see something like this:
|
||||
|
||||
```html
|
||||
<html>
|
||||
<head>
|
||||
...
|
||||
<style>
|
||||
.calendar {
|
||||
width: 200px;
|
||||
background: pink;
|
||||
}
|
||||
.calendar span {
|
||||
font-weight: bold;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
...
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
### 3. Write JS
|
||||
|
||||
Next we write a JavaScript file that specifies how to interact with this component.
|
||||
|
||||
You are free to use any javascript framework you want.
|
||||
|
||||
```js title="[project root]/components/calendar/calendar.js"
|
||||
(function () {
|
||||
document.querySelector(".calendar").onclick = () => {
|
||||
alert("Clicked calendar!");
|
||||
};
|
||||
})();
|
||||
```
|
||||
|
||||
A good way to make sure the JS of this component doesn't clash with other components is to define all JS code inside
|
||||
an [anonymous self-invoking function](https://developer.mozilla.org/en-US/docs/Glossary/IIFE) (`(() => { ... })()`).
|
||||
This makes all variables defined only be defined inside this component and not affect other components.
|
||||
|
||||
<!-- TODO: UPDATE AFTER FUNCTIONS WRAPPED -->
|
||||
!!! note
|
||||
|
||||
Soon, django-components will automatically wrap your JS in a self-invoking function by default
|
||||
(except for JS defined with `<script type="module">`).
|
||||
|
||||
Similarly to CSS, JS will be inserted into the page as an inlined `<script>` tag, at the position defined by
|
||||
[`{% component_js_dependencies %}`](../../reference/template_tags.md#component_js_dependencies),
|
||||
or at the end of the inside the `<body>` tag (See [JS and CSS output locations](../../advanced/rendering_js_css/#js-and-css-output-locations)).
|
||||
|
||||
So in your HTML, you may see something like this:
|
||||
|
||||
```html
|
||||
<html>
|
||||
<head>
|
||||
...
|
||||
</head>
|
||||
<body>
|
||||
...
|
||||
<script>
|
||||
(function () {
|
||||
document.querySelector(".calendar").onclick = () => {
|
||||
alert("Clicked calendar!");
|
||||
};
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
#### Rules of JS execution
|
||||
|
||||
1. **JS is executed in the order in which the components are found in the HTML**
|
||||
|
||||
By default, the JS is inserted as a **synchronous** script (`<script> ... </script>`)
|
||||
|
||||
So if you define multiple components on the same page, their JS will be
|
||||
executed in the order in which the components are found in the HTML.
|
||||
|
||||
So if we have a template like so:
|
||||
|
||||
```htmldjango
|
||||
<html>
|
||||
<head>
|
||||
...
|
||||
</head>
|
||||
<body>
|
||||
{% component "calendar" / %}
|
||||
{% component "table" / %}
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
Then the JS file of the component `calendar` will be executed first, and the JS file
|
||||
of component `table` will be executed second.
|
||||
|
||||
2. **JS will be executed only once, even if there is multiple instances of the same component**
|
||||
|
||||
In this case, the JS of `calendar` will STILL execute first (because it was found first),
|
||||
and will STILL execute only once, even though it's present twice:
|
||||
|
||||
```htmldjango
|
||||
<html>
|
||||
<head>
|
||||
...
|
||||
</head>
|
||||
<body>
|
||||
{% component "calendar" / %}
|
||||
{% component "table" / %}
|
||||
{% component "calendar" / %}
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
|
||||
### 4. Link JS and CSS to a component
|
||||
|
||||
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)).
|
||||
|
||||
```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"
|
||||
|
||||
def get_context_data(self):
|
||||
return {
|
||||
"date": "1970-01-01",
|
||||
}
|
||||
```
|
||||
|
||||
!!! info
|
||||
|
||||
The `Media` nested class is shaped based on [Django's Media class](https://docs.djangoproject.com/en/5.1/topics/forms/media/).
|
||||
|
||||
As such, django-components allows multiple formats to define the nested Media class:
|
||||
|
||||
```py
|
||||
# Single files
|
||||
class Media:
|
||||
js = "calendar.js"
|
||||
css = "calendar.css"
|
||||
|
||||
# Lists of files
|
||||
class Media:
|
||||
js = ["calendar.js", "calendar2.js"]
|
||||
css = ["calendar.css", "calendar2.css"]
|
||||
|
||||
# Dictionary of media types for CSS
|
||||
class Media:
|
||||
js = ["calendar.js", "calendar2.js"]
|
||||
css = {
|
||||
"all": ["calendar.css", "calendar2.css"],
|
||||
}
|
||||
```
|
||||
|
||||
If you define a list of JS files, they will be executed one-by-one, left-to-right.
|
||||
|
||||
!!! note
|
||||
|
||||
Same as with the template file, the file paths for the JS and CSS 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`)
|
||||
|
||||
|
||||
And that's it! If you were to embed this component in an HTML, django-components will
|
||||
automatically embed the associated JS and CSS.
|
||||
|
||||
Now that we have a fully-defined component, [next let's use it in a Django template ➡️](./components_in_templates.md).
|
295
docs/concepts/getting_started/adding_slots.md
Normal file
295
docs/concepts/getting_started/adding_slots.md
Normal file
|
@ -0,0 +1,295 @@
|
|||
---
|
||||
title: Adding slots
|
||||
weight: 5
|
||||
---
|
||||
|
||||
Our calendar component's looking great! But we just got a new assignment from
|
||||
our colleague - The calendar date needs to be shown on 3 different pages:
|
||||
|
||||
1. On one page, it needs to be shown as is
|
||||
2. On the second, the date needs to be **bold**
|
||||
3. On the third, the date needs to be in *italics*
|
||||
|
||||
As a reminder, this is what the component's template looks like:
|
||||
|
||||
```htmldjango
|
||||
<div class="calendar">
|
||||
Today's date is <span>{{ date }}</span>
|
||||
</div>
|
||||
```
|
||||
|
||||
There's many ways we could approach this:
|
||||
|
||||
- Expose the date in a slot
|
||||
- Style `.calendar > span` differently on different pages
|
||||
- Pass a variable to the component that decides how the date is rendered
|
||||
- Create a new component
|
||||
|
||||
First two options are more flexible, because the custom styling is not baked into a component's
|
||||
implementation. And for the sake of demonstration, we'll solve this challenge with slots.
|
||||
|
||||
### 1. What are slots
|
||||
|
||||
Components support something called [Slots](../fundamentals/slots.md).
|
||||
|
||||
When a component is used inside another template, slots allow the parent template
|
||||
to override specific parts of the child component by passing in different content.
|
||||
|
||||
This mechanism makes components more reusable and composable.
|
||||
|
||||
This behavior is similar to [slots in Vue](https://vuejs.org/guide/components/slots.html).
|
||||
|
||||
In the example below we introduce two tags that work hand in hand to make this work. These are...
|
||||
|
||||
- `{% slot <name> %}`/`{% endslot %}`: Declares a new slot in the component template.
|
||||
- `{% fill <name> %}`/`{% endfill %}`: (Used inside a [`{% component %}`](../../reference/template_tags.md#component)
|
||||
tag pair.) Fills a declared slot with the specified content.
|
||||
|
||||
### 2. Add a slot tag
|
||||
|
||||
Let's update our calendar component to support more customization. We'll add
|
||||
[`{% slot %}`](../../reference/template_tags.md#slot) tag to the template:
|
||||
|
||||
```htmldjango
|
||||
<div class="calendar">
|
||||
Today's date is
|
||||
{% slot "date" default %} {# <--- new #}
|
||||
<span>{{ date }}</span>
|
||||
{% endslot %}
|
||||
</div>
|
||||
```
|
||||
|
||||
Notice that:
|
||||
|
||||
1. We named the slot `date` - so we can fill this slot by using `{% fill "date" %}`
|
||||
|
||||
2. We also made it the [default slot](../fundamentals/slots.md#default-slot).
|
||||
|
||||
3. We placed our original implementation inside the [`{% slot %}`](../../reference/template_tags.md#slot)
|
||||
tag - this is what will be rendered when the slot is NOT overriden.
|
||||
|
||||
### 3. Add fill tag
|
||||
|
||||
Now we can use [`{% fill %}`](../../reference/template_tags.md#fill) tags inside the
|
||||
[`{% component %}`](../../reference/template_tags.md#component) tags to override the `date` slot
|
||||
to generate the bold and italics variants:
|
||||
|
||||
```htmldjango
|
||||
{# Default #}
|
||||
{% component "calendar" date="2024-12-13" / %}
|
||||
|
||||
{# Bold #}
|
||||
{% component "calendar" date="2024-12-13" %}
|
||||
<b> 2024-12-13 </b>
|
||||
{% endcomponent %}
|
||||
|
||||
{# Italics #}
|
||||
{% component "calendar" date="2024-12-13" %}
|
||||
<i> 2024-12-13 </i>
|
||||
{% endcomponent %}
|
||||
```
|
||||
|
||||
Which will render as:
|
||||
|
||||
```html
|
||||
<!-- Default -->
|
||||
<div class="calendar">
|
||||
Today's date is <span>2024-12-13</span>
|
||||
</div>
|
||||
|
||||
<!-- Bold -->
|
||||
<div class="calendar">
|
||||
Today's date is <b>2024-12-13</b>
|
||||
</div>
|
||||
|
||||
<!-- Italics -->
|
||||
<div class="calendar">
|
||||
Today's date is <i>2024-12-13</i>
|
||||
</div>
|
||||
```
|
||||
|
||||
!!! info
|
||||
|
||||
Since we used the `default` flag on `{% slot "date" %}` inside our calendar component,
|
||||
we can target the `date` component in multiple ways:
|
||||
|
||||
1. Explicitly by it's name
|
||||
```htmldjango
|
||||
{% component "calendar" date="2024-12-13" %}
|
||||
{% fill "date" %}
|
||||
<i> 2024-12-13 </i>
|
||||
{% endfill %}
|
||||
{% endcomponent %}
|
||||
```
|
||||
|
||||
2. Implicitly as the [default slot](../fundamentals/slots.md#default-slot) (Omitting the
|
||||
[`{% fill %}`](../../reference/template_tags.md#fill) tag)
|
||||
```htmldjango
|
||||
{% component "calendar" date="2024-12-13" %}
|
||||
<i> 2024-12-13 </i>
|
||||
{% endcomponent %}
|
||||
```
|
||||
|
||||
3. Explicitly as the [default slot](../fundamentals/slots.md#default-slot) (Setting fill name to `default`)
|
||||
```htmldjango
|
||||
{% component "calendar" date="2024-12-13" %}
|
||||
{% fill "default" %}
|
||||
<i> 2024-12-13 </i>
|
||||
{% endfill %}
|
||||
{% endcomponent %}
|
||||
```
|
||||
|
||||
### 5. Wait, there's a bug
|
||||
|
||||
There is a mistake in our code! `2024-12-13` is Friday, so that's fine. But if we updated
|
||||
the to `2024-12-14`, which is Saturday, our template from previous step would render this:
|
||||
|
||||
```html
|
||||
<!-- Default -->
|
||||
<div class="calendar">
|
||||
Today's date is <span>2024-12-16</span>
|
||||
</div>
|
||||
|
||||
<!-- Bold -->
|
||||
<div class="calendar">
|
||||
Today's date is <b>2024-12-14</b>
|
||||
</div>
|
||||
|
||||
<!-- Italics -->
|
||||
<div class="calendar">
|
||||
Today's date is <i>2024-12-14</i>
|
||||
</div>
|
||||
```
|
||||
|
||||
The first instance rendered `2024-12-16`, while the rest rendered `2024-12-14`!
|
||||
|
||||
Why? Remember that in the [`get_context_data()`](../../../reference/api#django_components.Component.get_context_data)
|
||||
method of our Calendar component, we pre-process the date. If the date falls on Saturday or Sunday, we shift it to next Monday:
|
||||
|
||||
```python title="[project root]/components/calendar/calendar.py"
|
||||
from datetime import date
|
||||
|
||||
from django_components import Component, register
|
||||
|
||||
# If date is Sat or Sun, shift it to next Mon, so the date is always workweek.
|
||||
def to_workweek_date(d: date):
|
||||
...
|
||||
|
||||
@register("calendar")
|
||||
class Calendar(Component):
|
||||
template_name = "calendar.html"
|
||||
...
|
||||
def get_context_data(self, date: date, extra_class: str | None = None):
|
||||
workweek_date = to_workweek_date(date)
|
||||
return {
|
||||
"date": workweek_date,
|
||||
"extra_class": extra_class,
|
||||
}
|
||||
```
|
||||
|
||||
And the issue is that in our template, we used the `date` value that we used *as input*,
|
||||
which is NOT the same as the `date` variable used inside Calendar's template.
|
||||
|
||||
### 5. Adding data to slots
|
||||
|
||||
We want to use the same `date` variable that's used inside Calendar's template.
|
||||
|
||||
Luckily, django-components allows passing data to the slot, also known as [Scoped slots](../fundamentals/slots.md#scoped-slots).
|
||||
|
||||
This consists of two steps:
|
||||
|
||||
1. Pass the `date` variable to the [`{% slot %}`](../../reference/template_tags.md#slot) tag
|
||||
2. Access the `date` variable in the [`{% fill %}`](../../reference/template_tags.md#fill)
|
||||
tag by using the special `data` kwarg
|
||||
|
||||
Let's update the Calendar's template:
|
||||
|
||||
```htmldjango
|
||||
<div class="calendar">
|
||||
Today's date is
|
||||
{% slot "date" default date=date %} {# <--- changed #}
|
||||
<span>{{ date }}</span>
|
||||
{% endslot %}
|
||||
</div>
|
||||
```
|
||||
|
||||
!!! info
|
||||
|
||||
The [`{% slot %}`](../../reference/template_tags.md#slot) tag has one special kwarg, `name`. When you write
|
||||
|
||||
```htmldjango
|
||||
{% slot "date" / %}
|
||||
```
|
||||
|
||||
It's the same as:
|
||||
|
||||
```htmldjango
|
||||
{% slot name="date" / %}
|
||||
```
|
||||
|
||||
Other than the `name` kwarg, you can pass any extra kwargs to the [`{% slot %}`](../../reference/template_tags.md#slot) tag,
|
||||
and these will be exposed as the slot's data.
|
||||
|
||||
```htmldjango
|
||||
{% slot name="date" kwarg1=123 kwarg2="text" kwarg3=my_var / %}
|
||||
```
|
||||
|
||||
### 6. Accessing slot data in fills
|
||||
|
||||
Now, on the [`{% fill %}`](../../reference/template_tags.md#fill) tags, we can use the `data` kwarg to specify the variable under which
|
||||
the slot data will be available.
|
||||
|
||||
The variable from the `data` kwarg contains all the extra kwargs passed to the [`{% slot %}`](../../reference/template_tags.md#slot) tag.
|
||||
|
||||
So if we set `data="slot_data"`, then we can access the date variable under `slot_data.date`:
|
||||
|
||||
```htmldjango
|
||||
{# Default #}
|
||||
{% component "calendar" date="2024-12-13" / %}
|
||||
|
||||
{# Bold #}
|
||||
{% component "calendar" date="2024-12-13" %}
|
||||
{% fill "date" data="slot_data" %}
|
||||
<b> {{ slot_data.date }} </b>
|
||||
{% endfill %}
|
||||
{% endcomponent %}
|
||||
|
||||
{# Italics #}
|
||||
{% component "calendar" date="2024-12-13" %}
|
||||
{% fill "date" data="slot_data" %}
|
||||
<i> {{ slot_data.date }} </i>
|
||||
{% endfill %}
|
||||
{% endcomponent %}
|
||||
```
|
||||
|
||||
By using the `date` variable from the slot, we'll render the correct date
|
||||
each time:
|
||||
|
||||
```html
|
||||
<!-- Default -->
|
||||
<div class="calendar">
|
||||
Today's date is <span>2024-12-16</span>
|
||||
</div>
|
||||
|
||||
<!-- Bold -->
|
||||
<div class="calendar">
|
||||
Today's date is <b>2024-12-16</b>
|
||||
</div>
|
||||
|
||||
<!-- Italics -->
|
||||
<div class="calendar">
|
||||
Today's date is <i>2024-12-16</i>
|
||||
</div>
|
||||
```
|
||||
|
||||
!!! info
|
||||
|
||||
**When to use slots vs variables?**
|
||||
|
||||
Generally, slots are more flexible - you can access the slot data, even the original slot content.
|
||||
Thus, slots behave more like functions that render content based on their context.
|
||||
|
||||
On the other hand, variables are static - the variable you pass to a component is what will be used.
|
||||
|
||||
Moreover, slots are treated as part of the template - for example the CSS scoping (work in progress)
|
||||
is applied to the slot content too.
|
196
docs/concepts/getting_started/components_in_templates.md
Normal file
196
docs/concepts/getting_started/components_in_templates.md
Normal file
|
@ -0,0 +1,196 @@
|
|||
---
|
||||
title: Components in templates
|
||||
weight: 3
|
||||
---
|
||||
|
||||
By the end of this section, we want to be able to use our components in Django templates like so:
|
||||
|
||||
```htmldjango
|
||||
{% load component_tags %}
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>My example calendar</title>
|
||||
</head>
|
||||
<body>
|
||||
{% component "calendar" / %}
|
||||
</body>
|
||||
<html>
|
||||
```
|
||||
|
||||
### 1. Register component
|
||||
|
||||
First, however, we need to register our component class with [`ComponentRegistry`](../../../reference/api#django_components.ComponentRegistry).
|
||||
|
||||
To register a component with a [`ComponentRegistry`](../../../reference/api#django_components.ComponentRegistry),
|
||||
we will use the [`@register`](../../../reference/api#django_components.register)
|
||||
decorator, and give it a name under which the component will be accessible from within the template:
|
||||
|
||||
```python title="[project root]/components/calendar/calendar.py"
|
||||
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"
|
||||
|
||||
def get_context_data(self):
|
||||
return {
|
||||
"date": "1970-01-01",
|
||||
}
|
||||
```
|
||||
|
||||
This will register the component to the default registry. Default registry is loaded into the template
|
||||
by calling `{% load component_tags %}` inside the template.
|
||||
|
||||
!!! info
|
||||
|
||||
Why do we have to register components?
|
||||
|
||||
We want to use our component as a template tag (`{% ... %}`) in Django template.
|
||||
|
||||
In Django, template tags are managed by the `Library` instances. Whenever you include `{% load xxx %}`
|
||||
in your template, you are loading a `Library` instance into your template.
|
||||
|
||||
[`ComponentRegistry`](../../../reference/api#django_components.ComponentRegistry) acts like a router
|
||||
and connects the registered components with the associated `Library`.
|
||||
|
||||
That way, when you include `{% load component_tags %}` in your template, you are able to "call" components
|
||||
like `{% component "calendar" / %}`.
|
||||
|
||||
`ComponentRegistries` also make it possible to group and share components as standalone packages.
|
||||
[Learn more here](../advanced/authoring_component_libraries.md).
|
||||
|
||||
!!! note
|
||||
|
||||
You can create custom [`ComponentRegistry`](../../../reference/api#django_components.ComponentRegistry)
|
||||
instances, which will use different `Library` instances.
|
||||
In that case you will have to load different libraries depending on which components you want to use:
|
||||
|
||||
Example 1 - Using component defined in the default registry
|
||||
```htmldjango
|
||||
{% load component_tags %}
|
||||
<div>
|
||||
{% component "calendar" / %}
|
||||
</div>
|
||||
```
|
||||
|
||||
Example 2 - Using component defined in a custom registry
|
||||
```htmldjango
|
||||
{% load my_custom_tags %}
|
||||
<div>
|
||||
{% my_component "table" / %}
|
||||
</div>
|
||||
```
|
||||
|
||||
Note that, because the tag name `component` is use by the default ComponentRegistry,
|
||||
the custom registry was configured to use the tag `my_component` instead. [Read more here](../advanced/component_registry.md)
|
||||
|
||||
### 2. Load and use the component in template
|
||||
|
||||
The component is now registered under the name `calendar`. All that remains to do is to load
|
||||
and render the component inside a template:
|
||||
|
||||
```htmldjango
|
||||
{% load component_tags %} {# Load the default registry #}
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>My example calendar</title>
|
||||
</head>
|
||||
<body>
|
||||
{% component "calendar" / %} {# Render the component #}
|
||||
</body>
|
||||
<html>
|
||||
```
|
||||
|
||||
!!! info
|
||||
|
||||
Component tags should end with `/` if they do not contain any [Slot fills](../fundamentals/slots.md).
|
||||
But you can also use `{% endcomponent %}` instead:
|
||||
|
||||
```htmldjango
|
||||
{% component "calendar" %}{% endcomponent %}
|
||||
```
|
||||
|
||||
We defined the Calendar's template as
|
||||
|
||||
```htmldjango
|
||||
<div class="calendar">
|
||||
Today's date is <span>{{ date }}</span>
|
||||
</div>
|
||||
```
|
||||
|
||||
and the variable `date` as `"1970-01-01"`.
|
||||
|
||||
Thus, the final output will look something like this:
|
||||
|
||||
```htmldjango
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>My example calendar</title>
|
||||
<style>
|
||||
.calendar {
|
||||
width: 200px;
|
||||
background: pink;
|
||||
}
|
||||
.calendar span {
|
||||
font-weight: bold;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="calendar">
|
||||
Today's date is <span>1970-01-01</span>
|
||||
</div>
|
||||
<script>
|
||||
(function () {
|
||||
document.querySelector(".calendar").onclick = () => {
|
||||
alert("Clicked calendar!");
|
||||
};
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
<html>
|
||||
```
|
||||
|
||||
This makes it possible to organize your front-end around reusable components, instead of relying on template tags
|
||||
and keeping your CSS and Javascript in the static directory.
|
||||
|
||||
!!! info
|
||||
|
||||
Remember that you can use
|
||||
[`{% component_js_dependencies %}`](../../reference/template_tags.md#component_js_dependencies)
|
||||
and [`{% component_css_dependencies %}`](../../reference/template_tags.md#component_css_dependencies)
|
||||
to change where the `<script>` and `<style>` tags will be rendered (See [JS and CSS output locations](../../advanced/rendering_js_css/#js-and-css-output-locations)).
|
||||
|
||||
!!! info
|
||||
|
||||
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.
|
||||
|
||||
This is because django-components automatically imports all Python files found in the component directories
|
||||
during an event called [Autodiscovery](../fundamentals/autodiscovery.md).
|
||||
|
||||
So with Autodiscovery, it's the same as if you manually imported the component files on the `ready()` hook:
|
||||
|
||||
```python
|
||||
class MyApp(AppConfig):
|
||||
default_auto_field = "django.db.models.BigAutoField"
|
||||
name = "myapp"
|
||||
|
||||
def ready(self):
|
||||
import myapp.components.calendar
|
||||
import myapp.components.table
|
||||
...
|
||||
```
|
||||
|
||||
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
|
||||
is configurable from within the template ➡️](./parametrising_components.md)
|
225
docs/concepts/getting_started/parametrising_components.md
Normal file
225
docs/concepts/getting_started/parametrising_components.md
Normal file
|
@ -0,0 +1,225 @@
|
|||
---
|
||||
title: Parametrising components
|
||||
weight: 4
|
||||
---
|
||||
|
||||
So far, our Calendar component will always render the date `1970-01-01`. Let's make it more useful and flexible
|
||||
by being able to pass in custom date.
|
||||
|
||||
What we want is to be able to use the Calendar component within the template like so:
|
||||
|
||||
```htmldjango
|
||||
{% component "calendar" date="2024-12-13" extra_class="text-red" / %}
|
||||
```
|
||||
|
||||
### 1. Understading component inputs
|
||||
|
||||
In section [Create your first component](./your_first_component.md), we defined
|
||||
the [`get_context_data()`](../../../reference/api#django_components.Component.get_context_data) method
|
||||
that defines what variables will be available within the template:
|
||||
|
||||
```python title="[project root]/components/calendar/calendar.py"
|
||||
from django_components import Component, register
|
||||
|
||||
@register("calendar")
|
||||
class Calendar(Component):
|
||||
template_name = "calendar.html"
|
||||
...
|
||||
def get_context_data(self):
|
||||
return {
|
||||
"date": "1970-01-01",
|
||||
}
|
||||
```
|
||||
|
||||
What we didn't say is that [`get_context_data()`](../../../reference/api#django_components.Component.get_context_data)
|
||||
actually receives the args and kwargs that were passed to a component.
|
||||
|
||||
So if we call a component with a `date` and `extra_class` keywords:
|
||||
|
||||
```htmldjango
|
||||
{% component "calendar" date="2024-12-13" extra_class="text-red" / %}
|
||||
```
|
||||
|
||||
This is the same as calling:
|
||||
|
||||
```py
|
||||
Calendar.get_context_data(date="2024-12-13", extra_class="text-red")
|
||||
```
|
||||
|
||||
And same applies to positional arguments, or mixing args and kwargs, where:
|
||||
|
||||
```htmldjango
|
||||
{% component "calendar" "2024-12-13" extra_class="text-red" / %}
|
||||
```
|
||||
|
||||
is same as
|
||||
|
||||
```py
|
||||
Calendar.get_context_data("2024-12-13", extra_class="text-red")
|
||||
```
|
||||
|
||||
### 2. Define inputs for `get_context_data`
|
||||
|
||||
Let's put this to test. We want to pass `date` and `extra_class` kwargs to the component.
|
||||
And so, we can write the [`get_context_data()`](../../../reference/api#django_components.Component.get_context_data)
|
||||
method such that it expects those parameters:
|
||||
|
||||
```python title="[project root]/components/calendar/calendar.py"
|
||||
from datetime import date
|
||||
|
||||
from django_components import Component, register
|
||||
|
||||
@register("calendar")
|
||||
class Calendar(Component):
|
||||
template_name = "calendar.html"
|
||||
...
|
||||
def get_context_data(self, date: date, extra_class: str | None = None):
|
||||
return {
|
||||
"date": date,
|
||||
"extra_class": extra_class,
|
||||
}
|
||||
```
|
||||
|
||||
!!! info
|
||||
|
||||
Since [`get_context_data()`](../../../reference/api#django_components.Component.get_context_data)
|
||||
is just a regular Python function, type hints annotations work the same way as anywhere else.
|
||||
|
||||
!!! warning
|
||||
|
||||
Since [`get_context_data()`](../../../reference/api#django_components.Component.get_context_data)
|
||||
is just a regular Python function, it will raise TypeError if it receives incorrect parameters.
|
||||
|
||||
Since `extra_class` is optional in the function signature, it's optional also in the template.
|
||||
So both following calls are valid:
|
||||
|
||||
```htmldjango
|
||||
{% component "calendar" "2024-12-13" / %}
|
||||
{% component "calendar" "2024-12-13" extra_class="text-red" / %}
|
||||
```
|
||||
|
||||
However, `date` is required. Thus we MUST provide it. Same with regular Python functions,
|
||||
`date` can be set either as positional or keyword argument. But either way it MUST be set:
|
||||
|
||||
```htmldjango
|
||||
✅
|
||||
{% component "calendar" "2024-12-13" / %}
|
||||
{% component "calendar" extra_class="text-red" date="2024-12-13" / %}
|
||||
|
||||
❌
|
||||
{% component "calendar" extra_class="text-red" / %}
|
||||
```
|
||||
|
||||
### 3. Process inputs in `get_context_data`
|
||||
|
||||
The [`get_context_data()`](../../../reference/api#django_components.Component.get_context_data)
|
||||
method is powerful, because it allows us to decouple
|
||||
component inputs from the template variables. In other words, we can pre-process
|
||||
the component inputs, and massage them into a shape that's most appropriate for
|
||||
what the template needs. And it also allows us to pass in static data into the template.
|
||||
|
||||
Imagine our component receives data from the database that looks like below
|
||||
([taken from Django](https://docs.djangoproject.com/en/5.1/ref/templates/builtins/#regroup)).
|
||||
|
||||
```py
|
||||
cities = [
|
||||
{"name": "Mumbai", "population": "19,000,000", "country": "India"},
|
||||
{"name": "Calcutta", "population": "15,000,000", "country": "India"},
|
||||
{"name": "New York", "population": "20,000,000", "country": "USA"},
|
||||
{"name": "Chicago", "population": "7,000,000", "country": "USA"},
|
||||
{"name": "Tokyo", "population": "33,000,000", "country": "Japan"},
|
||||
]
|
||||
```
|
||||
|
||||
We need to group the list items by size into following buckets by population:
|
||||
|
||||
- 0-10,000,000
|
||||
- 10,000,001-20,000,000
|
||||
- 20,000,001-30,000,000
|
||||
- +30,000,001
|
||||
|
||||
So we want to end up with following data:
|
||||
|
||||
```py
|
||||
cities_by_pop = [
|
||||
{
|
||||
"name": "0-10,000,000",
|
||||
"items": [
|
||||
{"name": "Chicago", "population": "7,000,000", "country": "USA"},
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "10,000,001-20,000,000",
|
||||
"items": [
|
||||
{"name": "Calcutta", "population": "15,000,000", "country": "India"},
|
||||
{"name": "Mumbai", "population": "19,000,000", "country": "India"},
|
||||
{"name": "New York", "population": "20,000,000", "country": "USA"},
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "30,000,001-40,000,000",
|
||||
"items": [
|
||||
{"name": "Tokyo", "population": "33,000,000", "country": "Japan"},
|
||||
]
|
||||
},
|
||||
]
|
||||
```
|
||||
|
||||
Without the `get_context_data()` method, we'd have to either:
|
||||
|
||||
1. Pre-process the data in Python before passing it to the components.
|
||||
2. Define a Django filter or template tag to take the data and process it on the spot.
|
||||
|
||||
Instead, with `get_context_data()`, we can keep this transformation private to this component,
|
||||
and keep the rest of the codebase clean.
|
||||
|
||||
```py
|
||||
def group_by_pop(data):
|
||||
...
|
||||
|
||||
@register("population_table")
|
||||
class PopulationTable(Component):
|
||||
template_name = "population_table.html"
|
||||
|
||||
def get_context_data(self, data):
|
||||
return {
|
||||
"data": group_by_pop(data),
|
||||
}
|
||||
```
|
||||
|
||||
Similarly we can make use of `get_context_data()` to pre-process the date that was given to the component:
|
||||
|
||||
```python title="[project root]/components/calendar/calendar.py"
|
||||
from datetime import date
|
||||
|
||||
from django_components import Component, register
|
||||
|
||||
# If date is Sat or Sun, shift it to next Mon, so the date is always workweek.
|
||||
def to_workweek_date(d: date):
|
||||
...
|
||||
|
||||
@register("calendar")
|
||||
class Calendar(Component):
|
||||
template_name = "calendar.html"
|
||||
...
|
||||
def get_context_data(self, date: date, extra_class: str | None = None):
|
||||
workweek_date = to_workweek_date(date) # <--- new
|
||||
return {
|
||||
"date": workweek_date, # <--- changed
|
||||
"extra_class": extra_class,
|
||||
}
|
||||
```
|
||||
|
||||
### 4. Pass inputs to components
|
||||
|
||||
Once we're happy with `Calendar.get_contex_data()`, we can update our templates to use
|
||||
the parametrized version of the component:
|
||||
|
||||
```htmldjango
|
||||
<div>
|
||||
{% component "calendar" date="2024-12-13" / %}
|
||||
{% component "calendar" date="1970-01-01" / %}
|
||||
</div>
|
||||
```
|
||||
|
||||
Next, you will learn [how to use slots give your components even more flexibility ➡️](./adding_slots.md)
|
157
docs/concepts/getting_started/your_first_component.md
Normal file
157
docs/concepts/getting_started/your_first_component.md
Normal file
|
@ -0,0 +1,157 @@
|
|||
---
|
||||
title: Create your first component
|
||||
weight: 1
|
||||
---
|
||||
|
||||
A component in django-components can be as simple as a Django template and Python code to declare the component:
|
||||
|
||||
```py
|
||||
from django_components import Component
|
||||
|
||||
class Calendar(Component):
|
||||
template = """
|
||||
<div class="calendar">
|
||||
Today's date is <span>{{ date }}</span>
|
||||
</div>
|
||||
"""
|
||||
```
|
||||
|
||||
Or a combination of Django template, Python, CSS, and Javascript:
|
||||
|
||||
```py
|
||||
from django_components import Component
|
||||
|
||||
class Calendar(Component):
|
||||
template = """
|
||||
<div class="calendar">
|
||||
Today's date is <span>{{ date }}</span>
|
||||
</div>
|
||||
"""
|
||||
|
||||
css = """
|
||||
.calendar {
|
||||
width: 200px;
|
||||
background: pink;
|
||||
}
|
||||
"""
|
||||
|
||||
js = """
|
||||
document.querySelector(".calendar").onclick = function () {
|
||||
alert("Clicked calendar!");
|
||||
};
|
||||
"""
|
||||
```
|
||||
|
||||
!!! 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.
|
||||
|
||||
|
||||
We'll start by creating a component that defines only a Django template:
|
||||
|
||||
### 1. Create project structure
|
||||
|
||||
Start by creating empty `calendar.py` and `calendar.html` files:
|
||||
|
||||
```
|
||||
sampleproject/
|
||||
├── calendarapp/
|
||||
├── components/ 🆕
|
||||
│ └── calendar/ 🆕
|
||||
│ ├── calendar.py 🆕
|
||||
│ └── calendar.html 🆕
|
||||
├── sampleproject/
|
||||
├── manage.py
|
||||
└── requirements.txt
|
||||
```
|
||||
|
||||
### 2. Write Django template
|
||||
|
||||
Inside `calendar.html`, write:
|
||||
|
||||
```htmldjango title="[project root]/components/calendar/calendar.html"
|
||||
<div class="calendar">
|
||||
Today's date is <span>{{ date }}</span>
|
||||
</div>
|
||||
```
|
||||
|
||||
In this example we've defined one template variable `date`. You can use any and as many variables as you like. These variables will be
|
||||
defined in the Python file in [`get_context_data()`](../../../reference/api#django_components.Component.get_context_data)
|
||||
when creating an instance of this component.
|
||||
|
||||
!!! note
|
||||
|
||||
The template will be rendered with whatever template backend you've specified in your Django settings file.
|
||||
|
||||
Currently django-components supports only the default `"django.template.backends.django.DjangoTemplates"` template backend!
|
||||
|
||||
### 3. Create new Component in Python
|
||||
|
||||
In `calendar.py`, create a subclass of [Component](../../../reference/api#django_components.Component)
|
||||
to create a new component.
|
||||
|
||||
To link the HTML template with our component, set [`template_name`](../../../reference/api#django_components.Component.template_name)
|
||||
to the name of the HTML file.
|
||||
|
||||
```python title="[project root]/components/calendar/calendar.py"
|
||||
from django_components import Component
|
||||
|
||||
class Calendar(Component):
|
||||
template_name = "calendar.html"
|
||||
```
|
||||
|
||||
!!! note
|
||||
|
||||
The path to the template file can be either:
|
||||
|
||||
1. Relative to the component's python 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`)
|
||||
|
||||
### 4. Define the template variables
|
||||
|
||||
In `calendar.html`, we've used the variable `date`. So we need to define it for the template to work.
|
||||
|
||||
This is done using [`Component.get_context_data()`](../../../reference/api#django_components.Component.get_context_data).
|
||||
It's a function that returns a dictionary. The entries in this dictionary
|
||||
will become available within the template as variables, e.g. as `{{ date }}`.
|
||||
|
||||
```python title="[project root]/components/calendar/calendar.py"
|
||||
from django_components import Component
|
||||
|
||||
class Calendar(Component):
|
||||
template_name = "calendar.html"
|
||||
|
||||
def get_context_data(self):
|
||||
return {
|
||||
"date": "1970-01-01",
|
||||
}
|
||||
```
|
||||
|
||||
Now, when we render the component with [`Component.render()`](../../../reference/api#django_components.Component.render)
|
||||
method:
|
||||
|
||||
```py
|
||||
Calendar.render()
|
||||
```
|
||||
|
||||
It will output
|
||||
|
||||
```html
|
||||
<div class="calendar">
|
||||
Today's date is <span>1970-01-01</span>
|
||||
</div>
|
||||
```
|
||||
|
||||
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