@timum/booking
timum booking
Introduction
timum BookingJs is a fully customizable appointment booking frontend app, intended to be integrated into every web page or web app or mobile app. It is ready to use out-of-the-box. And it provides the ability to customize it. This documentation guides you through all the customization capabilities.
Content
Features
- Real time availability and booking
- Booking requests for public appointments - approval required
- Multiple participant appointments
- Visitor legitimation - only invited visitors see the booking widget on public pages, listings, exposés
- Visitor identification - via customer ID from CRM
- Show/hide fully booked appointments option
- Prebooking period option - prevents booking if appointment is too soon from now
- Double booking prevention
- Cancelling appointment by customer with proper authentication
- Multiple instances on the same page - display multiple booking widgets without conflicts
- Instant loading skeleton with zero extra requests - dependency-free skeleton themed to your MUI theme loads instantly while booking.js downloads from CDN, eliminates white-space delay
- Multi-resource mode for CondensedView - display overlapping timeslots from multiple resources side-by-side with color-coded differentiation
- Fully customizable look and feel with mui theming (available with professional plans). This includes font customization; see Advanced Customisation → Font loading for details.
- Customizable wording across languages (availabe with professional plans)
- Dynamic definition of input fields with localized labels and validation (available with professional plans)
- Therefore, ability to request additional information from customers during booking process
- Support for multiple languages (English, Italian, Spanish, French and German included) - add your own language
- Callbacks for custom code execution and event response (e.g. booking created, appointments loaded, booking canceled)
- Responsive layout handling for embedded views - EyeCandy and PureListView automatically adapt to available space and viewport constraints
How to Integrate
These are some minimal examples. BookingJs is highly customizable. An overview over all configuration options can be found in Advanced Customisation
In a Script Tag
Add the following code to your webpage:
<div id="bookingjs" style="margin: 32px"></div>
<script type="module">
import * as timum from 'https://cdn.timum.de/bookingjs/1/booking.js';
timum.init({ ref: 'booking-widget-demo-resource@timum' });
</script>
A working fiddle can be found here.
Alternatively, you can add ref to your url as a parameter. This allows you to omit ref in the javascript portion. Like this:
https://your.website.nic?ref=<enter resource reference here>
<div id="bookingjs" style="margin: 32px"></div>
<script type="module">
import * as timum from 'https://cdn.timum.de/bookingjs/1/booking.js';
timum.init(); //<- no need for the reference here
</script>
As ESM import
- Add timum booking to your project with
yarn add @timum/bookingor use npm withnpm install @timum/booking - Add
<div id="bookingjs" style="margin: 32px"></div>where you want timum to be displayed (the margin is just a suggestion, not mandatory). - Add the following code to one of your .js files:
import { init } from '@timum/booking';
init({ ref: 'booking-widget-demo-resource@timum' }); // <- the ref used here is just an example. Use a reference to one of your own resources.
A example project can be found here
As React component import
- Add timum booking to your project with
yarn add @timum/bookingor use npm withnpm install @timum/booking - Add the following code anywhere in your jsx:
import { TimumBooking } from '@timum/booking';
// other code
<TimumBooking
appConfig={{
ref: 'booking-widget-demo-resource@timum',
// other options
}}
/>;
// other code
Multiple Instances
TimumBooking fully supports displaying multiple booking widgets on the same page. Each instance is automatically assigned a unique iframe ID and maintains its own isolated configuration, preventing any conflicts between instances. This is particularly useful when you need to display booking options for different resources or with different configurations on the same page.
import { TimumBooking } from '@timum/booking';
// Display multiple booking widgets for different resources
<div>
<h2>Resource A Bookings</h2>
<TimumBooking
appConfig={{
ref: 'resource-a@timum',
height: '500px',
}}
/>
<h2>Resource B Bookings</h2>
<TimumBooking
appConfig={{
ref: 'resource-b@timum',
height: '500px',
}}
/>
</div>;
Each instance:
- Gets a unique iframe ID (e.g.,
timum_booking_iframe_0,timum_booking_iframe_1) - Maintains its own configuration separately from other instances
- Can have different props, themes, and configurations
- Receives configuration updates and postMessages independently
- Is properly cleaned up when unmounted, preventing memory leaks
Entity Referencing
As shown in How to Integrate, there are two ways in which to provide bookingjs with the necessary references to retrieve public appointments from the backend.
Additionally, there are three different kinds of references you can provide, each having their own implications concerning the delivered appointments or whether the booking frontend is shown at all.
Channel Reference
Resource channels are entities in Timum, each storing a configuration that changes the booking experience for customers. Each individual resource has four channels that users can distribute to their customers through a link. To use these channels in an embed scenario, provide a channel reference in the ref property (either in the URL or as part of the config). In this case, nothing else is required.
Resource Reference
A reference that refers to a plain resource, where the channel is unknown. The default channel (and its corresponding configuration) is used instead.
Resource Reference with ChannelKey
If you don't have a channel reference but still want to use a different channel than the default one, you can provide a combination of resource reference and channelKey to uniquely identify the channel (and resource) to be used.
The following table gives an overview of the different permutations and how bookingjs behaves when encountering each of them:
| url RESref OR CHref | timum.init RESref OR CHref | timum.init chKey | show OR hide |
|---|---|---|---|
| - | - | ignored | HIDE |
| ResourceReference | ignored | YES | show widget for channelKey |
| ResourceReference | ignored | - | show default Channel |
| - | ResourceReference | - | show default Channel |
| - | ResourceReference | YES | show widget for channelKey |
| ChannelReference | ignored | ignored | show Channel |
| - | ChannelReference | ignored | show Channel |
- RESref: short for resource reference.
- CHref: short for channel reference.
Example
First row: If neither a resource reference nor a channel reference is provided, not in the URL nor in the config object, any given channel key is ignored, and bookingjs is hidden.
Third row: If a resource reference is given in the URL and in the config object, and no channelKey is provided, the ref in the URL takes priority. Since it is a resource reference and no channel key is provided, the default channel is used.
Fifth row: If a resource reference and a
channelKeyare provided in the config object, and there is nothing in the URL, the config object is used. BookingJs gets displayed using the channel uniquely identified by the resource reference and thechannelKey.
Customer pre-identification
BookingJs can identify customers automatically if configured correctly. This means they don't need to input their personal data prior to booking.
Identification via pDataId
Detection method: Is there a valid pDataId paired with a supported pDataPlatform (platform) in parent url matching an entity (contact, customer) in given CRM? General behaviour: If there is, then visitor is pre identified. If not: provide standard booking dialog. See appConfig for more information about pData.
Identification via Logged-in User
Even customers can register at timum and become registered and logged-in users. If the booking system identifies a logged-in user, the booking dialog does not provide personal data input fields but informs the visitor that they are identified.
Note that if the customer is logged in to timum and there is a pData config/url param as well then the auth cookie takes priority.
Advanced usage
timum booking has a lot of customisation options.
The init accepts the following arguements in the order listed here. The following table gives a rough overview over each.
| Arguments | Description |
|---|---|
| appConfig | object, containing various options like callbacks, localiation, input fields etc. |
| muiTheme | object, mui theme allowing you to change the look and feel of timum. Requires a professional plan. |
| fcConfig | object, only used if you use full calendar as a booking frontend (settable in appConfig). |
muiTheme
timum uses mui components for all frontends. The muiTheme object allows global design changes.
See here for a general overview.
See here for theme related documentation.
When you open the file config.js in this example, you can find an elaborate custom configuration which includes a redesign of the standard timum theme.
Needs professional plan.
Fonts
You can set global or per-component fonts via the MUI theme (e.g., typography.fontFamily or component styleOverrides). BookingJS can also load the corresponding font files for you; see Font Loading for how to declare font resources in the theme and control the loading strategy. Requires a professional plan for custom theming.
Seamless / edge-to-edge embed (host owns the chrome)
By default the widget renders as a self-contained card: it applies its own
outer padding (muiTheme.layout.cardPadding) and paints its own surface with
muiTheme.palette.background.paper, so it reads as a distinct panel wherever you
drop it. That is the right default for most integrations.
If instead you want the widget to sit edge-to-edge inside a container you have already styled — so its surface is continuous with your surrounding card, with no seam and no double padding — opt into host-owned chrome.
Opting in: data-timum-chrome-padding
Set the data-timum-chrome-padding attribute on the mount container or any
ancestor of it (the widget looks it up with closest()). When present, the
widget paints its own scope transparent — no background and no padding — and
hands both responsibilities to you.
The contract: paint the container background.paper
When you take over the chrome, you must paint the mount container with the widget's window-surface colour:
container background === muiTheme.palette.background.paper
This is the same colour the widget uses for its internal cards and its sticky
date header (the <ListSubheader> in the list views). The widget's surface is
background.paper by contract; the page behind the widget is a different token
(background.page / background.default).
Do not paint the container
background.page/background.default. The widget's own paper surfaces — most visibly the sticky date header, which stays pinned while the list scrolls under it — will then be a shade lighter/darker than the container and read as incongruous "islands" floating on your page, which is exactly the seam a seamless embed is trying to remove.
Minimal example (inline init)
<!-- The wrapper owns padding AND the surface colour (= background.paper). -->
<div
data-timum-chrome-padding
style="padding: 24px; background: #ffffff; border-radius: 8px"
>
<div id="bookingjs"></div>
</div>
<script type="module">
import * as timum from 'https://cdn.timum.de/bookingjs/1/booking.js';
timum.init({
ref: 'booking-widget-demo-resource@timum',
// If you theme the widget, keep the wrapper background in sync with
// muiTheme.palette.background.paper.
});
</script>
Inline (init) vs iframe (TimumBooking / initialise)
Inline (
init, script-tag / ESM): the widget shares your document — there is no iframe. Its component styles are isolated in a Shadow DOM, so they never leak. In addition, an inline embed does not mirror MUI's global CssBaselinehtml/bodyreset into your document — that reset (page background, text colour, base font, and thescrollbar-color/::-webkit-scrollbarstyling carried on thehtmlrule) is confined to the widget. So an inline embed — including a seamless one withdata-timum-chrome-padding— will not repaint your page background, retype your text, or restyle your site's scrollbar. The seam marker (data-timum-chrome-padding) only governs the widget's own padding/surface; it does not grant the widget your<body>. The global reset is retained only for a document that explicitly opts in withdata-timum-owns-body(see below) — you should not set that on a third-party page.If you are pinned to an older BookingJS that predates this fix, the inline embed's CssBaseline did leak those
html/bodyrules onto the host page. The remedy is to update, or to use the iframe embed below, which never leaked.iframe (
<TimumBooking>/initialise): the widget is fully isolated in a same-origin iframe, so nothing reaches your document by construction. The widget stampsdata-timum-owns-bodyon its own iframe body (it legitimately owns that document), anddata-timum-chrome-paddingis not needed here — the iframe body is the widget's surface. The widget also auto-detects its own iframe by theabout:srcdocURL, so the marker is not required for the reset to be retained even when an older outer bundle (from npm) predates the marker logic. Third-party pages embedding the widget inline should still not setdata-timum-owns-bodythemselves.
fcConfig
FullCalendar can be set as one of the frontends in appConfig. See here for a full list of configuration options.
Note that since fullCalendar isn't mui based, it does not consider the muiTheme object.
There are also some options specific to bookingjs. In addition to the configuration options of fullcalendar the following options are also available (all of them are optional):
| Property | Description |
|---|---|
| largeView | string, Determines which view is displayed for screen widths greater 601px. Please refer to the fullcalendar docs linked above for an overview of applicable values. |
| smallView | string, Determines which view is displayed for screen widths smaller 601px. Please refer to the fullcalendar docs linked above for an overview of applicable values. |
| useCustomTimumCss | boolean, to increase responsiveness, timum overrides some fullcalendar css classes. Set this to false to gain total control over fullcalendar's css.
|
appConfig
This object's options directly affect timum's behaviour or allow you to react to it.
| Property | Description |
|---|---|
| ref | string/array, Reference of the resource to show the appointments of. Either this or tslRefs (see below) must be defined. Everything else is optional. Can also be a url parameter. You can also provide a list of string references. Booking.js will then allow your customer to choose for which resource appointments should be displayed. |
| tslRefs | string/array, Reference(s) of specific appointments. For when you only want to share a selection of a resource's appointments. Either this or refs (see above) must be defined. Everything else is optional. Can also be a url parameter. You can also provide a list of string references. Booking.js will then load all appointments and corresponding resource information. If they belong to different resources, then bookingjs allows your customer to choose for which resource appointments should be displayed. Note: If this and ref are defined in conjunction: The following behaviour is not yet set in stone and may change in the future. |
| prdRefs | string/array, Reference(s) of specific products. For when you only want your customer to choose from a subset of your otherwise fully available list of active products.
Note: If this and |
| allResourcesOption | boolean, This option determines whether the resource selection screen presents an "All" option for customers. When this flag is true, customers can choose to see and book from all available appointments across different resources in a single interface. If the customer selects the "All" option, the system displays all bookable appointments for each resource that match their selected time and product. To ensure good performance, the system will filter out duplicate appointments. For instance, if there are multiple appointments that start at the same time and offer the same service, only one of these will be shown, until booked at which point the next appointment in line will be loaded and displayed. This feature is particularly useful when it's more important for customers to secure an appointment rather than choosing a specific resource. It allows for greater flexibility and ease in booking, especially during peak times or when specific resources may be limited. |
| platform | string, A fully qualified reference (id@someUuid@platform) can be unwieldy especially if used as a url param. This prop allows you to hardcode the platform part.
Note that this prop is ignored if ref is both:
|
| prvUuid | string, A fully qualified reference (id@someUuid@platform) can be unwieldy especially if used as a url param. This prop allows you to hardcode the someUuid part. This part is only sometimes necessary to uniquely identify a resource in timum. Note that this prop is ignored if ref is both:
|
| height | string, Height of timum on your page. `500px` is standard. |
| host | string, to which server requests are send. Possible values are https://www.timum.de (production server) or https://staging.timum.de (test server) |
| load | string, controls when booking.js is fetched inside the iframe. Possible values: 'eager' - fetch immediately, the loading spinner paints instantly while the bundle downloads, or 'idle' - hold the fetch until the host page has finished loading (its load event), then until the browser is idle, so the heavy download never competes with the host page's own critical resources - protecting the host's Core Web Vitals without requiring visitor interaction. |
| allowCloseOnBooking | boolean, whether the confirmation view is closeable once the booking was successfull. |
| allowCloseOnCancel | boolean, whether the cancel view is closeable once it has been opened. If false, it only closes after previous appointment was cancelled. |
| constrainDialogsToContainer | boolean, controls where dialogs from BookingJs are rendered. When enabled, all dialogs are confined to the BookingJs' designated container and are clipped/positioned within its bounds, behaving as part of it.
When disabled, dialogs are rendered at the page level and overlay the entire site, behaving like native site modals.
Note: this setting is ignored for small screens |
| hideTimumFooter | boolean, whether 'powered by timum' can be hidden or not. Needs professional plan. |
| hiddenForAnonymous | boolean, whether bookingjs should hide itself when no customer identifying pData props can be read from the config or the url. Useful for when you only want explicitly invited customers to see the booking frontend. |
| sendCustomValuesInMessage | boolean, whether values of custom fields are concatenated and comma-separated into a single string which is then sent to the backend as message. If field message is defined its input is concatenated to the generated string as well. |
| channelKey | string, timum has, as of now, 4 different channels through which you can share your resource's appointments. You can find them in the timum frontend under <resource name> -> Calendar Sharing (Terminbuchung freigeben). Every channel has its own settings allowing you to control whom of your customers can see certain appointments, whether they book directly or create a request first and other settings. Valid values for this attribute are: - RESOURCE_PUBLIC referring to "Public Booking Link" (Öffentlicher Buchungs-Link) - RESOURCE_EXCLUSIVE referring to "Exclusive Booking Access" (Exklusiver Buchungs-Zugang) - RESOURCE_REFERENCE referring to "Embedded booking calendars" (Eingebettete Buchungskalender) - CALENDAR_PUBLIC referring to "In all Website Plugin Views and your General Calendar" (In Website-Plugin Ansichten sowie Ihrem Gesamtkalender) RESOURCE_PUBLIC is the default, used if you specify nothing else. Can also be a url parameter. |
| calendarFrontend | string, one of
fullCalendar, detailsFullCalendar (these are what `init`'s 3rd parameter is for),
pureListView, detailsListView,
monthView,
detailsMonthView,
condensedView,
detailsCondensedView
Note: the
|
| calendarFrontendOptions | object, Contains view-specific configuration options. Currently supports:
condensedView (object):
|
| culture | string, The localisation to use. If not specified the browser language is used. Can also be a url parameter. |
| pData | object, Data indentifying the customer, so that they don't have to input their data again. Works in conjunction with timum and select, supported plaforms like OnOffice, Immosolve etc. Properties: { platform: string, personId: string }
Can also be a url parameter. -> the params are named differently though: pDataPlatform, pDataId and pDataIdPropName which hold the name of the platform (e.g 'onoffice') the id value (e.g 27) and the name of the id property within the chosen platform: (e.g personId) Note: pData is ignored in favor of a timum auth cookie. Use incognito mode, another browser or sign out to circumvent this. |
| callbacks | object, may contain various functions which allow to to run custom code in reaction to certain events. See below for a guide on how to do this. |
| postMessageTarget |
string, URL of the parent site that receives callback events via the postMessage API. This property enables all callback events to be sent using the postMessage() method, which is particularly useful when using bookingjs within an iframe, as it restricts access to the parent site's JavaScript code.
The event.data object includes all parameters typically passed to callback functions, along with an origin field (always "bookingjs") and a type field that indicates the event's name. This type matches the name of the callback function you would use in a non-iframe scenario. Refer to the guide on callback functions for specifics on available event names.
Note: Setting this property overrides the
|
| fields | object, You can customise what information is demanded of your customers prior to booking. timum requires certain fields to work and has some optional fields it can parse. Fields other than those supported by timum can be evaluated in a callback (see the callback guide below for further info). All fields (yours too!) support localization and input validation.
Needs professional plan. |
| localization | object. Contains all localization variables and their standard texts. timum nativly supports English, German, French, Spanish and Italian. Use this to override the standard text and/or add translations for e.g your custom field labels and input validations (see the localisation guide for more info.)
Needs professional plan. |
Font Loading
BookingJS supports two font-loading strategies, so you can either reuse an existing remote CSS (e.g., Google Fonts) or reference font files directly (no extra CSS round-trip). The widget detects your font declarations inside the MUI theme and loads fonts accordingly. This complements theming (muiTheme) and lets you apply custom fonts across the UI. Requires a professional plan for custom theming. See the theming entry points described under Advanced usage (init accepts appConfig, muiTheme, fcConfig) and the integration methods in How to Integrate (Script Tag / ESM / React) .
Where to declare fonts in the theme
- Inline faces: add fontFace (single) or fontFaces (array) anywhere in your theme object (e.g., top-level, typography, or component defaultProps/styleOverrides).
- Link CSS: add fontSource (string or string[]) anywhere in your theme. The widget will inject for each unique URL (loading duplicates is prevented).
Applying the font
- Set typography.fontFamily to your desired stack, or override per component (muiTheme → components.*.styleOverrides.root.fontFamily). BookingJS will load the faces you declared and use them if referenced in the theme (subject to your professional plan).
Examples
- Inline variable font (preferred for performance)
// passed as the "muiTheme" argument to init
<div id="bookingjs" style="margin: 32px"></div>
<script type="module">
import * as timum from 'https://cdn.timum.de/bookingjs/1/booking.js';
timum.init(
{ ref: 'booking-widget-demo-resource@timum' }, // appConfig
{
// other theme props...
typography: {
fontFamily:
'"My Font", Roboto, Helvetica, Arial',
// BookingJS will load these files directly via @font-face rules
fontFaces: [
{
family: 'My Font',
style: 'normal',
weight: '100 900', // variable axis range or single value like '400'
display: 'swap',
src: [
{ local: 'Inter' },
{ url: '/fonts/inter-var.woff2', format: 'woff2' },
],
},
{
family: 'My Font',
style: 'italic',
weight: '100 900',
display: 'swap',
src: [
{ local: 'Inter Italic' },
{ url: '/fonts/inter-italic-var.woff2', format: 'woff2' },
],
},
],
},
// other theme props...
}
);
</script>
- Link-based CSS (reuse existing CSS bundles)
// passed as the "muiTheme" argument to init
<div id="bookingjs" style="margin: 32px"></div>
<script type="module">
import * as timum from 'https://cdn.timum.de/bookingjs/1/booking.js';
timum.init(
{ ref: 'booking-widget-demo-resource@timum' }, // appConfig
{
// other theme props...
typography: {
fontFamily:
'"Roboto", "Helvetica", "Arial", sans-serif',
// BookingJS will inject <link rel="stylesheet"> for these URLs
// Note: this is just an example. We host our fonts on our own servers
fontSource: [
'https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;500;700&display=swap',
],
},
// other theme props...
}
);
</script>
Notes and best practices
- Use WOFF2 where possible (smaller, widely supported).
- Prefer variable fonts (weight: "100 900") instead of many static weights.
- Use display: 'swap' to avoid FOIT.
- If fonts are hosted on a different domain, ensure proper CORS headers so the browser can load them.
- You can still use per-component overrides to mix families (e.g., Sora for Buttons only) by setting components.MuiButton.styleOverrides.root.fontFamily; just ensure the face is declared under fontFaces or provided via fontSource as shown above.
How to use callbacks
The callbacks object may contain any of the following functions.
Booking related
openedBookingPageclosedBookingPage,createBookingStartedcreateBookingSuccessfulcreateBookingFailed
All Booking related callbacks receive a single obejct as argument containing the following properties:
- a
timeslotlooking like this:{ start: luxon Datetime object. Internal state. "2023-01-27T09:05:00.000Z", end: luxon Datetime object. Internal state. "2023-01-27T09:35:00.000Z", timeslot_uuid: uuid of the booked timeslot. e.g. "82ec5220-9d55-11ed-8617-e4a7a0ca32e8", product_uuid: uuid of the booked product e.g. "92867f70-4836-11e5-bc04-021a52c25043", product_name: string. e.g. "Meeting", resource_name: string. e.g. "Booking Widget DEMO", capacity: number e.g. 1, capacity_left: number e.g. 1, kind: either "models.Bookable" or "models.LotAppointment", untouchedStart: ISO String e.g: "2023-01-27T09:05:00+01:00" as the server sent it, untouchedEnd: ISO String e.g: "2023-01-27T09:35:00+01:00" as the server sent it } - a
dataobject looking like this:
{
firstName: 'string',
lastName: 'string',
email: 'string',
agbs: 'bool'
// plus whatever optional and/or custom fields you yourself have defined.
// -> so the makeup of this object changes depending on your settings.
}
Additionally, createBookingFailed and createBookingSuccessful also have a RTKQ response object in their respective argument. See here for a reference.
Cancelation Related
openedCancelPageclosedCancelPagecancelationStartedcancelationSuccessfulcancelationFailed
All cancelation related callbacks receive a single obejct as argument containing the same properties as described in Booking related
Additionally, cancelationSuccessful and cancelationFailed also have a RTKQ response object in their respecive argument. See here for a reference.
Dialog related
In addition to the dialog related callbacks already mentioned in Cancelation Related and Booking related, there are callbacks for all other dialogs as well.
openedProductSelectionopenedResourceSelectionopenedConfirmationPageclosedProductSelectionclosedResourceSelectionclosedConfirmationPage
These do not receive any parameters.
Data Fetching Related
In order to display public appointments, resource and calendar information timum sends several requests.
fetchingPublicDataSucceededGets passed in a single object looking like this:{ contact: { name: 'string', email: 'string', mobile: 'string', phone: 'string', }, resource: { uuid: 'string', name: 'string', //<- 80 chars, max length of names in timum description: 'string', msgHelpText: 'string', type: 'string', url: 'string', imgUrl: 'string', }, provider: { name: 'string', description: 'string', isThemingAllowed: 'boolean', isLocalisationAllowed: 'boolean', areCustomFieldsAllowed: 'boolean' }, channel: { bookingProcess: 'string' // One of 'IMMEDIATE' or 'REQUESTED', }, }fetchingPublicDataFailedreceives a single RTKQ error object. See here for a reference.fetchingProductsSucceededGets passed in a single object looking like this:{ products: [ { description: string, minDuration: number, name: string, uuid, }, //... ]; }fetchingProductsFailedreceives a single RTKQ error object. See here for a reference.fetchingBookablesSucceededGets passed in a single object looking like this:{ 2023-02-06: [ //<- this obviously changes depending on when appointments are available. 0: { timeslot_uuid: uuid of the booked timeslot, product_uuid: uuid of the booked product, product_name: string, resource_name: string, capacity: number, capacity_left: number, kind: one of "models.Bookable" or "models.LotAppointment", start: ISO String e.g: "2023-02-06T09:05:00+01:00", end: ISO String e.g: "2023-02-06T09:35:00+01:00" }, //... more appointments ... ], //... more dates ... }fetchingBookablesFailedreceives a single RTKQ error object. See here for a reference.
Localisation
timum booking has the ability to change all of its text. It comes with English, Italian, Spanish, French and German pre-installed, but you can add additional translations as well. You can also change the text for these languages to your preference. You don't have to redefine the entire localization object; you can just add or change the specific texts you want. Any missing texts will be taken from the default. The code snippet provided shows the complete localization object and the standard text for both English and German. For each language, it includes text for different parts of the booking process such as product selection, appointment booking, appointment request, appointment cancellation, form fields, and more.
localization: {
de: {
resource_selection_headline: 'Resource wählen',
product_selection_headline: 'Terminart wählen',
booked_successfully_header: 'Termin gebucht',
booked_successfully_message:
'Sie erhalten eine E-Mail mit den Termindetails an {{mail}}',
requested_successfully_header: 'Termin angefragt',
requested_successfully_message:
'Sie erhalten eine E-Mail mit den Termindetails an {{mail}}. Sie werden unter der gleichen Adresse benachrichtigt, sobald Ihre Anfrage bearbeitet wurde.',
submit_button_book: 'Buchen',
submit_button_request: 'Verbindlich Anfragen',
noEventsMessage:
'Zur Zeit sind leider keine buchbaren Termine verfügbar.',
appoinment_at_capacity: 'belegt',
add_to_calendar_btn: 'Zu Kalender hinzufügen',
until_reservation_expiration:
'{{expiration}} bis zum Ablauf der Reservierung',
reservation_expired: 'Reservierung abgelaufen.',
identified_customer_hint:
'Sie wurden mit persönlichem Link eingeladen und können direkt Ihren Termin buchen.',
reservation_failed: {
title: 'Hohe Nachfrage',
mesage:
'Dieser Termin wurde gerade durch jemanden reserviert. Bitte wählen' +
'Sie einen anderen Termin. Oder schauen Sie später noch einmal, ob' +
'ein Termin frei wird.',
},
product_unavailable_for_resource_warning:
'Die gewählte Terminart wird hier nicht angeboten. Bitte wählen Sie eine andere.',
resource_switch_confirm: {
title: 'Diese Aktion verwirft Ihre Produktauswahl',
current_label: 'Aktuell',
next_label: 'Nach dem Wechsel',
next_value: 'Produkt wählen',
offered_at: 'Angeboten bei {{resources}}',
more_resources: '{{count}} weitere',
more_resources_plural: '{{count}} weitere',
cancel: 'Produkt behalten',
confirm: 'Dennoch fortfahren',
},
cancellation: {
cancelation_successfull_message: 'Termin erfolgreich abgesagt',
cancellable_appointment_highlight: 'Mein Termin',
submit_button_cancel: 'Absagen',
cancel_appointment_header: 'Ihr Termin',
message_label: 'Nachricht zur Terminabsage',
},
validation: {
field_required: 'Notwendig',
privacy_field_required:
'Sie müssen die Datenschutzbestimmungen akzeptieren bevor Sie buchen können.',
email_field_must_be_valid: 'Geben Sie eine valide E-Mail Adresse ein',
},
fields: { // field labels are markdown capable. That's why you can use basic html as well.
firstName: 'Vorname',
lastName: 'Nachname',
name: 'Name',
email: 'E-Mail',
mobile: 'Mobil',
message: 'Ihre Nachricht',
accept_timum_privacy:
'<a href="https://info.timum.de/datenschutz" target="_blank">Datenschutzbestimmungen</a> gelesen und akzeptiert',
},
},
// the same for english.
en: {
resource_selection_headline: 'Choose Resource',
product_selection_headline: 'Choose Product',
booked_successfully_header: 'Appoinment Booked',
booked_successfully_message:
'You will receive an email with appointment details to {{mail}}',
requested_successfully_header: 'Appointment Requested',
requested_successfully_message:
'You will receive an email with appointment details to {{mail}}. You will be notified at the same address once your request has been processed.',
submit_button_book: 'Book',
submit_button_request: 'Request',
noEventsMessage:
'Unfortunately, there are no bookable dates available at the moment.',
appoinment_at_capacity: 'fully booked',
add_to_calendar_btn: 'Add to Calendar',
until_reservation_expiration:
'{{expiration}} until reservation expiration.',
reservation_expired: 'Reservation expired.',
identified_customer_hint:
'You have been invited with a personal link and can book your appointment directly.',
reservation_failed: {
title: 'High Demand',
mesage:
'This appointment has just been reserved by someone. Please choose another appointment. Or check back later to see if an appointment becomes available.',
},
product_unavailable_for_resource_warning:
"The service you picked isn't offered here. Please choose another one.",
resource_switch_confirm: {
title: 'This action cancels your service selection',
current_label: 'Currently',
next_label: 'If you switch',
next_value: 'Choose a service',
offered_at: 'Offered at {{resources}}',
more_resources: '{{count}} more',
cancel: 'Keep my service',
confirm: 'Proceed anyway',
},
cancellation: {
cancelation_successfull_message: 'Appointment canceled sucessfully.',
cancellable_appointment_highlight: 'My Appointment',
submit_button_cancel: 'Cancel',
cancel_appointment_header: 'Your Appointment',
message_label: 'You may enter a reason here.',
},
validation: {
field_required: 'Required',
privacy_field_required:
'You must accept the privacy policy prior to booking.',
email_field_must_be_valid: 'Enter a valid email address',
},
fields: { // field labels are markdown capable. That's why you can use basic html as well.
firstName: 'First name',
lastName: 'Last name',
name: 'name',
email: 'E-mail',
mobile: 'Mobile',
message: 'Your Message',
accept_timum_privacy:
'<a href="https://info.timum.de/datenschutz" target="_blank">Privacy policy</a> read and accepted.',
},
},
},
Booking Form Fields
You can customize the information you request from customers before booking by defining fields. The minimum required fields for timum are firstName, lastName, email, and agbs. By default, timum also requests mobile and message fields, but you can add your own fields as well.
Fields, including custom ones, support validation and localization. Validation is realized using the yup library, which is included with timum. You can import it using the following code:
import { yup } from '@timum/booking';
Localization is achieved by using the title property in the field definition. You can specify a translation key and add the corresponding translation to the localization object. The same process can be applied to custom field validations.
The standard configuration can be overridden except for the aforementioned, required fields. The following is the standard configuration:
fields: {
firstName: {
index: 0,
title: 'fields.firstName',
validation: yup.string().required('validation.field_required'), // <- compare with key in localisation
},
lastName: {
index: 1,
title: 'fields.lastName',
validation: yup.string().required('validation.field_required'),
},
email: {
index: 2,
title: 'fields.email',
format: 'email',
type: 'text',
validation: yup
.string()
.email('validation.email_field_must_be_valid')
.required('validation.field_required'),
},
mobile: {
index: 3,
title: 'fields.mobile',
type: 'phoneNumber',
isRequired: false,
defaultCountry: 'DE',
preferredCountries: ['DE', 'CH', 'AT'],
// validation: is in built and ignored
},
message: {
index: 7,
title: 'fields.message',
type: 'textarea',
validation: yup
.string()
.max(600),
limit: 600,
},
agbs: {
index: 8,
title: 'fields.accept_timum_privacy',
type: 'checkbox',
validation: yup
.boolean()
.required('validation.field_required')
.test(
'privacyAccepted',
'validation.privacy_field_required',
(value) => value === true,
),
},
locale: {
index: 9,
preventRendering: true,
},
},
Custom Fields
If the values retrieved via custom fields need to be transferred to timum backend as additional booking information, set the parameter sendCustomValuesInMessage. With this, the values of custom fields are attached to the customer message. They will be visible to the customer as well in booking confirmation mailings.
sendCustomValuesInMessage: true, // necessary to transmit custom field values as part of the customer message
Anatomy of a custom booking form field:
<fieldName> : {
index: number, index defines the order in which the fields get displayed.
(If you want to reorder the default fields or
want to inject a custom field between them,
you must redefine them as custom fields and manually change their indexes.)
title: string or JSXElement;
validation: yup based validation. See yup docu. Re-exported and accessible via timum.yup
type: string; either 'text' (default), 'phoneNumber', 'textarea', 'checkbox' or 'select'. Depending on the chosen type additional properties are available. See below.
onChange: function, allows to execute code whenever the user changes the fields value. Gets 'value' as function parameter.
prefilled: string; value a field is initiated with.
Note that for fields of type 'select' you must use the 'key' of one of it's options.
preventRendering: boolean, default false;
if true the field is not visible to the user.
Useful in conjunction with sendCustomValuesInMessage allowing you to
enrich booked appointments with internal data
without the customer becoming aware of your internal processes.
preventRenderingFor: array, prevents rendering of the field on the set booking screens. Valid values are
'identifiedCustomers' or 'unknownCustomers', which prevents rendering on the booking
screen shown to identified/unidentified customers, respectively. If both
'identifiedCustomers' and 'unknownCustomers' are set the behaviour is equal to
preventRendering.
Note that fields hidden in this manner get their validation disabled. This
ensures that the booking doesn't fail with a validation error the end user is unable to fix.
`}`Depending on the type there are additional properties you can/must specify:
Type
text:format: string; the native input field's 'type' (e.g. 'email', 'number', etc.).
Type
phoneNumber:- does NOT support
validation. Phone number validation is complex, so timum handles it for you. This does mean that field validation localisation is currently not supported for fields of this type. This will be fixed in a future update. isRequired: boolean; if true, this field must be filled with a valid numberdefaultCountry: string, denotes the country code which is preselected when first rendering the component. Default is 'DE',preferredCountries: list of strings; denotes which countries are displayed first in the country drop down. Defaults are ['DE', 'CH', 'AT'],
- does NOT support
Type
textArea:limit: number; sets the maximum number of characters customers can enter. Standard is 600 characters. 1024 is maximum. But if fully used by the customer, this may lead to error in combination with sendCustomValuesInMessage. Sending any more characters to the backend leads to error.
Type
checkbox:- no special properties.
Type
select:- options: array of objects with structure { title: string, key: string }. The title is displayed and the key is passed in the data object to callbacks.
Global config via window.timumBookingConfig
On top of the configs you pass directly to init() / initialise(), BookingJS also reads an optional global object from the host page: window.timumBookingConfig. This lets a page operator override BookingJS configuration without having to edit the script that initialises the widget — useful for environments where the embed code is generated by a CMS plugin or served by a backend snippet that you can't easily modify, but where you still control the surrounding HTML.
Cascade order
Configs are resolved in this order — later sources override earlier ones for the same key:
- Built-in defaults from BookingJS
appConfig/muiTheme/fcConfigpassed in toinit()/initialise()directlywindow.timumBookingConfig.appConfig/.muiTheme/.fcConfig— applied to every instance on the pagewindow.timumBookingConfig[<rootElId>].appConfig/.muiTheme/.fcConfig— applied only to the instance whoseappConfig.rootElIdmatches the key- URL parameters (highest precedence)
Deep merging is used at every step, so you can override individual nested keys without replacing the whole object.
Shape
window.timumBookingConfig = {
// Applied to every BookingJS instance on the page
appConfig: { culture: 'de' },
muiTheme: { palette: { primary: { main: '#abc' } } },
fcConfig: { /* … */ },
// Per-instance overrides: the key must match appConfig.rootElId
// of the instance you want to target
'bookingjs-dd8d32cc-b53b-4ab4-af52-41325000592b': {
appConfig: { ref: 'special-ref' },
},
'bookingjs-other-uuid': {
muiTheme: { palette: { primary: { main: '#def' } } },
},
};
All four slots are optional. You can set only top-level slots, only per-instance entries, or any mix of both. The global object must be set before BookingJS initialises — setting it after init() runs will not retroactively re-merge config.
Per-instance keying via rootElId
When multiple BookingJS instances live on the same page and you want to give them different overrides, set appConfig.rootElId to a stable identifier (typically the ID of the DOM element that hosts the widget) and use that same string as the key in window.timumBookingConfig.
If you omit rootElId, only the top-level slots apply — per-instance lookup is skipped entirely.
Scoping config to provider channel snippets
Each embed snippet you create in the Timum interface (found in "Appointment booking" ("Terminbuchung") in the top bar) represents a provider channel identified by a UUID. The generated snippet already stamps rootElId: "bookingjs-<provider-channel-uuid>" into its init() call. You can use that same key in window.timumBookingConfig to scope configuration to a specific snippet:
window.timumBookingConfig = {
// Shared across all snippets on the page
appConfig: { culture: 'de' },
// Only affects the snippet with this provider channel UUID
'bookingjs-dd8d32cc-b53b-4ab4-af52-41325000592b': {
appConfig: { height: 700 },
muiTheme: { palette: { primary: { main: '#ff5722' } } },
},
// A different snippet on the same page gets its own config
'bookingjs-a1b2c3d4-e5f6-7890-abcd-ef1234567890': {
appConfig: { height: 500 },
},
};
This is useful when you embed multiple booking widgets on the same page and want each one to have different styling or behavior, without having to configure all of that through the Timum interface. You find the provider channel UUID in the embed code that the Custom URL entry generates for you.
The configs get merged. Meaning the specific snippet configuration gets properties it doesn't override itself from the general top level config. Any properties not defined in there come either from the config of the snippet, provider or account wide configs or, if nothing else can be found, the timum default values. This is a cascade from most specific to least specific.
Reserved names
The strings 'appConfig', 'muiTheme', and 'fcConfig' are reserved as top-level slot names. If you accidentally set appConfig.rootElId to one of these values, BookingJS will log a console.warn and skip the per-instance lookup for that instance (only the top-level slots will apply). Pick a less generic identifier for your widgets.
The React component bypasses this layer
The <TimumBooking> React component intentionally ignores window.timumBookingConfig. React consumers pass appConfig / muiTheme / fcConfig directly as props per call and are expected to use React idioms (context, providers) for sharing config across components. The global override layer is therefore a script-tag / snippet feature only.
Examples
In this section you find examples of common patterns.
Customizing Fonts in timum's BookingJS
This guide demonstrates how to override the default font for the entire BookingJS widget and how to set specific fonts for individual UI components.
The example below shows how to set a new default font (Ysabeau Office) and then apply different fonts (Tangerine) to MuiButton and MuiLink components.
<div id="bookingjs" style="margin: 32px"></div>
<script type="module">
import * as timum from 'https://cdn.timum.de/bookingjs/1/booking.js';
// --- 1. Initialize BookingJS with Resource Reference and Configuration ---
timum.init(
{
// Your resource reference
ref: 'booking-widget-demo-resource@timum',
},
{
// --- Typography Configuration ---
typography: {
// **Default font** for the entire widget: A comma-separated list of font families.
fontFamily: '"Ysabeau Office", Roboto, Helvetica, Arial',
// **Font sources** to load via <link rel="stylesheet">.
fontSource: [
'https://fonts.googleapis.com/css2?family=Tangerine:wght@400;700&family=Ysabeau+Office:ital,wght@0,1..1000;1,1..1000&display=swap',
],
},
// --- Component-Specific Font Overrides ---
// This section uses the underlying MUI (Material-UI) component names.
components: {
// Override the font for all MUI Buttons
MuiButton: {
styleOverrides: {
root: {
fontFamily: '"Tangerine", Roboto, Helvetica, Arial',
},
},
},
// Override the font for all MUI Links
MuiLink: {
styleOverrides: {
root: {
fontFamily: '"Tangerine", Roboto, Helvetica, Arial',
},
},
},
},
},
);
</script>
Restrict BookingJS Access to Invited Customers
You can configure BookingJS to only display the booking interface for customers who have been explicitly invited via a unique link. This provides a layer of access control, ensuring that only qualified individuals (e.g., registered clients, specific leads) can proceed with a booking.
How it Works
This approach relies on URL query parameters to authenticate the user and passes these parameters into the timum.init configuration.
- Invitation Link: You generate unique invitation links containing a
ref(resource identifier) and aninvitee(customer identifier).- Example Link:
https://www.your-site.com/immo?ref=[uuid]&invitee=[addressId]
- Example Link:
- Validation: The embedded JavaScript checks for the presence of these parameters. If they are missing, the widget is not initialized.
- Authentication: The
inviteeID is passed to BookingJS via thepDataobject, allowing the backend to verify the customer against your platform data (e.g., onOffice, FlowFact, Immosolve).
Implementation
The following code demonstrates how to extract the necessary parameters from the URL and pass them to timum.init only when both are present.
<div id="bookingjs" style="margin: 32px"></div>
<script type="module">
import * as timum from 'https://cdn.timum.de/bookingjs/1/booking.js';
// Function to hide the element
const hideBookingJs = () => {
const el = document.getElementById('bookingjs');
if (!el) return;
// Hide the element if initialized but fails to find bookables
el.style.display = 'none';
};
// --- 1. Extract Parameters from the URL ---
const url = new URL(window.location.href);
const ref = url.searchParams.get('ref');
const inviteeId = url.searchParams.get('invitee');
// Stop execution if required invitation parameters are missing
if (!ref || !inviteeId) {
// Optionally, hide the empty container immediately if you want to be certain
document.getElementById('bookingjs')?.style.display = 'none';
return;
}
// --- 2. Initialize BookingJS with Customer Data ---
timum.init({
// Use the dynamic resource 'ref' from the URL
ref: ref,
// Pass the customer's identifier via pData for verification
pData: {
// Use the platform you are integrating with (e.g., 'onoffice')
platform: 'onoffice',
// 'personId' is the required key for onOffice/addressId matching
personId: inviteeId,
},
// --- 3. Optional Callbacks ---
callback: {
// If data fetching succeeds but returns no available slots for the user
fetchingBookablesSucceeded: (bookables) => {
const hasBookables = bookables && Object.values(bookables).length > 0;
if (!hasBookables) {
hideBookingJs();
}
},
// If data fetching fails (e.g., server error)
fetchingBookablesFailed: () => {
hideBookingJs();
},
},
});
</script>
Note on pData
The key used within the pData object (e.g., personId) must match the expected parameter name for your connected platform (onOffice, FlowFact, etc.) to correctly authenticate the user. Consult the platform-specific integration guide for the correct parameter name. (See also: pData Configuration
Conditionally Hide BookingJS When No Bookables Are Available
When the BookingJS widget loads, you may wish to automatically hide the widget container if no bookable resources are found or if the data retrieval fails.
You might want to hide the widget to:
- Improve UX: Avoid showing customers an empty element or informing them about a service they cannot currently book.
- Reduce Clutter: Keep your website clean by removing elements that serve no purpose at the moment.
- Display a Custom Message: Use the space to show your own fallback message (e.g., "All services are fully booked! Check back soon.").
The timum.init function provides callback hooks that fire after the widget attempts to fetch resources.
The following snippet demonstrates how to hide the main bookingjs container by checking if the bookables object is empty or if the fetch operation failed entirely.
<div id="bookingjs" style="margin: 32px"></div>
<script type="module">
import * as timum from 'https://cdn.timum.de/bookingjs/1/booking.js';
// Function to hide the element by setting its display style
const hideBookingJs = () => {
const el = document.getElementById('bookingjs');
if (!el) return; // Exit if the element wasn't found
//Set the style to hide element
el.style.display = 'none';
};
timum.init({
ref: 'booking-widget-demo-resource@timum',
callback: {
// 1. Check if the fetch succeeded but returned no bookables
fetchingBookablesSucceeded: (bookables) => {
// Use optional chaining for safety, then check the number of items
const hasBookables = bookables && Object.values(bookables).length > 0;
if (!hasBookables) {
hideBookingJs();
}
},
// 2. Hide the widget if the fetch failed (e.g., server error)
fetchingBookablesFailed: () => {
hideBookingJs();
},
// NOTE: Consider adding other related callbacks if needed.
},
});
</script>
Multi-Resource Mode for CondensedView
When managing multiple resources (e.g., different service providers, meeting rooms, or equipment), you may want customers to see available slots across all resources simultaneously. The multi-resource mode displays overlapping timeslots side-by-side with color-coded resource differentiation.
How it Works
When enabled, multi-resource mode:
- Loads bookables from all available resources
- Groups overlapping timeslots from different resources
- Displays overlapping bookables with intelligent layout: bookables from different resources are shown side-by-side, but when multiple bookables share the same timeslot UUID, they stack vertically in a single column
- Assigns each resource a color for easy differentiation
- Shows a color legend above the calendar for easy reference
- Automatically adjusts font size based on the number of parallel slots
- Suppresses the resource selector, streamlining the booking interface
The mode gracefully degrades to standard behavior when there is only one resource or no time overlaps exist.
Color Assignment
Resource colors are dynamically derived from your theme's primary color using the OKLCH color space, which provides perceptually uniform hue distribution. A 60-degree exclusion zone ensures resource colors never resemble the primary color (used for buttons, borders, and other UI elements), maintaining clear visual distinction.
When no primary color is configured, the system automatically falls back to a stable 10-color pastel palette. Color assignment is deterministic based on sorted resource UUIDs, ensuring consistent colors across sessions and page reloads.
Configuration
Enable multi-resource mode for CondensedView:
import { TimumBooking } from '@timum/booking';
<TimumBooking
appConfig={{
ref: 'your-resource-reference@timum',
calendarFrontend: 'condensedView', // or 'detailsCondensedView'
calendarFrontendOptions: {
condensedView: {
multiResourceMode: true, // Enable multi-resource display
},
},
}}
/>;
Example Scenario
Suppose you manage three consultants offering consultation services. With multi-resource mode enabled, customers see:
- Each day column shows all available slots across the three consultants
- Slot at 2:00 PM: If Consultants A, B, and C all have distinct timeslot UUIDs, they appear side-by-side (three columns)
- Slot at 2:15 PM: If all three consultants share the same timeslot UUID (e.g., a group session), they stack vertically in a single column for a compact display
- A legend above the calendar showing: "Consultant A (blue), Consultant B (orange), Consultant C (green)"
Customers can click any slot to book with their preferred consultant, without needing to switch between resource selection screens. The layout automatically optimizes space by stacking same-UUID bookables vertically while keeping different-UUID bookables side-by-side.
Notes
- Requires at least one product with availability across multiple resources
- The color assignment is deterministic and consistent across sessions (based on sorted resource UUIDs)
- Works seamlessly with other CondensedView options like
forceResourceSelectorDialog - Professional plan may be required for additional customizations (custom theming, form fields, etc.)
EyeCandy Responsive Layout in DetailsView
When using the EyeCandy calendar frontend inside DetailsView (with details prefix in calendarFrontend option), the component implements intelligent responsive layout management to ensure optimal display across all viewport sizes.
How It Works
Mobile-First Responsive Decision: EyeCandy determines its layout mode (mobile or desktop) using a hierarchical approach:
DetailsView Precedence (when applicable):
- If DetailsView has already determined the viewport is too small for optimal desktop display (
isViewportSmall: true), EyeCandy immediately switches to mobile layout - This prevents conflicts where DetailsView is in mobile mode but EyeCandy tries to display in desktop mode
- If DetailsView has already determined the viewport is too small for optimal desktop display (
Container-Based Measurement:
- When DetailsView is not constraining the layout, EyeCandy measures its own container width directly
- If the container width is below 500px, EyeCandy switches to mobile layout for optimal spacing
Layout Modes:
Mobile Layout (
isMobileLayout: true):- Calendar occupies full width with centered alignment
- Timeslot list appears below the calendar as an accordion with smooth height animation
- Tapping the selected date toggles the accordion closed for compact display
Desktop Layout (
isMobileLayout: false):- Calendar (320px) and timeslot list display side-by-side
- PureListView animates in from the left with width expansion
- DateCalendar shifts position smoothly via FLIP animation for polished transitions
PureListView Integration
When EyeCandy renders in desktop layout, it computes the remaining available space and communicates this to PureListView via the forceSmallContainer prop:
- If the remaining width (after DateCalendar's 320px) falls below the threshold (300px), PureListView uses compact layout (stacked time over product name)
- Otherwise, PureListView uses horizontal layout (time left, product centered)
- This ensures PureListView's responsive behavior is always based on accurate available space, not unreliable self-measurements from animated containers
Configuration
EyeCandy's responsive behavior is automatic and requires no configuration:
import { TimumBooking } from '@timum/booking';
<TimumBooking
appConfig={{
ref: 'your-resource-reference@timum',
calendarFrontend: 'detailsEyeCandy', // Automatically handles responsive layout
// ... other config
}}
/>;
The component respects both DetailsView's viewport assessment and its own container measurements to deliver consistent, responsive layouts across all screen sizes.
Custom Field Example
Here is an example tying all of the aforementioned concepts together. And here is a fiddle containing the very example detailed in this chapter.
The Scenario
It is necessary to determine the gender of customers for a specific purpose. The mobile and message fields are not necessary and will be removed. The new gender field must be filled, therefore a validation check is required to ensure it is completed. Both the name of the gender field and the validation messages must be translated into different languages.
The Implementation
This is the base configuration. As it uses the standard field configuration shown in Booking form fields :
<div id="bookingjs" style="margin: 32px"></div>
<script type="module">
import * as timum from 'https://cdn.timum.de/bookingjs/1/booking.js';
timum.init({ ref: 'booking-widget-demo-resource@timum' });
</script>
Let's add the necessary changes:
<div id="bookingjs" style="margin: 32px"></div>
<script type="module">
import * as timum from 'https://cdn.timum.de/bookingjs/1/booking.js';
timum.init({
ref: 'booking-widget-demo-resource@timum',
fields: {
// set these fields to undefined explicitly in order to remove them
message: undefined,
mobile: undefined,
firstName: {
// index defines the order in which the fields get displayed
index: 0,
title: 'fields.firstName',
validation: yup.string().required('validation.field_required'), // <- compare with key in localisation
},
lastName: {
index: 1,
title: 'fields.lastName',
validation: yup.string().required('validation.field_required'),
},
email: {
index: 2,
title: 'fields.email',
format: 'email',
type: 'text',
validation: yup
.string()
.email('validation.email_field_must_be_valid')
.required('validation.field_required'),
},
gender: {
index: 3,
// new language key
title: 'fields.gender.title',
// best for a limited number of predefined choices
type: 'select',
// could use 'validation.field_required' but
// for this example let's create a new key
validation: yup.string().required('validation.gender_field_required'),
options: [
{ key: 'm', title: 'fields.gender.male' },
{ key: 'w', title: 'fields.gender.female' },
{ key: 'd', title: 'fields.gender.other' },
],
},
agbs: {
index: 4,
title: 'Datenschutzbestimmungen gelesen und akzeptiert.',
type: 'checkbox',
validation: yup
.boolean()
.required('validation.field_required')
.test(
'privacyAccepted',
'validation.privacy_field_required',
(value) => value === true,
),
},
},
sendCustomValuesInMessage: true, // necessary to transmit custom field values as part of the customer message
// now we need to add the new translation keys and their translations.
localization: {
de: {
validation: {
gender_field_required: 'Bitte wählen.',
},
fields: {
gender: {
title: 'Geschlecht',
male: 'Männlich',
female: 'Weiblich',
other: 'Divers',
},
},
},
en: {
validation: {
gender_field_required: 'Please select an option.',
},
fields: {
gender: {
title: 'Gender',
male: 'Male',
female: 'Female',
other: 'Others',
},
},
},
},
// the following callback consumes the customers input, allowing you to act on
//the entered gender value.
callback: {
createBookingStarted: ({ data }) => {
console.log(data.gender);
},
},
});
</script>