SeatSquirrel
EmbeddingDesigner

Embedding the Stateless Designer

Learn how to embed SeatSquirrel seat layout designer on your website

Overview

The SeatSquirrel Stateless Designer allows you to embed a full-featured seat layout editor directly into your website using the Designer SDK. Unlike the database-driven designer, the stateless version gives you complete control over where and how layout data is stored.

Key Benefits:

  • Full designer experience embedded in your app
  • Complete control over data storage (your database, not ours)
  • PostMessage-based communication
  • Domain-verified security
  • No authentication required
  • Export layouts as JSON for use in the picker

Quick Start

Step 1: Add Your Domain

  1. Navigate to Organization → Embed Settings
  2. Click "Add Domain"
  3. Enter your full domain with protocol and without a trailing slash (e.g., https://example.com or http://localhost:3000)
  4. Save the domain

Important: Include the full URL with protocol (https:// or http://).

Self-Hosting

If you are self-hosting SeatSquirrel then you can add an approved domain to the APPROVED_DOMAINS environment variable. See the Self-Hosting Guide for details.

Examples:

  • https://example.com
  • https://www.example.com
  • http://localhost:3000

Step 2: Include the SDK

Add the SeatSquirrel Designer SDK to your page:

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

Or download and host it yourself for slightly better performance. Ideally this should be placed just before your closing </body> tag so that the script is loaded after the page has finished loading.

Step 3: Create a Container

Add a container element where the designer will render:

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

Important: The container must have an explicit height set. The designer will fill 100% of the container's dimensions.

Step 4: Initialize the Designer

<script>
  const designer = new SeatSquirrel.Designer({
    container: '#designer-container',
    baseUrl: 'https://seatsquirrel.com',
    onReady: function () {
      console.log('Designer is ready');
    },
    onExport: function (layoutData) {
      // Save the exported layout to your database
      console.log('Layout exported:', layoutData);
      saveToYourDatabase(layoutData);
    },
    onLayoutChanged: function (data) {
      // Track unsaved changes
      console.log('Has unsaved changes:', data.hasChanges);
    },
    onError: function (error) {
      console.error('Designer error:', error);
    },
  });
</script>

Ideally this should be placed just before your closing </body> tag after the container element and the sdk source script so that the elements are loaded in the correct order.

SDK API Reference

For complete documentation on all constructor options, methods, and callbacks, see the Designer SDK API Reference.

Key Methods

MethodDescription
loadLayout(data)Load an existing layout for editing
getLayout()Request the current layout (triggers onExport)
setReadOnly(bool)Enable/disable read-only mode
clearLayout()Clear the layout and start fresh
hasChanges()Check for unsaved changes
destroy()Cleanup the instance

Key Callbacks

CallbackDescription
onReady()Designer is ready to receive commands
onExport(layoutData)Important: User exported layout or getLayout() was called
onLayoutChanged(data)Unsaved changes status changed
onError(error)An error occurred

Complete Example

<!DOCTYPE html>
<html>
  <head>
    <title>Venue Layout Editor</title>
    <style>
      #designer-container {
        width: 100%;
        height: 800px;
        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>
    <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"></div>

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

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

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

        onExport: async function (layoutData) {
          // Save to your backend
          const response = await fetch('/api/layouts', {
            method: currentLayoutId ? 'PUT' : 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify({
              id: currentLayoutId,
              data: layoutData,
            }),
          });

          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);
        },
      });

      // Save button
      document.getElementById('save-btn').addEventListener('click', () => {
        designer.getLayout(); // Triggers onExport callback
      });

      // Load button
      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;
      });

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

Framework Integration

React Component

import { useEffect, useRef, useState } from 'react';

export function DesignerEmbed({ onSave, initialLayout }) {
  const containerRef = useRef(null);
  const designerRef = useRef(null);
  const [hasChanges, setHasChanges] = useState(false);

  useEffect(() => {
    // Load SDK dynamically
    const script = document.createElement('script');
    script.src = 'https://seatsquirrel.com/embed/seatsquirrel-designer-sdk.js';
    script.async = true;

    script.onload = () => {
      designerRef.current = new window.SeatSquirrel.Designer({
        container: containerRef.current,
        baseUrl: 'https://seatsquirrel.com',

        onReady: () => {
          if (initialLayout) {
            designerRef.current.loadLayout(initialLayout);
          }
        },

        onExport: layoutData => {
          onSave(layoutData);
          setHasChanges(false);
        },

        onLayoutChanged: data => {
          setHasChanges(data.hasChanges);
        },
      });
    };

    document.body.appendChild(script);

    return () => {
      if (designerRef.current) {
        designerRef.current.destroy();
      }
      document.body.removeChild(script);
    };
  }, [initialLayout, onSave]);

  const handleSave = () => {
    if (designerRef.current) {
      designerRef.current.getLayout();
    }
  };

  return (
    <div>
      {hasChanges && <div className="alert">You have unsaved changes</div>}
      <button onClick={handleSave}>Save Layout</button>
      <div ref={containerRef} style={{ width: '100%', height: '600px' }} />
    </div>
  );
}

Vue Component

<template>
  <div>
    <div v-if="hasChanges" class="alert">You have unsaved changes</div>
    <button @click="saveLayout">Save Layout</button>
    <div ref="container" style="width: 100%; height: 600px;"></div>
  </div>
</template>

<script>
export default {
  props: {
    initialLayout: Object,
    onSave: Function,
  },

  data() {
    return {
      designer: null,
      hasChanges: false,
    };
  },

  mounted() {
    this.loadSDK();
  },

  beforeUnmount() {
    if (this.designer) {
      this.designer.destroy();
    }
  },

  methods: {
    async loadSDK() {
      await new Promise(resolve => {
        const script = document.createElement('script');
        script.src = 'https://seatsquirrel.com/embed/seatsquirrel-designer-sdk.js';
        script.onload = resolve;
        document.body.appendChild(script);
      });

      this.designer = new window.SeatSquirrel.Designer({
        container: this.$refs.container,
        baseUrl: 'https://seatsquirrel.com',

        onReady: () => {
          if (this.initialLayout) {
            this.designer.loadLayout(this.initialLayout);
          }
        },

        onExport: layoutData => {
          this.onSave(layoutData);
          this.hasChanges = false;
        },

        onLayoutChanged: data => {
          this.hasChanges = data.hasChanges;
        },
      });
    },

    saveLayout() {
      if (this.designer) {
        this.designer.getLayout();
      }
    },
  },
};
</script>

Best Practices

1. Container Sizing

The designer requires significant screen space for usability:

  • Minimum height: 500px for basic use
  • Recommended: 700-800px for comfortable editing
  • Full screen: Best user experience for complex layouts

2. Unsaved Changes Tracking

Always track unsaved changes to prevent data loss:

let hasUnsavedChanges = false;

const designer = new SeatSquirrel.Designer({
  // ... other options
  onLayoutChanged: data => {
    hasUnsavedChanges = data.hasChanges;
  },
});

// Warn before leaving
window.addEventListener('beforeunload', e => {
  if (hasUnsavedChanges) {
    e.preventDefault();
    e.returnValue = 'You have unsaved changes. Are you sure you want to leave?';
  }
});

3. Auto-Save

Implement auto-save for better UX:

let autoSaveTimer;

const designer = new SeatSquirrel.Designer({
  // ... other options
  onLayoutChanged: data => {
    if (data.hasChanges) {
      // Auto-save after 30 seconds of inactivity
      clearTimeout(autoSaveTimer);
      autoSaveTimer = setTimeout(() => {
        designer.getLayout(); // Triggers onExport
      }, 30000);
    }
  },
});

4. Error Handling

Always handle errors gracefully:

const designer = new SeatSquirrel.Designer({
  // ... other options
  onError: error => {
    console.error('Designer error:', error);

    // Show user-friendly message
    showErrorNotification('An error occurred in the designer. Your changes may not have been saved.');

    // Optionally log to your error tracking service
    logToErrorService(error);
  },
});

Domain Verification

The designer uses the same domain verification as the picker:

  1. Go to Organization → Embed Settings
  2. Add your domain with protocol (e.g., https://example.com)
  3. The designer will verify the domain on load
  4. If verification fails, users see an "Access Denied" message

Troubleshooting

"Access Denied" Error

Problem: Designer shows "Access Denied"

Solutions:

  1. Verify domain is added with correct protocol (https:// or http://)
  2. Include port for localhost: http://localhost:3000
  3. Check browser console for specific error details

Designer Not Loading

Problem: Container stays blank

Solutions:

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

onExport Not Firing

Problem: onExport callback never gets called

Solutions:

  1. Ensure user clicks the Export button in the designer UI
  2. Call designer.getLayout() programmatically
  3. Check for JavaScript errors in console

Changes Not Persisting

Problem: Layout changes don't save

Solutions:

  1. Verify onExport callback saves to your database
  2. Check for errors in save operation
  3. Ensure layout data is being POSTed correctly
  4. Verify your backend API is working

Next Steps