Booking.js by Timekit
Releases & changelog
Make a beautiful embeddable booking widget in minutes.
Uses FullCalendar with a custom theme for dynamic calendar rendering with available timeslots fetched from Timekit (through the Javascript SDK). The shown appointment slots can be booked with automatic calendar invites sent to both host and visitor. Integrates with Google Calendar for automatic availability.
Maintainer: Lasse Boisen Andersen (la@timekit.io). PR's are welcome!
Get started
Visit Timekit.io to get started!
You can use the widget in two different ways:
- For non-developers where the widget is configured in our UI admin panel. For this, you'll never need to touch any code - just follow the admin panel to get your embed code or hosted url.
- For developers where you can configure the widget on-the-fly for multi-user usage and more control. In this way, you don't need to generate a widget for each user through the Timekit admin. Timekit provides you with a modular and flexible API platform that allows you to integrate availability and bookings deep into your own product.
This repo is mainly for community contributions, docs and the curious soul that would like to customize the widget.
Dependencies
Stuff you need to load:
- jQuery (v2 - v3) - primarily because it's a requisite for FullCalendar
Bundled together with the library:
- fullCalendar - a customizable and flexible event calendar built for the browser
- moment - parse, validate, manipulate, and display dates in JavaScript
- timekit-js-sdk - JavaScript SDK for the Timekit.io API
Module loading
CDN
To ensure that we can push out updates, improvements and bugfixes to the library, we recommend that you load the library through our CDN. It's hosted on Amazon Cloudfront so it's snappy enough for production.
<script type="text/javascript" src="//ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
<script type="text/javascript" src="//cdn.timekit.io/booking-js/v1/booking.min.js" defer></script>
UMD through NPM
The module is published on NPM and can be require'd as a CommonJS, AMD or in a script tag.
npm install timekit-booking
require('jquery')
var TimekitBooking = require('timekit-booking')
var widget = new TimekitBooking();
Usage
Autoload
The simplest and most universally compatible usage is with autoload. This will defer the loading of the library until the whole document has loaded and then look for window.timekitBookingConfig
- if found, the library is loaded automatically. Note that in single page applications, you should not use this approach (see next section).
<div id="bookingjs">
<script type="text/javascript" src="//ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
<script type="text/javascript" src="//cdn.timekit.io/booking-js/v1/booking.min.js" defer></script>
<script type="text/javascript">
window.timekitBookingConfig = {
app: 'back-to-the-future',
email: 'marty.mcfly@timekit.io',
apiToken: 'bNpbFHRmrfZbtS5nEtCVl8sY5vUkOFCL'
};
</script>
</div>
Instantiation
If you intent to run multiple instances or want more control over initialization order, create a new instance. This is ideal for usage in single page applications like Angular.js, where you'd injecting <div id="bookingjs">
async in your template and control it through JS in your controller/directive/component code.
<div id="bookingjs">
<script type="text/javascript" src="//ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
<script type="text/javascript" src="//cdn.timekit.io/booking-js/v1/booking.min.js"></script>
<script type="text/javascript">
var widget = new TimekitBooking();
widget.init({
app: 'back-to-the-future',
email: 'marty.mcfly@timekit.io',
apiToken: 'bNpbFHRmrfZbtS5nEtCVl8sY5vUkOFCL'
});
</script>
</div>
Authentication
The widget connects to the Timekit API behind the scenes and requires a Timekit account.
You can either connect with a Google account (recommended) or create a plain account (you'd have to enter availability and pull out events through the API).
Visit the setup wizard on Timekit.
The app
, email
and apiToken
settings are the key part here. When you've registered an app, created a user and generated a widget credentials, you get a special client-token with limited access. It's only capable of hitting certain endpoints so your account stays secure when using the widget in a public browser environment.
Configuration
Booking.js is made for various use-cases, so it's really extensible and customizable. We augment all the intrinsic options so you can overwrite them as needed, e.g. Timekit FindTime options or FullCalendar settings.
Example
{
app: '',
email: '',
apiToken: '',
name: '',
avatar: '',
calendar: '',
targetEl: '#bookingjs',
autoload: true,
goToFirstEvent: true,
bookingGraph: 'instant',
availabilityView: 'agendaWeek',
debug: false,
timekitConfig: { ... },
timekitFindTime: { ... },
timekitCreateBooking: { ... },
localization: { ... },
fullCalendar: { ... },
bookingFields: { ... },
callbacks: { ... }
}
- timekitConfig
You can pass any of the Timekit JS SDK settings directly to the widget. This is mostly relevant if you're building a tighter integration with Timekit and have your own app registered on the platform.
As a shorthand, the app slug can be set using a root-level app
config key too.
timekitConfig: {
app: 'your-app-slug-here'
}
- timekitFindTime
The Find Time algorithm is a powerful query tool for availability. Booking.js is calling the endpoint [POST] /findtime
through the JS SDK and takes all the arguments as mentioned on the official docs. The most powerful aspect are the filters. By default, there's no filters applied.
There's only three default arguments out of the box:
timekitFindTime: {
future: '4 weeks',
length: '1 hour',
emails: [config.email],
},
- timekitCreateBooking
When booking an event, the widget will call the [POST] /bookings
endpoint through the JS SDK.
The booking engine in Timekit is a powerful state machine that can take input data (event details and customer info) and perform actions on that based on a chosen "flow graph".
At the time of writing, Timekit supports two graphs:
instant
- automatically confirm any incoming bookings, save it to calendar and send out notificationsconfirm_decline
- creates the booking as tentative and send notification to owner with confirm/decline actions
These can be set using the bookingGraph
config key on the root config level.
You can override specific endpoint settings like so:
timekitCreateBooking: {
graph: 'instant',
action: 'confirm',
event: {
where: 'Online',
invite: true,
my_rsvp: 'accepted',
start: data.start,
end: data.end,
what: config.name + ' x '+ data.name,
calendar_id: config.calendar,
participants: [config.email, data.email],
description: data.comment || ''
},
customer: {
name: data.name,
email: data.email,
timezone: moment.tz.guess()
}
},
- fullCalendar
You can supply and override all the FullCalendar settings:
fullCalendar: {
header: {
left: '',
center: '',
right: 'today, prev, next'
},
views: {
agenda: {
displayEventEnd: false
}
},
allDaySlot: false,
scrollTime: '08:00:00',
timezone: 'local',
defaultView: sizing.view,
height: sizing.height,
eventClick: function(event),
windowResize: function(view)
}
See below for FullCalendar language support.
- localization
For quick localization of time/date formats, we provide a simple "preset" setting, timeDateFormat
, that sets a range of different FullCalendar and localization settings.
By default, it's set to "12-hour clock, M/D/Y date format, Sunday first day of week" (12h-mdy-sun
). It can be changed to "24-hour clock, D/M/Y date format, Monday first day of week" (24h-dmy-mon
).
See /examples/local-preset.htm
localization: {
showTimezoneHelper: true,
timeDateFormat: '12h-mdy-sun',
bookingDateFormat: 'MMMM D, YYYY',
bookingTimeFormat: 'h:mma'
strings: { ... }
},
For full language support, FullCalendar also takes a "locale" option, accompanied by a language file. Make sure to use defer attribute on a script tag loading the language file if you are deferring booking.js, language file should be loaded after booking.js, but before initialization.
Remember to set localization.timeDateFormat
to false so it doesn't override the language file's settings.
See /examples/local-language.htm
fullCalendar: {
locale: 'de'
},
localization: {
timeDateFormat: false
}
If you're using the widget in another language, you might want to customize the text strings used in e.g. submit button and success message. This can be done in the localization.strings
key.
See /examples/local-strings.htm
localization: {
strings: {
allocatedResourcePrefix: 'with',
submitText: 'Book it',
successMessageTitle: 'Thanks!',
successMessageBody: 'An invitation has been sent to: <br /> %s <br /><br /> Please accept the invitation to confirm the booking.',
timezoneHelperLoading: 'Loading..',
timezoneHelperDifferent: 'Your timezone is %s hours %s of %s (calendar shown in your local time)',
timezoneHelperSame: 'You are in the same timezone as %s'
}
}
- bookingFields
You can customize the booking form fields and their settings in this section. Only the name
, email
and comment
fields are enabled by default. The name
and email
fields have to be enabled and is always required (for the event creation to work properly). All other fields can be enabled/disabled.
If you're collecting user information before loading the widget, it can be useful to inject it into the form by setting the prefilled
keys - just pass in the values and they will be set upon load. Combine it with locked
to lock the fields for user input.
See /examples/fields.htm
bookingFields: {
name: {
placeholder: 'Full name',
prefilled: false,
locked: false
},
email: {
placeholder: 'E-mail',
prefilled: false,
locked: false
},
comment: {
enabled: true,
placeholder: 'Comment',
prefilled: false,
required: false,
locked: false
},
phone: {
enabled: false,
placeholder: 'Phone number',
prefilled: false,
required: false,
locked: false
},
voip: {
enabled: false,
placeholder: 'Skype username',
prefilled: false,
required: false,
locked: false
},
location: {
enabled: false,
placeholder: 'Location',
prefilled: false,
required: false,
locked: false
}
}
- callbacks
You can hook into events happening throughout the user flow and perform asynchronous events. This is especially powerful for saving user data to your CRM system or redirect users to a payment gateway after booking is finished.
Inspect to source code to learn more about in which order callbacks are fired. Complete list:
callbacks: {
findTimeStarted: function(args) {},
findTimeSuccessful: function(response) {},
findTimeFailed: function(response) {},
createBookingStarted: function(args) {},
createBookingSuccessful: function(response) {},
createBookingFailed: function(response) {},
getUserTimezoneStarted: function(args) {},
getUserTimezoneSuccessful:function(response) {},
getUserTimezoneFailed: function(response) {},
fullCalendarInitialized: function() {},
renderCompleted: function() {},
showBookingPage: function(event) {},
closeBookingPage: function() {},
submitBookingForm: function(values) {},
errorTriggered: function(message) {}
}
Methods
After you instantiated the widget, you can control it with the following methods:
var widget = new TimekitBooking();
widget.init(config);
widget.render();
widget.setConfig(config);
widget.getConfig();
widget.destroy();
widget.fullCalendar(action);
Only available when your using the instantiation approach and not autoload
Roadmap/todos
See Issues for feature requests, bugs etc.