SeatSquirrel
DeploymentSelf-Hosted Mode

iFrame Mode

Embed SeatSquirrel Designer and Picker via SDK with PostMessage communication

Overview

In iFrame mode, the SDK creates an iFrame on your page and handles all communication via PostMessage. This gives you CSS isolation, multiple-instance support, and cross-origin compatibility.

Designer

Step 1: Include the SDK

<script src="https://seatsquirrel.yourdomain.com/sdk/designer-sdk.js"></script>

Step 2: Add a Container

<div id="designer-container" style="width: 100%; height: 700px;"></div>

The container must have an explicit height. The designer fills 100% of the container's dimensions.

Step 3: Initialise

<script>
  const designer = new SeatSquirrel.Designer({
    container: '#designer-container',
    baseUrl: 'https://seatsquirrel.yourdomain.com',

    onReady: function () {
      console.log('Designer is ready');
    },

    onSave: function (data) {
      // Save the layout to your database
      saveToYourDatabase(data.layout);
    },

    onLayoutChanged: function (data) {
      console.log('Has unsaved changes:', data.hasChanges);
    },

    onError: function (error) {
      console.error('Designer error:', error);
    },
  });
</script>

For the full list of constructor options, methods, and callbacks see the Designer API Reference.


Picker

Step 1: Include the SDK

<script src="https://seatsquirrel.yourdomain.com/sdk/picker-sdk.js"></script>

Step 2: Add a Container

<div id="picker-container" style="width: 100%; height: 600px;"></div>

Step 3: Initialise

<script>
  const picker = new SeatSquirrel.StatelessPicker({
    container: '#picker-container',
    baseUrl: 'https://seatsquirrel.yourdomain.com',

    onReady: function () {
      picker.loadLayout(layoutDataFromYourDatabase);
      picker.setAvailability({ mode: 'by-slug', ...availabilityFromYourDatabase });
    },

    onSelectionChanged: function (data) {
      console.log('Selected:', data.selections);
      console.log('Total:', data.totals.amount);
      // data.lastSelection is the item just selected/modified, or null on deselect/clear
      if (data.lastSelection) {
        console.log('Last selected:', data.lastSelection.type, data.lastSelection.id);
      }
    },

    onComplete: function (data) {
      processCheckout(data.selections, data.totals);
    },

    onError: function (error) {
      console.error('Picker error:', error);
    },
  });
</script>

For the full list of constructor options, methods, and callbacks see the Picker API Reference.


Using Explicit URLs

Some web servers like IIS don't perform URL rewriting and require the .html file extension. Use designerUrl and pickerUrl instead of baseUrl:

const designer = new SeatSquirrel.Designer({
  container: '#designer-container',
  designerUrl: 'https://seatsquirrel.yourdomain.com/designer.html',
  // ...
});

const picker = new SeatSquirrel.StatelessPicker({
  container: '#picker-container',
  pickerUrl: 'https://seatsquirrel.yourdomain.com/picker.html',
  // ...
});

When to use explicit URLs

Use designerUrl / pickerUrl when:

  • Your server requires .html file extensions (e.g., IIS without URL rewrite)
  • You've deployed the files to custom paths
  • You need different base URLs for designer and picker

Complete Example

<!DOCTYPE html>
<html>
  <head>
    <title>SeatSquirrel Demo</title>
    <style>
      .container {
        width: 100%;
        height: 700px;
        border: 1px solid #e5e7eb;
        border-radius: 8px;
      }
      .toolbar {
        display: flex;
        gap: 10px;
        margin-bottom: 20px;
      }
      button {
        padding: 10px 20px;
        border-radius: 6px;
        border: none;
        cursor: pointer;
      }
      .primary {
        background: #3b82f6;
        color: white;
      }
      .secondary {
        background: #e5e7eb;
        color: #374151;
      }
    </style>
  </head>
  <body>
    <h1>Venue Layout Designer</h1>

    <div class="toolbar">
      <button id="save-btn" class="primary">Save Layout</button>
      <button id="load-btn" class="secondary">Load Layout</button>
      <button id="clear-btn" class="secondary">Clear</button>
      <span id="status"></span>
    </div>

    <div id="designer-container" class="container"></div>

    <script src="https://seatsquirrel.yourdomain.com/sdk/designer-sdk.js"></script>
    <script>
      const SEATSQUIRREL_URL = 'https://seatsquirrel.yourdomain.com';
      let currentLayoutId = null;

      const designer = new SeatSquirrel.Designer({
        container: '#designer-container',
        baseUrl: SEATSQUIRREL_URL,

        onReady: function () {
          document.getElementById('status').textContent = 'Ready';
        },

        onSave: async function (data) {
          const response = await fetch('/api/layouts', {
            method: currentLayoutId ? 'PUT' : 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify({ id: currentLayoutId, data: data.layout }),
          });
          const result = await response.json();
          currentLayoutId = result.id;
          document.getElementById('status').textContent = 'Saved!';
          setTimeout(
            () => (document.getElementById('status').textContent = ''),
            2000,
          );
        },

        onLayoutChanged: function (data) {
          if (data.hasChanges)
            document.getElementById('status').textContent = 'Unsaved changes';
        },

        onError: function (error) {
          alert('Error: ' + error.message);
        },
      });

      document.getElementById('save-btn').addEventListener('click', () => {
        designer.getLayout({ mode: 'current' });
      });

      document
        .getElementById('load-btn')
        .addEventListener('click', async () => {
          const layoutId = prompt('Enter layout ID:');
          if (!layoutId) return;
          const response = await fetch(`/api/layouts/${layoutId}`);
          const layoutData = await response.json();
          designer.loadLayout(layoutData);
          currentLayoutId = layoutId;
        });

      document.getElementById('clear-btn').addEventListener('click', () => {
        if (confirm('Clear the current layout?')) {
          designer.clearLayout();
          currentLayoutId = null;
        }
      });
    </script>
  </body>
</html>

Best Practices

Container Sizing

  • Minimum height: 500px for designer, 400px for picker
  • Recommended: 700–800px for comfortable editing
  • Full screen: Best user experience for complex layouts

Unsaved Changes Tracking

window.addEventListener('beforeunload', e => {
  if (designer.hasChanges().hasChanges) {
    e.preventDefault();
    e.returnValue = 'You have unsaved changes.';
  }
});

Error Handling

Always provide an onError callback and log to your error tracking service.


Fullscreen

Both the Designer and the Picker ship with a fullscreen toggle button (a maximise icon in the Designer toolbar and the top-right of the Picker canvas). Clicking it expands the iFrame to fill the browser viewport. Pressing Escape exits.

This is viewport-fill, not the browser Fullscreen API — the iFrame grows via CSS to cover the browser window while the browser's own chrome stays visible. No allowfullscreen attribute or user permission is required.

Hiding the built-in button

Pass ui.fullscreenButton: false to hide it:

const picker = new SeatSquirrel.StatelessPicker({
  container: '#picker-container',
  baseUrl: 'https://seatsquirrel.yourdomain.com',
  ui: { fullscreenButton: false },
});

Triggering fullscreen imperatively

Whether or not the built-in button is visible, you can drive fullscreen from your own UI:

myFullscreenButton.addEventListener('click', () => picker.setFullscreen(true));

Reacting to state changes

const picker = new SeatSquirrel.StatelessPicker({
  // ...
  onFullscreenChange: (isFullscreen) => {
    console.log('Picker fullscreen:', isFullscreen);
  },
});

See setFullscreen() and setFullscreen() (Designer).


Price category filter

The Picker ships with a "Filter categories" control (top-left of the canvas). It lists the pricing categories in the current view; checking one or more fades non-matching bookables so a booker can find a price tier quickly. It is purely visual — dimmed objects stay selectable — and auto-hides when a view has fewer than two categories.

It is shown by default. Pass ui.pricingCategoryFilter: false to hide it:

const picker = new SeatSquirrel.StatelessPicker({
  container: '#picker-container',
  baseUrl: 'https://seatsquirrel.yourdomain.com',
  ui: { pricingCategoryFilter: false },
});

Domain Verification

The iFrame mode uses the SEATSQUIRREL_APPROVED_DOMAINS environment variable to control which domains are allowed to embed the Designer and Picker. This is a security measure to prevent unauthorized embedding.

Important

Set the SEATSQUIRREL_APPROVED_DOMAINS environment variable before your application loads. The exact method depends on your deployment platform. For example, in a .env file:

SEATSQUIRREL_APPROVED_DOMAINS=https://example.com,https://www.example.com,http://localhost:3000

Format: Comma-separated list of full URLs with protocol

SEATSQUIRREL_APPROVED_DOMAINS=https://example.com,https://www.example.com,http://localhost:3000

Important Notes:

  • Include the full URL with protocol (https:// or http://)
  • Include the port for localhost: http://localhost:3000
  • No trailing slashes
  • Separate multiple domains with commas
  • The iFrame verifies the parent domain on load — if verification fails, users see an "Access Denied" message

Troubleshooting

"Access Denied" Error

  1. Verify domain is in SEATSQUIRREL_APPROVED_DOMAINS with correct protocol
  2. Include port for localhost: http://localhost:3000
  3. Check browser console for specific error details

Container Stays Blank

  1. Ensure container has an explicit height (not auto)
  2. Verify the SDK script loaded successfully (check Network tab)
  3. Check browser console for JavaScript errors
  4. Verify domain is on the allowed list

onSave / onComplete Not Firing

  1. Ensure the user clicks Save / Proceed in the UI, or call getLayout({ mode: 'current' }) programmatically
  2. Verify the callback is defined in the constructor options
  3. Check browser console for errors

On this page