Skip to content

Commit a30dec1

Browse files
committed
Store data on each component so that child components can be inited when needed. Original code by https://github.com/imankulov in bd45c5d.
1 parent 05daf2e commit a30dec1

File tree

4 files changed

+96
-9
lines changed

4 files changed

+96
-9
lines changed

django_unicorn/components/unicorn_template_response.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,8 @@ def render(self):
153153
root_element["unicorn:name"] = self.component.component_name
154154
root_element["unicorn:key"] = self.component.component_key
155155
root_element["unicorn:checksum"] = checksum
156+
root_element["unicorn:data"] = frontend_context_variables
157+
root_element["unicorn:calls"] = orjson.dumps(self.component.calls).decode("utf-8")
156158

157159
# Generate the checksum based on the rendered content (without script tag)
158160
content_hash = generate_checksum(UnicornTemplateResponse._desoupify(soup))

django_unicorn/static/unicorn/js/component.js

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@ export class Component {
8484
// Skip the component root element
8585
return;
8686
}
87+
8788
const componentId = el.getAttribute("unicorn:id");
8889

8990
if (componentId) {
@@ -550,4 +551,51 @@ export class Component {
550551
}
551552
});
552553
}
554+
555+
/**
556+
* Replace the target DOM with the rerendered component.
557+
*
558+
* The function updates the DOM, and updates the Unicorn component store by deleting
559+
* components that were removed, and adding new components.
560+
*/
561+
morph(targetDom, rerenderedComponent) {
562+
if (!rerenderedComponent) {
563+
return;
564+
}
565+
566+
// Helper function that returns an array of nodes with an attribute unicorn:id
567+
const findUnicorns = () => [
568+
...targetDom.querySelectorAll("[unicorn\\:id]"),
569+
];
570+
571+
// Find component IDs before morphing
572+
const componentIdsBeforeMorph = new Set(
573+
findUnicorns().map((el) => el.getAttribute("unicorn:id"))
574+
);
575+
576+
// Morph
577+
this.morpher.morph(targetDom, rerenderedComponent);
578+
579+
// Find all component IDs after morphing
580+
const componentIdsAfterMorph = new Set(
581+
findUnicorns().map((el) => el.getAttribute("unicorn:id"))
582+
);
583+
584+
// Delete components that were removed
585+
const removedComponentIds = [...componentIdsBeforeMorph].filter(
586+
(id) => !componentIdsAfterMorph.has(id)
587+
);
588+
removedComponentIds.forEach((id) => {
589+
Unicorn.deleteComponent(id);
590+
});
591+
592+
// Populate Unicorn with new components
593+
findUnicorns().forEach((el) => {
594+
Unicorn.insertComponentFromDom(el);
595+
});
596+
}
597+
598+
morphRoot(rerenderedComponent) {
599+
this.morph(this.root, rerenderedComponent);
600+
}
553601
}

django_unicorn/static/unicorn/js/messageSender.js

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -170,7 +170,7 @@ export function send(component, callback) {
170170
}
171171

172172
if (parent.dom) {
173-
component.morpher.morph(parentComponent.root, parent.dom);
173+
parentComponent.morphRoot(parent.dom);
174174

175175
parentComponent.loadingEls.forEach((loadingElement) => {
176176
if (loadingElement.loading.hide) {
@@ -198,10 +198,10 @@ export function send(component, callback) {
198198

199199
parentComponent.refreshEventListeners();
200200

201-
parentComponent.getChildrenComponents().forEach((child) => {
202-
child.init();
203-
child.refreshEventListeners();
204-
});
201+
// parentComponent.getChildrenComponents().forEach((child) => {
202+
// child.init();
203+
// child.refreshEventListeners();
204+
// });
205205
}
206206
parent = parent.parent || {};
207207
}
@@ -226,7 +226,7 @@ export function send(component, callback) {
226226
}
227227

228228
if (targetDom) {
229-
component.morpher.morph(targetDom, partial.dom);
229+
component.morph(targetDom, partial.dom);
230230
}
231231
}
232232

@@ -235,13 +235,18 @@ export function send(component, callback) {
235235
component.refreshChecksum();
236236
}
237237
} else if (rerenderedComponent) {
238-
component.morpher.morph(component.root, rerenderedComponent);
238+
component.morphRoot(rerenderedComponent);
239239
}
240240

241241
component.triggerLifecycleEvent("updated");
242242

243-
// Re-init to refresh the root and checksum based on the new data
244-
component.init();
243+
try {
244+
// Re-init to refresh the root and checksum based on the new data
245+
component.init();
246+
} catch (err) {
247+
// No id found error will be thrown here for child components.
248+
return;
249+
}
245250

246251
// Reset all event listeners
247252
component.refreshEventListeners();

django_unicorn/static/unicorn/js/unicorn.js

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,30 @@ export function componentInit(args) {
5454
component.setModelValues();
5555
}
5656

57+
/**
58+
* Initialize the component from the DOM element if it hasn't been initialized yet.
59+
*
60+
* Used to populate the components object with fresh components, created by the server.
61+
*
62+
* @param {Object} node The node to check for initialization.
63+
*/
64+
export function insertComponentFromDom(node) {
65+
const nodeId = node.getAttribute("unicorn:id");
66+
67+
if (!components[nodeId]) {
68+
const args = {
69+
id: nodeId,
70+
name: node.getAttribute("unicorn:name"),
71+
key: node.getAttribute("unicorn:key"),
72+
checksum: node.getAttribute("unicorn:checksum"),
73+
data: JSON.parse(node.getAttribute("unicorn:data")),
74+
calls: JSON.parse(node.getAttribute("unicorn:calls")),
75+
};
76+
77+
componentInit(args);
78+
}
79+
}
80+
5781
/**
5882
* Gets the component with the specified name or key.
5983
* Component keys are searched first, then names.
@@ -92,6 +116,14 @@ export function getComponent(componentNameOrKey) {
92116
return component;
93117
}
94118

119+
/**
120+
* Deletes the component from the component store.
121+
* @param {String} componentId.
122+
*/
123+
export function deleteComponent(componentId) {
124+
delete components[componentId];
125+
}
126+
95127
/**
96128
* Call an action on the specified component.
97129
*/

0 commit comments

Comments
 (0)