M.A. Heshmat Khah
M.A. Heshmat Khah

Reputation: 777

Using Odoo v16 OWL Component inside existing website page

I'm using Odoo v16 and I want to use Odoo Official Frontend Docs for v16 as an example,

In the tutorial, we created the Counter OWL Component.

But in the example, they created a controller and initiated the whole OWL stack from the ground (owl_playground.assets_playground asset).

I want to use the component inside an existing frontend page. assume I want to show the counter on my home page of the website (not a custom page initiated with from a controller and custom template and main.js)

How can I do that?

What should I do?

Alternatively, it will be good if I can create a website snippet for that counter or any other Odoo component.

Upvotes: 1

Views: 2055

Answers (1)

Ahrimann Steiner
Ahrimann Steiner

Reputation: 1314

CASE 1 : For Backoffice views (model views), you can mount it using js_class in the xml (ex: js_class="project_form" in project_views.xml), and define the corresponding name as class in js. Example: addons/project/project_form_view.js:

/** @odoo-module */

import { registry } from "@web/core/registry";
import { formViewWithHtmlExpander } from '../form_with_html_expander/form_view_with_html_expander';
import { ProjectFormRenderer } from "./project_form_renderer";

export const projectFormView = {
    ...formViewWithHtmlExpander,
    Renderer: ProjectFormRenderer,
};

registry.category("views").add("project_form", projectFormView);

CASE 2 : extending the Controller

Example in module mass_mailing > mailing_contact_view_list.js


/** @odoo-module **/

import { ListController } from "@web/views/list/list_controller";
import { listView } from '@web/views/list/list_view';
import { registry } from '@web/core/registry';
import { useService } from "@web/core/utils/hooks";

/**
 * List view for the <mailing.contact> model.
 *
 * Add an import button to open the wizard <mailing.contact.import>. This wizard
 * allows the user to import contacts line by line.
 */
export class MailingContactListController extends ListController {
    async setup() {
        super.setup();
        this.actionService = useService("action");
    }

    onImport() {
        const context = this.props.context;
        const actionParams = { additionalContext: context };
        if (!context.default_mailing_list_ids && context.active_model === 'mailing.list' && context.active_ids) {
            actionParams.additionalContext.default_mailing_list_ids = context.active_ids;
        }
        this.actionService.doAction('mass_mailing.mailing_contact_import_action', actionParams);
    }
};

registry.category('views').add('mailing_contact_list', {
    ...listView,
    Controller: MailingContactListController,
    buttonTemplate: 'MailingContactListView.buttons',
}); 

CASE 3: using a static/xml template

Example Emojis in mail (odoo/addons/mail/static/src/js /emojis_dropdown.js)

/** @odoo-module **/

import emojis from '@mail/js/emojis';

const { Component, useRef, onMounted } = owl;

export class EmojisDropdown extends Component {
    setup() {
        this.toggleRef = useRef('toggleRef');
        this.emojis = emojis;
        super.setup();
        onMounted(() => {
            new Dropdown(this.toggleRef.el, {
                  popperConfig: { placement: 'bottom-end', strategy: 'fixed' },
            });
        });
    }
};
EmojisDropdown.template = 'mail.EmojisDropdown';

CASE 4: mounting owl-component in any place using a DOM-selector (document.getElementById(...)):

Example: odoo/addons/mass_mailing/static/src/snippets/s_rating /options.js


/**
     * Allows to select a font awesome icon with media dialog.
     *
     * @see this.selectClass for parameters
     */
    customIcon: async function (previewMode, widgetValue, params) {
        const media = document.createElement('i');
        media.className = params.customActiveIcon === 'true' ? this.faClassActiveCustomIcons : this.faClassInactiveCustomIcons;
        const dialog = new ComponentWrapper(this, MediaDialogWrapper, {
            noImages: true,
            noDocuments: true,
            noVideos: true,
            media,
            save: icon => {
                const customClass = icon.className;
                const $activeIcons = this.$target.find('.s_rating_active_icons > i');
                const $inactiveIcons = this.$target.find('.s_rating_inactive_icons > i');
                const $icons = params.customActiveIcon === 'true' ? $activeIcons : $inactiveIcons;
                $icons.removeClass().addClass(customClass);
                this.faClassActiveCustomIcons = $activeIcons.length > 0 ? $activeIcons.attr('class') : customClass;
                this.faClassInactiveCustomIcons = $inactiveIcons.length > 0 ? $inactiveIcons.attr('class') : customClass;
                this.$target[0].dataset.activeCustomIcon = this.faClassActiveCustomIcons;
                this.$target[0].dataset.inactiveCustomIcon = this.faClassInactiveCustomIcons;
                this.$target[0].dataset.icon = 'custom';
                this.iconType = 'custom';
            }
        });
        dialog.mount(document.body);
    },

CASE 5 : Widget to display odoo field (odoo/addons/mrp/static/src/widgets/timer.js)

/** @odoo-module **/

import { registry } from "@web/core/registry";
import { useService } from "@web/core/utils/hooks";
import { parseFloatTime } from "@web/views/fields/parsers";
import { useInputField } from "@web/views/fields/input_field_hook";

const { Component, useState, onWillUpdateProps, onWillStart, onWillDestroy } = owl;

export class MrpTimer extends Component {
    setup() {
        this.orm = useService('orm');
        this.state = useState({
            // duration is expected to be given in minutes
            duration:
                this.props.value !== undefined ? this.props.value : this.props.record.data.duration,
        });
        useInputField({
            getValue: () => this.durationFormatted,
            refName: "numpadDecimal",
            parse: (v) => parseFloatTime(v),
        });

   
        onWillStart(async () => {
            if(this.props.ongoing === undefined && !this.props.record.model.useSampleModel && this.props.record.data.state == "progress") {
                const additionalDuration = await this.orm.call('mrp.workorder', 'get_working_duration', [this.props.record.resId]);
                this.state.duration += additionalDuration;
            }
            if (this.ongoing) {
                this._runTimer();
            }
        });
        

    _runTimer() {
        this.timer = setTimeout(() => {
            if (this.ongoing) {
                this.state.duration += 1 / 60;
                this._runTimer();
            }
        }, 1000);
    }
}

MrpTimer.supportedTypes = ["float"];
MrpTimer.template = "mrp.MrpTimer";

registry.category("fields").add("mrp_timer", MrpTimer);
registry.category("formatters").add("mrp_timer", formatMinutes);

Tutorial to create snippets:

Upvotes: 4

Related Questions