1
0
Fork 0
jinjax/docs/content/guide/components.md

348 lines
9.1 KiB
Markdown
Raw Normal View History

---
title: Components
description: Declaring and using components.
---
<Header title="Components">
</Header>
## Declaring and Using Components
The components are simple text files that look like regular Jinja templates, with three requirements:
**First**, components must be placed inside a folder registered in the catalog or a subfolder of it.
```python
catalog.add_folder("myapp/components")
```
You can call that folder whatever you want, not just "components". You can also add more than one folder:
```python
catalog.add_folder("myapp/layouts")
catalog.add_folder("myapp/components")
```
If you end up having more than one component with the same name, the one in the first folder will take priority.
**Second**, they must have a ".jinja" extension. This also helps code editors automatically select the correct language syntax to highlight. However, you can configure it in the catalog.
**Third**, the component name must start with an uppercase letter. Why? This is how JinjaX differentiates a component from a regular HTML tag when using it. I recommend using PascalCase names, like Python classes.
The name of the file (minus the extension) is also how you call the component. For example, if the file is "components/PersonForm.jinja":
```
└ myapp/
├── app.py
├── components/
└─ PersonForm.jinja
```
The name of the component is "PersonForm" and can be called like this:
From Python code or a non-component template:
- `catalog.render("PersonForm")`
From another component:
- `<PersonForm> some content </PersonForm>`, or
- `<PersonForm />`
If the component is in a subfolder, the name of that folder becomes part of its name too:
```
└ myapp/
├── app.py
├── components/
└─ person
└─ PersonForm.jinja
```
A "components/person/PersonForm.jinja" component is named "person.PersonForm", meaning the name of the subfolder and the name of the file separated by a dot. This is the full name you use to call it:
From Python code or a non-component template:
- `catalog.render("person.PersonForm")`
From another component:
- `<person.PersonForm> some content </person.PersonForm>`, or
- `<person.PersonForm />`
Notice how the folder name doesn't need to start with an uppercase if you don't want it to.
<a href="/static/img/anatomy-en.png" target="_blank">
<img src="/static/img/anatomy-en.png" style="margin:0 auto;width:90%;max-width:35rem;">
</a>
## Taking Arguments
More often than not, a component takes one or more arguments to render. Every argument must be declared at the beginning of the component with `{#def arg1, arg2, ... #}`.
```html+jinja
{#def action, method="post", multipart=False #}
<form method="{{ method }}" action="{{ action }}"
{%- if multipart %} enctype="multipart/form-data"{% endif %}
>
{{ content }}
</form>
```
In this example, the component takes three arguments: "action", "method", and "multipart". The last two have default values, so they are optional, but the first one doesn't. That means it must be passed a value when rendering the component.
The syntax is exactly like how you declare the arguments of a Python function (in fact, it's parsed by the same code), so it can even include type comments, although they are not used by JinjaX (yet!).
```python
{#def
data: dict[str, str],
method: str = "post",
multipart: bool = False
#}
...
```
## Passing Arguments
There are two types of arguments: strings and expressions.
### String
Strings are passed like regular HTML attributes:
```html+jinja
<Form action="/new" method="PATCH"> ... </Form>
<Alert message="Profile updated" />
<Card title="Hello world" type="big"> ... </Card>
```
### Expressions
There are two different but equivalent ways to pass non-string arguments:
"Jinja-like", where you use double curly braces instead of quotes:
```html+jinja title="Jinja-like"
<Example
columns={{ 2 }}
tabbed={{ False }}
panels={{ {'one': 'lorem', 'two': 'ipsum'} }}
class={{ 'bg-' + color }}
/>
```
... and "Vue-like", where you keep using quotes, but prefix the name of the attribute with a colon:
```html+jinja title="Vue-like"
<Example
:columns="2"
:tabbed="False"
:panels="{'one': 'lorem', 'two': 'ipsum'}"
:class="'bg-' + color"
/>
```
<Callout type="note">
For `True` values, you can just use the name, like in HTML:
<br>
```html+jinja
<Example class="green" hidden />
```
</Callout>
<Callout type="note">
You can also use dashes when passing an argument, but they will be translated to underscores:
<br>
```html+jinja
<Example aria-label="Hi" />
```
<br>
```html+jinja title="Example.jinja"
{#def aria_label = "" #}
...
```
</Callout>
## With Content
There is always an extra implicit argument: **the content** inside the component. Read more about it in the [next](/guide/slots) section.
## Extra Arguments
If you pass arguments not declared in a component, those are not discarded but rather collected in an `attrs` object.
You then call `attrs.render()` to render the received arguments as HTML attributes.
For example, this component:
```html+jinja title="Card.jinja"
{#def title #}
<div {{ attrs.render() }}>
<h1>{{ title }}</h1>
{{ content }}
</div>
```
Called as:
```html
<Card title="Products" class="mb-10" open>bla</Card>
```
Will be rendered as:
```html
<div class="mb-10" open>
<h1>Products</h1>
bla
</div>
```
You can add or remove arguments before rendering them using the other methods of the `attrs` object. For example:
```html+jinja
{#def title #}
{% do attrs.set(id="mycard") -%}
<div {{ attrs.render() }}>
<h1>{{ title }}</h1>
{{ content }}
</div>
```
Or directly in the `attrs.render()` call:
```html+jinja
{#def title #}
<div {{ attrs.render(id="mycard") }}>
<h1>{{ title }}</h1>
{{ content }}
</div>
```
<Callout type="info">
The string values passed into components as attrs are not cast to `str` until the string representation is **actually** needed, for example when `attrs.render()` is invoked.
</Callout>
### `attrs` Methods
#### `.render(name=value, ...)`
Renders the attributes and properties as a string.
Any arguments you use with this function are merged with the existing
attibutes/properties by the same rules as the `HTMLAttrs.set()` function:
- Pass a name and a value to set an attribute (e.g. `type="text"`)
- Use `True` as a value to set a property (e.g. `disabled`)
- Use `False` to remove an attribute or property
- The existing attribute/property is overwritten **except** if it is `class`.
The new classes are appended to the old ones instead of replacing them.
- The underscores in the names will be translated automatically to dashes,
so `aria_selected` becomes the attribute `aria-selected`.
To provide consistent output, the attributes and properties
are sorted by name and rendered like this:
`<sorted attributes> + <sorted properties>`.
```html+jinja
<Example class="ipsum" width="42" data-good />
```
```html+jinja
<div {{ attrs.render() }}>
<!-- <div class="ipsum" width="42" data-good> -->
<div {{ attrs.render(class="abc", data_good=False, tabindex=0) }}>
<!-- <div class="abc ipsum" width="42" tabindex="0"> -->
```
<Callout type="warning">
Using `<Component {{ attrs.render() }}>` to pass the extra arguments to other components **WILL NOT WORK**. That is because the components are translated to macros before the page render.
You must pass them as the special argument `_attrs`.
```html+jinja
{#--- WRONG 😵 ---#}
<MyButton {{ attrs.render() }} />
{#--- GOOD 👍 ---#}
<MyButton _attrs={{ attrs }} />
<MyButton :_attrs="attrs" />
```
</Callout>
#### `.set(name=value, ...)`
Sets an attribute or property
- Pass a name and a value to set an attribute (e.g. `type="text"`)
- Use `True` as a value to set a property (e.g. `disabled`)
- Use `False` to remove an attribute or property
- If the attribute is "class", the new classes are appended to
the old ones (if not repeated) instead of replacing them.
- The underscores in the names will be translated automatically to dashes,
so `aria_selected` becomes the attribute `aria-selected`.
```html+jinja title="Adding attributes/properties"
{% do attrs.set(
id="loremipsum",
disabled=True,
data_test="foobar",
class="m-2 p-4",
) %}
```
```html+jinja title="Removing attributes/properties"
{% do attrs.set(
title=False,
disabled=False,
data_test=False,
class=False,
) %}
```
#### `.setdefault(name=value, ...)`
Adds an attribute, but only if it's not already present.
The underscores in the names will be translated automatically to dashes, so `aria_selected`
becomes the attribute `aria-selected`.
```html+jinja
{% do attrs.setdefault(
aria_label="Products"
) %}
```
#### `.add_class(name1, name2, ...)`
Adds one or more classes to the list of classes, if not already present.
```html+jinja
{% do attrs.add_class("hidden") %}
{% do attrs.add_class("active", "animated") %}
```
#### `.remove_class(name1, name2, ...)`
Removes one or more classes from the list of classes.
```html+jinja
{% do attrs.remove_class("hidden") %}
{% do attrs.remove_class("active", "animated") %}
```
#### `.get(name, default=None)`
Returns the value of the attribute or property,
or the default value if it doesn't exist.
```html+jinja
{%- set role = attrs.get("role", "tab") %}
```
...