# Getting started with collaboration
# Quickly run collaboration example
Please refer to quickly-run-samples to start the service, and then open the following address in the browser to access the collaboration example:
http://127.0.0.1:8080/examples/UIExtension/collaboration/index.html (opens new window)
# Working principle
First, open the API reference, there are collaboration related interfaces and classes as shown below:
Among them, the operative keys are these:
CollaborationCommunicator
This interface is the communication bridge between the browser and the server. It is responsible for connecting to the server, creating a collaborative session, synchronizing data, etc. In the
/examples/UIExtension/collaboration
directory, there is a sockjs based communicator, you may refer to its implementation for more details.CollabroationDataHandler
Suppose we have two client devices: client A and client B. When client A creates an annotation, its related annotation data will be sent to the server, where data will be further forwarded to the client B. When client B receives the data, it immediately repeats the same operation as the client A based on the data content. At this point, the data synchronization is realized. The above operation process of client B is implemented by the
CollabroationDataHandler
interface, which defines two methods:accept(data: CollaborationData): Promise<boolean>
This method will be called and used to determine whether the current
CollaborationDataHandler
interface should handle the data when the data arrives.receive(data: CollaborationData, builtinHandler?: CollaborationDataHandler)
When the
accept()
method returns true, this method will be called with two parameters. The first is the currently received collaborative data and the second is a built-in data handler. In some custom cases, you should decide whether to call the built-in data handler, since if the built-in data handler is called when the operation action is not defined inCOLLABORATION_ACTION
, there is no longer to do other synchronizing operations.
CollaborationSessionInfo
When the client opens a PDF file, it will send a request to the server to create a new session. This session contains a unique shareId and information about the current PDF file. In this way, after sharing the link with the shareId to other users, the other users can open this link and see the original PDF file and start the collaboration session.
UserCustomizeCollaborationData
Our API design allows users to customize collaborative operations. You can send custom collaborative data through the
PDFViewer.collaborate
API, and then receive the custom collaborative data in the customCollaborationDataHandler
to perform synchronization.COLLABORATION_ACTION
Enumerations of the built-in collaboration actions.
How does the collaboration work? The following section explain a complete process in details.
Note: Our collaboration example uses Node.js as the backend server and uses websocket as a communication channel to establish a long connection for collaboration.
Client A opens a PDF document, where the document information will be sent to server to start a new session with a unique shareId. In the browser, you will see the address like this:
http://localhost:8080/examples/UIExtension/collaboration/index.html?shareId=2MF8Rp7-Q
Client A shares the collaborative link to other clients, who will be connected to the same server once they open the link.
Client A performs an action to create an annotation for example. The action operation data is sent to server synchronously on the background, and then forwarded to other clients.
When other clients receive the synchronizing data, Web SDK will find the first data handler that
accept
the operation data, and process the received data using theCollaborationDataHandler
, and then output the same action of the Client A on screen.
At this point, a complete collaborative operation is completed!
# The infinite loop problems
Looking at the following code, first, register the DataEvent.annotationAdded
event to send collaborative data after the event is triggered, then register a click event on a button to create annotation via PDFPage.addAnnot
API. The call of the PDFPage.addAnnot
method will trigger the DataEvent.annotationAdded
event. This logic looks perfect. However, assuming that there are two client A and B at this time. The button of client A is clicked first and a collaborative data is sent, and then client B also calls the PDFPage.addAnnot
method after receiving the collaborative data, and then the problem appears, because the PDFPage.addAnnot
triggered the DataEvents.annotationAdded
event, and the event callback sent a collaborative data, all clients would continue to send request after receiving the collaborative data. At this process an infinite loop problem rises.
var DataEvents = PDFViewCtrl.PDF.DataEvents;
pdfViewer.eventEmitter.on(DataEvents.annotationAdded, function(annots){
var doc = pdfViewer.getCurrentPDFDoc();
doc.exportAnnotsToJSON(annots).then(function(annotsJSONArray) {
pdfViewer.collaborate(PDFViewCtrl.collab.COLLABORATION_ACTION.CREATE_ANNOT, {
annots: annotsJSONArray
})
});
});
var $button = jQuery('#create-square-button');
$button.on('click', function() {
var doc = pdfViewer.getCurrentPDFDoc();
doc.getPageByIndex(0).then(function(page) {
page.addAnnot({
color: 0xff0000,
rect: { left: 300, right: 400, top: 300, bottom: 200 },
flags: 4,
type: 'square'
});
});
});
var $button = jQuery('#create-line-button');
$button.on('click', function() {
var doc = pdfViewer.getCurrentPDFDoc();
doc.getPageByIndex(0).then(function(page) {
page.addAnnot({
type: 'line',
startStyle: 0,
endStyle: PDF.constant.Ending_Style.Square,
startPoint: {x: 0, y: 0},
endPoint: {x: 100, y: 100},
rect: {
left: 0,
right: 100,
top: 0,
bottom: 100
}
});
});
});
To avoid the problem, we should move off the call of pdfViewer.collaborate
from the DataEvents
callback and place it in the click event callback.
var $button = jQuery('#create-square-button');
$button.on('click', function() {
var doc = pdfViewer.getCurrentPDFDoc();
doc.getPageByIndex(0).then(function(page) {
page.addAnnot({
color: 0xff0000,
rect: { left: 300, right: 400, top: 300, bottom: 200 },
flags: 4,
type: 'square'
}).then(function(annots) {
return doc.exportAnnotsToJSON(annots);
}).then(function(annotsJSONArray) {
pdfViewer.collaborate(PDFViewCtrl.collab.COLLABORATION_ACTION.CREATE_ANNOT, {
annots: annotsJSONArray
})
});
});
});
var $button = jQuery('#create-line-button');
$button.on('click', function() {
var doc = pdfViewer.getCurrentPDFDoc();
doc.getPageByIndex(0).then(function(page) {
page.addAnnot({
type: 'line',
startStyle: 0,
endStyle: PDF.constant.Ending_Style.Square,
startPoint: {x: 0, y: 0},
endPoint: {x: 100, y: 100},
rect: {
left: 0,
right: 100,
top: 0,
bottom: 100
}
}).then(function(annots) {
return doc.exportAnnotsToJSON(annots);
}).then(function(annotsJSONArray) {
pdfViewer.collaborate(PDFViewCtrl.collab.COLLABORATION_ACTION.CREATE_ANNOT, {
annots: annotsJSONArray
})
});
});
});