odoo18/addons/web/static/src/views/view_hook.js

142 lines
4.8 KiB
JavaScript

import { registry } from "@web/core/registry";
import { useService } from "@web/core/utils/hooks";
import { browser } from "@web/core/browser/browser";
import { evaluateExpr } from "@web/core/py_js/py";
import { useComponent, useEffect, xml } from "@odoo/owl";
export function useViewArch(arch, params = {}) {
const CATEGORY = "__processed_archs__";
arch = arch.trim();
const processedRegistry = registry.category(CATEGORY);
let processedArch;
if (!processedRegistry.contains(arch)) {
processedArch = {};
processedRegistry.add(arch, processedArch);
} else {
processedArch = processedRegistry.get(arch);
}
const { compile, extract } = params;
if (!("template" in processedArch) && compile) {
processedArch.template = xml`${compile(arch)}`;
}
if (!("extracted" in processedArch) && extract) {
processedArch.extracted = extract(arch);
}
return processedArch;
}
/**
* Allows for a component (usually a View component) to handle links with
* attribute type="action". This is used to support onboarding banners and content helpers.
*
* A @web/core/concurrency:KeepLast must be present in the owl environment to allow coordinating
* between clicks. (env.keepLast)
*
* Note that this is similar but quite different from action buttons, since action links
* are not dynamic according to the record.
* @param {Object} params
* @param {String} params.resModel The default resModel to which actions will apply
* @param {Function} [params.reload] The function to execute to reload, if a button has data-reload-on-close
*/
export function useActionLinks({ resModel, reload }) {
const component = useComponent();
const keepLast = component.env.keepLast;
const orm = useService("orm");
const { doAction } = useService("action");
async function handler(ev) {
ev.preventDefault();
ev.stopPropagation();
let target = ev.target;
if (target.tagName !== "A") {
target = target.closest("a");
}
const data = target.dataset;
if (data.method !== undefined && data.model !== undefined) {
const options = {};
if (data.reloadOnClose) {
options.onClose = reload || (() => component.render());
}
const action = await keepLast.add(orm.call(data.model, data.method));
if (action !== undefined) {
keepLast.add(Promise.resolve(doAction(action, options)));
}
} else if (target.getAttribute("name")) {
const options = {};
if (data.context) {
options.additionalContext = evaluateExpr(data.context);
}
keepLast.add(doAction(target.getAttribute("name"), options));
} else {
let views;
const resId = data.resid ? parseInt(data.resid, 10) : null;
if (data.views) {
views = evaluateExpr(data.views);
} else {
views = resId
? [[false, "form"]]
: [
[false, "list"],
[false, "form"],
];
}
const action = {
name: target.getAttribute("title") || target.textContent.trim(),
type: "ir.actions.act_window",
res_model: data.model || resModel,
target: "current",
views,
domain: data.domain ? evaluateExpr(data.domain) : [],
};
if (resId) {
action.res_id = resId;
}
const options = {};
if (data.context) {
options.additionalContext = evaluateExpr(data.context);
}
keepLast.add(doAction(action, options));
}
}
return (ev) => {
const a = ev.target.closest(`a[type="action"]`);
if (a && ev.currentTarget.contains(a)) {
handler(ev);
}
};
}
export function useBounceButton(containerRef, shouldBounce) {
let timeout;
const ui = useService("ui");
useEffect(
(containerEl) => {
if (!containerEl) {
return;
}
const handler = (ev) => {
const button = ui.activeElement.querySelector("[data-bounce-button]");
if (button && shouldBounce(ev.target)) {
button.classList.add("o_catch_attention");
browser.clearTimeout(timeout);
timeout = browser.setTimeout(() => {
button.classList.remove("o_catch_attention");
}, 400);
}
};
containerEl.addEventListener("click", handler);
return () => containerEl.removeEventListener("click", handler);
},
() => [containerRef.el]
);
}