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)
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:
<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
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
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
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
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
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
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:
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:
// 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
// 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:
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