# # 入门指南

## # 快速运行collaboration示例

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

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

## # 工作原理

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

其中，关键性的接口是：

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