Skip to Content
services inputs

Datetime Picker

Odoo 19 services — Datetime Picker (core)

Live preview Interactive
Source excerpt web/static/src/core/datetime/datetimepicker_service.js
import { markRaw, onPatched, onWillRender, reactive, useEffect, useRef } from "@odoo/owl";
import { areDatesEqual, formatDate, formatDateTime, parseDate, parseDateTime } from "../l10n/dates";
import { makePopover } from "../popover/popover_hook";
import { registry } from "../registry";
import { ensureArray, zip, zipWith } from "../utils/arrays";
import { shallowEqual } from "../utils/objects";
import { DateTimePicker } from "./datetime_picker";
import { DateTimePickerPopover } from "./datetime_picker_popover";

/**
 * @typedef {luxon["DateTime"]["prototype"]} DateTime
 *
 * @typedef {import("./datetime_picker").DateTimePickerProps} DateTimePickerProps
 * @typedef {import("../popover/popover_hook").PopoverHookReturnType} PopoverHookReturnType
 * @typedef {import("../popover/popover_service").PopoverServiceAddOptions} PopoverServiceAddOptions
 * @typedef {import("@odoo/owl").Component} Component
 * @typedef {ReturnType<typeof import("@odoo/owl").useRef>} OwlRef
 *
 * @typedef {{
 *  createPopover?: (component: Component, options: PopoverServiceAddOptions) => PopoverHookReturnType;
 *  ensureVisibility?: () => boolean;
 *  format?: string;
 *  getInputs?: () => HTMLElement[];
 *  onApply?: (value: DateTimePickerProps["value"]) => any;
 *  onChange?: (value: DateTimePickerProps["value"]) => any;
 *  onClose?: () => any;
 *  pickerProps?: DateTimePickerProps;
 *  showSeconds?: boolean;
 *  target: HTMLElement | string;
 *  useOwlHooks?: boolean;
 * }} DateTimePickerServiceParams
 */

/**
 * @template {object} T
 * @param {T} obj
 */
function markValuesRaw(obj) {
    /** @type {T} */
    const copy = {};
    for (const [key, value] of Object.entries(obj)) {
        if (value && typeof value === "object") {
            copy[key] = markRaw(value);
        } else {
            copy[key] = value;
        }
    }
    return copy;
}

/**
 * @param {Record<string, any>} props
 */
function stringifyProps(props) {
    const copy = {};
    for (const [key, value] of Object.entries(props)) {
        copy[key] = JSON.stringify(value);
    }
    return copy;
}

const FOCUS_CLASSNAME = "text-primary";

const formatters = {
    date: formatDate,
    datetime: formatDateTime,
};
const listenedElements = new WeakSet();
const parsers = {
    date: parseDate,
    datetime: parseDateTime,
};

export const datetimePickerService = {
    dependencies: ["popover"],
    start(env, { popover: popoverService }) {
        const dateTimePickerList = new Set();
        return {
            /**
             * @param {DateTimePickerServiceParams} [params]
             */
            create(params = {}) {
                /**
                 * Wrapper method on the "onApply" callback to only call it when the
                 * value has changed, and set other internal variables accordingly.
                 */
                async function apply() {
                    const { value } = pickerProps;
                    const stringValue = JSON.stringify(value);
                    if (
                        stringValue === lastAppliedStringValue ||
                        stringValue === stringProps.value
                    ) {
                        return;
                    }

                    lastAppliedStringValue = stringValue;
                    inputsChanged = ensureArray(value).map(() => false);

                    await params.onApply?.(value);

                    stringProps.value = stringValue;
                }

                function enable() {
                    for (const [el, value] of zip(
                        getInputs(),
                        ensureArray(pickerProps.value),
                        true
                    )) {
                        updateInput(el, value);
                        if (el && !el.disabled && !el.readOnly && !listenedElements.has(el)) {
                            listenedElements.add(el);
                            el.addEventListener("change", onInputChange);
                            el.addEventListener("click", onInputClick);
                            el.addEventListener("focus", onInputFocus);
                            el.addEventListener("keydown", onInputKeydown);
                        }
                    }
                    const calendarIconGroupEl = getInput(0)?.parentElement.querySelector(
Registry / API
Registry name
datetime_picker
Category
services
Module
web
Slug
datetime-picker
Nav group
inputs