Introduction to OWL Framework
What is OWL?
OWL (Odoo Web Library) is Odoo’s modern web framework for building user interface components. It’s a lightweight, component-based framework that provides:
- Reactive component system
- State management
- Event handling
- Template system
- Component lifecycle management
Why Use OWL?
- Better Performance: OWL is optimized for Odoo’s specific needs and provides better performance than jQuery-based widgets.
- Modern Development: Uses modern JavaScript features and follows current web development practices.
- Component-Based: Encourages reusable, modular code through components.
- Reactive: Automatically updates the UI when data changes.
Git Hub Repo Link
https://github.com/KtreeOpenSource/KTree_odoo_examples/tree/18.0
Module Structure
product_color_selector/
├── __init__.py
├── __manifest__.py
├── models/
│ ├── __init__.py
│ └── product_template.py
├── static/
│ └── src/
│ └── components/
│ └── color_selector/
│ ├── color_selector.js
│ ├── color_selector.xml
│ └── color_selector.scss
└── views/
└── product_template_views.xml
Key Files Explained
- manifest.py:
{
‘name’: ‘Product Color Selector’,
‘version’: ‘1.0’,
‘depends’: [‘product’],
‘data’ : [
‘views/product_template_views.xml’
],
‘assets’: {
‘web.assets_backend’: [
‘product_color_selector/static/src/components/color_selector/color_selector.js’,
‘product_color_selector/static/src/components/color_selector/color_selector.xml’,
‘product_color_selector/static/src/components/color_selector/color_selector.scss’,
],
},
}
The manifest defines module metadata and assets loading.
- Python Model (models/product_template.py):
from odoo import models, fields
class ProductTemplate(models.Model):
_inherit = ‘product.template’
product_color = fields.Selection([
(‘red’, ‘Red’),
(‘blue’, ‘Blue’),
(‘green’, ‘Green’),
(‘yellow’, ‘Yellow’),
(‘black’, ‘Black’),
(‘white’, ‘White’)
], string=’Product Color’, default=’white’)
This extends the product template to add the color field.
Implementation Guide
1. JavaScript Component (color_selector.js)
/** @odoo-module **/
import { _t } from "@web/core/l10n/translation";
import { registry } from "@web/core/registry";
import { Component } from "@odoo/owl";
import { Field } from "@web/views/fields/field";
import { standardFieldProps } from "@web/views/fields/standard_field_props";
export class ColorSelectorField extends Component {
static template = "custom_module.ColorSelector";
static components = {};
static props = {
…standardFieldProps,
name: { type: String },
record: { type: Object },
readonly: { type: Boolean, optional: true },
value: { optional: true },
};
setup() {
this.colors = [
{ value: ‘red’, label: _t(‘Red’), color: ‘#FF0000’ },
{ value: ‘blue’, label: _t(‘Blue’), color: ‘#0000FF’ },
{ value: ‘green’, label: _t(‘Green’), color: ‘#008000’ },
{ value: ‘yellow’, label: _t(‘Yellow’), color: ‘#FFFF00’ },
{ value: ‘black’, label: _t(‘Black’), color: ‘#000000’ },
{ value: ‘white’, label: _t(‘White’), color: ‘#FFFFFF’ },
];
}
get selectedColor() {
// Get the current value directly from the record
return this.props.record.data[this.props.name] || ‘white’;
}
async onColorClick(color) {
if (!this.props.readonly) {
try {
this.env.services.ui.block();
await this.props.record.update({ [this.props.name]: color });
await this.props.record.save({
stayInEdition: true,
noReload: true
});
} catch (error) {
this.env.services.notification.notify({
title: _t("Error"),
message: _t("Failed to update color"),
type: "danger",
});
console.error("Error updating product color:", error);
} finally {
this.env.services.ui.unblock();
}
}
}
}
export const colorSelector = {
component: ColorSelectorField,
supportedTypes: ["selection"],
extractProps: ({ attrs, field }) => ({
name: field.name,
}),
};
registry.category("fields").add("color_selector", colorSelector);
2. XML Template (color_selector.xml)
<?xml version="1.0" encoding="UTF-8"?>
<templates xml:space="preserve">
<t t-name="custom_module.ColorSelector" owl="1">
<div class="o_field_color_selector" t-att-class="{‘o_readonly’: props.readonly}">
<div class="d-flex flex-wrap gap-2">
<t t-foreach="colors" t-as="color" t-key="color.value">
<div
class="color-circle"
t-att-class="{‘selected’: color.value === selectedColor}"
t-on-click="() => this.onColorClick(color.value)"
t-att-style="’background-color: ‘ + color.color"
t-att-title="color.label"
/>
</t>
</div>
</div>
</t>
</templates>
Component Registration and Types
Registry Types in Odoo
- Fields Registry:
registry.category("fields").add("color_selector", colorSelector);
Used for registering field widgets.
- Views Registry:
registry.category("views").add("view_type", viewComponent);
Used for registering custom views.
- Services Registry:
registry.category("services").add("service_name", service);
Used for registering services.
- Actions Registry:
registry.category("actions").add("action_type", actionHandler);
Used for registering custom actions.
When to Use Each Registry
- Fields Registry: Use when creating custom field widgets (like our color selector)
- Views Registry: Use when creating entirely new view types
- Services Registry: Use when creating global services (like notifications)
- Actions Registry: Use when creating custom client actions
Detailed Component Analysis
JavaScript Component (color_selector.js)
Let’s break down the JavaScript component line by line:
1. Module Declaration and Imports
/** @odoo-module **/ import { _t } from "@web/core/l10n/translation"; import { registry } from "@web/core/registry"; import { Component } from "@odoo/owl"; import { standardFieldProps } from "@web/views/fields/standard_field_props";
- @odoo-module: Marks this as an Odoo module
- _t: Translation function for internationalization
- registry: Odoo’s registry system for component registration
- Component: Base OWL component class
- standardFieldProps: Standard properties for field components
2. Component Class Definition
export class ColorSelectorField extends Component { static template = "custom_module.ColorSelector";
- Extends OWL’s Component class
- static template: Links to the XML template file
3. Props Definition
static props = { …standardFieldProps, name: { type: String }, record: { type: Object }, readonly: { type: Boolean, optional: true }, value: { optional: true }, };
Props are properties passed to the component:
- standardFieldProps: Base field properties
- name: Field name (required)
- record: Odoo record object (required)
- readonly: Controls if field is editable
- value: Current field value
4. Setup Method
setup() {
this.colors = [ { value: ‘red’, label: _t(‘Red’), color: ‘#FF0000’ }, // … other colors ]; }
- setup(): Lifecycle method called when component is created
- Initializes color options with translations
- Runs before the first render
5. Getter Method
get selectedColor() { return this.props.record.data[this.props.name] || ‘white’; }
- Reactive getter for the currently selected color
- Accesses color value from record data
- Provides default ‘white’ if no color selected
6. Event Handler
sync onColorClick(color) {
if (!this.props.readonly) {
try {
this.env.services.ui.block();
await this.props.record.update({ [this.props.name]: color });
await this.props.record.save({
stayInEdition: true,
noReload: true
});
} catch (error) {
this.env.services.notification.notify({
title: _t("Error"),
message: _t("Failed to update color"),
type: "danger",
});
console.error("Error updating product color:", error);
} finally {
this.env.services.ui.unblock();
}
}
}
- this.env.services.ui.block(): Shows loading indicator
- record.update(): Updates field value
- record.save(): Saves to database
- Error handling with notification service
- ui.unblock(): Removes loading indicator
XML Template (color_selector.xml)
Let’s analyze the template structure:
<templates xml:space="preserve">
<t t-name="custom_module.ColorSelector" owl="1">
<div class="o_field_color_selector" t-att-class="{‘o_readonly’: props.readonly}">
<div class="d-flex flex-wrap gap-2">
<t t-foreach="colors" t-as="color" t-key="color.value">
<div
class="color-circle"
t-att-class="{‘selected’: color.value === selectedColor}"
t-on-click="() => this.onColorClick(color.value)"
t-att-style="’background-color: ‘ + color.color"
t-att-title="color.label"
/>
</t>
</div>
</div>
</t>
</templates>
Template Directives:
- t-name: Template identifier
- owl=”1″: Marks as OWL template
- t-att-class: Dynamic class binding
- t-foreach: Loop directive
- t-key: Unique key for loop items
- t-on-click: Event binding
- t-att-style: Dynamic style binding
- t-att-title: Dynamic title attribute
SCSS Styling (color_selector.scss)
.o_field_color_selector {
padding: 0;
.color-circle {
width: 28px;
height: 28px;
border-radius: 50%;
border: 2px solid #e2e2e2;
cursor: pointer;
transition: all 0.3s ease;
position: relative;
box-sizing: border-box;
&:hover {
transform: scale(1.1);
border-color: #7C7BAD;
}
&.selected {
transform: scale(1.1);
border: 2px solid white;
box-shadow: 0 0 0 2px #7C7BAD;
z-index: 1;
&:hover {
transform: scale(1.1);
}
}
/* Special handling for white color */
&[style*="background-color: #FFFFFF"],
&[style*="background-color: rgb(255, 255, 255)"] {
border: 2px solid #e2e2e2;
// &.selected {
// border: 2px solid #7C7BAD;
// box-shadow: 0 0 0 2px #7C7BAD;
// }
&:hover {
border-color: #7C7BAD;
}
}
}
&.o_readonly {
.color-circle {
cursor: not-allowed;
opacity: 0.6;
&:hover {
transform: none;
border-color: #e2e2e2;
}
&.selected {
opacity: 1;
border-color: #7C7BAD;
transform: scale(1.1);
}
}
}
}
SCSS Features:
- Base Styling
- Sets dimensions and shape
- Defines border and cursor
- Interactive States
- Hover effects with scaling
- Selection highlight
- Transition animations
- Special Cases
- White color handling
- Read-only state styling
- Nesting
- Uses SCSS nesting for organization
- Separates states with & operator
Best Practices
- Component Structure:
- Keep components small and focused
- Use proper props validation
- Handle errors gracefully
- State Management:
- Use reactive getters for computed values
- Avoid direct DOM manipulation
- Use props for parent-child communication
- Performance:
- Use t-key in loops for optimal rendering
- Avoid unnecessary computations in getters
- Use async/await for database operations
Troubleshooting
Common issues and solutions:
- Component Not Rendering:
- Check registry registration
- Verify asset loading in manifest
- Check browser console for errors
- Data Not Saving:
- Verify record.update() calls
- Check error handling
- Verify field name matches model
- Styling Issues:
- Check SCSS compilation
- Verify class names
- Check browser dev tools for CSS conflicts