In this tutorial, you're going to learn how to use and implement Bootstrap datetime picker using jQuery or Odoo default datetime picker using OWL Javascript Framework. Below are some options on how you can use datetime picker:
Browser Native Datetime Picker
jQuery Bootstrap Datetime Picker
Odoo Default Datetime Picker
Tempus Dominus Datetime Picker
Prepare the module
Let's preapre first a simple module using Odoo version 15. For all XML files, we need to add it under web.assets_qweb, and for js and styles, it will be under web.asset_backend. Then create the client action and the menu items under views folder. Then under the static, src, and components folder, create two files, datetime_pciker.js and datetime_pciker.xml.
The easiest way is using the browser native datetime picker since you don't need other assets like jQueary in order for this to work. It will work just like a normal input element by just defining the type as datetime-local. Using this type, you are able to set additional attributes like min and max date.
However, some browsers may not be compatible with this type. In addition, if you want to have a better design and funcitonalities, you need to use other open source libraries like jQeury Bootstrap Datetime Picker. You can also add additional setup like minDate.
jQuery Bootstrap Datetime Picker
Now let's use jQuery Datetime Picker from XDSoft.net Open . Since there's already working solution in codepen from Vlad Open , I will be using it as an example. For the HTML, copy the code under the template and make sure to add the closing tag to input elements. For the browser native datetime picker, this will work since it's using the browsers native date time picker. But for the jQuery Datetime picker, we still need to import some assets.
If you open the settings in codepen, it will show you all the assets for CSS and JS. But most of these are already present in Odoo under the web/static/lib folder. There's jQuery, Bootstrap, and Moment. This means we only need to add the jQuery datetime picker. But before we add it, let's import first the onWillStart life cycle method to load our asset and onMounted to initialize the date time picker from owl.hooks. Then import loadJS from web.ajax so that we don't need to manually import it in the document head.
Add the onWillStart method under setup, call loadJS, and add the link of jquery-datetimepicker. Then under the onMounted life cycle method, copy and paste the JS code since it's possible to merge jQuery in OWL Framework.
But this is still not enough if we can't use this date value right? We first need to check on how we can get the values. Since this is a jQuery, we can just add an on-change event and pass an anonymous function with e as a parameter. We can then use this value and pass it to the component state by importing useState from owl. Then under the setup method, create a new state date with the initial state as an empty string.
Then under on change event, update this.state.date is equal to e.target.value. But we can't use "this" variable in this event to access the component state since in jQuery, “this” pertains to the active element being clicked. To solve this, before we call any jQuery code, we need to initialize a new variable "self" which is equal to the current class component using "this". Then replace this with self.
To know, if the state is changing, import the useEffect method from web core utils hooks. Then under setup method, add the useEffect method by just having a console log to the state.date, which will work only if this.state.date has been changed.
Before we do the version which Odoo uses, we need to check the issues of this code. If you try other modules like CRM, and try to open some records with date field, it's overriding the existing date time picker of Odoo. So if you still want to use this version, we need to unload jQuery Datetime Picker script everytime this component will not be used
But it's not possible to unload the script if it's already added even if we remove this element on the document head. The only way is to reload the page. Let's do that by adding the onWillUnmount method, and selecting any script that has this src, and if there is, reload the page after 100 milliseconds just to give time to get the new route.
Now let's try the other version which is the one Odoo using. There are two versions used in Odoo 15, the first one is under web/static/src/core/datepickerOpen which is used under the search panel. The other one is under legacy/js/widgets/datepciker.jsOpen which is used in field widgets. Let's use the one that uses OWL since this is easier to implement.
Tempus Dominus Datetime Picker
If we check this component, Odoo uses a date picker using the Tempus Dominus Open date picker library. This is using jQuery, but in Odoo, they created a new module using OWL which will work since jQuery will work on OWL Framework. The template has also been modified with custom classes based on Odoo's custom design.
Luxon Datetime Library
Before we start implementing it, it's important to note that Odoo used Luxon Open Date and Time library. Since tempus dominus is using moment js by default, the values then coming from Luxon will be converted as a string. So if you want to use moment js in passing dates, it will throw an error.
Under our component, let's import both DatePicker and DateTimePciker from web/core/datepicker/datepicker and add them under the list of components. These two components require date prop so we need to import DateTime from luxon. Then we also need to comment jQuery codes to avoid date time picker version issues.
To pass date time from luxon, we need to create a new class field this.date is equal to DateTime.local().
Under our template, simply replace the input fields, add DatePicker and DateTimePicker components, and pass date props with the date class field we created.
To get the input values, we need to call t-on-datetime-changed which has been defined under onDateChange method in date picker component. So every time the date has been changed or when the bootstrap date picker has been closed, this trigger will be called and that's how we can access and get the value of the input field.
web/static/src/core/datepicker/datepicker.js
js
Show or Hide Code
/** * Called either when the input value has changed or when the boostrap * datepicker is closed. The actual "datetime-changed" emitted by the * component is only triggered if the date value has changed. * @param {Object} [params={}] * @param {boolean} [params.useStatic] */ onDateChange({ useStatic } = {}) { let date; try { date = this.parseValue(this.inputRef.el.value, this.getOptions(useStatic)); } catch (_err) { // Reset to default (= given) date. } if (!date || date.equals(this.date)) { this.updateInput(); } else { this.state.warning = date > DateTime.local(); this.trigger("datetime-changed", { date }); } }
Let's then pass our method onDateChanged, add it under our component, and access the date value as the parameter. In this object parameter, we particularly need the detail and date object. Since it's using the luxon date format, wecan get different formats using the toFormat function using these strings or use a pre-defined format easily under _macroTokenToFormatOpts
web/static/lib/luxon/luxon.js
js
Show or Hide Code
/** * Returns a string representation of this Duration formatted according to the specified format string. You may use these tokens: * * `S` for milliseconds * * `s` for seconds * * `m` for minutes * * `h` for hours * * `d` for days * * `M` for months * * `y` for years */ _proto.toFormat = function toFormat(fmt, opts) { //... }
Let's then update our date state by calling e.detail.date.toFormat and pass D as a short date and under onDateTimeChanged, add a custom format for the date and time.
You can also pass different props like min and max date. Let's try minDate, by adding a new class field that is equal to DateTime and using fromObject method, year as 2023, month, 11, and day 4, or just calling the same local method will also work fine.
Lastly, if something's missing with the Odoo date time picker component, you can simply go to tempus dominus documentation and copy the code samples. Let's try time only Open for the javascript part, do the same we did on the first example by adding it under the onMounted life cycle method for us to get the input values and pass them to the components state.
Read next article
Todo List App - OWL Javascript Framework Tutorial