Some test text!

menu

Realtime PDF collaboration with JavaScript

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 IE10) 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 StartedSamplesDownload

To run this sample, get started with a free trial of PDFTron SDK.

JavaScript

HTML

// eslint-disable-next-line no-undef
var server = new Server();












WebViewer({
  path: '../../../lib',
  pdftronServer: 'https://demo.pdftron.com/', // comment this out to do client-side only
  initialDoc: 'https://pdftron.s3.amazonaws.com/downloads/pl/demo-annotated.pdf'
}, document.getElementById('viewer'))
  .then(function(instance) {
    var docViewer = instance.docViewer;
    var annotManager = instance.annotManager;
    var urlInput = document.getElementById('url');
    var copyButton = document.getElementById('copy');
    instance.openElement('notesPanel');

    if (window.location.origin === 'http://localhost:3000') {
      var xhttp = new XMLHttpRequest();
      xhttp.onreadystatechange = function() {
        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://pdftron.com/samples/web/samples/annotation/realtime-collaboration/';
    }

    copyButton.onclick = function() {
      urlInput.select();
      document.execCommand('copy');
      document.getSelection().empty();
    };

    docViewer.on('documentLoaded', function() {
      var authorId = null;

      function onAnnotationCreated(data) {
        // Import the annotation based on xfdf command
        var annotation = annotManager.importAnnotCommand(data.val().xfdf)[0];
        // Set a custom field authorId to be used in client-side permission check
        annotation.authorId = data.val().authorId;
        annotManager.redrawAnnotation(annotation);
      }

      function onAnnotationUpdated(data) {
        // Import the annotation based on xfdf command
        var annotation = annotManager.importAnnotCommand(data.val().xfdf)[0];
        // Set a custom field authorId to be used in client-side permission check
        annotation.authorId = data.val().authorId;
        annotManager.redrawAnnotation(annotation);
      }

      function onAnnotationDeleted(data) {
        // data.key would return annotationId since our server method is designed as
        // annotationsRef.child(annotationId).set(annotationData)
        var command = '<delete><id>' + data.key + '</id></delete>';
        annotManager.importAnnotCommand(command);
      }

      function openReturningAuthorPopup(authorName) {
        // The author name will be used for both WebViewer and annotations in PDF
        annotManager.setCurrentUser(authorName);
        // Open popup for the returning author
        window.alert('Welcome back ' + authorName);
      }

      function openNewAuthorPopup() {
        // Open prompt for a new author
        var name = window.prompt('Welcome! Tell us your name :)');
        if (name) {
          updateAuthor(name);
        }
      }

      function updateAuthor(authorName) {
        // The author name will be used for both WebViewer and annotations in PDF
        annotManager.setCurrentUser(authorName);
        // Create/update author information in the server
        server.updateAuthor(authorId, { authorName: authorName });
      }

      // 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', function(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
      annotManager.on('annotationChanged', function(e, annotations, type) {
        // e.imported is true by default for annotations from pdf and annotations added by importAnnotCommand
        if (e.imported) {
          return;
        }
        // Iterate through all annotations and call appropriate server methods
        annotations.forEach(function(annotation) {
          var xfdf = annotManager.getAnnotCommand();
          var 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 = annotManager.getAnnotationById(annotation.InReplyTo).authorId || 'default';
            }
            server.createAnnotation(annotation.Id, {
              authorId: authorId,
              parentAuthorId: parentAuthorId,
              xfdf: xfdf
            });
          } 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 = annotManager.getAnnotationById(annotation.InReplyTo).authorId || 'default';
            }
            server.updateAnnotation(annotation.Id, {
              authorId: authorId,
              parentAuthorId: parentAuthorId,
              xfdf: 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
      annotManager.setPermissionCheckCallback(function(author, annotation) {
        return annotation.authorId === authorId;
      });
    });
  });
close

Free Trial

Get unlimited trial usage of PDFTron SDK to bring accurate, reliable, and fast document processing capabilities to any application or workflow.

Select a platform to get started with your free trial.

Unlimited usage. No email address required.