EmbedPDF

Several documents

A viewer can hold several documents at once, like tabs in a browser. One of them is the active document, and every component talks to it unless you say otherwise:

tabs.vue
<script setup lang="ts">
import { useDocuments, useDocumentsState } from '@embedpdf/vue/runtime';

const documents = useDocuments();
const { documents: open, activeId } = useDocumentsState();
</script>

<template>
  <div role="tablist">
    <div v-for="document in open" :key="document.id">
      <button
        role="tab"
        :aria-selected="document.id === activeId"
        @click="documents.setActive(document.id)"
      >
        {{ document.name }}
      </button>
      <button :aria-label="`Close ${document.name}`" @click="documents.close(document.id)">×</button>
    </div>
  </div>
</template>

Each document keeps its own state: its zoom and scroll position, its selection, its search and its active tool. Switch tabs and back, and everything is where you left it.

Loading live preview…

<script setup lang="ts">
import { DocumentGate, Viewer } from '@embedpdf/vue/runtime';
import type { OpenInput } from '@embedpdf/vue/runtime';
import { Stage, stagePlugin } from '@embedpdf/vue/stage';
import { RenderLayer, renderPlugin } from '@embedpdf/vue/render';
import { localEngine } from '@embedpdf/engine';
import TabTools from './TabTools.vue';
import Tabs from './Tabs.vue';

import '../tabs.css';

const engine = localEngine();
const plugins = [stagePlugin(), renderPlugin()];

const ebook = async (): Promise<OpenInput> => {
  const response = await fetch('https://snippet.embedpdf.com/ebook.pdf');
  return { kind: 'bytes', bytes: new Uint8Array(await response.arrayBuffer()) };
};
</script>

<template>
  <Viewer
    :engine="engine"
    :plugins="plugins"
    :initial-documents="[
      { source: ebook, name: 'Contract' },
      { source: ebook, name: 'Report' },
    ]"
  >
    <Tabs :ebook="ebook" />
    <DocumentGate>
      <template #fallback><p class="loading">Opening…</p></template>
      <TabTools />
      <Stage class="stage">
        <template #page><RenderLayer /></template>
      </Stage>
    </DocumentGate>
  </Viewer>
</template>

Manage the tabs#

documents.setActive(id);
documents.rename(id, 'Signed contract');
documents.move(id, 0); // to the front
await documents.close(id);

Each entry in documents, from useDocumentsState(), has the document’s id, its name, and its status: 'loading', 'locked', 'ready' or 'error', so a tab can show a spinner or a lock. Every field is listed under State.

Talk to a specific document#

A component talks to the active document. Inside <DocumentScope>, it talks to the one you name, whatever is active:

document-scope.vue
<script setup lang="ts">
import { DocumentScope } from '@embedpdf/vue/runtime';
import { RenderLayer } from '@embedpdf/vue/render';
import { Stage } from '@embedpdf/vue/stage';
import ZoomControls from './ZoomControls.vue';

defineProps<{ documentId: string }>();
</script>

<template>
  <DocumentScope :id="documentId">
    <Stage>
      <template #page><RenderLayer /></template>
    </Stage>
    <ZoomControls />
  </DocumentScope>
</template>

Everything inside, such as the Stage, useSearch() and useSelection(), uses that document. That’s how you show two documents at once, or a preview of one while another is active.

Loading live preview…

<script setup lang="ts">
import { Viewer } from '@embedpdf/vue/runtime';
import type { OpenInput } from '@embedpdf/vue/runtime';
import { stagePlugin } from '@embedpdf/vue/stage';
import { renderPlugin } from '@embedpdf/vue/render';
import { localEngine } from '@embedpdf/engine';
import SideBySide from './SideBySide.vue';

import '../scope.css';

const engine = localEngine();
const plugins = [stagePlugin(), renderPlugin()];

const ebook = async (): Promise<OpenInput> => {
  const response = await fetch('https://snippet.embedpdf.com/ebook.pdf');
  return { kind: 'bytes', bytes: new Uint8Array(await response.arrayBuffer()) };
};
</script>

<template>
  <Viewer
    :engine="engine"
    :plugins="plugins"
    :initial-documents="[
      { source: ebook, name: 'Original' },
      { source: ebook, name: 'Revised' },
    ]"
  >
    <SideBySide />
  </Viewer>
</template>

Side by side#

The view manager arranges documents into panes, each with its own tabs, like split editors in a code editor. Register viewManagerPlugin(), then render one Stage per pane:

panes.vue
<script setup lang="ts">
import { DocumentScope } from '@embedpdf/vue/runtime';
import { RenderLayer } from '@embedpdf/vue/render';
import { Stage } from '@embedpdf/vue/stage';
import { useViewManager, useViewManagerState } from '@embedpdf/vue/view-manager';
import PaneTabs from './PaneTabs.vue';

const views = useViewManager();
const { panes, focusedPaneId } = useViewManagerState();
</script>

<template>
  <div class="panes">
    <section
      v-for="pane in panes"
      :key="pane.id"
      :data-focused="pane.id === focusedPaneId"
      @focusin="views.setFocusedPane(pane.id)"
    >
      <PaneTabs :pane="pane" />
      <DocumentScope v-if="pane.activeDocumentId" :id="pane.activeDocumentId">
        <Stage>
          <template #page><RenderLayer /></template>
        </Stage>
      </DocumentScope>
    </section>
  </div>
</template>
views.splitPane(documentId); // the document in a new pane, beside the focused one
views.setActiveDocument(paneId, documentId); // show another of the pane's tabs
views.moveDocumentBetween(fromPaneId, toPaneId, documentId); // drag a tab to another pane
views.removePane(paneId); // its documents move to another pane

A document opened with documents.open() lands in the focused pane. Every call is listed under Methods.

Loading live preview…

<script setup lang="ts">
import { Viewer } from '@embedpdf/vue/runtime';
import type { OpenInput } from '@embedpdf/vue/runtime';
import { stagePlugin } from '@embedpdf/vue/stage';
import { renderPlugin } from '@embedpdf/vue/render';
import { viewManagerPlugin } from '@embedpdf/vue/view-manager';
import { localEngine } from '@embedpdf/engine';
import Panes from './Panes.vue';

import '../panes.css';

const engine = localEngine();
const plugins = [stagePlugin(), renderPlugin(), viewManagerPlugin()];

const ebook = async (): Promise<OpenInput> => {
  const response = await fetch('https://snippet.embedpdf.com/ebook.pdf');
  return { kind: 'bytes', bytes: new Uint8Array(await response.arrayBuffer()) };
};
</script>

<template>
  <Viewer
    :engine="engine"
    :plugins="plugins"
    :initial-documents="[
      { source: ebook, name: 'Contract' },
      { source: ebook, name: 'Report' },
    ]"
  >
    <Panes />
  </Viewer>
</template>

Methods#

What useDocuments() gives you for several documents. Opening and closing are on Opening documents, downloading and printing on Saving & downloading.

MethodWhat it does
setActive(id)Makes a document the active one: the one every component talks to outside <DocumentScope>. Manage the tabs
rename(id, name)Changes the name its tab shows. The file doesn’t change.
move(id, index)Moves a document to another place in the tab order
swap(id, otherId)Swaps two documents in the tab order
setOrder(ids)Puts every document in a new order at once. ids holds the id of every open document.

Everything useViewManager() gives you.

MethodWhat it does
splitPane(documentId, { from? })Moves an open document into a new pane beside the focused one, or beside from, and focuses it. Returns the new pane’s id. Side by side
createPane({ documentIds? })Adds a pane, empty or with these documents moved into it, and focuses it. Returns its id.
removePane(paneId, { moveDocumentsTo? })Closes a pane. Its documents move to moveDocumentsTo, or else to the focused or the last pane.
movePane(paneId, index)Moves a pane to another place in the order, for reordering panes by dragging
setFocusedPane(paneId)Makes a pane the focused one, where new documents open. null focuses none.
setActiveDocument(paneId, documentId)Shows another of the pane’s documents
addDocument(paneId, documentId, index?)Adds a document to a pane’s tabs. A document is in one pane at a time, so it leaves the pane it was in.
removeDocument(paneId, documentId)Takes a document out of a pane, without closing it
moveDocumentWithin(paneId, documentId, index)Moves a tab to another place inside its pane
moveDocumentBetween(fromPaneId, toPaneId, documentId, index?)Moves a tab from one pane to another, for dragging tabs between panes

State#

Everything useDocumentsState() gives you, as refs: your template updates when a value changes. The method in the last column reads the same value once.

FieldWhat it isRead once
documentsEvery open document, in tab order, including ones that are loading, locked or failed. Each has the fields useDocument() gives you.list(), has(id), getCount(), getOrder()
activeIdThe active document’s id, or null with no document opengetActiveId(), or getActive() for its fields

has(id) says whether a document is open, getCount() how many are, and getOrder() gives just their ids. One document’s fields come from get(id), on Opening documents.

Everything useViewManagerState() gives you, as refs: your template updates when a value changes. The method in the last column reads the same value once.

FieldWhat it isRead once
panesEvery pane, in order. Each has its id, its documentIds (its tabs, in order) and the activeDocumentId it shows.listPanes(), getPane(paneId), getPaneOrder(), getPaneOfDocument(documentId)
focusedPaneIdThe pane new documents open in, or nullgetFocusedPaneId()

getPane(paneId) reads one pane, getPaneOrder() gives just their ids, and getPaneOfDocument(documentId) says which pane holds a document.

Events#

active-route.vue
<script setup lang="ts">
import { useRouter } from 'vue-router';
import { useDocumentsEvent } from '@embedpdf/vue/runtime';

const router = useRouter();

useDocumentsEvent(
  (documents) => documents.onActiveChanged,
  ({ documentId }) => router.replace(`/documents/${documentId}`),
);
</script>
EventFires whenCarries
onActiveChangedAnother document became the active onedocumentId, previousDocumentId
View manager onPaneCreatedA pane was addedpaneId
View manager onPaneRemovedA pane was closedpaneId
View manager onFocusChangedAnother pane got the focuspaneId, or null when none has it
View manager onDocumentMovedA document moved between panesdocumentId, fromPaneId, toPaneId

The view manager’s events come from useViewManagerEvent(). Opening and closing documents are on Opening documents.

Good to know#

Memory#

Each open document holds its file in the engine’s memory. Close documents the user is done with, especially large ones.

One Stage per view#

A Stage shows one document. For two documents at once, render two Stages, each in its own <DocumentScope>. For two views of one document, see Two views of one document.

Was this page helpful?

Your feedback goes directly to the documentation team.