Loading...
Loading...
Odoo frontend JavaScript patterns for website themes. Covers publicWidget framework (complete pattern with editableMode handling), Owl v1/v2 component patterns, _t() translation best practices, Bootstrap 4-to-5 migration, version detection, and critical development rules. Supports Odoo 14-19. <example> Context: User wants to create a publicWidget user: "Create a publicWidget for my Odoo website" assistant: "I will create a publicWidget with editableMode handling and proper cleanup." <commentary>publicWidget creation.</commentary> </example> <example> Context: User asks about Owl components user: "How do I create an Owl component in Odoo 18?" assistant: "I will show the Owl v2 pattern with static template and props." <commentary>Owl component pattern.</commentary> </example> <example> Context: User needs help with translations user: "How do I translate JavaScript strings in Odoo?" assistant: "Use _t() at DEFINITION TIME for static labels, not runtime wrappers." <commentary>Translation best practices.</commentary> </example> <example> Context: User migrating Bootstrap classes user: "Convert Bootstrap 4 classes to Bootstrap 5 for Odoo 17" assistant: "Replace ml-* with ms-*, mr-* with me-*, text-left with text-start." <commentary>Bootstrap migration.</commentary> </example>
npx skill4agent add ahmed-lakosha/odoo-upgrade-skill frontend-jspublicWidget/** @odoo-module **/static/src/js/static/src/scss/_t()| Odoo | Bootstrap | Owl | JavaScript |
|---|---|---|---|
| 14 | 4.x | — | ES6+ |
| 15 | 4.x | v1 | ES6+ |
| 16 | 5.1.3 | v1 | ES2020+ |
| 17 | 5.1.3 | v2 | ES2020+ |
| 18-19 | 5.1.3 | v2 | ES2020+ |
odoo17//** @odoo-module **/
import publicWidget from "@web/legacy/js/public/public_widget";
publicWidget.registry.MyWidget = publicWidget.Widget.extend({
selector: '.my-selector',
disabledInEditableMode: false, // Allow in website builder
events: {
'click .button': '_onClick',
'change input': '_onChange',
'submit form': '_onSubmit',
},
/**
* CRITICAL: Check editableMode for website builder compatibility
*/
start: function () {
if (!this.editableMode) {
this._initializeAnimation();
this._bindExternalEvents();
}
return this._super.apply(this, arguments);
},
_initializeAnimation: function () {
this.$el.addClass('animated');
},
_bindExternalEvents: function () {
$(window).on('scroll.myWidget', this._onScroll.bind(this));
$(window).on('resize.myWidget', this._onResize.bind(this));
},
_onClick: function (ev) {
ev.preventDefault();
if (this.editableMode) return;
// Handler logic
},
/**
* CRITICAL: Clean up event listeners to prevent memory leaks
*/
destroy: function () {
$(window).off('.myWidget'); // Remove namespaced events
this._super.apply(this, arguments);
},
});
export default publicWidget.registry.MyWidget;this.editableModedisabledInEditableMode: falsedestroy().myWidget'assets': {
'web.assets_frontend': [
'module_name/static/src/js/my_widget.js',
],
}/** @odoo-module **/
import { Component, useState } from "@odoo/owl";
import { registry } from "@web/core/registry";
class MyComponent extends Component {
setup() {
this.state = useState({ items: [], loading: false });
}
async willStart() {
await this.loadData();
}
}
MyComponent.template = "module_name.MyComponentTemplate";
registry.category("public_components").add("MyComponent", MyComponent);/** @odoo-module **/
import { Component, useState } from "@odoo/owl";
class MyComponent extends Component {
static template = "module_name.MyComponentTemplate"; // Static property
static props = {
title: { type: String, optional: true },
items: { type: Array },
};
setup() {
this.state = useState({ selectedId: null });
}
}<template id="MyComponentTemplate" name="My Component">
<div class="my-component">
<h3 t-if="props.title"><t t-esc="props.title"/></h3>
<ul>
<li t-foreach="props.items" t-as="item" t-key="item.id">
<t t-esc="item.name"/>
</li>
</ul>
</div>
</template>/** @odoo-module **/
import { _t } from "@web/core/l10n/translation";
// Static labels wrapped where defined
const MONTHS = [
{value: 1, short: _t("Jan"), full: _t("January")},
{value: 2, short: _t("Feb"), full: _t("February")},
// ...
];
const STATUS_LABELS = {
draft: _t("Draft"),
pending: _t("Pending"),
approved: _t("Approved"),
};// WRONG: Strings without _t() at definition
const MONTHS = [{value: 1, label: "Jan"}]; // NOT found by PO extractor!
// WRONG: Variable passed to _t() at runtime
translateLabel(key) {
return _t(key); // PO extractor can't find string literals
}| Use _t() | Don't use _t() |
|---|---|
| Static labels in JS arrays/objects | Static text in XML templates (auto-translated) |
| Error messages in JS constants | Dynamic variables passed at runtime |
| User-facing strings defined in JS | Hardcoded strings in .xml files |
| Bootstrap 4 | Bootstrap 5 |
|---|---|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| Bootstrap 4 | Bootstrap 5 |
|---|---|
| |
| |
| |
form-inlinejumbotronmediad-flexstatic/src/scss/bootstrap_overridden.scssweb._assets_frontend_helpers@import "~bootstrap/scss/functions";
@import "~bootstrap/scss/variables";
$spacer: 1rem !default;
$border-radius: 0.25rem !default;
$border-radius-lg: 0.5rem !default;
$box-shadow: 0 .5rem 1rem rgba(0, 0, 0, .15) !default;!default@web/legacy/js/public/public_widgettype='json'type='jsonrpc'