Skip to content

Commit acd97f6

Browse files
committed
Add unicorn:ignore attribute to skip morphing an HTML element. Support arguments to Unicorn.call.
1 parent 187c8df commit acd97f6

File tree

7 files changed

+234
-12
lines changed

7 files changed

+234
-12
lines changed

django_unicorn/static/js/morphdom/2.6.1/morphdom.js

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -505,8 +505,18 @@ function morphdomFactory(morphAttrs) {
505505
return;
506506
}
507507

508+
// @unicornModification from @livewireModification.
509+
// Don't update an element or its children if it's been ignored
510+
if (
511+
fromEl.hasAttribute("u:ignore") ||
512+
fromEl.hasAttribute("unicorn:ignore")
513+
) {
514+
return;
515+
}
516+
508517
// update attributes on original DOM element first
509518
morphAttrs(fromEl, toEl);
519+
510520
// optional
511521
onElUpdated(fromEl);
512522

django_unicorn/static/js/unicorn.js

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,8 +67,24 @@ export function getComponent(componentNameOrKey) {
6767
/**
6868
* Call an action on the specified component.
6969
*/
70-
export function call(componentNameOrKey, methodName) {
70+
export function call(componentNameOrKey, methodName, ...args) {
7171
const component = getComponent(componentNameOrKey);
72+
let argString = "";
73+
74+
args.forEach((arg) => {
75+
if (typeof arg !== "undefined") {
76+
if (typeof arg === "string") {
77+
argString = `${argString}'${arg}', `;
78+
} else {
79+
argString = `${argString}${arg}, `;
80+
}
81+
}
82+
});
83+
84+
if (argString) {
85+
argString = argString.slice(0, -2);
86+
methodName = `${methodName}(${argString})`;
87+
}
7288

7389
component.callMethod(methodName, null, (err) => {
7490
console.error(err);

example/unicorn/components/js.py

Lines changed: 18 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,31 @@
1+
from django.utils.timezone import now
2+
13
from django_unicorn.components import UnicornView
24

35

46
class JsView(UnicornView):
5-
def mount(self):
6-
# self.call("callAlert")
7-
pass
8-
9-
def call_javascript(self):
10-
self.call("callAlert", "world")
11-
12-
def choose_state():
13-
pass
14-
157
states = (
168
"Alabama",
179
"Alaska",
1810
"Wisconsin",
1911
"Wyoming",
2012
)
13+
selected_state = ""
14+
select2_datetime = now()
15+
16+
def call_javascript(self):
17+
self.call("callAlert", "world")
18+
19+
def get_now(self):
20+
self.select2_datetime = now()
21+
22+
def change_states(self):
23+
self.states = ("Pennsylvania",)
24+
25+
def select_state(self, val, idx):
26+
print("select_state called", val)
27+
print("select_state called idx", idx)
28+
self.selected_state = val
2129

2230
class Meta:
2331
javascript_excludes = ("states",)

example/unicorn/templates/unicorn/js.html

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,8 @@
11
<div>
2+
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/jquery.min.js"></script>
3+
<link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/select2.min.css" rel="stylesheet" />
4+
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/select2.min.js"></script>
5+
26
<script>
37
function callAlert(name) {
48
alert("hello, " + name);
@@ -11,10 +15,43 @@ <h2>
1115

1216
<div>
1317
<h2>
14-
<code>javascript_exclude</code>
18+
<code>javascript_exclude states</code>
1519
</h2>
1620
{% for state in states %}
1721
{{ state }}
1822
{% endfor %}
1923
</div>
24+
25+
<h2>Select2</h2>
26+
27+
<div u:ignore>
28+
<select unicorn:model="selected_state" class="form-control" id="select2-example" required onchange="Unicorn.call('js', 'select_state', this.value, this.selectedIndex);">
29+
{% for state in states %}
30+
<option value="{{ state }}">{{ state }}</option>
31+
{% endfor %}
32+
</select>
33+
34+
States (in ignored div): {{ states }}
35+
</div>
36+
37+
selected_state: {{ selected_state }}
38+
39+
<script>
40+
$(document).ready(function() {
41+
$('#select2-example').select2();
42+
});
43+
</script>
44+
45+
<div>
46+
States (not in ignored div): {{ states }}<br />
47+
<button type="submit" u:click="change_states">Change states</button>
48+
</div>
49+
50+
<div>
51+
<input type="text" u:model="select2_datetime" />
52+
<button type="submit" u:click="get_now">Get now</button>
53+
<div>
54+
select2_datetime: {{ select2_datetime }}
55+
</div>
56+
</div>
2057
</div>

tests/js/unicorn/call.test.js

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import test from "ava";
2+
import { call } from "../../../django_unicorn/static/js/unicorn.js";
3+
import { components } from "../../../django_unicorn/static/js/store.js";
4+
import { getComponent } from "../utils.js";
5+
6+
test("call a method", (t) => {
7+
const component = getComponent();
8+
components[component.id] = component;
9+
10+
component.callMethod = (methodName) => {
11+
t.true(methodName === "testMethod");
12+
};
13+
14+
call("text-inputs", "testMethod");
15+
});
16+
17+
test("call a method with string argument", (t) => {
18+
const component = getComponent();
19+
components[component.id] = component;
20+
21+
component.callMethod = (methodName) => {
22+
t.true(methodName === "testMethod('test1')");
23+
};
24+
25+
call("text-inputs", "testMethod", "test1");
26+
});
27+
28+
test("call a method with string and int argument", (t) => {
29+
const component = getComponent();
30+
components[component.id] = component;
31+
32+
component.callMethod = (methodName) => {
33+
t.true(methodName === "testMethod('test1', 2)");
34+
};
35+
36+
call("text-inputs", "testMethod", "test1", 2);
37+
});
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import test from "ava";
2+
import { getComponent } from "../../../django_unicorn/static/js/unicorn.js";
3+
import { components } from "../../../django_unicorn/static/js/store.js";
4+
import { getComponent as getComponentUtil } from "../utils.js";
5+
6+
test("getComponent by name", (t) => {
7+
const component = getComponentUtil();
8+
component.name = "text-inputs-name";
9+
components[component.id] = component;
10+
11+
t.truthy(getComponent("text-inputs-name"));
12+
});
13+
14+
test("getComponent by key", (t) => {
15+
const component = getComponentUtil();
16+
component.key = "text-inputs-key";
17+
components[component.id] = component;
18+
19+
t.truthy(getComponent("text-inputs-key"));
20+
});
21+
22+
test("getComponent missing", (t) => {
23+
const component = getComponentUtil();
24+
component.name = "text-inputs-name";
25+
components[component.id] = component;
26+
27+
const error = t.throws(
28+
() => {
29+
getComponent("text-inputs-missing");
30+
},
31+
{ instanceOf: Error }
32+
);
33+
t.is(error.message, "No component found for: text-inputs-missing");
34+
});

tests/js/utils/morphdom.test.js

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,3 +24,83 @@ test("contains", (t) => {
2424
morphdom(componentRoot, rerenderedComponent, MORPHDOM_OPTIONS);
2525
t.true(componentRoot.outerHTML.indexOf("Name: Test") > -1);
2626
});
27+
28+
test("ignore element", (t) => {
29+
const componentRootHtml = `
30+
<div unicorn:id="5jypjiyb" unicorn:name="text-inputs" unicorn:checksum="GXzew3Km">
31+
<input unicorn:model="name" type="text" id="name"><br />
32+
Name:
33+
34+
<div unicorn:ignore>
35+
Something here
36+
</div>
37+
</div>
38+
`;
39+
const componentRoot = getEl(componentRootHtml);
40+
41+
const rerenderedComponentHtml = `
42+
<div unicorn:id="5jypjiyb" unicorn:name="text-inputs" unicorn:checksum="GXzew3Km">
43+
<input unicorn:model="name" type="text" id="name"><br />
44+
Name: Test
45+
46+
<div unicorn:ignore>
47+
Something else here
48+
</div>
49+
</div>
50+
`;
51+
const rerenderedComponent = getEl(rerenderedComponentHtml);
52+
53+
t.true(componentRoot.outerHTML.indexOf("Name: Test") === -1);
54+
t.true(componentRoot.outerHTML.indexOf("Something here") > -1);
55+
56+
morphdom(componentRoot, rerenderedComponent, MORPHDOM_OPTIONS);
57+
58+
t.true(componentRoot.outerHTML.indexOf("Name: Test") > -1);
59+
t.true(componentRoot.outerHTML.indexOf("Something here") > -1);
60+
});
61+
62+
test("ignore all children elements", (t) => {
63+
const componentRootHtml = `
64+
<div unicorn:id="5jypjiyb" unicorn:name="text-inputs" unicorn:checksum="GXzew3Km">
65+
<input unicorn:model="name" type="text" id="name"><br />
66+
Name:
67+
68+
<div unicorn:ignore>
69+
<div>
70+
Something here
71+
</div>
72+
<div>
73+
More here
74+
</div>
75+
</div>
76+
</div>
77+
`;
78+
const componentRoot = getEl(componentRootHtml);
79+
80+
const rerenderedComponentHtml = `
81+
<div unicorn:id="5jypjiyb" unicorn:name="text-inputs" unicorn:checksum="GXzew3Km">
82+
<input unicorn:model="name" type="text" id="name"><br />
83+
Name: Test
84+
85+
<div unicorn:ignore>
86+
<div>
87+
Something else here
88+
</div>
89+
<div>
90+
More else here
91+
</div>
92+
</div>
93+
</div>
94+
`;
95+
const rerenderedComponent = getEl(rerenderedComponentHtml);
96+
97+
t.true(componentRoot.outerHTML.indexOf("Name: Test") === -1);
98+
t.true(componentRoot.outerHTML.indexOf("Something here") > -1);
99+
t.true(componentRoot.outerHTML.indexOf("More here") > -1);
100+
101+
morphdom(componentRoot, rerenderedComponent, MORPHDOM_OPTIONS);
102+
103+
t.true(componentRoot.outerHTML.indexOf("Name: Test") > -1);
104+
t.true(componentRoot.outerHTML.indexOf("Something here") > -1);
105+
t.true(componentRoot.outerHTML.indexOf("More here") > -1);
106+
});

0 commit comments

Comments
 (0)