此文章由机器翻译。

Microsoft Office

探讨 JavaScript API for Office: 数据访问和事件

Stephen Oliver
Eric Schmidt

 

本文是 JavaScript API for Office 的深入演练系列文章的第二篇。第 1 部分(可在 msdn.microsoft.com/magazine/jj891051 处获得)是对象模型的宽泛概述。本文接着第 1 部分的内容开始介绍,详细演练了如何访问文件内容并考察了事件模型。

在整个系列中,我们经常引用 JavaScript API for Office 参考文档。您可以在 MSDN 上的“Office 和 SharePoint 应用程序开发人员中心”(dev.office.com) 找到官方文档、示例代码和社区资源。

从 Office 应用程序访问 Office 文件内容

JavaScript API for Office 为访问 Office 文件中的数据提供了几个基本方式: 可以获取或设置当前所选数据,也可以获取整个文件。这种数据访问级别可能看上去比较简单,而且这两种方法的确十分容易使用。但是,这两种方法的灵活性和自定义程度非常高,从而为您的应用程序提供了诸多可能。

除了访问所选数据或整个文件,JavaScript API for Office 还允许绑定到数据或在文档中操作自定义 XML 部件。我们会更深入地了解这些使用 Office 内容的技术。

获取和设置所选数据

如前所述,通过 Document 对象,应用程序可以对文件中的数据进行访问。对于任务窗格和内容应用程序,可以使用 Document.getSelectedDataAsync 和 Document.setSelectedDataAsync 方法获取或设置 Office 文件中的所选内容。

通过调用这两种方法,可对多种类型的数据格式进行操作。getSelectedDataAsync 和 setSelectedDataAsync 方法都具有一个参数 coercionType,该参数的值是 Office.CoercionType 枚举常量。coercionType 参数指定所要获取或设置的内容的数据格式。根据 coercionType 参数的值,可以按纯文本、表、矩阵、HTML、甚至是“原始”Office Open XML (OOXML) 的形式选择数据。(请注意,截止发布时,仅 Word 2013 中支持以 HTML 或 OOXML 的形式获取和设置文本。)

使用 getSelectedDataAsync 和 setSelectedDataAsync 时,可以不指定 coercionType。会尽可能从上下文推断 coercionType。例如,如果调用 setSelectedDataAsync 时传入的是字面字符串,则默认 coercionType 为“text”。如果以数组的数组的形式传入相同数据,则默认 coercionType 为“matrix”。

我们会提供一些示例来说明这些简单方法有多么强大(主要以 setSelectedDataAsync 方法为例)。首先演示一段将某些简单文本插入 Word 文档的代码:

// Define some data to set in the document.
var booksToRead = "Anabasis by Xenophon; \n" +
  "Socrates' Apology by Plato; \n" +
  "The Illiad by Homer.";
// Set some data to the document as simple text.
Office.context.document.setSelectedDataAsync(
  booksToRead,
  { coercionType: Office.CoercionType.Text },
  function (result) {
    // Access the results, if necessary.
});

结果如图 1 所示。

Results of Inserting Data as Simple Text
图 1 以简单文本形式插入数据的结果

现在,我们对该示例做些更改,以“矩阵”强制类型 (coercionType) 的形式插入文本。矩阵是数组的数组,在 Excel 中以一系列单元格(简单范围)的形式插入,在 Word 中以简单表的形式插入。

插入到 Word 中时,此代码会插入包含两个列的表,该表不含标题且未设置格式。第一级数组中的每个项分别对应于所生成的表的一行;子数组中的每个项则分别对应于该行中的一个数据单元格:

// Define a matrix of data to set in the document.
var booksToRead = [["Xenophon", "Anabasis"],
  ["Plato", "Socrates' Apology"],
  ["Homer", "The Illiad"]];
// Set some data to the document as an unformatted table.
Office.context.document.setSelectedDataAsync(
  booksToRead,
  { coercionType: Office.CoercionType.Matrix },
  function (result) {
    // Access the results, if necessary.
});

结果如图 2 所示。

Results of Inserting Data as a Matrix
图 2 以矩阵形式插入数据的结果

除了矩阵强制类型之外,还可以使用 TableData 对象以表的形式获取或设置数据。这使我们可以为结果增加一些格式设置 — 在此特定示例中,我们来增加一个标题行。我们将分别使用标题和行属性访问 TableData 对象的标题行和内容。

对于 TableData 对象,还可以使用 startRow 和 startColumn 参数指定要插入的数据子集。例如,在一个含有五个列的表中,您可以使用该功能将数据填充到某个列中。在本系列的下一篇文章中,我们会更深入地探讨 startRow 和 startColumn 参数。

注: 如果文档中的选中内容是一个表,则选中内容形状必须与待插入数据相匹配(若未指定 startRow 和 startColumn 参数)。即,如果待插入数据是一个 2 x 2 表,而文档中的选中内容是表中的 3 x 2 个单元格,则方法会失败。这也适用于以矩阵形式插入数据。

与矩阵强制类型一样,标题和行属性会返回数组的数组,其中第一级数组中的各项分别对应一个数据行,子数组中的各项分别对应表中的一个数据单元格,如图 3 所示。

图 3 以表形式将数据插入文档

// Define some tabular data to set in the document,
// including a header row.
var booksToRead = new Office.TableData();
booksToRead.headers = [["Author", "Title"]];
booksToRead.rows = [["Xenophon", "Anabasis"],
  ["Plato", "Socrates' Apology"],
  ["Homer", "The Illiad"]];
// Set some data to the document as a table with a header.
Office.context.document.setSelectedDataAsync(
  booksToRead,
  { coercionType: Office.CoercionType.Table },
  function (result) {
    // Access the results, if necessary.
});

图 4 显示图 3 中代码的结果。

Results of Inserting Data as a Table
图 4 以表形式插入数据的结果

对于下一个示例,会插入相同数据,不过这次格式化为 HTML,强制类型为 Office.CoercionType.HTML。现在,我们可以为插入的数据添加其他格式设置(如 CSS 样式),如图 5 所示。

图 5 以 HTML 形式将数据插入文档

// Define some HTML data to set in the document,
// including header row, text formatting and CSS styles.
var booksToRead =
  "<table style='font-family:Segoe UI'>" +
    "<thead style='background-color:#283E75;color:white'>" +
      "<tr><th>Authors</th><th>Books</th></tr>" +
    "</thead>" +
    "<tbody>" +
      "<tr><td>Xenophon</td><td><u>Anabasis</u></td></tr>" +
      "<tr><td>Plato</td><td><u>Socrates' Apology</u></td></tr>" +
      "<tr><td>Homer</td><td><u>The Iliad</u></td></tr>" +
    "</tbody>" +
  "</table>";
// Set some data to the document as a table with styles applied.
Office.context.document.setSelectedDataAsync(
  booksToRead,
  { coercionType: Office.CoercionType.Html },
    function (result) {
    // Access the results, if necessary.
});

图 6 显示图 5 中代码的结果。

Results of Inserting Data as HTML
图 6 以 HTML 形式插入数据的结果

最后,我们还可以将文本以 OOXML 的形式插入文档,这使我们可以在很大程度上自定义数据,并在 Word 中使用许多更高级的内容类型(例如 SmartArt 或嵌入式图片)。

我们将用到的数据表如图 7 中的代码所示,该数据表为 OOXML 的形式并存储在字面字符串中,(注意: 为简洁起见,仅展示了部分表)。

图 7 表示一个 Word 表的 OOXML 代码段,存储为 JavaScript 字面字符串

var newTable = "<w:tbl>" +
  "<w:tblPr>" +
    "<w:tblStyle w:val=\"TableGrid\"/>" +
    "<w:tblW w:w=\"0\" w:type=\"auto\"/>" +
    "<w:tblBorders>" +
      "<w:top w:val=\"single\" w:sz=\"4\" w:space=\"0\"" +
        "w:color=\"283E75\"/>" +
      "<w:left w:val=\"single\" w:sz=\"4\" w:space=\"0\"" +
        "w:color=\"283E75\"/>" +
      "<w:bottom w:val=\"single\" w:sz=\"4\" w:space=\"0\"" +
        "w:color=\"283E75\"/>" +
      "<w:right w:val=\"single\" w:sz=\"4\" w:space=\"0\"" +
        "w:color=\"283E75\"/>" +
      "<w:insideH w:val=\"single\" w:sz=\"4\" w:space=\"0\"" +
        "w:color=\"283E75\"/>" +
      "<w:insideV w:val=\"single\" w:sz=\"4\" w:space=\"0\"" +
        "w:color=\"283E75\"/>" +
      "</w:tblBorders>" +
    "<w:tblLook w:val=\"04A0\" w:firstRow=\"1\" w:lastRow=\"0\"" +
      "w:firstColumn=\"1\" w:lastColumn=\"0\""  +
      "w:noHBand=\"0\" w:noVBand=\"1\"/>" +
  "</w:tblPr>" +
  "<w:tblGrid>" +
    "<w:gridCol w:w=\"4675\"/>" +
    "<w:gridCol w:w=\"4675\"/>" +
  "</w:tblGrid>" +
  "<w:tr w:rsidR=\"00431544\" w:rsidTr=\"00620187\">" +
    "<w:tc>" +
      "<w:tcPr>" +
        "<w:tcW w:w=\"4675\" w:type=\"dxa\"/>" +
        "<w:shd w:val=\"clear\" w:color=\"auto\" w:fill=\"283E75\"/>" +
      "</w:tcPr>" +
      "<w:p w:rsidR=\"00431544\" w:rsidRPr=\"00236B94\""  +
        "w:rsidRDefault=\"00431544\" w:rsidP=\"00620187\">" +
        "<w:pPr>" +
          "<w:rPr>" +
            "<w:b/>" +
            "<w:color w:val=\"FEFEFE\"/>" +
          "</w:rPr>" +
        "</w:pPr>" +
        "<w:r w:rsidRPr=\"00236B94\">" +
          "<w:rPr>" +
            "<w:b/>" +
            "<w:color w:val=\"FEFEFE\"/>" +
          "</w:rPr>" +
          "<w:t>Authors</w:t>" +
        "</w:r>" +
      "</w:p>" +
    "</w:tc>" +
    "<w:tc>" +
      "<w:tcPr>" +
        "<w:tcW w:w=\"4675\" w:type=\"dxa\"/>" +
        "<w:shd w:val=\"clear\" w:color=\"auto\" w:fill=\"283E75\"/>" +
      "</w:tcPr>" +
      "<w:p w:rsidR=\"00431544\" w:rsidRPr=\"00236B94\"" +
        "w:rsidRDefault=\"00431544\" w:rsidP=\"00620187\">" +
        "<w:pPr>" +
          "<w:rPr>" +
            "<w:b/>" +
            "<w:color w:val=\"FEFEFE\"/>" +
          "</w:rPr>" +
        "</w:pPr>" +
        "<w:r w:rsidRPr=\"00236B94\">" +
          "<w:rPr>" +
            "<w:b/>" +
            "<w:color w:val=\"FEFEFE\"/>" +
          "</w:rPr>" +
          "<w:t>Books</w:t>" +
        "</w:r>" +
      "</w:p>" +
    "</w:tc>" +
  "</w:tr>" +
  "<w:tr w:rsidR=\"00431544\" w:rsidTr=\"00620187\">" +
    "<w:tc>" +
      "<w:tcPr>" +
        "<w:tcW w:w=\"4675\" w:type=\"dxa\"/>" +
      "</w:tcPr>" +
      "<w:p w:rsidR=\"00431544\" w:rsidRDefault=\"00431544\"" +
        "w:rsidP=\"00620187\">" +
        "<w:r>" +
          "<w:t>Xenophon</w:t>" +
        "</w:r>" +
      "</w:p>" +
    "</w:tc>" +
    "<w:tc>" +
      "<w:tcPr>" +
        "<w:tcW w:w=\"4675\" w:type=\"dxa\"/>" +
      "</w:tcPr>" +
      "<w:p w:rsidR=\"00431544\" w:rsidRDefault=\"00431544\"" +
        "w:rsidP=\"00620187\">" +
        "<w:r>" +
          "<w:t>Anabasis</w:t>" +
        "</w:r>" +
      "</w:p>" +
    "</w:tc>" +
  "</w:tr>" +
  // The rest of the code has been omitted for the sake of brevity.
"
</w:tbl>";

此方法还需要非常熟悉 XML,特别是 OOXML 标准 (ECMA-376) 描述的结构。将 OOXML 插入到文档中时,数据必须存储为一个字符串(但不能插入 HTML 文档对象),其中包含全部所需的信息,包括文件格式包中的关系及相关文档部件。因而,在使用 OOXML 将更高级的内容类型插入 Word 时,必须遵循使用 OOXML 的最佳做法和开放数据包约定来操作 OOXML 数据。

图 8 中,我们分步解决此问题,首先以 OOXML 形式获取数据,然后将我们的数据与来自文档的 OOXML 拼接(将获取的数据和新数据作为字符串进行操作),最后将其插回文档。(当然,此代码之所以能够工作,部分原因在于我们所添加的内容都不要求在文件中添加或更改任何关系或文档部件。)

图 8 使用 OOXML 以表形式将数据插入文档

// Get the OOXML for the data at the point of insertion
// and add a table at the beginning of the selection.
Office.context.document.getSelectedDataAsync(
  Office.CoercionType.Ooxml,
  {
    valueFormat: Office.ValueFormat.Formatted,
    filterType: Office.FilterType.All
  },
  function (result) {
    if (result.status == "succeeded") {
      // Get the OOXML returned from the getSelectedDataAsync call.
var selectedData = result.value.toString();
      // Define the new table in OOXML.
var newTable = "<!--Details omitted for brevity.-->";
      // Find the '<w:body>' tag in the returned data—the tag
      // that represents the body content of the selection, contained
      // within the main document package part (/word/document.xml)—
      // and then insert the new table into the OOXML at that point.
var newString = selectedData.replace(
        "<w:body>",
        "<w:body>" + newTable,
        "gi");
        // Insert the data back into the document with the table added.
Office.context.document.setSelectedDataAsync(
          newString,
          { coercionType: Office.CoercionType.Ooxml },
          function () {
        });
    }
});

图 9 显示图 8 中代码的结果。

Results of Inserting Data as OOXML
图 9 以 OOXML 形式插入数据的结果

注: 有一个比较好的方法可以了解如何从应用程序操作 OOXML:使用 UI 添加要使用的内容(例如,通过单击“单击”|“插图”|“SmartArt”插入 SmartArt),然后使用 getSelectedDataAsync 获取内容的 OOXML,最后读取结果。有关更多详细信息,请参见 bit.ly/SeU3MS 上的“使用 Office 应用程序插入图像”。

获取文件中的所有内容

获取或设置选择点处的数据固然很有用处,但某些情况下,我们需要获取文件中的所有内容。例如,应用程序可能需要以文本形式获取文档中的所有内容,分析这些内容,然后在气泡图中表示。另一个示例是,应用程序可能需要将文件中的所有内容发送到远程 Web 服务,以便进行远程打印或传真。

JavaScript API for Office 恰好提供了用于这些情况的功能。通过使用 JavaScript API,应用程序可以为被插入文件创建一个副本,将副本划分为指定大小的数据区块或“切片”(最大 4MB),然后读取切片中的数据。

获取文件中的所有内容的过程基本上包括三个步骤:

  1. 对于插入 Word 或 PowerPoint 的应用程序,应用程序会调用 Document.getFileAsync 方法,它将返回一个 File 对象,该对象对应于一个文件副本。
  2. 应用程序获得对该文件的引用之后,便可以调用 File.getSliceAsync 方法访问文件中的特定切片(通过传入要获取的切片的索引)。如果采用 for 循环来实现,则调用代码必须谨慎对待对闭包的处理。
  3. 最后,应用程序应在使用完 File 对象之后,通过调用 File.closeAsync 方法关闭该对象。任何时候内存中只能驻留两个文件;尝试使用 Document.getFileAsync 打开第三个文件会引发错误:“发生了内部错误”。

图 10 中,我们获取一个划分为 1KB 区块的 Word 文档,遍历文件中的每个区块,完成后关闭文件。

图 10 以文本形式获取文件中的所有内容,然后遍历切片

// Get all of the content from a Word document in 1KB chunks of text.
function getFileData() {
  Office.context.document.getFileAsync(
  Office.FileType.Text,
  {
    sliceSize: 1000
  },
  function (asyncResult) {
    if (asyncResult.status === 'succeeded') {
      var myFile = asyncResult.value,
        state = {
          file: myFile,
          counter: 0,
          sliceCount: myFile.sliceCount
        };
      getSliceData(state);
    }
  });
}
// Get a slice from the file, as specified by
// the counter contained in the state parameter.
function getSliceData(state) {
  state.file.getSliceAsync(
    state.counter,
    function (result) {
    var slice = result.value,
      data = slice.data;
    state.counter++;
    // Do something with the data.
// Check to see if the final slice in the file has
    // been reached—if not, get the next slice;
    // if so, close the file.
if (state.counter < state.sliceCount) {
      getSliceData(state);
    }
    else {
      closeFile(state);
    }
  });
}
// Close the file when done with it.
function closeFile(state) {
  state.file.closeAsync(
    function (results) {
      // Inform the user that the process is complete.
});
}

有关如何在 Office 应用程序中获取所有文件内容的更多信息,请参见“如何: 从 PowerPoint 应用程序获取整个文档”,地址为 bit.ly/12Asi4x

从项目中获取任务数据、视图数据和资源数据

对于插入 Project 的任务窗格应用程序,JavaScript API for Office 提供了其他方法为活动项目和所选任务、资源或视图读取数据。project-15.js 脚本扩展了 office.js,还为任务、资源和视图添加了选中内容更改事件。例如,当用户在“工作组规划器”视图中选择一个任务时,应用程序可以在一个位置将信息集成起来并进行显示,这些信息可以是为该任务安排的剩余工作、可参与该任务的人员,以及其他 SharePoint 任务列表中或 Project Server 中会对日程安排产生影响的相关项目。

插入项目的任务窗格应用程序对项目中内容只有读取访问权限。但是,因为任务窗格应用程序处于网页的核心位置,所以可以使用 JavaScript 和协议(如具象状态传输 (REST))对外部应用程序进行读取和写入。例如,用于 Office 和 SharePoint 文档的应用程序含有一个针对 Project Professional 示例应用程序,该应用程序将 jQuery 与 Project 中的 OData 报告服务结合使用,将活动项目的总成本和工作数据与 Project Web App 中所有项目的平均值进行比较(请参见图 11)。

A Task Pane App that Uses jQuery with an OData Reporting Service
图 11 A 结合使用 jQuery 与 OData 报告服务的任务窗格应用程序

有关更多信息,请参见文档页面“如何: 创建结合使用 REST 与内部部署 Project Server OData 服务的 Project 应用程序”,地址为 bit.ly/T80W2H

因为 ProjectDocument 扩展了 Document 对象,所以 Office.context.document 对象捕获一个对活动项目的引用 — 类似于一个插入其他主机应用程序的应用程序。Project 中提供的异步方法的签名与 JavaScript API for Office 中的其他方法类似。例如,getProjectFieldAsync 方法具有三个参数:

  • fieldId: 指定要在 callback 参数的对象中返回的字段。Office.ProjectProjectFields 枚举包括 12 个字段,如项目 GUID、开始日期、完成日期和(如果有)Project Server URL 或 SharePoint 任务列表 URL。
  • asyncContext: (可选)是在 asyncResult 对象中返回的任何用户定义类型。
  • callback: 包含一个对调用返回时运行的函数的引用,并包含用于处理成功或失败的选项。

图 12 所示,特定于 Project 中的应用程序的方法的使用类似于其他应用程序中承载的应用程序。在该脚本片段中,一个本地定义的函数调用了一个在应用程序中显示错误消息的例程。该脚本未使用 asyncContext 参数。

图 12 从插入项目的任务窗格获取字段的 GUID

var _projectUid = "";
// Get the GUID of the active project.
function getProjectGuid() {
  Office.context.document.getProjectFieldAsync(
    Office.ProjectProjectFields.GUID,
    function (asyncResult) {
      if (asyncResult.status == Office.AsyncResultStatus.Succeeded) {
        _projectUid = asyncResult.value.fieldValue;
        }
      else {
        // Display error message to user.
}
    }
  );
}

虽然 getProjectFieldAsync 方法对于常规项目只能获取 12 个字段,不过 getTaskFieldAsync 方法可以使用 ProjectTaskFields 枚举为获取任务的 282 个不同字段中的任何一个。并且 getResourceFieldAsync 方法可以使用 ProjectResourceFields 枚举获取资源的 200 个字段中的任何一个。ProjectDocument 对象中的其他常规方法有:getSelectedDataAsync(返回支持的任何视图中的所选文本数据)和 getTaskAsync(返回所选任务的常规数据的几个项目)。任务窗格应用程序在 Project 中可使用 16 个不同视图。

此外,Project 中的任务窗格应用程序可以在用户更改视图、选择任务或选择资源时,添加或移除事件处理程序。

Office 应用程序中的事件

JavaScript API for Office 使您可以通过事件创建响应能力更好的应用程序。API 的事件模型支持四个关键事件方案,这些方案是为 Office 开发应用程序的基础(后面讨论)。了解这四个方案可牢固掌握 API 的事件模型。

也就是说,Office 应用程序的事件模型是始终一致的,因此了解事件处理的常见设计便可了解这一重要概念。

关于 Office 应用程序 API 中的事件常见设计,以下对象具有与之关联的事件:

  • 绑定
  • CustomXMLPart
  • Document
  • RoamingSettings(邮件应用程序)
  • Settings(设置)

除了相关联的事件之外,列出的每个对象都具有两个用于处理其事件的方法:

  • addHandlerAsync
  • removeHandlerAsync

因为 removeHandlerAsync 方法只是从事件取消订阅处理程序,并且因为其签名几乎与 addHandlerAsync 的签名相同,所以在下一节中,我们仅专心探讨 addHandlerAsync。

注: remove­HandlerAsync 与 addHandlerAsync 方法有一个非常重要的差异。handler 参数对于 removeHandlerAsync 是可选的。如果未指定,则会移除给定事件类型的所有处理程序。

AddHandlerAsync 方法

addHandlerAsync 方法将事件处理程序绑定到指定事件,对于实现它的每个对象具有相同签名:

objectName.addHandlerAsync(eventType, handler [, options], callback);

现在来讨论此方法的参数。

EventType 参数 必需的 eventType 参数采用 EventType 枚举,会向方法告知要绑定的事件类型。

Handler 参数 eventType 参数后跟 handler 参数。handler 可以是命名函数或匿名内联函数。请注意,如同许多编程语言的事件模型一样,Office 应用程序运行时会调用处理程序并传入事件对象实际参数作为唯一形式参数。此外,如果将内联匿名函数用于 handler 参数,则移除处理程序的唯一方式是通过调用 removeHandler­Async 并且不指定 handler 参数,从事件中移除所有处理程序。

选项参数 与 Office 应用程序 API 中的所有异步函数一样,可以指定包含可选参数的对象,但是对于所有 addHandlerAsync 方法,可以指定的唯一可选参数是 asyncContext。提供该参数是为了通过异步方法传递可以在回调中检索的任何所需数据。

Callback 参数 callback 的行为如同在 Office 应用程序 API 中其他位置的行为一样,只不过有一个显著例外: AsyncResult 对象的值属性。如本文前面所讨论的一样,当运行时调用回调时,它会传入一个 Async­Result 对象,可使用 AsyncResult 对象的值属性获取异步调用的返回值。对于 addHandlerAsync 方法中的回调,Async­Result 对象的值始终未定义。

图 13 演示如何对 DocumentSelectionChanged 事件的 addHandlerAsync 方法编写代码(代码假设具有一个 <div> 元素,其 id 属性值为“message”)。

图 13 使用 Document.addHandlerAsync 方法为 DocumentSelection­Changed 事件绑定事件处理程序

Office.initialize = function (reason) {
  $(document).ready(function () {       
    Office.context.document.addHandlerAsync(
      Office.EventType.DocumentSelectionChanged, onDocSelectionChanged,
        addHandlerCallback);
      Office.context.document.addHandlerAsync(
        Office.EventType.DocumentSelectionChanged, onDocSelectionChanged2,
        addHandlerCallback2);
  });
};
function onDocSelectionChanged(docSelectionChangedArgs) {
  write("onDocSelectionChanged invoked each event.");
}
function onDocSelectionChanged2(docSelectionChangedArgs) {
  write("onDocSelectionChanged2 invoked each event.");
}
function addHandlerCallback(asyncResult) {
  write("addHandlerCallback only called once on app initialize.");
}
function addHandlerCallback2(asyncResult) {
  write("addHandlerCallback2 only called once on app initialize.");
}
function write(message) {$('#message').append(message + "\n");

初始化应用程序时,图 13 中的代码会将 onDocSelectionChanged 和 onDocSelectionChanged2 处理程序函数绑定到 Document­SelectionChanged 事件,演示了同一个事件可以具有多个事件处理程序。 当 DocumentSelectionChanged 事件触发时,这两个处理程序只是写入到 <div>(“message”)。

addHandlerAsync 的调用还分别包括回调 addHandlerCallback 和 addHandlerCallback2。 回调也写入 <div>(“message”),但是仅在 addHandlerAsync 完成时调用一次。

同样,可以使用 addHandlerAsync 方法为 JavaScript API for Office 中的任何事件绑定事件处理程序。

Office 应用程序事件模型中的关键方案

如前所述,在 JavaScript API for Office 中,考虑 Office 应用程序事件模型时,可以围绕四个关键事件方案来整理您的理解。 API 中的所有事件都属于这四个关键事件方案之一:

  • Office.initialize 事件
  • 文档级选中内容更改事件
  • 绑定级选中内容和数据更改事件
  • 设置更改事件

Office.initialize 事件 迄今为止,JavaScript API for Office 中您会遇到的最常见事件是 Office.initialize 事件。 对于您所创建的每个 Office 应用程序,initialize 事件都会发生。 事实上,这是运行时首先执行的代码部分。

如果查看 Visual Studio 2012 为针对 Office 项目的任何新应用程序提供的起始代码,则会发现应用程序的 ProjectName.js 文件中的前几行起始代码会为 Office.initialize 事件绑定事件处理程序,如下所示:

// This function is run when the app is ready to
// start interacting with the host application;
// it ensures the DOM is ready before adding click handlers to buttons.
Office.initialize = function (reason) { /* handler code */ };

从上一篇文章中的对象模型层次结构部分可知,Office 对象是 JavaScript API for Office 中的最顶层对象,表示应用程序在运行时的实例。 当 Office 应用程序运行时完全加载并且准备好与应用程序进行交互时,Initialize 事件会触发。 因此,Initialize 事件的事件处理程序本质上是应用程序与运行时之间的“握手”,必须在代码其余部分运行之前发生。

作为 Office.initialize 事件处理程序而提供的函数只有一个参数 — InitializationReason 枚举。 Initialization­Reason 枚举只有两个枚举值 — Inserted 和 documentOpened:

  • Inserted 表示由于应用程序刚刚插入文档而正在初始化应用程序。
  • documentOpened 表示由于刚刚打开已插入了应用程序的文档而正在初始化应用程序。

运行时会传入 InitializationReason enumeration 枚举作为处理程序函数的唯一参数。 在其中可以根据原因对代码的反应方式进行分支处理。

下面说明了这种方法的工作原理:

Office.initialize = function (reason) {
  // Display initialization reason.
if (reason == "inserted")
  write("The app was just inserted.");
  if (reason == "documentOpened")
  write(
    "The app is already part of the document.");
}
// Function that writes to a div with
// id='message' on the page.
function write(message){
  document.getElementById(
  'message').innerText += message;
}

注: 上面的代码段假设具有一个 <div> 元素,其 id 属性值为“message”。

有趣的是,不必在作为处理程序而提供的函数中包含任何内容,但是该函数必须存在,否则应用程序会在启动时引发错误。

顺便说一句,若要初始化可在应用程序中使用的其他框架(如 jQuery),Office.initialize 事件的事件处理程序是个不错的位置。 同样,在 Visual Studio 为针对 Office 项目的新应用程序提供的起始代码中,会发现类似于图 14 中的代码的内容。

图 14 在 Office.initialize 事件处理程序中初始化其他框架

Office.initialize = function (reason) {
  $(document).ready(function () {
    $('#getDataBtn').click(function () { getData('#selectedDataTxt'); });
    // If setSelectedDataAsync method is supported
    // by the host application, setDatabtn is hooked up
    // to call the method, else setDatabtn is removed.
if (Office.context.document.setSelectedDataAsync) {
        $('#setDataBtn').click(function () { setData('#selectedDataTxt'); });
    }
    else {
      $('#setDataBtn').remove();
    }
  });
};

jQuery .ready 事件在 Office.initialize 事件处理程序中处理。 这可确保首先加载并准备好 JavaScript API for Office,然后供 JQuery 代码调用。

文档级选中内容更改事件 文档级选中内容更改事件在文档中的选中内容从一个选中内容移动到另一个选中内容时发生。 例如,当用户单击 Word 文档中当前选中内容之外的一段文本时,或是单击文档中其他位置的对象或位置时,会在文档级针对选中内容的更改触发事件。

以下代码阐明了如何响应当前选中内容的更改:

function addEventHandlerToDocument() {
    Office.context.document.addHandlerAsync(
      Office.EventType.DocumentSelectionChanged,
      MyHandler);
  }
  function MyHandler(eventArgs) {
    doSomethingWithDocument(eventArgs.document);

绑定级选中内容和数据更改事件 通过 Office 应用程序对象模型中的绑定,可以按一致方式访问文档(或电子表格)中的特定区域,具体方式是建立与文档中唯一命名的区域的链接(或绑定)。 要使用绑定,请先使用 API 提供的方法之一创建一个绑定。 随后可以使用其唯一标识符引用所创建的特定绑定。

绑定也会触发事件,应用程序可以根据需要响应这些事件。 具体而言,当绑定区域内发生选中内容更改以及数据更改时,绑定会触发事件。 以下两个代码段演示如何处理给定绑定中的选中内容更改和数据更改(都假设具有一个 <div> 元素,其 id 属性值为“message”)。

响应 Binding.bindingSelectionChanged 事件:

function addEventHandlerToBinding() {
  Office.select("bindings#MyBinding").addHandlerAsync(
    Office.EventType.BindingSelectionChanged,
    onBindingSelectionChanged);
}
function onBindingSelectionChanged(eventArgs) {
  write(eventArgs.binding.id + " has been selected.");
}
// Function that writes to a div with id='message' on the page.
function write(message){
  document.getElementById('message').innerText += message;
}

响应 Binding.bindingDataChanged 事件:

function addEventHandlerToBinding() {
  Office.select("bindings#MyBinding").addHandlerAsync(
    Office.EventType.BindingDataChanged, onBindingDataChanged);
}
function onBindingDataChanged(eventArgs) {
  write("Data has changed in binding: " + eventArgs.binding.id);
}
// Function that writes to a div with id='message' on the page.
function write(message){
  document.getElementById('message').innerText += message;
}

设置更改事件 Office 应用程序对象模型为开发者提供了一种方式,来持久保存与其应用程序相关的设置。Settings 对象充当属性包,自定义应用程序设置在其中以键/值对的形式存储。Settings 对象也有与之关联的事件(即 Settings.settingsChanged),该事件在所存储的设置发生更改时触发。

有关 Settings.settingsChanged 事件的更多信息,请参见 bit.ly/U92Sbe 上关于 JavaScript API for Office 的 MSDN 文档。

接下来: 更高级主题

在本系列中的第二篇文章中,我们回顾了有关在 Office 应用程序中获取和设置 Office 文件内容的基础知识。我们演示了如何获取和设置选中内容数据以及如何获取所有文件数据。了解了如何从 Project 应用程序获取项目、任务、视图和资源数据。最后,我们讨论了 JavaScript API for Office 中的事件以及如何对它们编写代码。

注: 我们要感谢 Office 部门的程序员 Jim Corbin,他对涉及 Project 应用程序方面的内容贡献良多。

接下来,我们将详细介绍 JavaScript API for Office 中的一些更高级主题: 数据绑定和自定义 XML 部件。

Stephen Oliver 是 Office 部门的一位程序员,也是 Microsoft 认证专业开发人员 (SharePoint 2010)。 他针对 Excel 服务和 Word 自动化服务编写开发人员文档,还编写 PowerPoint 自动化服务开发人员文档。 他帮助组织和设计过 Excel Mashup 站点,网址是 ExcelMashup.com

Eric Schmidt 是 Office 部门的一位程序员。 他为 Office 应用程序创建了多段示例代码,包括流行的“持久保存自定义设置”示例代码。 此外,他还就 Office 可编程性撰写过有关其他产品和技术的文章并创建过相关视频。

衷心感谢以下技术专家对本文的审阅: Mark Brewster、Shilpa Kothari 和 Juan Balmori Labra
Mark Brewster 于 2008 年获得了亚利桑那大学的数学与计算机科学学士学位,已在 Microsoft 从事了四年的软件开发。他热衷于骑自行车,喜欢喝啤酒和听唱片。

Shilpa Kothari (Bhavsar) 是一位在 Microsoft 从事测试工作的软件工程师。她参与过多项 Microsoft 产品的研发工作,其中包括 Bing Mobile、Visual Studio 和 Office。她对软件 QA 和用户体验充满热情,联系方式:shilpak@microsoft.com

Juan Balmori Labra 是一位项目经理,至少从事了三年的 Microsoft Office JavaScript API 工作。他以前曾参与过 Office 2010 发布(带有 Business Connectivity Services 和 Duet)。在为了追逐梦想而搬到雷蒙德之前,Juan 曾在 Microsoft Mexico 中担任过公众部门咨询事务的首席架构师。