All web applications involve interactions with the user. If the user clicks on a button, selects a record, or updates a field, each of these actions emits an event. Odoo 19's OWL components interact with one another and handle user interactions using those events.
Instead of calling the methods from another component directly, the two components may share information via a callback event. The components can be kept separated and be reusable.
In this blog, we'll see how widget events work in Odoo 19 and how to use them when creating a new interface.
What are widget events in Odoo 19?
Widget events enable one component to announce that something occurred to another component.
Widget events are usually used when:
- A child component wants to inform the parent component that an event occurred
- Several components must respond to a user interaction
- A part of the data must be shared between components
- A component needs to be able to be reused in any component
Event Flow in Components
In Odoo 19, components follow a parent-child structure. A parent component passes callback methods to its child through props.
Parent Component
|
+-- Child Component A
|
+-- Child Component B
When a child component calls one of these callbacks, the corresponding method in the parent is executed.
This keeps communication simple and organized.
Setting Up an OWL Component
Every OWL component starts with the following import:
/** @odoo-module **/
import { Component, useState } from "@odoo/owl";
The @odoo-module directive allows Odoo to recognize the file as an ES module.
Sending Events from Child Components
In Odoo 19, child components communicate with their parents using callback functions passed through props.
For example:
this.props.onRecordSelected({
recordId: 10,
});The object passed to the callback contains the required data.
Parent Component
The parent component defines the handler and passes it to the child.
/** @odoo-module **/
import { Component, useState } from "@odoo/owl";
import { ChildWidget } from "@my_module/js/child_widget";
export class ParentWidget extends Component {
static template = "MyModule.ParentWidget";
static components = { ChildWidget };
setup() {
this.state = useState({ logs: [] });
}
_onRecordSelected(data) {
console.log(data.recordId);
}
}
In the template:
<ChildWidget
onRecordSelected.bind="_onRecordSelected"
/>
The .bind suffix ensures that the method is executed using the parent component's context.
Creating a Child Component
Suppose we have a button inside a child component. When the button is clicked, the child notifies the parent.
child_widget.js
/** @odoo-module **/
import { Component } from "@odoo/owl";
export class ChildWidget extends Component {
static template = "MyModule.ChildWidget";
static props = {
onRecordSelected: Function,
onProductChanged: Function,
onReloadParent: Function,
};
_onSelectRecord() {
this.props.onRecordSelected({
recordId: 25,
});
}
_onChangeProduct() {
this.props.onProductChanged({
productId: 45,
quantity: 3,
price: 120,
});
}
_onCreateRecord() {
this.props.onReloadParent({});
}
}
Child Template
<t t-name="MyModule.ChildWidget">
<div>
<button class="btn btn-primary" t-on-click="_onSelectRecord">
Select Record
</button>
<button class="btn btn-secondary" t-on-click="_onChangeProduct">
Change Product
</button>
<button class="btn btn-success" t-on-click="_onCreateRecord">
Create Record
</button>
</div>
</t>
When a button is clicked:
- The corresponding method is executed.
- The child component calls the callback.
- The parent receives the data.
- The parent performs the required action.
Passing Multiple Values
Callbacks can also send multiple values.
_onChangeProduct() {
this.props.onProductChanged({
productId: 45,
quantity: 3,
price: 120,
});
}In the parent component:
_onProductChanged(data) {
const productId = data.productId;
const quantity = data.quantity;
const price = data.price;
}This makes it easy to transfer related information between components.
DOM Events and Component Events
Both DOM events and component events are used in Odoo 19, but they serve different purposes.
| DOM Events | Component Events |
| Triggered by browser actions | Triggered between components |
| Used for user interactions | Used for component communication |
| Defined with t-on-click, t-on-change, etc. | Implemented using callback props |
| Example: button click | Example: record selection |
Example of DOM events:
<button t-on-click="_onSave">
Save
</button>
<input t-on-change="_onQuantityChange"/>
Example of component communication:
<ChildWidget
onProductChanged.bind="_onProductChanged"
/>
Both approaches are often used together.
Refreshing the Parent Component
A child component can notify the parent after creating a record.
Child Component
_createRecord() {
this.props.onReloadParent({});
}Parent Component
_onReloadParent() {
this.reload();
}This pattern is commonly used in dialogs, dashboards, Kanban views, and custom interfaces.
Registering the Component
To use the component as a client action, register it in the actions registry.
import { registry } from "@web/core/registry";
registry.category("actions").add(
"my_module_action",
ParentWidget
);
Then create the client action:
<record id="action_my_widget" model="ir.actions.client">
<field name="name">My Widget Demo</field>
<field name="tag">my_module_action</field>
</record>The value of the tag field must match the key used while registering the component.
Reactive State with useState()
useState() allows components to update the UI automatically whenever data changes.
setup() {
this.state = useState({
logs: [],
recordId: null,
});
}
_onRecordSelected(data) {
this.state.recordId = data.recordId;
}Whenever the state changes, OWL re-renders the template automatically.
Best Practices
Use Meaningful Callback Names
Choose names that clearly describe the action.
static props = {
onRecordSaved: Function,
onInvoiceConfirmed: Function,
onLineRemoved: Function,
onProductUpdated: Function,
};
Avoid generic names such as:
static props = {
onEvent: Function,
onUpdate: Function,
};Keep Components Independent
Child components should not directly access parent methods. Instead, use callback props.
this.props.onReloadParent({});This makes the component reusable in different situations.
Declare Props Explicitly
Always define the expected props.
static props = {
onRecordSelected: Function,
onProductChanged: Function,
onReloadParent: Function,
};Pass Only Required Data
When sending data between components, it is better to pass only the information that the parent component actually needs. Sending unnecessary data increases the amount of information being transferred and can make the code harder to maintain.
For example, if the parent only needs the record ID, you can send just that value:
this.props.onRecordSelected({
recordId: 15,
});Instead of sending an entire record object with many unused fields, keep the payload small and focused. This makes the communication between components clearer and easier to manage.
Widget events represent one of the fundamental features of component communication within Odoo 19. The use of callback props allows parent and child components to share information without the burden of forming undesirable dependencies between components. By rendering components as independent entities and only transferring essential data between them, it becomes feasible to engineer more maintainable and reusable interfaces. These event mechanisms will prove helpful while working on custom components, dashboards, dialogs, or Kanban views in Odoo 19.
To read more about How Widget Rendering Works in Odoo 19, refer to our blog How Widget Rendering Works in Odoo 19.