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?

  1. Better Performance: OWL is optimized for Odoo's specific needs and provides better performance than jQuery-based widgets.
  2. Modern Development: Uses modern JavaScript features and follows current web development practices.
  3. Component-Based: Encourages reusable, modular code through components.
  4. Reactive: Automatically updates the UI when data changes.

Git Hub Repo Link
https://github.com/KtreeOpenSource/KTree_odoo_examples/tree/18.0

Module Structure

Plain text
1product_color_selector/
2├── __init__.py
3├── __manifest__.py
4├── models/
5│ ├── __init__.py
6│ └── product_template.py
7├── static/
8│ └── src/
9│ └── components/
10│ └── color_selector/
11│ ├── color_selector.js
12│ ├── color_selector.xml
13│ └── color_selector.scss
14└── views/
15 └── product_template_views.xml

Key Files Explained

  1. manifest.py:
Plain text
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.

  1. Python Model (models/product_template.py):
Python
1from odoo import models, fields
2class 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)

JavaScript
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";
7
8
9export 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 };
19
20
21 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 }
31
32
33 get selectedColor() {
34 // Get the current value directly from the record
35 return this.props.record.data[this.props.name] || 'white';
36 }
37
38
39 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: true
47 });
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}
61
62
63export const colorSelector = {
64 component: ColorSelectorField,
65 supportedTypes: ["selection"],
66 extractProps: ({ attrs, field }) => ({
67 name: field.name,
68 }),
69};
70
71
72registry.category("fields").add("color_selector", colorSelector);

2. XML Template (color_selector.xml)

HTML / 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 <div
8 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

  1. Fields Registry:
Plain text
1registry.category("fields").add("color_selector", colorSelector);

Used for registering field widgets.

  1. Views Registry:
Plain text
1registry.category("views").add("view_type", viewComponent);

Used for registering custom views.

  1. Services Registry:
Plain text
1registry.category("services").add("service_name", service);

Used for registering services.

  1. Actions Registry:
Plain text
1registry.category("actions").add("action_type", actionHandler);

Used for registering custom actions.

When to Use Each Registry

  1. Fields Registry: Use when creating custom field widgets (like our color selector)
  2. Views Registry: Use when creating entirely new view types
  3. Services Registry: Use when creating global services (like notifications)
  4. 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

Plain text
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

Plain text
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

Plain text
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

Plain text
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

Plain text
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

JavaScript
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: true
9 });
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:

HTML / XML
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 <div
7 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)

CSS
1.o_field_color_selector {
2 padding: 0;
3
4 .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;
13
14
15 &:hover {
16 transform: scale(1.1);
17 border-color: #7C7BAD;
18 }
19
20
21 &.selected {
22 transform: scale(1.1);
23 border: 2px solid white;
24 box-shadow: 0 0 0 2px #7C7BAD;
25 z-index: 1;
26
27
28 &:hover {
29 transform: scale(1.1);
30 }
31 }
32
33
34 /* Special handling for white color */
35 &[style*="background-color: #FFFFFF"],
36 &[style*="background-color: rgb(255, 255, 255)"] {
37 border: 2px solid #e2e2e2;
38
39 // &.selected {
40 // border: 2px solid #7C7BAD;
41 // box-shadow: 0 0 0 2px #7C7BAD;
42 // }
43
44
45 &:hover {
46 border-color: #7C7BAD;
47 }
48 }
49 }
50
51
52 &.o_readonly {
53 .color-circle {
54 cursor: not-allowed;
55 opacity: 0.6;
56
57
58 &:hover {
59 transform: none;
60 border-color: #e2e2e2;
61 }
62
63
64 &.selected {
65 opacity: 1;
66 border-color: #7C7BAD;
67 transform: scale(1.1);
68 }
69 }
70 }
71}

SCSS Features:

  1. Base Styling
    • Sets dimensions and shape
    • Defines border and cursor
  2. Interactive States
    • Hover effects with scaling
    • Selection highlight
    • Transition animations
  3. Special Cases
    • White color handling
    • Read-only state styling
  4. Nesting
    • Uses SCSS nesting for organization
    • Separates states with & operator

Best Practices

  1. Component Structure:
    • Keep components small and focused
    • Use proper props validation
    • Handle errors gracefully
  2. State Management:
    • Use reactive getters for computed values
    • Avoid direct DOM manipulation
    • Use props for parent-child communication
  3. 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:

  1. Component Not Rendering:
    • Check registry registration
    • Verify asset loading in manifest
    • Check browser console for errors
  2. Data Not Saving:
    • Verify record.update() calls
    • Check error handling
    • Verify field name matches model
  3. Styling Issues:
    • Check SCSS compilation
    • Verify class names
    • Check browser dev tools for CSS conflicts