/booking-js

Make a beautiful embeddable booking widget in minutes

Primary LanguageJavaScript

Booking.js by Timekit

Codacy Badge

Latest release: v1.2.2

Make a beautiful embeddable booking widget in minutes.

Booking.js Screenshot

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 booking.timekit.io to set up your account and generate a config.

Booking.js is meant as an easy to use, drop-in script that does it's job without any coding required. It's made for the browser and is quite similar to Stripe's Checkout.js.

This repo is mainly for community contributions and the curious soul that would like to customize the widget beyond settings provided in the wizard.

Dependencies

Stuff you need to load:

  • jQuery - 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

Usage

To ensure that we can push out updates, improvements and bugfixes to the library, you should load the library through our CDN. It's hosted on Amazon Cloudfront so it's snappy enough for production.

The simplest and most universally compatible usage is with autoload:

<div id="bookingjs">
  <script type="text/javascript" src="//ajax.googleapis.com/ajax/libs/jquery/2.1.3/jquery.min.js"></script>
  <script type="text/javascript" src="//cdn.timekit.io/bookingjs/1/booking.min.js" defer></script>
  <script type="text/javascript">
    window.timekitBookingConfig = {
      email:    'marty.mcfly@timekit.io',
      apiToken: 'bNpbFHRmrfZbtS5nEtCVl8sY5vUkOFCL',
      calendar: '8687f058-5b52-4fa4-885c-9294e52ab7d4',
      name:     'Marty McFly',
      avatar:   '../misc/avatar-mcfly.png'
    };
  </script>
</div>

If you intent to run multiple instances or want more control:

<div id="bookingjs">
  <script type="text/javascript" src="//ajax.googleapis.com/ajax/libs/jquery/2.1.3/jquery.min.js"></script>
  <script type="text/javascript" src="//cdn.timekit.io/bookingjs/1/booking.min.js"></script>
  <script type="text/javascript">
    var widget = new TimekitBooking();
    widget.init({
      email:    'marty.mcfly@timekit.io',
      apiToken: 'bNpbFHRmrfZbtS5nEtCVl8sY5vUkOFCL',
      calendar: '8687f058-5b52-4fa4-885c-9294e52ab7d4'
    });
  </script>
</div>

See /examples for more implementation examples.

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 here.

The apiToken setting is the key part here. When you specify domain and generate credentials in the setup wizard, 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

{
  // Required
  email:                    '',   // Your Timekit user's email (used for auth)
  apiToken:                 '',   // Your Timekit user's apiToken (as generated through the wizard)
  calendar:                 '',   // Your Timekit calendar ID that bookings should end up in
  
  // Optional
  targetEl:                 '#bookingjs', // Which element should we the library load into
  name:                     '',   // Display name to show in the header and timezone helper
  avatar:                   '',   // Provide an image URL for a circular image avatar
  autoload:                 true, // Auto initialization if config object is found on window var
  includeStyles:            true, // Inject fullCalendar and library styles in <head>
  showCredits:              true, // Display a "Powered by Timekit" attribution footer (thanks!)

  // Internationalization
  localization: {
    showTimezoneHelper:     true, // Should the timezone difference helper (bottom) be shown?
    timeDateFormat:         '12h-mdy-sun' // For EU-style formatting, use '24h-dmy-mon'
  }

  // Timekit JS SDK (see below)
  timekitConfig:            { ... },

  // Timekit FindTime endpoint (see below)
  timekitFindTime:          { ... },

  // Timekit CreateEvent endpoint (see below)
  timekitCreateEvent:       { ... },

  // FullCalendar options (see below)
  fullCalendar:             { ... },

  // Register callbacks on events
  callbacks: {

    findTimeStarted:          function(args) {},
    findTimeSuccessful:       function(response) {},
    findTimeFailed:           function(response) {},

    createEventStarted:       function(args) {},
    createEventSuccessful:    function(response) {},
    createEventFailed:        function(response) {},

    getUserTimezoneStarted:   function(args) {},
    getUserTimezoneSuccesful: function(response) {},
    getUserTimezoneFailed:    function(response) {},

    fullCalendarInitialized:  function() {},
    renderCompleted:          function() {},

    showBookingPage:          function() {},
    closeBookingPage:         function() {},
    submitBookingForm:        function() {}

  }
}

Timekit SDK

You can pass any of the Timekit JS SDK settings directly to the widget. This is mostly revelant if you're building a tighter integration with Timekit and have your own app registered on the platform.

timekitConfig: {
  app:          'bookingjs' // Default
}

Timekit Find Time

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',      // Default, how long time into the future that timeslots should be returned
  length:       '1 hour',       // Default, the duration of the bookable timeslots
  emails:       [config.email], // Inserted from the "email" setting in the general config
},

Timekit Create Event

When booking an event, the widget will call the [POST]/events endpoint through the JS SDK, with the following settings:

timekitCreateEvent: {
  where:        'Online',        // Default, you may want to customize this to a specific location, TBD or whatever fits
  invite:       true,            // Default, makes sure that participants (the visitor) is sent a Google invite
  my_rsvp:      'needsAction',   // Default, makes sure that the host also will be able to RSVP to the created event
  start:        data.start,      // Inserted from the chosen timeslot
  end:          data.end,        // Inserted from the chosen timeslot
  what:         config.name + ' x '+ data.name, // Inserted based on the host and visitors names (you can replace it with a static string)
  calendar_id:  config.calendar, // Inserted from the "calendar" setting in the general config
  participants: [config.email, data.email], // Inserted based on host and visitors ()
  description:  data.comment || '' // Inserted based on the visitor's supplied comment
},

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,     // Inserted based on the current width of the widget
  height:       sizing.height,   // Inserted based on the current width of the widget
  eventClick:   function(event), // Handled internally in Booking.js (overwrite if you want to replace the booking page)
  windowResize: function(view)   // Recalculates the view and height based on the widget's width (if resized)
}

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).

localization: {
  timeDateFormat: '12h-mdy-sun', // Default, alternative mode "24h-dmy-mon",
  bookingDateFormat: 'MMMM D, YYYY', // Override the default date format on the booking page
  bookingTimeFormat: 'h:mma' // Override the default time format on the booking page
},

For full language support, FullCalendar also takes a "lang" 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/language.htm for full implementation example.

fullCalendar: {
  lang: 'de'
},
localization: {
  timeDateFormat: false
}

Methods

After you instantiated the widget, you can control it with the following methods:

var widget = new TimekitBooking();
widget.init(config);          // Initalizes the widget with the given config
widget.render();              // Re-renders the widget with it's instance config
widget.setConfig(config);     // Push a new config into it (call render() afterwards)
widget.getConfig();           // Returns the current config
widget.destroy();             // Cleans the DOM element and empty config
widget.fullCalendar(action);  // Direct access to FullCalendar's own method (for advanced use)

Roadmap/todos

See Issues for feature requests, bugs etc.