# 入门指南

# 快速运行collaboration示例

请参阅 快速运行示例 章节启动服务器,然后在浏览器中打开以下地址以访问collaboration示例:

http://127.0.0.1:8080/examples/UIExtension/collaboration/index.html (opens new window)

# 工作原理

首先,打开API reference,找到collaboration相关的接口和类,如下所示:

Collaboration classes list in API reference

其中,关键性的接口是:

  1. CollaborationCommunicator

    该接口是浏览器和服务器之间的通信桥梁。它负责连接服务器、创建协作会话、同步数据等。在 /examples/UIExtension/collaboration 目录下,有一个基于communicator的sockjs,您可以参考其具体的实现,以了解更多详细信息。

  2. CollabroationDataHandler

    假设我们有两个客户端设备:客户端 A 和客户端 B。当客户端 A 创建一个注释时,其相关的注释数据将被发送到服务器,进而将进一步转发给客户端 B。当客户端 B 收到数据时,它会立即根据数据内容重复与客户端 A 相同的操作。此时,数据同步就实现了。客户端 B 的上述操作过程由 CollabroationDataHandler 接口实现,其定义了两种方法:

    • accept(data: CollaborationData): Promise<boolean>

      当接收到数据时,该方法将被调用并用于确认当前 CollaborationDataHandler 接口是否需要对数据进行处理。

    • receive(data: CollaborationData, builtinHandler?: CollaborationDataHandler)

      accept() 方法返回true时,此方法将被调用,并传入两个参数。第一个参数是当前收到的协作数据,第二个是内置数据handler。在某些情况下,您需要决定是否调用内置数据handler。当操作行为没有定义在 COLLABORATION_ACTION 时调用了内置数据handler,则会导致不再执行其他同步操作。

  3. CollaborationSessionInfo

    当客户端打开 PDF 文件时,它将向服务器发送一个创建新会话的请求。该会话包含当前 PDF 文件的信息以及其唯一标识shareId。通过这种方式,当分享带有shareId的链接给其他用户时,其他用户可以打开此链接并查看原始 PDF 文件,以及启动协作会话。

  4. UserCustomizeCollaborationData

    我们的 API 设计允许用户自定义协作操作。您可以通过 PDFViewer.collaborate API发送自定义协作数据,然后在自定义的 CollaborationDataHandler 中接收自定义协作数据以执行同步。

  5. COLLABORATION_ACTION

    内置协作操作的枚举。

协作如何工作?下面将详细解释整个过程。

备注:Foxit PDF SDK for Web提供的collaboration 示例使用了Node.js作为后端服务器,并使用websocket作为通信通道来建立长时间的协作连接。

  1. 客户端A打开PDF文档,其中文档信息将被发送到服务器,并以唯一的shareID发起一个新的会话。在浏览器中,您将看到如下类似的地址: http://localhost:8080/examples/UIExtension/collaboration/index.html?shareId=2MF8Rp7-Q

  2. 客户端A分享协作链接给其他的客户端,这些客户端在打开链接后将被连接到同一台服务器。

  3. 例如,客户端A执行创建一个注释的动作,该动作的操作数据将在后台同步发送到服务器,然后转发给其他的客户端。

  4. 当其他的客户端收到同步的数据后,Web SDK将找到接收(通过 accept 方法)操作数据的第一个数据handler,并使用 CollaborationDataHandler 处理接收的数据,然后在屏幕上输出与客户端 A 相同的操作。

至此,一个完整的协作操作过程已完成!

# 无线循环问题

查看以下代码,首先,注册 DataEvent.annotationAdded 事件,以便在事件触发后发送协作数据。然后,在一个button上注册一个单击事件,通过 PDFPage.addAnnot API创建注释。调用 PDFPage.addAnnot 方法将触发 DataEvent.annotationAdded 事件。这个逻辑看起来很完美。但是,假设此时有两个客户端 A 和 B。首先单击客户端 A 的按钮并发送协作数据,客户端 B 在收到协作数据后也调用 PDFPage.addAnnot 方法,然后问题就出现了。因为 PDFPage.addAnnot 触发了 DataEvents.annotationAdded 事件,事件回调函数也将发送协作数据,所有客户端在收到协作数据后将继续发送请求。在此过程中,无限循环问题就出现了。

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
            }
        });
    });
});

为了避免该问题,需要从 DataEvents 回调中移除 pdfViewer.collaborate 的调用,并将其放置到单击事件回调中。

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
            })
        });
    });
});