Vue 3 Integration: Complete Example
A Vue 3 composable and component for the Wedding Band Builder.
Composable
javascript
// useWeddingBandBuilder.js
import { ref, onMounted, onUnmounted } from 'vue';
const SCRIPT_URL = 'https://releases.ijewel3d.com/libs/mini-viewer/latest/bundle.iife.js';
let scriptLoaded = false;
function loadScript() {
if (scriptLoaded) return Promise.resolve();
return new Promise((resolve, reject) => {
const script = document.createElement('script');
script.src = SCRIPT_URL;
script.onload = () => { scriptLoaded = true; resolve(); };
script.onerror = reject;
document.head.appendChild(script);
});
}
export function useWeddingBandBuilder(containerRef, options = {}) {
const api = ref(null);
const price = ref(null);
const ready = ref(false);
const {
manifestUrl = 'wedding-band-project.json',
basePath = 'https://your-cdn.com/wbb-assets/',
showUI = true,
theme = null,
} = options;
function handleReady(e) {
const viewer = e.detail.viewer;
const controller = viewer.getPluginByType('WeddingBandBuilder')?.controller;
if (!controller) return;
api.value = controller;
ready.value = true;
if (theme) controller.setTheme(theme);
controller.events.on('price:updated', (data) => {
if (data.pricing) price.value = data.pricing;
});
}
onMounted(async () => {
await loadScript();
if (!containerRef.value) return;
window.addEventListener('ijewel-viewer-ready', handleReady);
new window.ijewelViewer.Viewer(containerRef.value, {
name: 'Wedding Band Builder',
version: 'v5',
basePath,
plugins: {
WeddingBandBuilder: { manifestUrl, showUI },
},
}, {
showCard: false,
showSwitchNode: false,
showUiButtons: showUI,
showConfigurator: false,
showZoomButtons: showUI,
enableZoom: true,
});
});
onUnmounted(() => {
window.removeEventListener('ijewel-viewer-ready', handleReady);
});
return { api, price, ready };
}Component with Built-in UI
vue
<!-- WeddingBandBuilder.vue -->
<template>
<div class="wbb-container">
<div ref="viewerEl" class="wbb-viewer" />
<div v-if="price" class="wbb-price">
${{ price.totalUsd.toFixed(2) }}
</div>
</div>
</template>
<script setup>
import { ref } from 'vue';
import { useWeddingBandBuilder } from './useWeddingBandBuilder';
const props = defineProps({
basePath: { type: String, default: 'https://your-cdn.com/wbb-assets/' },
showUI: { type: Boolean, default: true },
theme: { type: [String, Object], default: null },
});
const emit = defineEmits(['ready', 'price-change']);
const viewerEl = ref(null);
const { api, price, ready } = useWeddingBandBuilder(viewerEl, {
basePath: props.basePath,
showUI: props.showUI,
theme: props.theme,
});
watch(ready, (val) => { if (val) emit('ready', api.value); });
watch(price, (val) => { if (val) emit('price-change', val); });
</script>
<style scoped>
.wbb-container { width: 100%; height: 100%; position: relative; }
.wbb-viewer { width: 100%; height: 100%; }
.wbb-price {
position: absolute;
bottom: 16px;
right: 16px;
font-size: 24px;
font-weight: 600;
background: rgba(0,0,0,0.7);
color: #fff;
padding: 8px 16px;
border-radius: 8px;
}
</style>Usage
vue
<template>
<div style="width: 100vw; height: 100vh;">
<WeddingBandBuilder
theme="luxury-gold"
@ready="onReady"
@price-change="onPrice"
/>
</div>
</template>
<script setup>
import WeddingBandBuilder from './WeddingBandBuilder.vue';
function onReady(api) {
console.log('Profiles:', api.getAvailableProfiles());
}
function onPrice(pricing) {
console.log(`Total: $${pricing.totalUsd.toFixed(2)}`);
}
</script>Headless with Custom Controls
vue
<template>
<div class="app">
<div ref="viewerEl" class="viewer" />
<div v-if="api" class="controls">
<div class="row">
<button
v-for="p in profiles" :key="p.index"
:class="{ active: activeProfile === p.index }"
@click="selectProfile(p.index)"
>
{{ p.name }}
</button>
</div>
<div class="row">
<button
v-for="m in metals" :key="m.id"
@click="api.setMaterial(1, m.id, 'Polished')"
>
{{ m.name }}
</button>
</div>
<div class="row">
<input type="range" min="2" max="10" step="0.1" v-model.number="width" @input="api.setWidth(width)" />
<span>{{ width.toFixed(1) }} mm</span>
</div>
<div v-if="price" class="price">${{ price.totalUsd.toFixed(2) }}</div>
</div>
</div>
</template>
<script setup>
import { ref, computed } from 'vue';
import { useWeddingBandBuilder } from './useWeddingBandBuilder';
const viewerEl = ref(null);
const { api, price } = useWeddingBandBuilder(viewerEl, { showUI: false });
const width = ref(4.0);
const activeProfile = ref(0);
const profiles = computed(() => api.value?.getAvailableProfiles() || []);
const metals = computed(() => api.value?.getAvailableMetals() || []);
async function selectProfile(index) {
await api.value.setProfile(index);
activeProfile.value = index;
}
</script>