Odoo Web Library (OWL) is a modern, lightweight TypeScript UI framework for building reactive web applications with components, templates, and state management.
XML-based template system with compilation, runtime evaluation, and template management for component rendering.
Creates template strings using tagged template literals for component templates.
/**
* Tagged template literal for creating XML templates
* @param args - Template string parts and interpolated values
* @returns Template string with unique identifier
*/
function xml(...args: Parameters<typeof String.raw>): string;Usage Examples:
import { Component, xml, useState } from "@odoo/owl";
// Basic template
class SimpleComponent extends Component {
static template = xml`
<div class="simple">
<h1>Hello World!</h1>
</div>
`;
}
// Template with dynamic content
class DynamicComponent extends Component {
static template = xml`
<div>
<h1><t t-esc="props.title" /></h1>
<p t-if="state.showDescription">
<t t-esc="props.description" />
</p>
<button t-on-click="toggleDescription">
<t t-esc="state.showDescription ? 'Hide' : 'Show'" />
</button>
</div>
`;
setup() {
this.state = useState({ showDescription: false });
}
toggleDescription() {
this.state.showDescription = !this.state.showDescription;
}
}
// Template with loops and conditions
class ListComponent extends Component {
static template = xml`
<div class="list-container">
<h2>Items (<t t-esc="props.items.length" />)</h2>
<ul t-if="props.items.length > 0">
<li t-foreach="props.items" t-as="item" t-key="item.id"
t-att-class="{ 'completed': item.completed }">
<span t-esc="item.title" />
<button t-on-click="() => this.toggleItem(item.id)">
<t t-esc="item.completed ? 'Undo' : 'Complete'" />
</button>
</li>
</ul>
<p t-else="">No items found.</p>
</div>
`;
toggleItem(id) {
const item = this.props.items.find(i => i.id === id);
if (item) {
item.completed = !item.completed;
}
}
}
// Template with sub-components
class ParentComponent extends Component {
static template = xml`
<div class="parent">
<Header title="props.title" />
<main>
<TodoList items="state.todos" />
</main>
<Footer />
</div>
`;
static components = { Header, TodoList, Footer };
setup() {
this.state = useState({
todos: [
{ id: 1, title: "Learn OWL", completed: false }
]
});
}
}Configuration options for template sets and compilation.
/**
* Template set configuration options
*/
interface TemplateSetConfig {
/** Enable development mode with additional checks */
dev?: boolean;
/** List of attributes that should be translated */
translatableAttributes?: string[];
/** Function to translate strings */
translateFn?: (s: string, translationCtx: string) => string;
/** Initial templates as string, Document, or record */
templates?: string | Document | Record<string, string>;
/** Custom template getter function */
getTemplate?: (s: string) => Element | Function | string | void;
/** Custom directive definitions */
customDirectives?: customDirectives;
/** Global values object for template access */
globalValues?: object;
}Usage Examples:
import { TemplateSet, App, Component } from "@odoo/owl";
// Internationalization setup
const i18nTemplateSet = new TemplateSet({
dev: process.env.NODE_ENV === "development",
translateFn: (text, context) => {
return i18n.t(text, { context });
},
translatableAttributes: [
"title", "placeholder", "aria-label", "aria-description",
"alt", "label", "data-tooltip"
],
globalValues: {
formatDate: (date) => new Intl.DateTimeFormat().format(date),
formatCurrency: (amount, currency) => new Intl.NumberFormat('en', {
style: 'currency',
currency
}).format(amount)
}
});
// Template set with custom directives
const customTemplateSet = new TemplateSet({
customDirectives: {
"tooltip": {
compile(node, directive, context) {
const expr = directive.value;
return {
pre: `node.setAttribute('data-tooltip', ${expr});`,
post: `initTooltip(node);`
};
}
},
"lazy-load": {
compile(node, directive, context) {
return {
pre: `
if ('IntersectionObserver' in window) {
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
entry.target.src = entry.target.dataset.src;
observer.unobserve(entry.target);
}
});
});
observer.observe(node);
}
`
};
}
}
}
});
// App with custom template configuration
const app = new App(RootComponent, {
dev: true,
templates: `
<templates>
<t t-name="shared-button">
<button t-att-class="className" t-on-click="onClick">
<t t-esc="label" />
</button>
</t>
</templates>
`,
translateFn: (text) => translations[text] || text,
globalValues: {
utils: {
capitalize: (str) => str.charAt(0).toUpperCase() + str.slice(1),
truncate: (str, length) => str.length > length ? str.slice(0, length) + "..." : str
}
}
});// Content directives
t-esc="expression" // Escaped text content
t-raw="expression" // Raw HTML content
t-out="expression" // Output expression
// Conditional rendering
t-if="condition" // Conditional rendering
t-elif="condition" // Else-if condition
t-else="" // Else condition
// Loops
t-foreach="items" t-as="item" t-key="item.id" // Loop over items
// Attributes
t-att="object" // Set multiple attributes from object
t-att-class="expression" // Dynamic class attribute
t-att-style="expression" // Dynamic style attribute
t-att-[attr]="expression" // Dynamic single attribute
// Event handling
t-on-[event]="handler" // Event listener
t-on-[event].prevent="handler" // With preventDefault
t-on-[event].stop="handler" // With stopPropagation
// Components
t-component="ComponentClass" // Render component
t-props="propsObject" // Pass props to component
// References and slots
t-ref="refName" // Element reference
t-slot="slotName" // Named slot
t-set="variable" t-value="expression" // Set variable
// Sub-templates
t-call="templateName" // Call named templatet-key for list items to optimize renderingt-esc over t-raw for securityt-att-class with objects for conditional classesInstall with Tessl CLI
npx tessl i tessl/npm-odoo--owl