services
data_display
command
Odoo 19 services — command (core)
Live preview
Interactive
Source excerpt
web/static/src/core/commands/command_service.js
import { registry } from "@web/core/registry";
import { CommandPalette } from "./command_palette";
import { Component, EventBus } from "@odoo/owl";
/**
* @typedef {import("./command_palette").CommandPaletteConfig} CommandPaletteConfig
* @typedef {import("../hotkeys/hotkey_service").HotkeyOptions} HotkeyOptions
*/
/**
* @typedef {{
* name: string;
* action: ()=>(void | CommandPaletteConfig);
* category?: string;
* href?: string;
* className?: string;
* }} Command
*/
/**
* @typedef {{
* category?: string;
* isAvailable?: ()=>(boolean);
* global?: boolean;
* hotkey?: string;
* hotkeyOptions?: HotkeyOptions
* }} CommandOptions
*/
/**
* @typedef {Command & CommandOptions & {
* removeHotkey?: ()=>void;
* }} CommandRegistration
*/
const commandCategoryRegistry = registry.category("command_categories");
const commandProviderRegistry = registry.category("command_provider");
const commandSetupRegistry = registry.category("command_setup");
class DefaultFooter extends Component {
static template = "web.DefaultFooter";
static props = {
switchNamespace: { type: Function },
};
setup() {
this.elements = commandSetupRegistry
.getEntries()
.map((el) => ({ namespace: el[0], name: el[1].name }))
.filter((el) => el.name);
}
onClick(namespace) {
this.props.switchNamespace(namespace);
}
}
export const commandService = {
dependencies: ["dialog", "hotkey", "ui"],
start(env, { dialog, hotkey: hotkeyService, ui }) {
/** @type {Map<CommandRegistration>} */
const registeredCommands = new Map();
let nextToken = 0;
let isPaletteOpened = false;
const bus = new EventBus();
hotkeyService.add("control+k", openMainPalette, {
bypassEditableProtection: true,
global: true,
});
/**
* @param {CommandPaletteConfig} config command palette config merged with default config
* @param {Function} onClose called when the command palette is closed
* @returns the actual command palette config if the command palette is already open
*/
function openMainPalette(config = {}, onClose) {
const configByNamespace = {};
for (const provider of commandProviderRegistry.getAll()) {
const namespace = provider.namespace || "default";
if (!configByNamespace[namespace]) {
configByNamespace[namespace] = {
categories: [],
categoryNames: {},
};
}
}
for (const [category, el] of commandCategoryRegistry.getEntries()) {
const namespace = el.namespace || "default";
const name = el.name;
if (namespace in configByNamespace) {
configByNamespace[namespace].categories.push(category);
configByNamespace[namespace].categoryNames[category] = name;
}
}
for (const [
namespace,
{ emptyMessage, debounceDelay, placeholder },
] of commandSetupRegistry.getEntries()) {
if (namespace in configByNamespace) {
if (emptyMessage) {
configByNamespace[namespace].emptyMessage = emptyMessage;
}
if (debounceDelay !== undefined) {
configByNamespace[namespace].debounceDelay = debounceDelay;
}
if (placeholder) {
configByNamespace[namespace].placeholder = placeholder;
}
}
}
config = Object.assign(
{
configByNamespace,
FooterComponent: DefaultFooter,
providers: commandProviderRegistry.getAll(),
},
Registry / API
- Registry name
command- Category
services- Module
web- Slug
command- Nav group
data_display