Skip to Content
services data_display

sortable

Odoo 19 services — sortable (core)

Live preview Interactive
Source excerpt web/static/src/core/utils/sortable_service.js
import { registry } from "../registry";
import { useSortable } from "@web/core/utils/sortable";
import { throttleForAnimation } from "@web/core/utils/timing";
import { reactive } from "@odoo/owl";

/**
 * @typedef SortableServiceHookParams
 * @extends SortableParams
 * @property {{el: HTMLElement} | ReturnType<typeof import("@odoo/owl").useRef>} [ref] container of sortable
 * @property {string | Symbol} [sortableId] identifier when multiple sortable on the same container
 */

const DEFAULT_SORTABLE_ID = Symbol.for("defaultSortable");
export const sortableService = {
    start() {
        /**
         * Map to avoid to setup/enable twice or more time the same element
         * @type {Map<Element, Object>}
         */
        const boundElements = new Map();
        return {
            /**
             * @param {SortableServiceHookParams} hookParams
             */
            create: (hookParams) => {
                const element = hookParams.ref.el;
                const sortableId = hookParams.sortableId ?? DEFAULT_SORTABLE_ID;
                if (boundElements.has(element)) {
                    const boundElement = boundElements.get(element);
                    if (sortableId in boundElement) {
                        return {
                            enable() {
                                return {
                                    cleanup: boundElement[sortableId],
                                };
                            },
                        };
                    }
                }
                /**
                 * @type {Map<Function, function():Array>}
                 */
                const setupFunctions = new Map();
                /**
                 * @type {Array<Function>}
                 */
                const cleanupFunctions = [];

                const cleanup = () => {
                    const boundElement = boundElements.get(element);
                    if (sortableId in boundElement) {
                        delete boundElement[sortableId];
                        if (boundElement.length === 0) {
                            boundElements.delete(element);
                        }
                    }
                    cleanupFunctions.forEach((fn) => fn());
                };

                // Setup hookParam
                const setupHooks = {
                    wrapState: reactive,
                    throttle: throttleForAnimation,
                    addListener: (el, type, listener) => {
                        el.addEventListener(type, listener);
                        cleanupFunctions.push(() => el.removeEventListener(type, listener));
                    },
                    setup: (setupFn, dependenciesFn) => setupFunctions.set(setupFn, dependenciesFn),
                    teardown: (fn) => cleanupFunctions.push(fn),
                };

                useSortable({ setupHooks, ...hookParams });

                const boundElement = boundElements.get(element);
                if (boundElement) {
                    boundElement[sortableId] = cleanup;
                } else {
                    boundElements.set(element, { [sortableId]: cleanup });
                }

                return {
                    enable() {
                        setupFunctions.forEach((dependenciesFn, setupFn) =>
                            setupFn(...dependenciesFn())
                        );
                        return {
                            cleanup,
                        };
                    },
                };
            },
        };
    },
};

registry.category("services").add("sortable", sortableService);
Registry / API
Registry name
sortable
Category
services
Module
web
Slug
sortable
Nav group
data_display