Bookable Widget

An embeddable booking widget for venue reservations, integrated with the DesignMyNight (Collins) booking system.

Quick Start

Basic Implementation

<!-- Include the widget CSS and JavaScript -->
<link rel="stylesheet" href="https://widget.bookabletech.com/style.css">
<script src="https://widget.bookabletech.com/bookings.js"
        data-bookable-widget
        data-venue-id="51e00dde0df690ee62001cea">
</script>

With Configuration

<script src="https://widget.bookabletech.com/bookings.js"
        data-bookable-widget
        data-venue-id="51e00dde0df690ee62001cea"
        data-guests="4"
        data-date="2026-03-21"
        data-booking-type="5c4af02d6354a83e3a0ea3b4"
        data-alternatives-limit="5">
</script>

Installation

Via CDN (Recommended)

<link rel="stylesheet" href="https://widget.bookabletech.com/style.css">
<script src="https://widget.bookabletech.com/bookings.js"
        data-bookable-widget
        data-venue-id="YOUR_VENUE_ID">
</script>

Self-Hosted

  1. Download bookings.js and style.css
  2. Host them on your server
  3. Include them in your HTML
<link rel="stylesheet" href="/path/to/style.css">
<script src="/path/to/bookings.js"
        data-bookable-widget
        data-venue-id="YOUR_VENUE_ID">
</script>

Configuration

Required Attributes

Attribute Type Description
data-bookable-widget - Required flag to initialize the widget
data-venue-id string Collins venue ID or "all" for multi-venue selection

Optional Attributes

Pre-fill Data

Attribute Type Default Description
data-guests number 2 Default number of guests
data-date string - Default date (YYYY-MM-DD format)
data-booking-type string - Default booking type ID
Behavior:
  • If all three are provided: Widget skips directly to time selection
  • If data-date and data-booking-type (no data-guests): Shows condensed view with guest selector
  • Otherwise: Normal step-by-step flow

Analytics

Attribute Type Description
data-ga4-id string Google Analytics 4 Measurement ID (e.g., G-XXXXXXXXXX)
data-gtm-id string Google Tag Manager Container ID (e.g., GTM-XXXXXX)

Tracking

Attribute Type Description
data-custom-source string Custom source identifier for tracking bookings
data-affiliate-id string Affiliate ID for commission tracking

Widget Options

Attribute Type Default Description
data-hide-offers boolean false Hide special offers display
data-hide-cross-sell boolean false Hide cross-sell promotions
data-monday-first boolean true Calendar starts on Monday (false for Sunday)

Alternatives Settings

Attribute Type Default Description
data-alternatives-limit number 5 Number of closest alternative venues to show
data-alternatives-radius number - Show venues within X miles (overrides limit if set)
Alternatives Behavior:
  • If data-alternatives-radius is set: Shows all venues within that radius (in miles)
  • Otherwise: Shows the closest X venues based on data-alternatives-limit

Return URL Settings

Attribute Type Default Description
data-return-url string - URL to redirect to after booking completion on DesignMyNight
data-return-method string post HTTP method for return redirect (post or get)
Return URL Behavior:
  • When set, users are redirected to this URL after completing their booking on DesignMyNight
  • The return_method determines how data is sent: post sends booking data in the request body, get appends it as query parameters

Complete Example

<script src="https://widget.bookabletech.com/bookings.js"
        data-bookable-widget
        data-venue-id="51e00dde0df690ee62001cea"
        data-guests="4"
        data-date="2026-03-21"
        data-booking-type="5c4af02d6354a83e3a0ea3b4"
        data-ga4-id="G-XXXXXXXXXX"
        data-gtm-id="GTM-XXXXXX"
        data-custom-source="email-campaign"
        data-affiliate-id="partner-123"
        data-monday-first="true"
        data-alternatives-limit="5"
        data-alternatives-radius="2">
</script>

JavaScript API

Control the widget programmatically using the JavaScript API.

Getting a Widget Instance

// Get the first widget on the page
var widget = BookableWidget.getWidget();

// Get widget by index (0-based)
var widget = BookableWidget.getWidget(0);

// Get widget by container element
var container = document.querySelector('.bookable-widget-root');
var widget = BookableWidget.getWidget(container);

// Get all widgets on the page
var allWidgets = BookableWidget.getAllWidgets();

API Methods

setVenue(venueId)

Set the venue for the widget.

widget.setVenue('589478b336288d992b46ffdd');

Parameters:

Behavior:

setDate(date)

Set the booking date.

// Using date string (YYYY-MM-DD)
widget.setDate('2026-03-21');

// Using Date object
widget.setDate(new Date('2026-03-21'));

Parameters:

Behavior:

setGuests(count)

Set the number of guests.

widget.setGuests(10);

Parameters:

Behavior:

setBookingType(bookingTypeId)

Set the booking type and advance to time selection.

widget.setBookingType('5c4af02d6354a83e3a0ea3b4');

Parameters:

Behavior:

setNotes(notes)

Set special requests or notes for the booking.

widget.setNotes('Please seat us by the window');

Parameters:

Behavior:

reset()

Reset the widget to its initial state.

widget.reset();

Behavior:

getState()

Get the current widget state.

var state = widget.getState();
console.log(state);

Returns:

{
  venue: {...},              // Selected venue object
  date: Date,                // Selected date
  guests: number,            // Number of guests
  bookingType: {...},        // Selected booking type object
  stage: number,             // Current stage (0-3)
  availableBookingTypes: []  // Available booking types array
}

getContainer()

Get the widget's container DOM element.

var container = widget.getContainer();
container.style.border = '2px solid red';

Returns:

Complete API Example

// Wait for widget to load
window.addEventListener('DOMContentLoaded', function() {
  // Get widget instance
  var widget = BookableWidget.getWidget();

  // Configure the widget
  widget.setVenue('589478b336288d992b46ffdd');
  widget.setGuests(10);
  widget.setDate('2026-03-21');
  widget.setBookingType('5c4af02d6354a83e3a0ea3b4');

  // Check state
  setTimeout(() => {
    var state = widget.getState();
    console.log('Widget is on stage:', state.stage);
  }, 1000);
});

Features

1. Venue Selection

2. Guest and Date Selection

3. Booking Type Selection

4. Time Selection

5. Alternative Venues

When a selected time is unavailable, users can:

  1. Click "Find alternatives" button
  2. View carousel of nearby venues
  3. See available times within ±30 minutes
  4. Navigate between venues with Previous/Next
  5. Book directly at alternative venue

Features:

6. Condensed Mode

Activated when data-date and data-booking-type are provided without data-guests:

<script src="bookings.js"
        data-bookable-widget
        data-venue-id="51e00dde0df690ee62001cea"
        data-date="2026-03-21"
        data-booking-type="5c4af02d6354a83e3a0ea3b4">
</script>

Display:

User Flow:

  1. Confirm or change guest count
  2. Click CONTINUE
  3. Advances to time selection

Action Types

Action Type Icon Behavior
accept ⚡ Lightning Instant booking available
can_accept 🕐 Clock Enquiry (can be accepted)
may_enquire 🕐 Clock Enquiry only
reject - Not available (disabled)

API Endpoints

The widget backend provides several REST endpoints:

GET /api/health

Health check endpoint.

Response:

{
  "status": "ok",
  "timestamp": "2026-03-12T10:00:00.000Z"
}

GET /api/venues

Get all available venues.

Response:

{
  "venues": [
    {
      "id": "589478b336288d992b46ffdd",
      "name": "The Director's Box, Manchester",
      "address": "123 Street Name, Manchester"
    }
  ]
}

GET /api/venues/:venueId

Get specific venue details with offers.

Parameters:

Response:

{
  "id": "589478b336288d992b46ffdd",
  "name": "The Director's Box, Manchester",
  "address": "123 Street Name, Manchester",
  "offers": [
    {
      "_id": "offer123",
      "title": "Early Bird Special",
      "description": "20% off before 6pm"
    }
  ]
}

GET /api/types

Get available booking types for a venue on a specific date.

Query Parameters:

Response:

{
  "availableTypes": [
    {
      "id": "5c4af02d6354a83e3a0ea3b4",
      "name": "Lunch",
      "valid": true,
      "offers": ["offer123"]
    }
  ],
  "unavailableTypes": [
    {
      "id": "5c5ac86d6a2c3d0af2068a49",
      "name": "Dinner",
      "valid": false,
      "message": "Not available on this date"
    }
  ]
}

GET /api/availability

Get availability, rules, and times for a booking type.

Query Parameters:

Response (Standard):

{
  "rules": {
    "min_duration": 90,
    "max_duration": 180,
    "no_duration": false
  },
  "disabledDates": ["2026-03-25", "2026-03-26"],
  "times": [
    {
      "time": "12:00",
      "action": "accept",
      "available": true
    }
  ]
}

Response (Duration Mode - when fields=duration):

{
  "durations": [
    {
      "value": 90,
      "action": "accept"
    },
    {
      "value": 120,
      "action": "accept"
    }
  ]
}

GET /api/alternatives

Get alternative venues when requested time is unavailable.

Query Parameters:

Response:

{
  "venueId": "589478b336288d992b46ffdd",
  "bookingTypeId": "5c4af02d6354a83e3a0ea3b4",
  "guests": 10,
  "date": "2026-03-21",
  "requestedTime": "14:00",
  "alternatives": [
    {
      "id": "512f77e80df690715c0000a3",
      "name": "Slug and Lettuce Albert Square",
      "distance": "0.17",
      "products": [
        {
          "id": "5c4af02d6354a83e3a0ea3b4",
          "name": "Lunch",
          "valid": true,
          "times": [
            {
              "time": "13:30",
              "duration": 90
            }
          ]
        }
      ]
    }
  ]
}

Algorithm:

  1. Finds venues with matching product category
  2. Uses PostGIS for distance calculation
  3. Queries DesignMyNight API for availability
  4. Filters to ±30 minute time window
  5. Returns venues sorted by distance (closest first)