Skip to content

Vue 3 Integration: Complete Example

A Vue 3 composable and component for the Wedding Band Builder.

Back to Script Tag guide

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>