Guide · Vue

Zumly with Vue

A view can be a Vue component. Zumly hands you an empty element; you mount an app in it and unmount it when the view goes away.

Live example · VueOpen ↗

Each planet and the list are Vue components in their own views. Like a few times, open a planet, then come back: the count is still there.

The pattern

Make the view a function. It receives a context; mount an app in target.

import Zumly from 'zumly'
import 'zumly/style.css'
import { createApp } from 'vue'
import Detail from './Detail.vue'

const app = new Zumly({
  mount: '.canvas',
  initialView: 'home',
  views: {
    home: '<div class="z-view"><div class="zoom-me" data-to="detail" data-id="42">Open</div></div>',
    detail: ({ target, props, onCleanup }) => {
      const view = createApp(Detail, { id: props.id })
      view.mount(target)
      onCleanup(() => view.unmount())
    }
  }
})
app.init()

A list that opens a detail

Zumly is created once, in the component that owns the canvas, and destroyed with it. The list renders the triggers; any element with zoom-me and data-to works, whoever rendered it.

App.vue

<script setup>
import { ref, onMounted, onBeforeUnmount, createApp } from 'vue'
import Zumly from 'zumly'
import 'zumly/style.css'
import List from './List.vue'
import Detail from './Detail.vue'

// Each view gets its own Vue app
const mountView = (component, toProps = () => ({})) => ({ target, props, onCleanup }) => {
  const view = createApp(component, toProps(props))
  view.mount(target)
  onCleanup(() => view.unmount())
}

const canvas = ref(null)
let app

onMounted(() => {
  app = new Zumly({
    mount: canvas.value,
    initialView: 'list',
    views: {
      list: mountView(List),
      detail: mountView(Detail, props => ({ id: props.id }))
    }
  })
  app.init()
})

onBeforeUnmount(() => app?.destroy())   // removes its views too
</script>

<template>
  <!-- The canvas fills its nearest positioned ancestor: give that a height -->
  <div style="position: relative; height: 100vh">
    <div ref="canvas" class="zumly-canvas"></div>
  </div>
</template>

List.vue

<script setup>
const items = [
  { id: '1', name: 'Engine' },
  { id: '2', name: 'Battery' },
  { id: '3', name: 'Brakes' }
]
</script>

<template>
  <div class="list">
    <h1>Subsystems</h1>
    <div v-for="item in items" :key="item.id" class="zoom-me" data-to="detail" :data-id="item.id">
      {{ item.name }}
    </div>
  </div>
</template>

When a view comes back

Sharing services and state

Each view is its own Vue app, so install Pinia, the router or any plugin on it. Pass the shared instances once in componentContext; every view receives them as context.

const app = new Zumly({
  ...,
  componentContext: { pinia },
  views: {
    detail: ({ target, props, context, onCleanup }) => {
      const view = createApp(Detail, { id: props.id }).use(context.pinia)
      view.mount(target)
      onCleanup(() => view.unmount())
    }
  }
})

Things to know

import { createApp } from 'vue'
import type { ViewFunction } from 'zumly'

const detail: ViewFunction = ({ target, props, onCleanup }) => {
  const view = createApp(Detail, { id: String(props.id) })
  view.mount(target)
  onCleanup(() => view.unmount())
}

Next

Where views come from, in step 4. The same pattern for React · Svelte · Angular, and for radial layouts with Orbit.

Open this example ↗ Learn in six steps