@timum/booking
v1.20.3
Published
## Introduction
Readme
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
- 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)
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 codeMultiple 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.
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):
appConfig
This object's options directly affect timum's behaviour or allow you to react to it.
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.',
},
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.',
},
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(1024),
limit: 1024,
},
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> : {
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 number
- defaultCountry: 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 1024 characters. Setting this to a higher number 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.
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>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>