Working with WebViewer loading events

This guide will go over the order of events that happen when WebViewer is instantiated and a document is loaded. The events in this guide are important and often used for certain behaviors while using WebViewer. For example, opening a notes panel after a document loads.

Here is a complete visualization of the order of events that occur. For a more detailed description, see the sections below.

Apryse Docs Image

Instantiation and Resource Loading

The first step you take when implementing WebViewer is creating a new instance of WebViewer. This is done with the PDFTron.WebViewer constructor, like so:

JavaScript

1WebViewer({
2 path: '/path/lib',
3 initialDoc: 'https://myserver.com/myfile.pdf'
4}, document.getElementById('viewer'));

WebViewer then creates/mounts an iframe in the DOM element that you provide and starts loading all the necessary resources in that iframe, including the UI and web workers.

The loading and mounting process is done asynchronously. When those resources are finished loading and WebViewer can be interacted with, the WebViewer promise resolves.

WebViewer promise (ready event)

Once the WebViewer promise resolves, you can begin to interact with WebViewer. This is the best point in time to perform setup like UI customizations, loading documents, subscribing to other WebViewer events, and any other functionality you may want to use.

Keep in mind that at this point, the document (if one was provided) has not been loaded and cannot be interacted with yet. See document loaded for more info.

1WebViewer({
2 initialDoc: 'https://myserver.com/myfile.pdf'
3}, document.getElementById('viewer'))
4 .then(instance => {
5 // you can disable annotations
6 instance.UI.disableAnnotations();
7
8 // or customize the UI
9 instance.UI.setTheme({ primary: 'blue', secondary: 'white' });
10
11 // etc..
12 });

Document Loaded

Once all the required WebViewer resources are loaded, documents can start loading. You can load a document either by passing a URL to the initialDoc constructor option, or calling loadDocument after the WebViewer promise resolves (as seen above).

The first step of loading a document is loading all, or part, of the document into memory. Once we have enough information about the document stored in memory, the first document lifecycle event is called, DocumentViewer.documentLoaded.

DocumentViewer.documentLoaded

This event is called when the document is loaded into memory and you can start interacting with the document. This includes page manipulation, loading annotations, initializing collaboration, and more!

You can bind to the event through the DocumentViewer like so:

1WebViewer({
2 initialDoc: 'https://myserver.com/myfile.pdf'
3}, document.getElementById('viewer'))
4 .then(instance => {
5 const { documentViewer, annotationManager } = instance.Core;
6
7 documentViewer.addEventListener('documentLoaded', () => {
8 // here you can get the document and perform actions on it,
9 // such as removing pages
10 documentViewer.getDocument().removePages([2]).then(() => { })
11
12
13 // or importing annotations from your server
14 getAnnotationsFromServer(DOCUMENT_ID).then(async xfdfString => {
15 const annotations = await annotationManager.importAnnotations(xfdfString);
16 annotationManager.drawAnnotationsFromList(annotations);
17 });
18 })
19 });

The documentLoaded event gets triggered every time a document gets loaded throughout the life of your app. Adding listeners for other events during this event will cause the event binding to occur multiple times when switching between documents and thus causes event handlers to trigger more than once. Unless this is a temporary binding or intentional, it would be best to hook into other events outside of this event scope.

Unsubscribe/Trigger once

If you want the callback to only be fired once, you can unsubscribe from the event like so:

1WebViewer(...)
2 .then(instance => {
3 const docViewer = instance.Core.documentViewer;
4
5 const callback = () => {
6 // unsubscribe immediatly after invoking
7 docViewer.removeEventListener('documentLoaded', callback);
8 }
9
10 docViewer.addEventListener('documentLoaded', callback);
11
12 // or
13
14 docViewer.addEventListener('documentLoaded', () => { }, { once: true });
15 })

Document Load Error Handling

On the other hand, if a document fails to load, a loaderror event will be triggered on the iframe window. Although WebViewer can recover from most loading errors, you may want to show a custom error message, submit a log to an API, and/or load a new document.

1WebViewer({
2 initialDoc: 'https://myserver.com/myfile.pdf'
3}, document.getElementById('viewer'))
4 .then(function(instance) {
5 const UIEvents = instance.UI.Events;
6 instance.UI.addEventListener(UIEvents.LOAD_ERROR, function(err) {
7 // Do something with error. eg. instance.showErrorMessage('An error has occurred')
8 });
9 });

Document and Annotation Rendering

After the document is in memory, we are ready to start rendering to the screen. The document and its annotations are rendered in parallel, and there are two main events that are fired during this cycle: pageComplete and annotationsLoaded.

DocumentViewer.pageComplete

The pageComplete event is fired for each page that is rendered. We only render the pages that are visible on the screen, so this event won't get fired for every page in the document at once. This event will get called when the user scrolls up and down the document, or when a page is zoomed or rotated, or anything else that makes it rerender.

A few extra pages nearby may be prerendered at lower priority, so pageComplete may be called for pages that are not currently visibie. You can set the prerender level with the SetPreRenderLevel function.

You can subscribe to this event similar to how you subscribe to the documentLoaded event (as seen in the previous section).

1WebViewer({
2 initialDoc: 'https://myserver.com/myfile.pdf'
3}, document.getElementById('viewer'))
4 .then(instance => {
5 const { documentViewer } = instance.Core;
6
7
8 documentViewer.addEventListener('pageComplete', (pageNumber, canvas) => {
9 // here it's guaranteed that page {pageNumber} is fully rendered
10 // you can get or set pixels on the canvas, etc
11 })
12 });

Remember that this callback is fired for every page on every document that is loaded. You can unsubscribe using the same way mentioned above.

DocumentViewer.annotationsLoaded

The annotationsLoaded event is fired when all the annotations have been loaded into memory. This is the ideal time that you can start interacting with the annotations, such as saving and loading, since all the annotations should be ready.

We load annotations asyncronously in the background and they may be rendered before this event is fired.

You can subscribe to the event on the AnnotationManager like so:

1WebViewer({
2 initialDoc: 'https://myserver.com/myfile.pdf'
3}, document.getElementById('viewer'))
4 .then(instance => {
5 const { documentViewer, annotationManager } = instance.Core;
6
7 documentViewer.addEventListener('annotationsLoaded', async () => {
8 // here you can start interacting with annotations,
9 // like saving the original annotations to your server
10
11 const xfdfString = await annotationManager.exportAnnotations({ widgets: false });
12 saveAnnotsStringToServer(xfdfString);
13 })
14 });

Did you find this helpful?

Trial setup questions?

Ask experts on Discord

Need other help?

Contact Support

Pricing or product questions?

Contact Sales