Skip to content

Headless API

Build your own fully custom interface for the Wedding Band Builder. Set showUI: false to hide the built-in panel and drive everything through the API.

Image

Screenshot showing a Wedding Band Builder with a custom-designed interface: 3D viewer full width, with a brand-styled control bar featuring profile thumbnails, material swatches, and a width slider.

Getting Started

Script Tag (Direct)

javascript
new ijewelViewer.Viewer(document.getElementById('viewer'), {
  name: 'Wedding Band Builder',
  version: 'v5',
  basePath: 'https://your-cdn.com/wbb-assets/',
  plugins: {
    WeddingBandBuilder: {
      manifestUrl: 'wedding-band-project.json',
      showUI: false,
    },
  },
}, {
  showCard: false, showSwitchNode: false, showUiButtons: false,
  showConfigurator: false, showZoomButtons: false, enableZoom: true,
});

window.addEventListener('ijewel-viewer-ready', (e) => {
  const api = e.detail.viewer.getPluginByType('WeddingBandBuilder').controller;
  buildUI(api);
});

iframe

Load the iframe with ?ui=false and use postMessage to control everything:

html
<iframe src="wbb-viewer.html?ui=false" style="width: 100%; height: 500px; border: none;"></iframe>

The same API methods are available via postMessage. See iframe Integration for the protocol.

Building Controls

Each control follows the same pattern: read options from the catalog, create your UI, call an API setter on interaction.

Profile Selector

javascript
function buildProfileSelector(api) {
  const profiles = api.getAvailableProfiles();
  const container = document.getElementById('profiles');

  profiles.forEach((profile) => {
    const btn = document.createElement('button');
    btn.textContent = profile.name;
    if (profile.thumbnail) {
      btn.style.backgroundImage = `url(${profile.thumbnail})`;
    }
    btn.addEventListener('click', () => api.setProfile(profile.index));
    container.appendChild(btn);
  });
}

Image

A row of profile buttons showing ring cross section silhouettes: D-Shape, Flat, Court, Beveled, Knife Edge, Round.

Material Swatches

javascript
function buildMetalSwatches(api) {
  const metals = api.getAvailableMetals();
  const finishes = api.getAvailableFinishes();
  const container = document.getElementById('metals');

  metals.forEach((metal) => {
    const swatch = document.createElement('button');
    swatch.className = 'swatch';
    swatch.title = metal.name;
    if (metal.thumbnail) {
      swatch.style.backgroundImage = `url(${metal.thumbnail})`;
    }
    swatch.addEventListener('click', () => {
      api.setMaterial(1, metal.id, finishes[0]?.id || 'Polished');
    });
    container.appendChild(swatch);
  });
}

Dimension Sliders

javascript
function buildDimensionSliders(api) {
  const widthSlider = document.getElementById('width-slider');
  const widthLabel = document.getElementById('width-value');

  // Ring width: 0.5mm increments
  widthSlider.min = 2;
  widthSlider.max = 10;
  widthSlider.step = 0.5;

  const dims = api.getDimensions();
  widthSlider.value = dims.widthMm;
  widthLabel.textContent = `${dims.widthMm.toFixed(1)} mm`;

  widthSlider.addEventListener('input', (e) => {
    const mm = parseFloat(e.target.value);
    api.setWidth(mm);
    widthLabel.textContent = `${mm.toFixed(1)} mm`;
  });
}

Diamond Controls

javascript
function buildDiamondControls(api) {
  const types = api.getAvailableSettingTypes();
  const container = document.getElementById('diamonds');

  // "No diamonds" button
  const noneBtn = document.createElement('button');
  noneBtn.textContent = 'No Diamonds';
  noneBtn.addEventListener('click', () => api.setDiamonds(null));
  container.appendChild(noneBtn);

  // Setting type buttons
  types.filter(t => t !== 'none').forEach((type) => {
    const btn = document.createElement('button');
    btn.textContent = type;
    btn.addEventListener('click', () => api.setDiamonds({ settingType: type }));
    container.appendChild(btn);
  });
}

Ring Size

javascript
function buildRingSizeControl(api) {
  const slider = document.getElementById('ring-size');
  const label = document.getElementById('ring-size-value');

  // Ring size: inner diameter in mm, snapping to standard half-sizes
  slider.min = 14.04;  // US 3
  slider.max = 22.32;  // US 13
  slider.step = 0.4;   // ~half-size increments

  slider.addEventListener('input', (e) => {
    const diamMm = parseFloat(e.target.value);
    api.setRingSize(diamMm / 2);  // API takes radius
    label.textContent = `${diamMm.toFixed(1)} mm`;
  });
}

Live Price Display

javascript
function buildPriceDisplay(api) {
  const el = document.getElementById('price');

  api.events.on('price:updated', (data) => {
    const p = data.pricing;
    let text = `$${p.totalUsd.toFixed(2)}`;
    if (p.diamonds) {
      text += ` (${p.diamonds.count} diamonds, ${p.diamonds.totalCarats.toFixed(2)}ct)`;
    }
    el.textContent = text;
  });

  // Show initial price
  const price = api.getPrice();
  if (price) el.textContent = `$${price.totalUsd.toFixed(2)}`;
}

Staying in Sync

When state changes externally (e.g., user switches bands), update your UI to match:

javascript
api.events.on('band:switched', (data) => {
  const snapshot = api.getSnapshot(data.to);
  updateProfileHighlight(snapshot.profile.index);
  updateMetalHighlight(snapshot.materials.slots[0].metal);
  updateWidthSlider(snapshot.dimensions.widthMm);
});

api.events.on('build:started', () => showSpinner());
api.events.on('build:complete', () => hideSpinner());

TIP

When the user switches bands (her / his), all your controls should update to reflect the new band's state. Call getSnapshot() to get the full configuration.

Batch Updates and Presets

Apply multiple changes in one geometry rebuild instead of triggering separate rebuilds:

javascript
// Apply a "Classic Gold" preset
await api.batch({
  profile: { name: 'D-Shape' },
  dimensions: { widthMm: 4.0, heightMm: 1.8 },
  materials: {
    partition: 1,
    slots: [{ slot: 1, metal: 'Yellow', finish: 'Polished' }],
  },
  diamonds: null,
  edge: { type: 'None' },
});

Image

A row of styled preset cards: "Classic Gold", "Diamond Elegance", "Two-Tone Modern", each with a small preview image.

Save and Restore

javascript
// Save current configuration
const config = api.exportConfig();
localStorage.setItem('wbb-config', JSON.stringify(config));

// Restore saved configuration
const saved = localStorage.getItem('wbb-config');
if (saved) await api.importConfig(JSON.parse(saved));

// Send to backend for order processing
const herConfig = api.getSnapshot('her');
const herPrice = api.getPrice('her');
await fetch('/api/cart/add', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ config: herConfig, price: herPrice }),
});

Design Tips

Keep it simple. Start with profile, metal, and width. You don't need to expose every API option.

Use thumbnails. Catalog methods return thumbnail URLs when available. Use them for visual selectors instead of text labels.

Show loading state. Profile changes require geometry computation. Listen for build:started / build:complete to show a spinner.

Debounce sliders. For continuous sliders, debounce to avoid excessive rebuilds:

javascript
let timer;
slider.addEventListener('input', (e) => {
  clearTimeout(timer);
  timer = setTimeout(() => api.setWidth(parseFloat(e.target.value)), 50);
});

Test on mobile. Make sure your custom controls work on touch devices. The 3D viewer handles touch/pinch natively.

Next Steps

  • API Reference for complete method and event documentation
  • Pricing to configure the pricing engine
  • Measurements for ring size, diamond carat, and dimension details