CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-odoo--owl

Odoo Web Library (OWL) is a modern, lightweight TypeScript UI framework for building reactive web applications with components, templates, and state management.

Overview
Eval results
Files

blockdom.mddocs/

BlockDOM System

Virtual DOM implementation with block-based rendering, patching, and lifecycle management optimized for template-driven UIs.

Capabilities

BlockDOM Object

The main BlockDOM API object containing core virtual DOM manipulation functions.

/**
 * BlockDOM API object with core virtual DOM functions
 */
const blockDom: {
  /** Configuration object */
  config: Config;
  /** Mount a VNode to DOM */
  mount: (vnode: VNode, fixture: HTMLElement, afterNode?: Node) => void;
  /** Patch one VNode with another */
  patch: (vnode1: VNode, vnode2: VNode, withBeforeRemove?: boolean) => void;
  /** Remove a VNode from DOM */
  remove: (vnode: VNode, withBeforeRemove?: boolean) => void;
  /** Create list block */
  list: (items: any[], template: Function) => VNode;
  /** Create multi-block container */
  multi: (blocks: VNode[]) => VNode;
  /** Create text node */
  text: (value: string) => VNode;
  /** Create comment node */
  comment: (value: string) => VNode;
  /** Create conditional block */
  toggler: (condition: boolean, template: Function) => VNode;
  /** Create generic block */
  createBlock: (template: Function) => VNode;
  /** Create raw HTML block */
  html: (htmlString: string) => VNode;
};

Mount Function

Mounts a virtual node to the DOM.

/**
 * Mounts a VNode to a DOM element
 * @param vnode - Virtual node to mount
 * @param fixture - Target DOM element
 * @param afterNode - Optional reference node for positioning
 */
function mount(vnode: VNode, fixture: HTMLElement, afterNode?: Node | null): void;

Usage Examples:

import { blockDom } from "@odoo/owl";

// Mount a simple text node
const textNode = blockDom.text("Hello World");
blockDom.mount(textNode, document.body);

// Mount with positioning
const containerDiv = document.getElementById("container");
const existingElement = containerDiv.firstChild;
const newNode = blockDom.text("Insert before first child");
blockDom.mount(newNode, containerDiv, existingElement);

// Mount complex structures
const listNode = blockDom.list([1, 2, 3], (item) =>
  blockDom.text(`Item: ${item}`)
);
blockDom.mount(listNode, document.getElementById("list-container"));

Patch Function

Updates one virtual node with the content of another.

/**
 * Patches vnode1 with the content of vnode2
 * @param vnode1 - Target VNode to be updated
 * @param vnode2 - Source VNode with new content
 * @param withBeforeRemove - Whether to call beforeRemove hooks
 */
function patch(vnode1: VNode, vnode2: VNode, withBeforeRemove?: boolean): void;

Usage Examples:

import { blockDom } from "@odoo/owl";

// Initial render
let currentNode = blockDom.text("Loading...");
blockDom.mount(currentNode, document.body);

// Update content
const newNode = blockDom.text("Content loaded!");
blockDom.patch(currentNode, newNode);
currentNode = newNode;

// Patch lists
let currentList = blockDom.list([1, 2, 3], (item) =>
  blockDom.text(`Item ${item}`)
);
blockDom.mount(currentList, document.getElementById("list"));

// Update list with new items
const newList = blockDom.list([1, 2, 3, 4, 5], (item) =>
  blockDom.text(`Updated Item ${item}`)
);
blockDom.patch(currentList, newList);
currentList = newList;

// Patch with cleanup
blockDom.patch(currentNode, newNode, true); // Calls beforeRemove hooks

Remove Function

Removes a virtual node from the DOM.

/**
 * Removes a VNode from DOM
 * @param vnode - Virtual node to remove
 * @param withBeforeRemove - Whether to call beforeRemove hooks
 */
function remove(vnode: VNode, withBeforeRemove?: boolean): void;

Usage Examples:

import { blockDom } from "@odoo/owl";

// Simple removal
const node = blockDom.text("Temporary content");
blockDom.mount(node, document.body);

setTimeout(() => {
  blockDom.remove(node); // Remove after delay
}, 2000);

// Removal with cleanup
const complexNode = blockDom.multi([
  blockDom.text("Part 1"),
  blockDom.html("<div>Part 2</div>"),
  blockDom.comment("End of content")
]);
blockDom.mount(complexNode, document.getElementById("temp-container"));

// Later remove with beforeRemove hooks
blockDom.remove(complexNode, true);

Block Creation Functions

Text Block

Creates a text node block.

/**
 * Creates a text node VNode
 * @param value - Text content
 * @returns Text VNode
 */
function text(value: string): VNode;

Usage Examples:

import { blockDom } from "@odoo/owl";

// Simple text
const greeting = blockDom.text("Hello, World!");
blockDom.mount(greeting, document.body);

// Dynamic text content
const counter = { value: 0 };
const updateCounter = () => {
  const newText = blockDom.text(`Count: ${counter.value}`);
  blockDom.patch(currentText, newText);
  currentText = newText;
};

let currentText = blockDom.text(`Count: ${counter.value}`);
blockDom.mount(currentText, document.getElementById("counter"));

// Update every second
setInterval(() => {
  counter.value++;
  updateCounter();
}, 1000);

Comment Block

Creates a comment node block.

/**
 * Creates a comment node VNode
 * @param value - Comment text
 * @returns Comment VNode
 */
function comment(value: string): VNode;

Usage Examples:

import { blockDom } from "@odoo/owl";

// Debug comments
const debugComment = blockDom.comment("Debug: Component rendered at " + new Date());
blockDom.mount(debugComment, document.body);

// Conditional comments
const renderWithComments = (showComments, data) => {
  const blocks = [];

  if (showComments) {
    blocks.push(blockDom.comment("Data rendering start"));
  }

  blocks.push(blockDom.text(JSON.stringify(data)));

  if (showComments) {
    blocks.push(blockDom.comment("Data rendering end"));
  }

  return blockDom.multi(blocks);
};

HTML Block

Creates a block with raw HTML content.

/**
 * Creates a raw HTML VNode
 * @param htmlString - Raw HTML string
 * @returns HTML VNode
 */
function html(htmlString: string): VNode;

Usage Examples:

import { blockDom } from "@odoo/owl";

// Simple HTML
const richContent = blockDom.html("<p><strong>Bold</strong> and <em>italic</em> text</p>");
blockDom.mount(richContent, document.getElementById("content"));

// HTML from markdown or other sources
const markdownHTML = "<h1>Title</h1><p>Some content with <a href='#'>links</a></p>";
const markdownBlock = blockDom.html(markdownHTML);
blockDom.mount(markdownBlock, document.getElementById("markdown-content"));

// Dynamic HTML content
const renderHTML = (data) => {
  const html = `
    <div class="card">
      <h2>${data.title}</h2>
      <div class="content">${data.htmlContent}</div>
      <footer>Created: ${data.createdAt}</footer>
    </div>
  `;
  return blockDom.html(html);
};

const htmlBlock = renderHTML({
  title: "Article Title",
  htmlContent: "<p>This is <strong>rich content</strong>.</p>",
  createdAt: "2024-01-01"
});

List Block

Creates a list block for rendering arrays of data.

/**
 * Creates a list VNode for rendering arrays
 * @param items - Array of items to render
 * @param template - Function that creates VNode for each item
 * @returns List VNode
 */
function list<T>(items: T[], template: (item: T, index: number) => VNode): VNode;

Usage Examples:

import { blockDom } from "@odoo/owl";

// Simple list
const numbers = [1, 2, 3, 4, 5];
const numberList = blockDom.list(numbers, (num, index) =>
  blockDom.text(`${index + 1}. ${num}`)
);
blockDom.mount(numberList, document.getElementById("numbers"));

// Complex object list
const users = [
  { id: 1, name: "Alice", email: "alice@example.com" },
  { id: 2, name: "Bob", email: "bob@example.com" },
  { id: 3, name: "Charlie", email: "charlie@example.com" }
];

const userList = blockDom.list(users, (user) => {
  const userHTML = `
    <div class="user-card" data-user-id="${user.id}">
      <h3>${user.name}</h3>
      <p>${user.email}</p>
    </div>
  `;
  return blockDom.html(userHTML);
});

blockDom.mount(userList, document.getElementById("user-list"));

// Nested lists
const categories = [
  {
    name: "Fruits",
    items: ["Apple", "Banana", "Orange"]
  },
  {
    name: "Vegetables",
    items: ["Carrot", "Broccoli", "Spinach"]
  }
];

const categoryList = blockDom.list(categories, (category) => {
  const categoryHeader = blockDom.html(`<h2>${category.name}</h2>`);
  const itemList = blockDom.list(category.items, (item) =>
    blockDom.html(`<li>${item}</li>`)
  );

  return blockDom.multi([
    categoryHeader,
    blockDom.html("<ul>"),
    itemList,
    blockDom.html("</ul>")
  ]);
});

Multi Block

Creates a container for multiple blocks.

/**
 * Creates a multi-block container
 * @param blocks - Array of VNodes to group together
 * @returns Multi VNode containing all blocks
 */
function multi(blocks: VNode[]): VNode;

Usage Examples:

import { blockDom } from "@odoo/owl";

// Group related elements
const header = blockDom.html("<h1>Welcome</h1>");
const content = blockDom.text("This is the main content.");
const footer = blockDom.html("<footer>&copy; 2024</footer>");

const page = blockDom.multi([header, content, footer]);
blockDom.mount(page, document.body);

// Conditional multi-blocks
const renderPage = (user, showHeader = true, showFooter = true) => {
  const blocks = [];

  if (showHeader) {
    blocks.push(blockDom.html(`<header><h1>Welcome, ${user.name}!</h1></header>`));
  }

  blocks.push(blockDom.html(`
    <main>
      <p>User ID: ${user.id}</p>
      <p>Email: ${user.email}</p>
    </main>
  `));

  if (showFooter) {
    blocks.push(blockDom.html("<footer>Thank you for visiting!</footer>"));
  }

  return blockDom.multi(blocks);
};

// Dynamic multi-blocks
const renderDashboard = (widgets) => {
  const blocks = [
    blockDom.html("<div class='dashboard-header'><h1>Dashboard</h1></div>")
  ];

  widgets.forEach(widget => {
    blocks.push(blockDom.html(`
      <div class="widget" data-widget-id="${widget.id}">
        <h2>${widget.title}</h2>
        <div class="widget-content">${widget.content}</div>
      </div>
    `));
  });

  blocks.push(blockDom.html("<div class='dashboard-footer'>Last updated: " + new Date().toLocaleString() + "</div>"));

  return blockDom.multi(blocks);
};

Toggler Block

Creates a conditional block that shows/hides content.

/**
 * Creates a conditional block
 * @param condition - Boolean condition for showing content
 * @param template - Function that creates VNode when condition is true
 * @returns Toggler VNode
 */
function toggler(condition: boolean, template: () => VNode): VNode;

Usage Examples:

import { blockDom } from "@odoo/owl";

// Simple conditional rendering
let showMessage = false;
const messageToggler = blockDom.toggler(showMessage, () =>
  blockDom.text("This message is conditionally shown!")
);
blockDom.mount(messageToggler, document.getElementById("message-area"));

// Toggle visibility
document.getElementById("toggle-button").onclick = () => {
  showMessage = !showMessage;
  const newToggler = blockDom.toggler(showMessage, () =>
    blockDom.text("This message is conditionally shown!")
  );
  blockDom.patch(messageToggler, newToggler);
};

// Complex conditional content
const user = { isAdmin: false, name: "John" };
const adminPanel = blockDom.toggler(user.isAdmin, () => {
  const adminContent = `
    <div class="admin-panel">
      <h2>Admin Panel</h2>
      <button>Manage Users</button>
      <button>System Settings</button>
      <button>View Logs</button>
    </div>
  `;
  return blockDom.html(adminContent);
});

// Loading states
let isLoading = true;
const loadingToggler = blockDom.toggler(isLoading, () =>
  blockDom.html("<div class='spinner'>Loading...</div>")
);

const content = blockDom.multi([
  loadingToggler,
  blockDom.toggler(!isLoading, () =>
    blockDom.html("<div>Content loaded successfully!</div>")
  )
]);

// Simulate loading completion
setTimeout(() => {
  isLoading = false;
  // Re-render with updated condition
}, 2000);

Create Block

Creates a generic block from a template function.

/**
 * Creates a generic block from template function
 * @param template - Function that generates DOM structure
 * @returns Generic block VNode
 */
function createBlock(template: () => HTMLElement | string): VNode;

Usage Examples:

import { blockDom } from "@odoo/owl";

// Create custom block
const customBlock = blockDom.createBlock(() => {
  const div = document.createElement("div");
  div.className = "custom-widget";
  div.innerHTML = "<p>Custom widget content</p>";

  // Add event listeners
  div.addEventListener("click", () => {
    console.log("Custom widget clicked!");
  });

  return div;
});

blockDom.mount(customBlock, document.getElementById("widget-container"));

// Block with dynamic content
const dynamicBlock = (data) => blockDom.createBlock(() => {
  const container = document.createElement("div");
  container.className = "dynamic-content";

  // Create elements programmatically
  const title = document.createElement("h2");
  title.textContent = data.title;
  container.appendChild(title);

  const list = document.createElement("ul");
  data.items.forEach(item => {
    const listItem = document.createElement("li");
    listItem.textContent = item;
    list.appendChild(listItem);
  });
  container.appendChild(list);

  return container;
});

// Chart integration
const chartBlock = (chartData) => blockDom.createBlock(() => {
  const canvas = document.createElement("canvas");
  canvas.width = 400;
  canvas.height = 300;

  // Initialize chart library
  const ctx = canvas.getContext("2d");
  new ThirdPartyChart(ctx, chartData);

  return canvas;
});

VNode Interface

The core virtual node interface that all BlockDOM elements implement.

/**
 * Virtual node interface implemented by all BlockDOM elements
 */
interface VNode<T = any> {
  /** Mounts the VNode to a parent element */
  mount(parent: HTMLElement, afterNode: Node | null): void;
  /** Moves the VNode before a DOM node */
  moveBeforeDOMNode(node: Node | null, parent?: HTMLElement): void;
  /** Moves the VNode before another VNode */
  moveBeforeVNode(other: T | null, afterNode: Node | null): void;
  /** Patches this VNode with another VNode */
  patch(other: T, withBeforeRemove: boolean): void;
  /** Called before removal for cleanup */
  beforeRemove(): void;
  /** Removes the VNode from DOM */
  remove(): void;
  /** Gets the first DOM node of this VNode */
  firstNode(): Node | undefined;
  /** Associated DOM element (if any) */
  el?: HTMLElement | Text;
  /** Parent DOM element */
  parentEl?: HTMLElement;
  /** Whether this is the only child */
  isOnlyChild?: boolean;
  /** Unique key for list optimizations */
  key?: any;
}

/**
 * Type alias for any BlockDOM VNode
 */
type BDom = VNode<any>;

Install with Tessl CLI

npx tessl i tessl/npm-odoo--owl

docs

app-components.md

blockdom.md

hooks.md

index.md

lifecycle.md

reactivity.md

templates.md

utils-validation.md

tile.json