0
# BlockDOM System
1
2
Virtual DOM implementation with block-based rendering, patching, and lifecycle management optimized for template-driven UIs.
3
4
## Capabilities
5
6
### BlockDOM Object
7
8
The main BlockDOM API object containing core virtual DOM manipulation functions.
9
10
```typescript { .api }
11
/**
12
* BlockDOM API object with core virtual DOM functions
13
*/
14
const blockDom: {
15
/** Configuration object */
16
config: Config;
17
/** Mount a VNode to DOM */
18
mount: (vnode: VNode, fixture: HTMLElement, afterNode?: Node) => void;
19
/** Patch one VNode with another */
20
patch: (vnode1: VNode, vnode2: VNode, withBeforeRemove?: boolean) => void;
21
/** Remove a VNode from DOM */
22
remove: (vnode: VNode, withBeforeRemove?: boolean) => void;
23
/** Create list block */
24
list: (items: any[], template: Function) => VNode;
25
/** Create multi-block container */
26
multi: (blocks: VNode[]) => VNode;
27
/** Create text node */
28
text: (value: string) => VNode;
29
/** Create comment node */
30
comment: (value: string) => VNode;
31
/** Create conditional block */
32
toggler: (condition: boolean, template: Function) => VNode;
33
/** Create generic block */
34
createBlock: (template: Function) => VNode;
35
/** Create raw HTML block */
36
html: (htmlString: string) => VNode;
37
};
38
```
39
40
### Mount Function
41
42
Mounts a virtual node to the DOM.
43
44
```typescript { .api }
45
/**
46
* Mounts a VNode to a DOM element
47
* @param vnode - Virtual node to mount
48
* @param fixture - Target DOM element
49
* @param afterNode - Optional reference node for positioning
50
*/
51
function mount(vnode: VNode, fixture: HTMLElement, afterNode?: Node | null): void;
52
```
53
54
**Usage Examples:**
55
56
```typescript
57
import { blockDom } from "@odoo/owl";
58
59
// Mount a simple text node
60
const textNode = blockDom.text("Hello World");
61
blockDom.mount(textNode, document.body);
62
63
// Mount with positioning
64
const containerDiv = document.getElementById("container");
65
const existingElement = containerDiv.firstChild;
66
const newNode = blockDom.text("Insert before first child");
67
blockDom.mount(newNode, containerDiv, existingElement);
68
69
// Mount complex structures
70
const listNode = blockDom.list([1, 2, 3], (item) =>
71
blockDom.text(`Item: ${item}`)
72
);
73
blockDom.mount(listNode, document.getElementById("list-container"));
74
```
75
76
### Patch Function
77
78
Updates one virtual node with the content of another.
79
80
```typescript { .api }
81
/**
82
* Patches vnode1 with the content of vnode2
83
* @param vnode1 - Target VNode to be updated
84
* @param vnode2 - Source VNode with new content
85
* @param withBeforeRemove - Whether to call beforeRemove hooks
86
*/
87
function patch(vnode1: VNode, vnode2: VNode, withBeforeRemove?: boolean): void;
88
```
89
90
**Usage Examples:**
91
92
```typescript
93
import { blockDom } from "@odoo/owl";
94
95
// Initial render
96
let currentNode = blockDom.text("Loading...");
97
blockDom.mount(currentNode, document.body);
98
99
// Update content
100
const newNode = blockDom.text("Content loaded!");
101
blockDom.patch(currentNode, newNode);
102
currentNode = newNode;
103
104
// Patch lists
105
let currentList = blockDom.list([1, 2, 3], (item) =>
106
blockDom.text(`Item ${item}`)
107
);
108
blockDom.mount(currentList, document.getElementById("list"));
109
110
// Update list with new items
111
const newList = blockDom.list([1, 2, 3, 4, 5], (item) =>
112
blockDom.text(`Updated Item ${item}`)
113
);
114
blockDom.patch(currentList, newList);
115
currentList = newList;
116
117
// Patch with cleanup
118
blockDom.patch(currentNode, newNode, true); // Calls beforeRemove hooks
119
```
120
121
### Remove Function
122
123
Removes a virtual node from the DOM.
124
125
```typescript { .api }
126
/**
127
* Removes a VNode from DOM
128
* @param vnode - Virtual node to remove
129
* @param withBeforeRemove - Whether to call beforeRemove hooks
130
*/
131
function remove(vnode: VNode, withBeforeRemove?: boolean): void;
132
```
133
134
**Usage Examples:**
135
136
```typescript
137
import { blockDom } from "@odoo/owl";
138
139
// Simple removal
140
const node = blockDom.text("Temporary content");
141
blockDom.mount(node, document.body);
142
143
setTimeout(() => {
144
blockDom.remove(node); // Remove after delay
145
}, 2000);
146
147
// Removal with cleanup
148
const complexNode = blockDom.multi([
149
blockDom.text("Part 1"),
150
blockDom.html("<div>Part 2</div>"),
151
blockDom.comment("End of content")
152
]);
153
blockDom.mount(complexNode, document.getElementById("temp-container"));
154
155
// Later remove with beforeRemove hooks
156
blockDom.remove(complexNode, true);
157
```
158
159
### Block Creation Functions
160
161
#### Text Block
162
163
Creates a text node block.
164
165
```typescript { .api }
166
/**
167
* Creates a text node VNode
168
* @param value - Text content
169
* @returns Text VNode
170
*/
171
function text(value: string): VNode;
172
```
173
174
**Usage Examples:**
175
176
```typescript
177
import { blockDom } from "@odoo/owl";
178
179
// Simple text
180
const greeting = blockDom.text("Hello, World!");
181
blockDom.mount(greeting, document.body);
182
183
// Dynamic text content
184
const counter = { value: 0 };
185
const updateCounter = () => {
186
const newText = blockDom.text(`Count: ${counter.value}`);
187
blockDom.patch(currentText, newText);
188
currentText = newText;
189
};
190
191
let currentText = blockDom.text(`Count: ${counter.value}`);
192
blockDom.mount(currentText, document.getElementById("counter"));
193
194
// Update every second
195
setInterval(() => {
196
counter.value++;
197
updateCounter();
198
}, 1000);
199
```
200
201
#### Comment Block
202
203
Creates a comment node block.
204
205
```typescript { .api }
206
/**
207
* Creates a comment node VNode
208
* @param value - Comment text
209
* @returns Comment VNode
210
*/
211
function comment(value: string): VNode;
212
```
213
214
**Usage Examples:**
215
216
```typescript
217
import { blockDom } from "@odoo/owl";
218
219
// Debug comments
220
const debugComment = blockDom.comment("Debug: Component rendered at " + new Date());
221
blockDom.mount(debugComment, document.body);
222
223
// Conditional comments
224
const renderWithComments = (showComments, data) => {
225
const blocks = [];
226
227
if (showComments) {
228
blocks.push(blockDom.comment("Data rendering start"));
229
}
230
231
blocks.push(blockDom.text(JSON.stringify(data)));
232
233
if (showComments) {
234
blocks.push(blockDom.comment("Data rendering end"));
235
}
236
237
return blockDom.multi(blocks);
238
};
239
```
240
241
#### HTML Block
242
243
Creates a block with raw HTML content.
244
245
```typescript { .api }
246
/**
247
* Creates a raw HTML VNode
248
* @param htmlString - Raw HTML string
249
* @returns HTML VNode
250
*/
251
function html(htmlString: string): VNode;
252
```
253
254
**Usage Examples:**
255
256
```typescript
257
import { blockDom } from "@odoo/owl";
258
259
// Simple HTML
260
const richContent = blockDom.html("<p><strong>Bold</strong> and <em>italic</em> text</p>");
261
blockDom.mount(richContent, document.getElementById("content"));
262
263
// HTML from markdown or other sources
264
const markdownHTML = "<h1>Title</h1><p>Some content with <a href='#'>links</a></p>";
265
const markdownBlock = blockDom.html(markdownHTML);
266
blockDom.mount(markdownBlock, document.getElementById("markdown-content"));
267
268
// Dynamic HTML content
269
const renderHTML = (data) => {
270
const html = `
271
<div class="card">
272
<h2>${data.title}</h2>
273
<div class="content">${data.htmlContent}</div>
274
<footer>Created: ${data.createdAt}</footer>
275
</div>
276
`;
277
return blockDom.html(html);
278
};
279
280
const htmlBlock = renderHTML({
281
title: "Article Title",
282
htmlContent: "<p>This is <strong>rich content</strong>.</p>",
283
createdAt: "2024-01-01"
284
});
285
```
286
287
#### List Block
288
289
Creates a list block for rendering arrays of data.
290
291
```typescript { .api }
292
/**
293
* Creates a list VNode for rendering arrays
294
* @param items - Array of items to render
295
* @param template - Function that creates VNode for each item
296
* @returns List VNode
297
*/
298
function list<T>(items: T[], template: (item: T, index: number) => VNode): VNode;
299
```
300
301
**Usage Examples:**
302
303
```typescript
304
import { blockDom } from "@odoo/owl";
305
306
// Simple list
307
const numbers = [1, 2, 3, 4, 5];
308
const numberList = blockDom.list(numbers, (num, index) =>
309
blockDom.text(`${index + 1}. ${num}`)
310
);
311
blockDom.mount(numberList, document.getElementById("numbers"));
312
313
// Complex object list
314
const users = [
315
{ id: 1, name: "Alice", email: "alice@example.com" },
316
{ id: 2, name: "Bob", email: "bob@example.com" },
317
{ id: 3, name: "Charlie", email: "charlie@example.com" }
318
];
319
320
const userList = blockDom.list(users, (user) => {
321
const userHTML = `
322
<div class="user-card" data-user-id="${user.id}">
323
<h3>${user.name}</h3>
324
<p>${user.email}</p>
325
</div>
326
`;
327
return blockDom.html(userHTML);
328
});
329
330
blockDom.mount(userList, document.getElementById("user-list"));
331
332
// Nested lists
333
const categories = [
334
{
335
name: "Fruits",
336
items: ["Apple", "Banana", "Orange"]
337
},
338
{
339
name: "Vegetables",
340
items: ["Carrot", "Broccoli", "Spinach"]
341
}
342
];
343
344
const categoryList = blockDom.list(categories, (category) => {
345
const categoryHeader = blockDom.html(`<h2>${category.name}</h2>`);
346
const itemList = blockDom.list(category.items, (item) =>
347
blockDom.html(`<li>${item}</li>`)
348
);
349
350
return blockDom.multi([
351
categoryHeader,
352
blockDom.html("<ul>"),
353
itemList,
354
blockDom.html("</ul>")
355
]);
356
});
357
```
358
359
#### Multi Block
360
361
Creates a container for multiple blocks.
362
363
```typescript { .api }
364
/**
365
* Creates a multi-block container
366
* @param blocks - Array of VNodes to group together
367
* @returns Multi VNode containing all blocks
368
*/
369
function multi(blocks: VNode[]): VNode;
370
```
371
372
**Usage Examples:**
373
374
```typescript
375
import { blockDom } from "@odoo/owl";
376
377
// Group related elements
378
const header = blockDom.html("<h1>Welcome</h1>");
379
const content = blockDom.text("This is the main content.");
380
const footer = blockDom.html("<footer>© 2024</footer>");
381
382
const page = blockDom.multi([header, content, footer]);
383
blockDom.mount(page, document.body);
384
385
// Conditional multi-blocks
386
const renderPage = (user, showHeader = true, showFooter = true) => {
387
const blocks = [];
388
389
if (showHeader) {
390
blocks.push(blockDom.html(`<header><h1>Welcome, ${user.name}!</h1></header>`));
391
}
392
393
blocks.push(blockDom.html(`
394
<main>
395
<p>User ID: ${user.id}</p>
396
<p>Email: ${user.email}</p>
397
</main>
398
`));
399
400
if (showFooter) {
401
blocks.push(blockDom.html("<footer>Thank you for visiting!</footer>"));
402
}
403
404
return blockDom.multi(blocks);
405
};
406
407
// Dynamic multi-blocks
408
const renderDashboard = (widgets) => {
409
const blocks = [
410
blockDom.html("<div class='dashboard-header'><h1>Dashboard</h1></div>")
411
];
412
413
widgets.forEach(widget => {
414
blocks.push(blockDom.html(`
415
<div class="widget" data-widget-id="${widget.id}">
416
<h2>${widget.title}</h2>
417
<div class="widget-content">${widget.content}</div>
418
</div>
419
`));
420
});
421
422
blocks.push(blockDom.html("<div class='dashboard-footer'>Last updated: " + new Date().toLocaleString() + "</div>"));
423
424
return blockDom.multi(blocks);
425
};
426
```
427
428
#### Toggler Block
429
430
Creates a conditional block that shows/hides content.
431
432
```typescript { .api }
433
/**
434
* Creates a conditional block
435
* @param condition - Boolean condition for showing content
436
* @param template - Function that creates VNode when condition is true
437
* @returns Toggler VNode
438
*/
439
function toggler(condition: boolean, template: () => VNode): VNode;
440
```
441
442
**Usage Examples:**
443
444
```typescript
445
import { blockDom } from "@odoo/owl";
446
447
// Simple conditional rendering
448
let showMessage = false;
449
const messageToggler = blockDom.toggler(showMessage, () =>
450
blockDom.text("This message is conditionally shown!")
451
);
452
blockDom.mount(messageToggler, document.getElementById("message-area"));
453
454
// Toggle visibility
455
document.getElementById("toggle-button").onclick = () => {
456
showMessage = !showMessage;
457
const newToggler = blockDom.toggler(showMessage, () =>
458
blockDom.text("This message is conditionally shown!")
459
);
460
blockDom.patch(messageToggler, newToggler);
461
};
462
463
// Complex conditional content
464
const user = { isAdmin: false, name: "John" };
465
const adminPanel = blockDom.toggler(user.isAdmin, () => {
466
const adminContent = `
467
<div class="admin-panel">
468
<h2>Admin Panel</h2>
469
<button>Manage Users</button>
470
<button>System Settings</button>
471
<button>View Logs</button>
472
</div>
473
`;
474
return blockDom.html(adminContent);
475
});
476
477
// Loading states
478
let isLoading = true;
479
const loadingToggler = blockDom.toggler(isLoading, () =>
480
blockDom.html("<div class='spinner'>Loading...</div>")
481
);
482
483
const content = blockDom.multi([
484
loadingToggler,
485
blockDom.toggler(!isLoading, () =>
486
blockDom.html("<div>Content loaded successfully!</div>")
487
)
488
]);
489
490
// Simulate loading completion
491
setTimeout(() => {
492
isLoading = false;
493
// Re-render with updated condition
494
}, 2000);
495
```
496
497
#### Create Block
498
499
Creates a generic block from a template function.
500
501
```typescript { .api }
502
/**
503
* Creates a generic block from template function
504
* @param template - Function that generates DOM structure
505
* @returns Generic block VNode
506
*/
507
function createBlock(template: () => HTMLElement | string): VNode;
508
```
509
510
**Usage Examples:**
511
512
```typescript
513
import { blockDom } from "@odoo/owl";
514
515
// Create custom block
516
const customBlock = blockDom.createBlock(() => {
517
const div = document.createElement("div");
518
div.className = "custom-widget";
519
div.innerHTML = "<p>Custom widget content</p>";
520
521
// Add event listeners
522
div.addEventListener("click", () => {
523
console.log("Custom widget clicked!");
524
});
525
526
return div;
527
});
528
529
blockDom.mount(customBlock, document.getElementById("widget-container"));
530
531
// Block with dynamic content
532
const dynamicBlock = (data) => blockDom.createBlock(() => {
533
const container = document.createElement("div");
534
container.className = "dynamic-content";
535
536
// Create elements programmatically
537
const title = document.createElement("h2");
538
title.textContent = data.title;
539
container.appendChild(title);
540
541
const list = document.createElement("ul");
542
data.items.forEach(item => {
543
const listItem = document.createElement("li");
544
listItem.textContent = item;
545
list.appendChild(listItem);
546
});
547
container.appendChild(list);
548
549
return container;
550
});
551
552
// Chart integration
553
const chartBlock = (chartData) => blockDom.createBlock(() => {
554
const canvas = document.createElement("canvas");
555
canvas.width = 400;
556
canvas.height = 300;
557
558
// Initialize chart library
559
const ctx = canvas.getContext("2d");
560
new ThirdPartyChart(ctx, chartData);
561
562
return canvas;
563
});
564
```
565
566
### VNode Interface
567
568
The core virtual node interface that all BlockDOM elements implement.
569
570
```typescript { .api }
571
/**
572
* Virtual node interface implemented by all BlockDOM elements
573
*/
574
interface VNode<T = any> {
575
/** Mounts the VNode to a parent element */
576
mount(parent: HTMLElement, afterNode: Node | null): void;
577
/** Moves the VNode before a DOM node */
578
moveBeforeDOMNode(node: Node | null, parent?: HTMLElement): void;
579
/** Moves the VNode before another VNode */
580
moveBeforeVNode(other: T | null, afterNode: Node | null): void;
581
/** Patches this VNode with another VNode */
582
patch(other: T, withBeforeRemove: boolean): void;
583
/** Called before removal for cleanup */
584
beforeRemove(): void;
585
/** Removes the VNode from DOM */
586
remove(): void;
587
/** Gets the first DOM node of this VNode */
588
firstNode(): Node | undefined;
589
/** Associated DOM element (if any) */
590
el?: HTMLElement | Text;
591
/** Parent DOM element */
592
parentEl?: HTMLElement;
593
/** Whether this is the only child */
594
isOnlyChild?: boolean;
595
/** Unique key for list optimizations */
596
key?: any;
597
}
598
599
/**
600
* Type alias for any BlockDOM VNode
601
*/
602
type BDom = VNode<any>;
603
```