HTML attributes
New in version 0.74:
You can use the {% html_attrs %}
tag to render various data as key="value"
HTML attributes.
{% html_attrs %}
tag is versatile, allowing you to define HTML attributes however you need:
- Define attributes within the HTML template
- Define attributes in Python code
- Merge attributes from multiple sources
- Boolean attributes
- Append attributes
- Remove attributes
- Define default attributes
From v0.135 onwards, {% html_attrs %}
tag also supports merging style
and class
attributes the same way how Vue does.
To get started, let's consider a simple example. If you have a template:
You can rewrite it with the {% html_attrs %}
tag:
The {% html_attrs %}
tag accepts any number of keyword arguments, which will be merged and rendered as HTML attributes:
Moreover, the {% html_attrs %}
tag accepts two positional arguments:
attrs
- a dictionary of attributes to be rendereddefaults
- a dictionary of default attributes
You can use this for example to allow users of your component to add extra attributes. We achieve this by capturing the extra attributes and passing them to the {% html_attrs %}
tag as a dictionary:
@register("my_comp")
class MyComp(Component):
# Capture extra kwargs in `attrs`
def get_context_data(self, **attrs):
return {
"attrs": attrs,
"classes": "text-red",
"my_id": 123,
}
template: t.django_html = """
{# Pass the extra attributes to `html_attrs` #}
<div {% html_attrs attrs class=classes data-id=my_id %}>
</div>
"""
This way you can render MyComp
with extra attributes:
Either via Django template:
Or via Python:
In both cases, the attributes will be merged and rendered as:
Summaryยค
-
The two arguments,
attrs
anddefaults
, can be passed as positional args:or as kwargs:
-
Both
attrs
anddefaults
are optional and can be omitted. -
Both
attrs
anddefaults
are dictionaries. As such, there's multiple ways to define them:-
By referencing a variable:
-
By defining a literal dictionary:
-
Or by defining the dictionary keys:
-
-
All other kwargs are merged and can be repeated.
Will render:
Usageยค
Boolean attributesยค
In HTML, boolean attributes are usually rendered with no value. Consider the example below where the first button is disabled and the second is not:
HTML rendering with html_attrs
tag or format_attributes
works the same way - an attribute set to True
is rendered without the value, and an attribute set to False
is not rendered at all.
So given this input:
And template:
Then this renders:
Removing attributesยค
Given how the boolean attributes work, you can "remove" or prevent an attribute from being rendered by setting it to False
or None
.
So given this input:
And template:
Then this renders:
Default attributesยค
Sometimes you may want to specify default values for attributes. You can pass a second positional argument to set the defaults.
In the example above, if attrs
contains a certain key, e.g. the class
key, {% html_attrs %}
will render:
Otherwise, {% html_attrs %}
will render:
Appending attributesยค
For the class
HTML attribute, it's common that we want to join multiple values, instead of overriding them.
For example, if you're authoring a component, you may want to ensure that the component will ALWAYS have a specific class. Yet, you may want to allow users of your component to supply their own class
attribute.
We can achieve this by adding extra kwargs. These values will be appended, instead of overwriting the previous value.
So if we have a variable attrs
:
And on {% html_attrs %}
tag, we set the key class
:
Then these will be merged and rendered as:
To simplify merging of variables, you can supply the same key multiple times, and these will be all joined together:
{# my_var = "class-from-var text-red" #}
<div {% html_attrs attrs class="some-class another-class" class=my_var %}>
</div>
Renders:
Merging class
attributesยค
The class
attribute can be specified as a string of class names as usual.
If you want granular control over individual class names, you can use a dictionary.
-
String: Used as is.
Renders:
-
Dictionary: Keys are the class names, and values are booleans. Only keys with truthy values are rendered.
Renders:
If a certain class is specified multiple times, it's the last instance that decides whether the class is rendered or not.
Example:
In this example, the other-class
is specified twice. The last instance is {"other-class": False}
, so the class is not rendered.
Renders:
Merging style
Attributesยค
The style
attribute can be specified as a string of style properties as usual.
If you want granular control over individual style properties, you can use a dictionary.
-
String: Used as is.
Renders:
-
Dictionary: Keys are the style properties, and values are their values.
Renders:
If a style property is specified multiple times, the last value is used.
- If the last time the property is set is
False
, the property is removed. - Properties set to
None
are ignored.
Example:
In this example, the width
property is specified twice. The last instance is {"width": False}
, so the property is removed.
Secondly, the background-color
property is also set twice. But the second time it's set to None
, so that instance is ignored, leaving us only with background-color: blue
.
The color
property is set to a valid value in both cases, so the latter (green
) is used.
{% html_attrs
style="color: red; background-color: blue; width: 100px;"
style={"color": "green", "background-color": None, "width": False}
%}
Renders:
Usage outside of templatesยค
In some cases, you want to prepare HTML attributes outside of templates.
To achieve the same behavior as {% html_attrs %}
tag, you can use the merge_attributes()
and format_attributes()
helper functions.
Merging attributesยค
merge_attributes()
accepts any number of dictionaries and merges them together, using the same merge strategy as {% html_attrs %}
.
from django_components import merge_attributes
merge_attributes(
{"class": "my-class", "data-id": 123},
{"class": "extra-class"},
{"class": {"cool-class": True, "uncool-class": False} },
)
Which will output:
Warning
Unlike {% html_attrs %}
, where you can pass extra kwargs, merge_attributes()
requires each argument to be a dictionary.
Formatting attributesยค
format_attributes()
serializes attributes the same way as {% html_attrs %}
tag does.
from django_components import format_attributes
format_attributes({
"class": "my-class text-red pa-4",
"data-id": 123,
"required": True,
"disabled": False,
"ignored-attr": None,
})
Which will output:
Note
Prior to v0.135, the format_attributes()
function was named attributes_to_string()
.
This function is now deprecated and will be removed in v1.0.
Cheat sheetยค
Assuming that:
class_from_var = "from-var"
attrs = {
"class": "from-attrs",
"type": "submit",
}
defaults = {
"class": "from-defaults",
"role": "button",
}
Then:
-
Empty tag
renders nothing:
-
Only kwargs
renders:
-
Only attrs
renders:
-
Attrs as kwarg
renders:
-
Only defaults (as kwarg)
renders:
-
Attrs using the
prefix:key=value
constructrenders:
-
Defaults using the
prefix:key=value
constructrenders:
-
All together (1) - attrs and defaults as positional args:
renders:
-
All together (2) - attrs and defaults as kwargs args:
<div {% html_attrs class="added_class" class=class_from_var data-id=123 attrs=attrs defaults=defaults %}></div>
renders:
-
All together (3) - mixed:
<div {% html_attrs attrs defaults:class="default-class" class="added_class" class=class_from_var data-id=123 %}></div>
renders:
Full exampleยค
@register("my_comp")
class MyComp(Component):
template: t.django_html = """
<div
{% html_attrs attrs
defaults:class="pa-4 text-red"
class="my-comp-date"
class=class_from_var
data-id="123"
%}
>
Today's date is <span>{{ date }}</span>
</div>
"""
def get_context_data(self, date: Date, attrs: dict):
return {
"date": date,
"attrs": attrs,
"class_from_var": "extra-class"
}
@register("parent")
class Parent(Component):
template: t.django_html = """
{% component "my_comp"
date=date
attrs:class="pa-0 border-solid border-red"
attrs:data-json=json_data
attrs:@click="(e) => onClick(e, 'from_parent')"
/ %}
"""
def get_context_data(self, date: Date):
return {
"date": datetime.now(),
"json_data": json.dumps({"value": 456})
}
Note: For readability, we've split the tags across multiple lines.
Inside MyComp
, we defined a default attribute
So if attrs
includes key class
, the default above will be ignored.
MyComp
also defines class
key twice. It means that whether the class
attribute is taken from attrs
or defaults
, the two class
values will be appended to it.
So by default, MyComp
renders:
Next, let's consider what will be rendered when we call MyComp
from Parent
component.
MyComp
accepts a attrs
dictionary, that is passed to html_attrs
, so the contents of that dictionary are rendered as the HTML attributes.
In Parent
, we make use of passing dictionary key-value pairs as kwargs to define individual attributes as if they were regular kwargs.
So all kwargs that start with attrs:
will be collected into an attrs
dict.
attrs:class="pa-0 border-solid border-red"
attrs:data-json=json_data
attrs:@click="(e) => onClick(e, 'from_parent')"
And get_context_data
of MyComp
will receive attrs
input with following keys:
attrs = {
"class": "pa-0 border-solid",
"data-json": '{"value": 456}',
"@click": "(e) => onClick(e, 'from_parent')",
}
attrs["class"]
overrides the default value for class
, whereas other keys will be merged.
So in the end MyComp
will render: