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
1product_color_selector/2├── __init__.py3├── __manifest__.py4├── models/5│ ├── __init__.py6│ └── product_template.py7├── static/8│ └── src/9│ └── components/10│ └── color_selector/11│ ├── color_selector.js12│ ├── color_selector.xml13│ └── color_selector.scss14└── views/15 └── product_template_views.xml
Key Files Explained
- manifest.py:
1{2 'name': 'Product Color Selector',3 'version': '1.0',4 'depends': ['product'],5 'data' : [6 'views/product_template_views.xml'7 ],8 'assets': {9 'web.assets_backend': [10 'product_color_selector/static/src/components/color_selector/color_selector.js',11 'product_color_selector/static/src/components/color_selector/color_selector.xml',12 'product_color_selector/static/src/components/color_selector/color_selector.scss',13 ],14 },15}
The manifest defines module metadata and assets loading.
- Python Model (models/product_template.py):
1from odoo import models, fields2class ProductTemplate(models.Model):3 _inherit = 'product.template'4 product_color = fields.Selection([5 ('red', 'Red'),6 ('blue', 'Blue'),7 ('green', 'Green'),8 ('yellow', 'Yellow'),9 ('black', 'Black'),10 ('white', 'White')11 ], string='Product Color', default='white')
This extends the product template to add the color field.
Implementation Guide
1. JavaScript Component (color_selector.js)
1/** @odoo-module **/2import { _t } from "@web/core/l10n/translation";3import { registry } from "@web/core/registry";4import { Component } from "@odoo/owl";5import { Field } from "@web/views/fields/field";6import { standardFieldProps } from "@web/views/fields/standard_field_props";789export class ColorSelectorField extends Component {10 static template = "custom_module.ColorSelector";11 static components = {};12 static props = {13 ...standardFieldProps,14 name: { type: String },15 record: { type: Object },16 readonly: { type: Boolean, optional: true },17 value: { optional: true },18 };192021 setup() {22 this.colors = [23 { value: 'red', label: _t('Red'), color: '#FF0000' },24 { value: 'blue', label: _t('Blue'), color: '#0000FF' },25 { value: 'green', label: _t('Green'), color: '#008000' },26 { value: 'yellow', label: _t('Yellow'), color: '#FFFF00' },27 { value: 'black', label: _t('Black'), color: '#000000' },28 { value: 'white', label: _t('White'), color: '#FFFFFF' },29 ];30 }313233 get selectedColor() {34 // Get the current value directly from the record35 return this.props.record.data[this.props.name] || 'white';36 }373839 async onColorClick(color) {40 if (!this.props.readonly) {41 try {42 this.env.services.ui.block();43 await this.props.record.update({ [this.props.name]: color });44 await this.props.record.save({45 stayInEdition: true,46 noReload: true47 });48 } catch (error) {49 this.env.services.notification.notify({50 title: _t("Error"),51 message: _t("Failed to update color"),52 type: "danger",53 });54 console.error("Error updating product color:", error);55 } finally {56 this.env.services.ui.unblock();57 }58 }59 }60}616263export const colorSelector = {64 component: ColorSelectorField,65 supportedTypes: ["selection"],66 extractProps: ({ attrs, field }) => ({67 name: field.name,68 }),69};707172registry.category("fields").add("color_selector", colorSelector);
2. XML Template (color_selector.xml)
1<?xml version="1.0" encoding="UTF-8"?>2<templates xml:space="preserve">3 <t t-name="custom_module.ColorSelector" owl="1">4 <div class="o_field_color_selector" t-att-class="{'o_readonly': props.readonly}">5 <div class="d-flex flex-wrap gap-2">6 <t t-foreach="colors" t-as="color" t-key="color.value">7 <div8 class="color-circle"9 t-att-class="{'selected': color.value === selectedColor}"10 t-on-click="() => this.onColorClick(color.value)"11 t-att-style="'background-color: ' + color.color"12 t-att-title="color.label"13 />14 </t>15 </div>16 </div>17 </t>18</templates>
Component Registration and Types
Registry Types in Odoo
- Fields Registry:
1registry.category("fields").add("color_selector", colorSelector);
Used for registering field widgets.
- Views Registry:
1registry.category("views").add("view_type", viewComponent);
Used for registering custom views.
- Services Registry:
1registry.category("services").add("service_name", service);
Used for registering services.
- Actions Registry:
1registry.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
1/** @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
1export 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
1static 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
1setup() {2 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
1get 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
1sync onColorClick(color) {2 if (!this.props.readonly) {3 try {4 this.env.services.ui.block();5 await this.props.record.update({ [this.props.name]: color });6 await this.props.record.save({7 stayInEdition: true,8 noReload: true9 });10 } catch (error) {11 this.env.services.notification.notify({12 title: _t("Error"),13 message: _t("Failed to update color"),14 type: "danger",15 });16 console.error("Error updating product color:", error);17 } finally {18 this.env.services.ui.unblock();19 }20 }21 }
- 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:
1<templates xml:space="preserve">2 <t t-name="custom_module.ColorSelector" owl="1">3 <div class="o_field_color_selector" t-att-class="{'o_readonly': props.readonly}">4 <div class="d-flex flex-wrap gap-2">5 <t t-foreach="colors" t-as="color" t-key="color.value">6 <div7 class="color-circle"8 t-att-class="{'selected': color.value === selectedColor}"9 t-on-click="() => this.onColorClick(color.value)"10 t-att-style="'background-color: ' + color.color"11 t-att-title="color.label"12 />13 </t>14 </div>15 </div>16 </t>17</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)
1.o_field_color_selector {2 padding: 0;34 .color-circle {5 width: 28px;6 height: 28px;7 border-radius: 50%;8 border: 2px solid #e2e2e2;9 cursor: pointer;10 transition: all 0.3s ease;11 position: relative;12 box-sizing: border-box;131415 &:hover {16 transform: scale(1.1);17 border-color: #7C7BAD;18 }192021 &.selected {22 transform: scale(1.1);23 border: 2px solid white;24 box-shadow: 0 0 0 2px #7C7BAD;25 z-index: 1;262728 &:hover {29 transform: scale(1.1);30 }31 }323334 /* Special handling for white color */35 &[style*="background-color: #FFFFFF"],36 &[style*="background-color: rgb(255, 255, 255)"] {37 border: 2px solid #e2e2e2;3839 // &.selected {40 // border: 2px solid #7C7BAD;41 // box-shadow: 0 0 0 2px #7C7BAD;42 // }434445 &:hover {46 border-color: #7C7BAD;47 }48 }49 }505152 &.o_readonly {53 .color-circle {54 cursor: not-allowed;55 opacity: 0.6;565758 &:hover {59 transform: none;60 border-color: #e2e2e2;61 }626364 &.selected {65 opacity: 1;66 border-color: #7C7BAD;67 transform: scale(1.1);68 }69 }70 }71}
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



