Opening documents
Give the viewer the documents to open when it starts, and <DocumentGate> shows your content once
the document is ready:
<script setup lang="ts">
import { DocumentGate, Viewer } from '@embedpdf/vue/runtime';
import { RenderLayer } from '@embedpdf/vue/render';
import { Stage } from '@embedpdf/vue/stage';
import { engine, plugins } from './pdf';
import Spinner from './Spinner.vue';
</script>
<template>
<Viewer
:engine="engine"
:plugins="plugins"
:initial-documents="[{ source: { kind: 'url', url: '/files/contract.pdf' } }]"
>
<DocumentGate>
<template #fallback><Spinner /></template>
<Stage>
<template #page><RenderLayer /></template>
</Stage>
</DocumentGate>
</Viewer>
</template>The viewer is on screen at once; the document opens in the background. Components outside
<DocumentGate>, such as your toolbar’s frame, render right away.
Open a document later#
useDocuments() opens documents while the app runs, for example one the user picked:
<script setup lang="ts">
import { useDocuments } from '@embedpdf/vue/runtime';
const documents = useDocuments();
async function onPick(event: Event) {
const file = (event.target as HTMLInputElement).files![0];
await documents.open({ kind: 'bytes', bytes: await file.arrayBuffer() }, { name: file.name });
}
</script>
<template>
<input type="file" accept="application/pdf" @change="onPick" />
</template>open() resolves with { document } once it’s open, and the new document becomes the active one.
Pass { activate: false } to open it in the background. Pick a PDF of your own here:
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 Empty from './Empty.vue';
import Toolbar from './Toolbar.vue';
import '../open.css';
const engine = localEngine();
const plugins = [stagePlugin(), renderPlugin()];
const welcome = async (): Promise<OpenInput> => {
const response = await fetch('/pdfs/welcome.pdf');
return { kind: 'bytes', bytes: new Uint8Array(await response.arrayBuffer()) };
};
</script>
<template>
<Viewer
:engine="engine"
:plugins="plugins"
:initial-documents="[{ source: welcome, name: 'welcome.pdf' }]"
>
<Toolbar :welcome="welcome" />
<DocumentGate>
<template #fallback><Empty /></template>
<Stage class="stage">
<template #page><RenderLayer /></template>
</Stage>
</DocumentGate>
</Viewer>
</template>Where a document comes from#
| Source | What it is |
|---|---|
{ kind: 'url', url } | A file the viewer fetches. The URL can be relative to your site. |
{ kind: 'bytes', bytes } | A file you already have: an upload, a file picker, a download. |
async (signal) => source | Anything else: a function that returns one of the above. |
The viewer calls a function under the document’s loading state, and signal fires if the user
closes the document first, so pass it on to what the function waits for.
Files that need a sign-in#
When your server wants a header or a cookie for a file, give the viewer your own fetch. Every
{ kind: 'url' } document downloads through it: it gets the URL, and an init with the
document’s cancel signal to pass on.
<script setup lang="ts">
import { DocumentGate, Viewer } from '@embedpdf/vue/runtime';
import { RenderLayer } from '@embedpdf/vue/render';
import { Stage } from '@embedpdf/vue/stage';
import { getToken } from './auth';
import { engine, plugins } from './pdf';
const withToken = (url: string, init: RequestInit) =>
fetch(url, { ...init, headers: { Authorization: `Bearer ${getToken()}` } });
</script>
<template>
<Viewer
:engine="engine"
:plugins="plugins"
:initial-documents="[{ source: { kind: 'url', url: '/api/files/contract.pdf' } }]"
:fetch="withToken"
>
<DocumentGate>
<Stage>
<template #page><RenderLayer /></template>
</Stage>
</DocumentGate>
</Viewer>
</template>For your cookies on another site, add credentials: 'include' instead; that site’s CORS headers
have to allow it. A server that answers 401 or 403 makes the document’s error
permission-denied, and documents.retry(id) calls your fetch again, so a token it reads when
it’s called is a fresh one. The engine’s own files never go through it.
Loading, errors and passwords#
<DocumentGate> has something to show for each state a document can be in:
<script setup lang="ts">
import { DocumentGate } from '@embedpdf/vue/runtime';
import { RenderLayer } from '@embedpdf/vue/render';
import { Stage } from '@embedpdf/vue/stage';
import PasswordForm from './PasswordForm.vue';
import Spinner from './Spinner.vue';
</script>
<template>
<DocumentGate>
<template #fallback><Spinner /></template>
<template #locked="{ document }"><PasswordForm :document="document" /></template>
<template #error="{ document }">
<p>Couldn't open {{ document.name }}: {{ document.error.message }}</p>
</template>
<Stage>
<template #page><RenderLayer /></template>
</Stage>
</DocumentGate>
</template>A password-protected PDF opens locked. Ask for the password and unlock it:
<script setup lang="ts">
import { ref } from 'vue';
import { useDocuments, type DocumentInfo } from '@embedpdf/vue/runtime';
import PasswordPrompt from './PasswordPrompt.vue';
const props = defineProps<{ document: DocumentInfo }>();
const documents = useDocuments();
const wrong = ref(false);
async function submit(password: string) {
try {
await documents.unlock(props.document.id, { password });
} catch {
wrong.value = true; // it stays locked; ask again
}
}
</script>
<template>
<PasswordPrompt :wrong="wrong" @submit="submit" />
</template>After an error, such as a network failure, documents.retry(id) tries again. Here the second
document isn’t a PDF, so it shows its error:
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 OpenError from './OpenError.vue';
import PasswordForm from './PasswordForm.vue';
import Tabs from './Tabs.vue';
import '../states.css';
const engine = localEngine();
const plugins = [stagePlugin(), renderPlugin()];
const welcome = async (): Promise<OpenInput> => {
const response = await fetch('/pdfs/welcome.pdf');
return { kind: 'bytes', bytes: new Uint8Array(await response.arrayBuffer()) };
};
// A file that isn't a PDF, so its document can't be opened.
const broken: OpenInput = { kind: 'bytes', bytes: new TextEncoder().encode('Not a PDF') };
</script>
<template>
<Viewer
:engine="engine"
:plugins="plugins"
:initial-documents="[
{ source: welcome, name: 'welcome.pdf' },
{ source: broken, name: 'broken.pdf', active: true },
]"
>
<Tabs />
<DocumentGate>
<template #fallback><div class="panel">Opening…</div></template>
<template #locked="{ document }"><PasswordForm :document="document" /></template>
<template #error="{ document }"><OpenError :document="document" /></template>
<Stage class="stage">
<template #page><RenderLayer /></template>
</Stage>
</DocumentGate>
</Viewer>
</template>To show the state yourself, for example on a tab, useDocument() gives you the document’s
status: 'loading', 'locked', 'ready' or 'error'. Every field is listed under
State.
Once a document is 'ready', each page’s picture can still take a moment to arrive. To show that
too, see Show that a page is loading.
Who the user is and what they may do#
Tell the viewer who the user is and what they may do, once. Every document it opens uses them:
<script setup lang="ts">
import { Viewer } from '@embedpdf/vue/runtime';
import { engine, plugins } from './pdf';
const identity = { userId: 'u_381', displayName: 'Dana Smith' };
const scope = ['doc.open', 'doc.render', 'doc.text.select', 'doc.annotate.read', 'annotations:create:self'];
</script>
<template>
<Viewer :engine="engine" :plugins="plugins" :identity="identity" :scope="scope" />
</template>identityis who the user is. TheirdisplayNamebecomes the author of the annotations they make.scopeis what they may do, as a list of permissions. Leave it out and they may do what the file allows: everything, unless the PDF restricts itself, as in Acrobat.
Loading live preview…
<script setup lang="ts">
import { ref } from 'vue';
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 { roles, type Role } from './roles';
import Toolbar from './Toolbar.vue';
import '../identity.css';
const engine = localEngine();
const plugins = [stagePlugin(), renderPlugin()];
const welcome = async (): Promise<OpenInput> => {
const response = await fetch('/pdfs/welcome.pdf');
return { kind: 'bytes', bytes: new Uint8Array(await response.arrayBuffer()) };
};
const dana = { userId: 'u_381', displayName: 'Dana Smith' };
const role = ref<Role>('reader');
</script>
<template>
<Viewer
:engine="engine"
:plugins="plugins"
:identity="dana"
:scope="roles[role]"
:initial-documents="[{ source: welcome, name: 'welcome.pdf' }]"
>
<Toolbar v-model:role="role" :welcome="welcome" />
<DocumentGate>
<template #fallback><p class="loading">Opening…</p></template>
<Stage class="stage">
<template #page><RenderLayer /></template>
</Stage>
</DocumentGate>
</Viewer>
</template>A document can have its own, which replace the viewer’s for that document:
const initialDocuments = [{ source, scope: ['doc.open', 'doc.render'] }]; // this one is read-only
await documents.open(source, { scope: ['doc.open', 'doc.render'] }); // and so is this onePermissions lists every permission.
Close a document#
await documents.close(document.id);Closing frees the document’s memory in the engine. The next document becomes the active one.
Methods#
What useDocuments() gives you for opening and closing documents and reading their pages. Tabs and
the active document are on Several documents, downloading and printing on
Saving & downloading.
| Method | What it does |
|---|---|
open(source, options?) | Opens a document and makes it the active one. Resolves { document } once it’s ready, or locked with a password; rejects when it can’t be opened. Open a document later |
canOpenFiles() | Whether open() takes a file’s bytes, such as a file the user picked, or a URL it downloads: true on the local engine, false on CloudPDF, where every document comes from your server |
openAll(documents) | Opens several documents, as initialDocuments does: every tab appears at once, in order, and the one marked active (or the first) becomes the active one. A failure shows in that document’s status instead of throwing. |
unlock(id, { password }) | Unlocks a locked document. Rejects on a wrong password, and the document stays locked so you can ask again. Loading, errors and passwords |
retry(id, options?) | Tries a failed document again, with the same source and options. Resolves like open(). A document from a grant takes what it waited for: { passphrase } or { identity }. Loading, errors and passwords |
identify(id, answers) | Answers what the document’s grant asks before the first change (its identityRequest): the change waiting goes out. Rejects while a required answer is missing or one can’t be taken, and the change keeps waiting. |
declineIdentity(id) | Takes back the change waiting for the visitor’s answers; the next change asks again. |
close(id), closeAll() | Closes one document, or every document, and frees its memory. Close a document |
get(id?) | A document’s fields, the ones useDocument() gives you, or null when it isn’t open. Without an id, the document in scope |
listPages(id?) | The document’s pages, in order. In a component, use usePageList(). |
getPage(page, id?) | One page, by its ref or its index, or null when the document doesn’t have it |
getPageIndex(ref, id?) | A page’s position, from 0, or -1 when the document doesn’t have it |
getRevision(id?) | A number that goes up each time pages are added, removed, moved or rotated; -1 with no document |
options for open() are name (what its tab shows), activate: false (open it in the
background), password, identity (who your page knows the user is), and, on the local engine,
scope. Without an id, the page
methods use the document in scope: the one a <DocumentScope>
around your component names, or else the active one. To stop an open that’s still loading, close the
document: that cancels its download too.
State#
useDocument() gives you the document a component talks to: the active one, or the one its
<DocumentScope> names. Its fields are refs: your template updates when one changes. Outside a component,
get(id) gives the same fields. With no document open yet, it reads as one still on its way: an
empty id and status: 'loading', so a component that shows it never has to check for null.
| Field | What it is |
|---|---|
id | The document’s id, for close(id), unlock(id, …) and <DocumentScope> |
name | The name its tab shows, from open() or rename() |
status | 'loading', 'locked', 'ready' or 'error' |
pageCount | How many pages it has; 0 until it’s ready |
hasUnsavedChanges | Whether it has changes that weren’t downloaded yet. Warn before changes are lost |
passwordProvided | When it’s locked: whether a password was tried and was wrong |
error | Why it couldn’t be opened, when status is 'error': { code, message } |
grantRequest | When it’s from a grant: the passphrase or the questions it waits for, to open or to go on |
identityRequest | When it’s ready and a change waits for the visitor’s answers to the grant’s questions |
Every open document, with the same fields, comes from useDocumentsState().
Events#
<script setup lang="ts">
import { ref } from 'vue';
import { useDocumentsEvent } from '@embedpdf/vue/runtime';
const message = ref('');
useDocumentsEvent(
(documents) => documents.onOpenFailed,
({ error }) => (message.value = `Couldn't open the file: ${error.message}`),
);
</script>
<template>
<p role="alert">{{ message }}</p>
</template>| Event | Fires when | Carries |
|---|---|---|
onOpened | A document is open and ready | documentId, info |
onOpenFailed | A document couldn’t be opened | documentId, error |
onLocked | A document needs a password | documentId, passwordProvided |
onClosed | A document was closed | documentId |
onPagesChanged | Pages were added, removed, moved or rotated | documentId, pages, revision |
Switching the active document is on Several documents.
Good to know#
Opening large files#
The local engine needs the whole file before it can show the first page, so a large PDF on a slow connection takes as long as its download.
initialDocuments is read once#
Like engine and plugins, the viewer reads initialDocuments when it mounts. To open another
document later, use documents.open().
Several documents#
A viewer can hold several documents at once, like tabs. See Several documents.
Your feedback goes directly to the documentation team.