Some test text!
Enable real-time collaboration on PDF, DOCX, PPTX and XLSX documents using this JavaScript sample. When a user creates a new annotation it will immediately be displayed in another user’s browser, where they can reply to annotations in real-time by adding their own comments. This sample works on all browsers (including IE11) and mobile devices without using plug-ins. For more details, refer to our real-time collaboration guide or visit our collaboration demo. Note: this example is setup with a Firebase backend, but you can use whichever backend you prefer. Learn more about our JavaScript PDF Library.
Get Started Samples DownloadTo run this sample, get started with a free trial of Apryse SDK.
JavaScript
HTML
const IDS = {
'https://pdftron.s3.amazonaws.com/downloads/pl/demo-annotated.pdf': 'foo-12',
'https://pdftron.s3.amazonaws.com/downloads/pl/report.docx': 'foo-13',
'https://pdftron.s3.amazonaws.com/downloads/pl/presentation.pptx': 'foo-14',
};
// eslint-disable-next-line no-undef
const server = new Server();
const initialDoc = 'https://pdftron.s3.amazonaws.com/downloads/pl/demo-annotated.pdf';
// NOTE: not for production use, delete this function when in production
const periodicallyRemoveOldAnnotations = data => {
// "1 * 60 * 60 * 1000" is one hour
const twentyFourHours = 24 * 60 * 60 * 1000;
const item = data.val();
if (!item.timestamp) {
return false;
}
const now = Date.now();
const passedTime = now - item.timestamp;
// If the timestamp of the annotation is passed
// 24 hours we delete this annotation here.
if (passedTime > twentyFourHours) {
server.deleteAnnotation(data.key);
return true;
}
return false;
};
// eslint-disable-next-line no-undef
const { WebViewerConstructor, uiOption } = getSampleOptions();
WebViewerConstructor(
{
path: '../../../lib',
initialDoc,
documentId: IDS[initialDoc],
ui: uiOption,
},
document.getElementById('viewer')
).then(instance => {
samplesSetup(instance);
const { documentViewer, annotationManager } = instance.Core;
let authorId = null;
const urlInput = document.getElementById('url');
const copyButton = document.getElementById('copy');
instance.UI.openElements(['notesPanel']);
let hasSeenPopup = false;
if (window.location.origin === 'http://localhost:3000') {
const xhttp = new XMLHttpRequest();
xhttp.onreadystatechange = () => {
if (xhttp.readyState === 4 && xhttp.status === 200) {
urlInput.value = `http://${xhttp.responseText}:3000/samples/annotation/realtime-collaboration/`;
}
};
xhttp.open('GET', '/ip', true);
xhttp.send();
} else {
urlInput.value = 'https://docs.apryse.com/samples/web/samples/annotation/realtime-collaboration/';
}
copyButton.onclick = () => {
urlInput.select();
document.execCommand('copy');
document.getSelection().empty();
};
documentViewer.addEventListener('documentLoaded', () => {
const documentId = documentViewer.getDocument().getDocumentId();
server.selectDocument(documentId);
const onAnnotationCreated = async data => {
// NOTE: not for production use, delete this "if block" when in production
if (periodicallyRemoveOldAnnotations(data)) {
return;
}
// Import the annotation based on xfdf command
const annotations = await annotationManager.importAnnotationCommand(data.val().xfdf);
const annotation = annotations[0];
if (annotation) {
await annotation.resourcesLoaded();
// Set a custom field authorId to be used in client-side permission check
annotation.authorId = data.val().authorId;
annotationManager.redrawAnnotation(annotation);
// viewerInstance.fireEvent('updateAnnotationPermission', [annotation]); //TODO
}
};
const onAnnotationUpdated = async data => {
// Import the annotation based on xfdf command
const annotations = await annotationManager.importAnnotationCommand(data.val().xfdf);
const annotation = annotations[0];
if (annotation) {
await annotation.resourcesLoaded();
// Set a custom field authorId to be used in client-side permission check
annotation.authorId = data.val().authorId;
annotationManager.redrawAnnotation(annotation);
}
};
const onAnnotationDeleted = data => {
// data.key would return annotationId since our server method is designed as
// annotationsRef.child(annotationId).set(annotationData)
const command = `<delete><id>${data.key}</id></delete>`;
annotationManager.importAnnotationCommand(command);
};
const openReturningAuthorPopup = authorName => {
if (hasSeenPopup) {
return;
}
// The author name will be used for both WebViewer and annotations in PDF
annotationManager.setCurrentUser(authorName);
// Open popup for the returning author
window.alert(`Welcome back ${authorName}`);
hasSeenPopup = true;
};
const updateAuthor = authorName => {
// The author name will be used for both WebViewer and annotations in PDF
annotationManager.setCurrentUser(authorName);
// Create/update author information in the server
server.updateAuthor(authorId, { authorName });
};
const openNewAuthorPopup = () => {
// Open prompt for a new author
const name = window.prompt('Welcome! Tell us your name :)');
if (name) {
updateAuthor(name);
}
};
// Bind server-side authorization state change to a callback function
// The event is triggered in the beginning as well to check if author has already signed in
server.bind('onAuthStateChanged', user => {
// Author is logged in
if (user) {
// Using uid property from Firebase Database as an author id
// It is also used as a reference for server-side permission
authorId = user.uid;
// Check if author exists, and call appropriate callback functions
server.checkAuthor(authorId, openReturningAuthorPopup, openNewAuthorPopup);
// Bind server-side data events to callback functions
// When loaded for the first time, onAnnotationCreated event will be triggered for all database entries
server.bind('onAnnotationCreated', onAnnotationCreated);
server.bind('onAnnotationUpdated', onAnnotationUpdated);
server.bind('onAnnotationDeleted', onAnnotationDeleted);
} else {
// Author is not logged in
server.signInAnonymously();
}
});
});
// Bind annotation change events to a callback function
annotationManager.addEventListener('annotationChanged', async (annotations, type, info) => {
// info.imported is true by default for annotations from pdf and annotations added by importAnnotationCommand
if (info.imported) {
return;
}
const xfdf = await annotationManager.exportAnnotationCommand();
// Iterate through all annotations and call appropriate server methods
annotations.forEach(annotation => {
let parentAuthorId = null;
if (type === 'add') {
// In case of replies, add extra field for server-side permission to be granted to the
// parent annotation's author
if (annotation.InReplyTo) {
parentAuthorId = annotationManager.getAnnotationById(annotation.InReplyTo).authorId || 'default';
}
if (authorId) {
annotation.authorId = authorId;
}
server.createAnnotation(annotation.Id, {
authorId,
parentAuthorId,
xfdf,
timestamp: Date.now(),
});
} else if (type === 'modify') {
// In case of replies, add extra field for server-side permission to be granted to the
// parent annotation's author
if (annotation.InReplyTo) {
parentAuthorId = annotationManager.getAnnotationById(annotation.InReplyTo).authorId || 'default';
}
server.updateAnnotation(annotation.Id, {
authorId,
parentAuthorId,
xfdf,
});
} else if (type === 'delete') {
server.deleteAnnotation(annotation.Id);
}
});
});
// Overwrite client-side permission check method on the annotation manager
// The default was set to compare the authorName
// Instead of the authorName, we will compare authorId created from the server
// Note that authorId can be undefined when annotation has just been created, need to handle it for fixing WVR-3217
annotationManager.setPermissionCheckCallback((author, annotation) => !annotation.authorId || annotation.authorId === authorId);
document.getElementById('select').onchange = e => {
const documentId = IDS[e.target.value];
instance.UI.loadDocument(e.target.value, { documentId });
};
});