Templates#
Templates are normal Django HTML templates, so anything you could normally do in a Django template will still work, including template tags, filters, loops, if statements, etc.
Warning
Unicorn
requires there to be one root element that contains the component HTML. Valid HTML and a wrapper element is required for the DOM diffing algorithm to work correctly, so Unicorn
will try to log a warning message if they seem invalid.
For example, this is an invalid template:
<input unicorn:model="name"></input><br />
Name: {{ name }}
This template is valid:
<div>
<input unicorn:model="name"></input><br />
Name: {{ name }}
</div>
Unicorn attributes#
Unicorn
element attributes usually start with unicorn:
, however the shortcut u:
is also supported. So, for example, unicorn:model
could also be written as u:model
.
Accessing nested fields#
Fields in a dictionary
or Django model can be accessed similarly to the Django template language with “dot notation”.
# hello_world.py
from django_unicorn.components import UnicornView
from book.models import Book
class HelloWorldView(UnicornView):
book: Book
book_ratings: dict[str[dict[str, str]]]
def mount(self):
book = Book.objects.get(title='American Gods')
book_ratings = {'excellent': {'title': 'American Gods'}}
<!-- hello-world.html -->
<div>
<input unicorn:model="book.title" type="text" id="model" />
<input
unicorn:model="book_ratings.excellent.title"
type="text"
id="dictionary"
/>
</div>
Note
Django models has many more details about using Django models in Unicorn
.
Model modifiers#
Lazy#
To prevent updates from happening on every input, you can append a lazy
modifier to the end of unicorn:model
. That will only update the component when a blur
event happens.
<!-- waits-for-blur.html -->
<div>
<input unicorn:model.lazy="name" type="text" id="name" />
Hello {{ name|title }}
</div>
Debounce#
The debounce
modifier configures how long to wait to fire an event. The time is always specified in milliseconds, for example: unicorn:model.debounce-1000
would wait for 1000 milliseconds (1 second) before firing the message.
<!-- waits-1-second.html -->
<div>
<input unicorn:model.debounce-1000="name" type="text" id="name" />
Hello {{ name|title }}
</div>
Defer#
The defer
modifier will store and save model changes until the next action gets triggered. This is useful to prevent additional network requests until an action is triggered.
<!-- defer.html -->
<div>
<input unicorn:model.defer="task" type="text" id="task" />
<button unicorn:click="add">Add task</button>
<ul>
{% for task in tasks %}
<li>{{ task }}</li>
{% endfor %}
</ul>
</div>
Chaining modifiers#
Lazy
and debounce
modifiers can even be chained together.
<!-- waits-for-blur-and-then-5-seconds.html -->
<div>
<input unicorn:model.lazy.debounce-5000="name" type="text" id="text" />
Hello {{ name|title }}
</div>
Key#
Smooth updates#
Setting a unique id
on elements with unicorn:model
will prevent changes to an input from being choppy when there are lots of updates in quick succession.
<!-- choppy-updates.html -->
<input type="text" unicorn:model="name"></input>
!-- smooth-updates.html -->
<input type="text" id="someFancyId" unicorn:model="name"></input>
The unicorn:key
attribute can be used when multiple elements have the same id
.
<!-- missing-updates.html -->
<input type="text" id="someFancyId" unicorn:model="name"></input>
<input type="text" id="someFancyId" unicorn:model="name"></input>
<!-- this-works.html -->
<input type="text" id="someFancyId" unicorn:model="name"></input>
<input type="text" id="someFancyId" unicorn:model="name" unicorn:key="someFancyKey"></input>
DOM merging#
To merge changes in the DOM, Unicorn
uses, in order, unicorn:id
, unicorn:key
, or the element’s id
to intelligently update DOM elements.
Lifecycle events#
Unicorn
provides events that fire when different parts of the lifecycle occur.
updated#
The updated
event is fired after the AJAX call finishes and the component is merged with the newly rendered component template. The callback gets called with one argument, component
.
<!-- updated-event.html -->
<script type="application/javascript">
window.addEventListener("DOMContentLoaded", (event) => {
Unicorn.addEventListener("updated", (component) =>
console.log("got updated", component)
);
});
</script>
Ignore elements#
Some JavaScript libraries will change the DOM (such as Select2
) after the page renders. That can cause issues for Unicorn
when trying to merge that DOM with what Unicorn
thinks the DOM should be. unicorn:ignore
can be used to prevent Unicorn
from morphing that element or its children.
Note
When the component is initially rendered, normal Django template functionality can be used.
<!-- ignored-element.html -->
<div>
<script src="jquery.min.js"></script>
<link href="select2.min.css" rel="stylesheet" />
<script src="select2.min.js"></script>
<div unicorn:ignore>
<select
id="select2-example"
onchange="Unicorn.call('ignored-element', 'select_state', this.value, this.selectedIndex);"
>
{% for state in states %}
<option value="{{ state }}">{{ state }}</option>
{% endfor %}
</select>
</div>
<script>
$(document).ready(function () {
$("#select2-example").select2();
});
</script>
</div>
# ignored_element.py
from django_unicorn.components import UnicornView
class JsView(UnicornView):
states = (
"Alabama",
"Alaska",
"Wisconsin",
"Wyoming",
)
selected_state = ""
def select_state(self, state_name, selected_idx):
print("select_state state_name", state_name)
print("select_state selected_idx", selected_idx)
self.selected_state = state_name