Skip to content

Commit 3f7ac76

Browse files
committed
Add model update before a callMethod if it is a lazy model. Add is_valid to base component for a quicker check for validation errors. Handle validation errors slightly better when there are multiple models.
1 parent 1747e8c commit 3f7ac76

File tree

4 files changed

+51
-22
lines changed

4 files changed

+51
-22
lines changed

django_unicorn/components.py

Lines changed: 31 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,7 @@ def __init__(self, **kwargs):
172172
if "request" in kwargs:
173173
self.setup(kwargs["request"])
174174

175+
self._validate_called = False
175176
self.errors = {}
176177
self._set_default_template_name()
177178
self._set_caches()
@@ -358,6 +359,9 @@ def get_context_data(self, **kwargs):
358359

359360
return context
360361

362+
def is_valid(self, model_names: List = None) -> bool:
363+
return len(self.validate(model_names).keys()) == 0
364+
361365
def validate(self, model_names: List = None) -> Dict:
362366
"""
363367
Validates the data using the `form_class` set on the component.
@@ -367,30 +371,35 @@ def validate(self, model_names: List = None) -> Dict:
367371
"""
368372
# TODO: Handle form.non_field_errors()?
369373

374+
if self._validate_called:
375+
return self.errors
376+
377+
self._validate_called = True
378+
370379
data = self._attributes()
371380
form = self._get_form(data)
372381

373382
if form:
374383
form_errors = form.errors.get_json_data(escape_html=True)
375384

376-
if model_names is not None:
377-
# This code is confusing, but handles this use-case:
378-
# the component has two models, one that starts with an error and one
379-
# that is valid. Validating the valid one should not show an error for
380-
# the invalid one. Only after the invalid field is updated, should the
381-
# error show up and persist, even after updating the valid form.
382-
if self.errors:
383-
keys_to_remove = []
384-
385-
for key, value in self.errors.items():
386-
if key in form_errors:
387-
self.errors[key] = value
388-
else:
389-
keys_to_remove.append(key)
390-
391-
for key in keys_to_remove:
392-
self.errors.pop(key)
385+
# This code is confusing, but handles this use-case:
386+
# the component has two models, one that starts with an error and one
387+
# that is valid. Validating the valid one should not show an error for
388+
# the invalid one. Only after the invalid field is updated, should the
389+
# error show up and persist, even after updating the valid form.
390+
if self.errors:
391+
keys_to_remove = []
393392

393+
for key, value in self.errors.items():
394+
if key in form_errors:
395+
self.errors[key] = value
396+
else:
397+
keys_to_remove.append(key)
398+
399+
for key in keys_to_remove:
400+
self.errors.pop(key)
401+
402+
if model_names is not None:
394403
for key, value in form_errors.items():
395404
if key in model_names:
396405
self.errors[key] = value
@@ -514,6 +523,7 @@ def _is_public(self, name: str) -> bool:
514523
"calling",
515524
"called",
516525
"validate",
526+
"is_valid",
517527
"get_frontend_context_variables",
518528
"errors",
519529
"updated",
@@ -551,7 +561,9 @@ def create(
551561
key = f"{component_name}-{component_id}"
552562

553563
if key in constructed_views_cache:
554-
return constructed_views_cache[key]
564+
component = constructed_views_cache[key]
565+
component._validate_called = False
566+
return component
555567

556568
if component_name in views_cache:
557569
component = views_cache[component_name](
@@ -582,6 +594,7 @@ def create(
582594
key = f"{component_name}-{component_id}"
583595
constructed_views_cache[key] = component
584596

597+
component._validate_called = False
585598
return component
586599

587600
locations = []

django_unicorn/static/js/unicorn.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -388,6 +388,11 @@ const Unicorn = (() => {
388388
this.actionEvents[eventType].forEach((element) => {
389389
// Use isSameNode (not isEqualNode) because we want to check the nodes reference the same object
390390
if (targetElement.el.isSameNode(element.el)) {
391+
if (!isEmpty(targetElement.model) && targetElement.model.isLazy) {
392+
const action = { type: "syncInput", payload: { name: targetElement.model.name, value: targetElement.getValue() } };
393+
this.actionQueue.push(action);
394+
}
395+
391396
if (element.action.key) {
392397
if (element.action.key === toKebabCase(event.key)) {
393398
this.callMethod(element.action.name);

example/unicorn/components/todo.py

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,18 @@
11
from django_unicorn.components import UnicornView
2+
from django import forms
3+
4+
5+
class TodoForm(forms.Form):
6+
task = forms.CharField(min_length=3, max_length=10, required=True)
27

38

49
class TodoView(UnicornView):
10+
form_class = TodoForm
11+
512
task = ""
613
tasks = []
714

815
def add(self):
9-
self.tasks.append(self.task)
10-
self.task = ""
16+
if self.is_valid():
17+
self.tasks.append(self.task)
18+
self.task = ""

example/unicorn/templates/unicorn/todo.html

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
11
<div>
2+
<div>{{ unicorn.errors.task.0.message }}</div>
3+
24
<div class="field has-addons">
35
<p class="control">
4-
<input class="input" type="text" unicorn:model.lazy="task" placeholder="New task" id="task"></input>
6+
<input class="input" type="text" unicorn:model.lazy="task" unicorn:keyup.enter="add" placeholder="New task" id="task"></input>
57
</p>
68
<p class="control">
7-
<button unicorn:click="add" class="button is-info">Add</button>&nbsp;&nbsp;<button unicorn:click="reset" class="button is-danger" {% if not tasks %}disabled{% endif %} style="border-radius: 4px;">Clear all tasks</button>
9+
<button unicorn:click="add" class="button is-info">Add</button>&nbsp;&nbsp;
10+
<button unicorn:click="reset" class="button is-danger" {% if not tasks %}disabled{% endif %} style="border-radius: 4px;">Clear all tasks</button>
811
</p>
912
</div>
1013

0 commit comments

Comments
 (0)