Microsoft Office

探讨 JavaScript API for Office: 数据绑定和自定义 XML 部件

Stephen Oliver
Eric Schmidt

下载代码示例

本文是 JavaScript API for Office 的深入演练系列文章的第 3 部分。 本文将继续介绍该 API 的主要概念,重点介绍数据绑定以及对使用自定义 XML 部件的支持。 第 1 部分“探索新的 JavaScript API for Office”(msdn.microsoft.com/magazine/jj891051) 提供了对象模型的宽泛概述。 第 2 部分“探讨 JavaScript API for Office: 数据访问和事件”(msdn.microsoft.com/magazine/jj991976) 深入探讨了与如何获取文件内容相关的重要概念并对事件模型进行了全面综述。 本文之后的第 4 部分将专门重点讲述第三个类型的 Office 应用程序: 邮件应用程序。

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

Office 应用程序中的数据绑定

通过数据绑定,文档中的特定数据区域与应用程序之间可以紧密集成。 区域数据绑定到应用程序中的一个命名对象,这样应用程序可以访问指定区域中的数据,即使用户选择了别的内容也是如此。

绑定一经创建便会一直存在,即使在页面上移动了区域(在 Word 中)或者将区域复制到了其他工作表(在 Excel 中)。 例如,表的绑定一直存在,即使用户对其进行了重命名。

当区域数据发生更改时,绑定会引发一个应用程序可挂接到的事件。 通过此事件,应用程序可以访问发生更改的数据并做出相应的反应。

绑定和应用程序“视图” 毫无疑问,Office 应用程序中的数据绑定使应用程序可以直接访问 Office 文件中的数据集,从而使应用程序可以更轻松地分析数据,无需依赖用户的直接操作。 不过,通过数据绑定,不仅可以实现目标数据访问,开发者还可以将 Office 文件本身包含为应用程序的可自定义和必备组件。

许多 Office 应用程序为其用户提供的界面单独包含在任务窗格或内容应用程序 UI 范围内,这并没有什么不妥。 不过,简单说来,Office 文件中的数据及其表示形式本身就是应用程序的“视图”。 用户与 Office 文件内的数据进行交互。 他们输入新数据,更改现有数据,从文档内容中删除不需要的数据。 Office 应用程序提供了用户十分了解的数据视图。

通过 JavaScript API for Office 中的数据绑定功能,可以在应用程序中利用 Office 应用程序所提供的数据视图。 开发者可以使用 Office 中的现成功能为应用程序开发“界面”。 这样,可以使用 Office 应用程序中的现成功能为应用程序视图设置样式。 因此,数据绑定提供了将应用程序的视图连接到 JavaScript 文件中包含的业务逻辑“模型”的强大功能。

当然,反之依然。 可以将 Office 文件用作数据源存储数据模型的内容。 随后可以使用应用程序提供数据视图。 凭借绑定的灵活性,可以根据需要将模型-视图-控制器 (MVC) 模式应用于应用程序和 Office 文件。

使用绑定的方案 对开发者的创造力没有硬性限制,应用程序可在以下三种一般化方法的任何组合中使用绑定:

  • 应用程序在用户更改区域中的数据时进行响应。
  • 应用程序选取区域中的数据,对其进行分析,并向用户提供建模或提交数据的选项。
  • 应用程序将数据从外部数据源推送到绑定区域。

举例来说,有一个简单的股票行情应用程序插入到 Excel 工作簿,工作簿中有一列包含股票代码,另一列包含当前股价。 通过数据绑定,该应用程序可以绑定到包含股票代码的列,从而选取该列中的股票代码。 然后,应用程序通过 Web 服务订阅这些股票的价格,对从该服务发送的结果进行分析。 最后,应用程序可以绑定到工作表中的股价列并实时更新这些值。

我将实现上述功能 — 创建一个股票行情工作簿 — 下一部分将介绍 Binding 对象。

使用 Binding 对象 数据绑定的奇妙之处全在 Bindings 集合和 Binding 对象中。

  • Bindings 集合表示在 Office 文件与 Office 应用程序之间创建的所有绑定。 应用程序不能访问其他应用程序创建的任何绑定。
  • Binding 对象表示 Office 文件中的某一区域与应用程序之间的一个命名绑定。 它公开几个成员,用于获取、读取和设置数据,以及对绑定区域中的更改进行响应。

我们将在构建股票行情应用程序时更详细地了解这些对象。

在继续讨论之前,让我们快速看一下数据。 图 1 显示了此应用程序视图的外观。 可以看到,使用的股票代码是虚构的,仅供演示之用。

A Table Named “Stocks” in an Excel Workbook with Formulas and Conditional Formatting Applied
图 1 Excel 工作薄中一个应用了公式和条件格式、名为“Stocks”的表

此外,我们还在此工作簿中添加了一些“智能”。 要绑定到的数据区域已设置为表格式并命名为“Stocks”。右列中的值已添加了一个自定义公式以便与表中的其他值进行比较。 我们还向表应用了条件格式,让图标集显示在右列中。

值得注意的是,我们已在 Visual Studio 2012 中将此工作簿添加到解决方案中,这样不必每次调试应用程序都重新创建表。 要向解决方案添加工作簿,请在解决方案中右键单击应用程序项目(使用默认模板时列在解决方案资源管理器中的第一个项目),单击“添加现有项”,然后选择相关工作簿。 然后,在应用程序项目的属性中,为工作簿文件设置“启动操作”。 调试时,需要手动将应用程序插入工作簿中(“插入”选项卡 |“适合于 Office 的应用程序”按钮)。

初始化后,应用程序的业务逻辑需要设置绑定,然后向绑定的事件 Office.EventType.BindingDataChanged 添加事件处理程序。 图 2 显示了相应代码。 请注意,我们已将代码封装在一个自解压缩匿名函数中,该函数存储在 StockTicker 变量中。 电子表格上表的名称、绑定名称及绑定全都存储在 StockTicker“类”的类字段中。StockTicker“类”仅公开一个成员: initializeBinding。

图 2 创建 Excel 工作簿的绑定并向该绑定中的数据更改事件添加处理程序。

var StockTicker = (function () {   var tableName = "Sheet1!Stocks",       bindingName = "Stocks",       binding;   // Create the binding to the table on the spreadsheet.
function initializeBinding() {     Office.context.document.bindings.addFromNamedItemAsync(       tableName,       Office.BindingType.Table,       { id: bindingName },       function (results) {         binding = results.value;         addBindingsHandler(function () { refreshData(); });     });   }   // Event handler to refresh the table when the   // data in the table changes.
var onBindingDataChanged = function (result) {     refreshData();   }   // Add the handler to the BindingDataChanged event of the binding.
function addBindingsHandler(callback) {     Office.select("bindings#" + bindingName).addHandlerAsync(       Office.EventType.BindingDataChanged,       onBindingDataChanged,       function () {         if (callback) { callback(); }     });   }   // Other member methods of this "class" ...
return {     initializeBinding: initializeBinding   }; })();

要在应用程序与工作表中的表之间建立绑定,我们可以使用 JavaScript API 中 Document 类的几种不同方法之一,这些方法包括 addFromNamedItemAsync、addFromPromptAsync 和 addFromSelectionAsync。 (请注意,addFromPromptAsync 仅在 Excel 和 Excel Web App 中可用。)

因为我们知道要绑定到的区域的名称,该名称就是 Sheet1 上名为“Stocks”的表,所以我们使用了 addFromNamedItemAsync 方法来建立绑定。 我们使用 Excel 区域表示法 (Sheet1!Stocks) 传入了该表的名称。 此方法调用的结果包括对绑定自身的引用,使我们可以将对绑定的引用存储在绑定变量(类字段)中。

在代码中,我们为该方法的 bindingType 参数传入了 Office.BindingType.Table 值。 这将指定我们需要使用自己的数据创建“表”类型的绑定,但我们也可以指定文本或矩阵类型的绑定。 以表形式绑定到区域有几项优点。 例如,如果用户向表新增列或行,绑定区域的范围也会增加。 在其他方面也是如此。 TableBinding 对象是绑定的基础,它公开一些属性用于添加列、添加行甚至删除表中所有数据。

(有关 JavaScript API for Office 中的文本和矩阵数据类型的详细信息,请参阅本系列文章的第二篇文章中的“从 Office 应用程序访问 Office 文件内容”部分。)

随后,代码向绑定的 BindingDataChanged 事件添加处理程序。 当绑定区域中的数据发生更改时,即用户更改区域中的数据时,需要调用本地定义的 refreshData 函数以启动更新表的过程。 此外,因为该表尚未使用数据源中的数据进行更新,所以我们将在添加事件处理程序后调用 refreshData。

可以看到,addBindingsHandler 函数使用 Office.select 方法获取绑定,不过我们可以改用 Bindings.getByIdAsync 方法。 这两个方法之间的主要差异在于对结果中返回的数据的访问级别。 Office.select 方法返回一个可能调用代码的 Binding 对象。 如果该方法成功,返回的 Binding 对象只有数量有限的成员可供使用。 通过使用 Office.select 选择绑定,可以直接从 Binding 对象调用成员。 这样,无需向获取绑定的函数添加回调即可向绑定添加处理程序。

(你可能会想,我们可以只使用本地“绑定”变量即可捕获对绑定的引用。是的,我们可以这样做。 我们编写此代码只是为了演示。)

图 3 显示 refreshData 和 getBindingData 函数。 refreshData 函数只是启动一系列异步调用,通过调用 getBindingData 从工作表获取表数据。 getBindingData 函数包含对 Binding.getDataAsync 方法的调用,以 TableData 对象的形式返回数据。

图 3 从表绑定获取数据并调用 Web 服务

var StockTicker = (function () {   // Other members of this "class"...
// Refresh the data displayed in the bound table of the workbook.
// This function begins a chain of asynchronous calls that   // updates the bound table.
function refreshData() {     getBindingData();   }   // Get the stock symbol data from the bound table and   // then call the stock quote information service.
function getBindingData() {     binding.getDataAsync(       {         startRow: 0,         startColumn: 0,         columnCount: 1       },       function (results) {         var bindingData = results.value,             stockSymbols = [];         for (var i = 0; i < bindingData.rows.length; i++) {           stockSymbols.push(bindingData.rows[i][0]);         }         getStockQuotes(stockSymbols);     });   }   return {     // Exposed members of the "class."   }; })();

在调用图 3 所示的 getDataAsync 时,我们可以通过为 options 参数传入匿名对象 {coercionType: Office.CoercionType.Table},指定要显式检索的数据类型 (或更改数据类型)。 因为尚未指定要检索的数据类型,getDataAsync 调用在其原始数据类型(TableData 对象)中返回绑定数据。

第二篇文章中讨论了 TableData 对象,该对象为要使用的数据提供了更多结构,也就是说我们可以使用标题和行属性从表中选择数据。 在本示例中,我们只需获取表中第一列的股票代码。 你可能还记得,行属性以数组的形式存储表中的数据,其中第一个数组中的每一项都与表中的一行相对应。

在使用 TableData 对象的绑定时,可以使用 startRow 和 startColumn 参数指定要从绑定获取的行和列的子集。 这两个参数为要从表中提取的数据指定从零开始的起点,其中以表左上角为原点。 (请注意,必须同时使用 startRow 和 startColumn 参数,否则会引发异常。) 因为只需获取表中第一列的数据,所以我们还将传入设置为 1 的 columnCount 参数。

获得该列数据后,将每个值推送到一维数组中。 在图 3 中,可以看到我们调用了 getStockQuotes 函数,该函数以参数形式接受股票代码数组。 在图 4 中,使用 getStockQuotes 函数从股票报价 Web 服务检索数据。 (为便于演示,省略了有关该 Web 服务的代码。) 在对来自 Web 服务的结果进行分析后,调用本地定义的 removeHandler 方法。

图 4 调用 Web 服务并删除 BindingDataChanged 事件处理程序

var StockTicker = (function () {   // Other members of this "class"...
// Call a Web service to get new stock quotes.
function getStockQuotes(stockSymbols) {     var stockValues = [];     // Make a call to the Web service and parse the results.
// The results are stored in the stockValues variable, which     // contains an array of arrays that include the stock symbol     // with the current value.
removeHandler(function () {       updateTable(stockValues);     });   }   // Disables the BindingDataChanged event handler   // while the table is being updated.
function removeHandler(callback) {     binding.removeHandlerAsync(       Office.EventType.BindingDataChanged,       { handler: onBindingDataChanged },       function (results) {         if (results.status == Office.AsyncResultStatus.Succeeded) {            if (callback) { callback(); }         }     });   }   return {     // Exposed members of the "class."   }; })();

removeHandler 函数调用 binding.removeHandlerAsync 方法,该方法删除 BindingDataChanged 事件的事件处理程序。 现在,如果保留了附加到事件的处理程序,则更新表时将引发该事件。 随后会再次调用事件处理程序并更新表,从而造成无限循环。 使用新数据更新表后,将事件处理程序添加回事件中。

(当然,我们也可以使用矩阵强制类型创建不同的绑定以分隔表中的列。 这样,可只将事件挂接到用户可编辑的列。)

removeHandlerAsync 方法采用参数 handler,该参数指定要删除的处理程序的名称。 最好使用 handler 参数从绑定事件中删除处理程序。

图 5 中,通过调用本地定义的 updateTable 函数,使用新股票值更新表。

图 5 从表绑定获取数据并调用 Web 服务

var StockTicker = (function () {   // Other members of this "class"...
// Update the TableData object referenced by the binding   // and then update the data in the table on the worksheet.
function updateTable(stockValues) {     var stockData = new Office.TableData(),         newValues = [];     for (var i = 0; i < stockValues.length; i++) {       var stockSymbol = stockValues[i],           newValue = [stockSymbol[1]];       newValues.push(newValue);     }     stockData.rows = newValues;     binding.setDataAsync(       stockData,       {         coercionType: Office.CoercionType.Table,         startColumn: 3,         startRow: 0       },       function (results) {         if (results.status == Office.AsyncResultStatus.Succeeded) {           addBindingsHandler();         }     });     }   return {     // Exposed members of the "class."   }; })();

updateTable 函数获取从 Web 服务传入的数据,然后将其写回绑定表中。 在本示例中,stockValues 参数包含另一个数组的数组,其中第一个数组中的每一项都是一个包含股票代码及其当前股价的数组。 为了将这些数据设置回绑定表中,创建一个新的 TableData 对象并将股票值数据插入该对象中。

需要注意的是,TableData.rows 属性中设置的数据必须与插入绑定中的数据的形状相匹配。 如果盲目地将全新的 TableData 对象设置到绑定表中,可能会丢失表中的部分数据,如公式等。 在图 5 中,将数据作为一个数据列(数组的数组,其中每个子数组包含一个项)添加到 TableData 对象中。 在将此数据插入回绑定表时,需要将这一更新的数据列插入适当的列中。

这里,再次使用了 startRow 和 startColumn 属性。 updateTable 函数包含对 binding.setDataAsync 的调用,该方法通过指定 startColumn 和 startRow 参数,将 TableData 推送回工作表的表中。 startColumn 参数设置为 3,这意味着插入的 TableData 对象将从表中的第四列开始插入其数据。 在 setDataAsync 方法的回调过程中,再次调用 addBindings­Handler 函数将事件处理程序重新应用到事件。

当 binding.setDataAsync 方法成功完成时,新表数据推送到绑定区域并立即显示出来。 从用户角度来说,这种体验是无缝的。 用户在表的单元格中键入数据并按 Enter 后,表中的“值”列会自动更新。

自定义 XML 部件

JavaScript API for Office 支持一个特别重要的功能,即在 Word 中创建和操作自定义 XML 部件。 要充分了解 JavaScript API for Office 用于自定义 XML 部件的巨大潜力,掌握一些背景知识十分有帮助。 具体来说,需要了解如何组合使用 Office Open XML(OOXML 或 OpenXML)文件格式、自定义 XML 部件、内容控件和 XML 映射来创建十分强大的解决方案,也就是涉及创建动态 Word 文档的解决方案。

OOXML 格式 Office 2007 为 Office 文档引入了新的 OOXML 文件格式,现在这是 Office 2010 和 Office 2013 的默认文件格式。 (可以看出哪些 Office 文档是 OOXML 文件格式,因为这些文档的扩展名现在由四个字母组成,其中许多是以“x”结尾,例如“.docx”表示 Word 文档,“.xlsx”表示 Excel 电子表格,“.pptx”表示 PowerPoint 文档。)

OOXML 格式的 Office 文档基本上是 .zip 文件。 每个 .zip 文件都包含一系列 XML 文件(称为“部件”),它们共同构成了 Office 文档。 如果将 Office 文档(如 Word .docx 文档)重命名为 .zip,然后检查其文件里面的内容,会发现该文档实际上就是一系列不同的 XML 文件,它们组织到 .zip 包内的文件夹中,如图 6 所示。

File Structure of an Office Open XML Format Document
图 6 Office Open XML 格式文档的文件结构

自定义 XML 部件基础知识 虽然有现成的标准 XML 部件,Office 应用程序总是会为每个 OOXML 格式的新 Office 文档创建这些部件(例如,有一种内置 XML 部件用于描述核心文档属性),你还可能有兴趣向 Word 文档、Excel 工作簿或 PowerPoint 演示文稿添加自己的“自定义 XML”部件。 自定义 XML 部件添加到 .zip 包的 XML 文件集合中,这些文件构成了 Office 文档。 自定义 XML 部件存储在文档的文件结构中,但不显示给最终用户。 这使你可以插入随隐藏在文件结构内的 Office 文档的特定实例移动的业务数据。 这样,你可以在自己的应用程序中使用该自定义 XML,而这就是 JavaScript API for Office 所支持的功能。

内容控件 除了允许将自定义 XML 包含在文档中的 OOXML 格式及其文件结构之外,Word 2007 还新增了内容控件,该功能对自定义 XML 部件进行了大量补充。

内容控件是一种在 Word 文档中定义固定区域的方法,这些区域可存放特定种类的数据,如纯文本、格式文本、图片、日期甚至重复数据。 内容控件对自定义 XML 部件进行补充的重要方面是,使用 XML 映射的数据绑定。

XML 映射 一种内容控件,可绑定或“映射”到包含在文档中的 XML 部件的 XML 中的元素。 例如,企业可以将业务数据以自定义 XML 部件的形式从后端系统注入到其内容控件映射到该自定义 XML 部件的 Word 文档。 内容控件绑定到自定义 XML 部件中的特定节点,这样,当最终用户打开文档时,XML 映射内容控件使用自定义 XML 部件中的数据进行自动填充。 反之亦然,企业可以使用包含映射内容控件的同一 Word 文档,但需要最终用户将数据输入内容控件中。 在保存文档后,映射内容控件中的数据将保存回 XML 文件中。 这样,应用程序可以从已保存文档中的自定义 XML 部件获取数据并将其推送到后端系统。 综上所述,JavaScript API for Office 为开发应用程序提供了丰富的支持功能。

通过 JavaScript API for Office 使用自定义 XML 部件 要详细探讨 Office 应用程序 JavaScript 对象模型中自定义 XML 部件 API 的一些比较重要的功能,最好是通过举例说明。 在这一部分中,我们使用 Office 应用程序和 SharePoint 开发者门户的“示例”区域中的“发票管理器”示例 (bit.ly/YRdlwt),以便你可以遵照操作。 发票管理器示例是动态文档方案的一个示例,在该方案中,企业希望生成从后端系统提取数据的文档以制成发票。 在此例中,数据是客户名称和送货地址以及关联的客户采购清单。

示例包含一个用于创建新发票的模板文档。 该模板文档有一个布局,其中包含客户名称、地址和客户采购表。 文档的客户名称、地址和采购部分都是内容控件。 每个内容控件都映射到架构中的一个节点,该架构是为保存客户发票数据而创建的,如图 7 所示。

Content Controls on the Document Surface Mapped to a Custom XML Part
图 7 文档图面上映射到自定义 XML 部件的内容控件

发票管理器示例应用程序的 UI 十分简单,如图 8 所示。

The UI for the Invoice Manager Sample App
图 8 发票管理器示例应用程序的 UI

最终用户从应用程序 UI 的下拉框中选择发票编号,与该发票编号关联的客户数据将显示在应用程序主体中,如图 9 所示。

The Invoice Manager UI Populated with Data from a Custom XML Part
图 9 使用自定义 XML 部件中的数据进行填充的发票管理器 UI

当用户选择“填充”按钮时,应用程序将显示的数据以自定义 XML 部件的形式推送到文档中。 因为内容控件映射到自定义 XML 部件中的节点,所以将自定义 XML 部件推送到文档后,内容控件立即为它们映射到的每个 XML 节点显示数据。 你可以实时替换自定义 XML 部件(就像我们在这里执行的操作一样),只要该部件符合内容控件映射到的架构,内容控件就会显示映射数据。 图 10 显示了内容控件映射到文档中的自定义 XML 部件时的发票管理器装箱单。

Content Controls Mapped to Nodes in a Custom XML Part Showing Bound Data
图 10 映射到自定义 XML 部件中的节点的内容控件显示绑定数据

CustomXmlParts 对象

CustomXmlParts.addAsync 使用自定义 XML 部件的第一步是了解如何使用 JavaScript API for Office 将其添加到文档中。 只能使用 customXml­Parts.addAsync 方法执行此操作。 顾名思义,customXmlParts.addAsync 方法以异步方式添加自定义 XML 部件,其签名如下:

Office.context.document.customXmlParts.addAsync(xml [, options], callback);

请注意,该函数的第一个必需参数是 XML 字符串。 这是自定义 XML 部件的 XML。 如前所述,发票管理器使用自定义 XML 部件映射到文档图面上的内容控件,但首先必须获取要以自定义 XML 形式插入的客户数据。 在保存整个应用程序逻辑的 InvoiceManager.js 文件中,应用程序模拟使用用户定义的函数 setupMyOrders 从后端系统获取客户数据的过程。 此函数创建一个由三个对象组成的数组,这些对象表示三个客户订单。 当然,可以设想,企业可采用各种方法来存储和获取客户采购历史记录,如 SQL 数据库,但简单起见,只在应用程序中创建三个“硬编码”客户订单。

在创建订单对象后,它们所表示的数据必须在 XML 中呈现,这样才能在对 custom­XmlParts.addAsync 的调用中使用。 这是在 initializeOrders 函数中发生的过程,这同时会设置应用程序 UI 并将事件处理程序绑定到 UI 上的控件。 要注意的重要内容是 jQuery 代码,该代码绑定 Populate 按钮单击事件的事件处理程序,如图 11 所示。

图 11 绑定 Populate 按钮单击事件的事件处理程序

$("#populate").click(function () {   var selectedOrderID = parseInt($("#orders option:selected").val());   _document.customXmlParts.getByNamespaceAsync("", function (result) {     if (result.value.length > 0) {       for (var i = 0; i < result.value.length; i++) {         result.value[i].deleteAsync(function () {         });       }     }   });   var xml = $.json2xml(findOrder(myOrders, selectedOrderID));   _document.customXmlParts.addAsync(xml, function (result) { }); });

实际上,充当 Populate 按钮单击事件的事件处理程序的匿名函数将一个订单对象(是用 setupMyOrders 函数创建的)转换为一个 XML 字符串,然后调用 customXmlParts.addAsync 方法将包含订单信息的该 XML 字符串传递为第一个必需参数。

customXml­Parts.addAsync 的另一个参数是回调函数。 当然,这可以是对在代码其他位置定义的方法的引用,也可以是匿名函数。 发票管理器示例使用内联匿名函数:

_document.customXmlParts.addAsync(xml,   function (result) { });

对于 Java­Script API for Office 中的所有回调都是如此,AsyncResult 对象将作为回调的唯一参数传入。 对于 customXmlParts.addAsync 和所有 customXmlParts 函数,可以使用 AsyncResult 对象实现以下目的:

  • 使用 Async­Result.value 属性获取对新创建的自定义 XML 部件的引用
  • 使用 AsyncResult.status 属性获取请求的结果
  • 使用 Async­Result.error 属性获取有关错误(如果出现)的信息
  • 使用 AsyncResult.asyncContext 属性获取你自己的状态数据(如果在对 custom­XmlParts.addAsync 的调用中包含任意状态数据)

对于最后一项,请注意,customXmlParts.addAsync 方法中的另一个参数是一个可选的 options 对象:

Office.context.document.customXmlParts.addAsync(   xml [, options], callback);

通过提供的 options 对象,可以将你自己的用户定义对象传入回调的调用中。

如发票管理器示例所示,对 customXmlParts.addAsync 的调用中的匿名函数不执行任何操作,但在生产环境中,可能希望执行错误检查,以便在自定义 XML 部件因某种原因未成功添加时正常处理实例。

CustomXmlParts.getByNamespaceAsync 如发票管理器示例所示,通过 JavaScript API for Office 使用自定义 XML 部件的另一个重要内容是使用 customXmlParts.getByNamespaceAsync 方法,在 Populate 按钮的单击事件处理程序代码中可以查看该方法。 customXmlParts.getByNamespaceAsync 的签名如下:

Office.context.document.customXmlParts.getByNamespaceAsync(   ns [, options], callback);

第一个必需参数 ns 是一个字符串,它指定要获取的自定义 XML 部件的命名空间。 因此,customXmlParts.getByNamespaceAsync 返回指定了命名空间的文档中的自定义 XML 部件数组。 因为发票管理器示例中创建的自定义 XML 部件不使用命名空间,所以对 customXmlParts.getByNamespaceAsync 的调用会传入一个空字符串作为命名空间形参的实参,如图 12 所示。

图 12 使用方法 CustomXmlParts.getByNamespaceAsync

$("#populate").click(function () {   var selectedOrderID = parseInt($("#orders option:selected").val());   _document.customXmlParts.getByNamespaceAsync("", function (result) {     if (result.value.length > 0) {       for (var i = 0; i < result.value.length; i++) {         result.value[i].deleteAsync(function () {         });       }                     }      });      var xml = $.json2xml(findOrder(myOrders, selectedOrderID));      _document.customXmlParts.addAsync(xml, function (result) { });    });    var selOrder = $("#orders option:selected");    popOrder(selOrder.val());

与 API 中的所有异步函数一样,customXmlParts.getByNamespaceAsync 也有可选的 options 和 callback 参数。

CustomXmlParts.getByIdAsync 最后一种在文档中获取自定义 XML 部件的编程方式是使用 customXmlParts.getByIdAsync。 签名如下:

Office.context.document.customXmlParts.getByIdAsync(id [, options], callback);

此函数使用部件的 GUID 获取单个自定义 XML 部件。 在文档包的 itemPropsn.xml 文件中可以找到自定义 XML 部件的 GUID。 也可以使用 customXmlPart 的 id 属性获取 GUID。 这里要注意的一个要点是,GUID 的字符串必须用大括号 (“{}”) 将 GUID 括起。

发票管理器示例不使用 customXml­Parts.getByIdAsync 函数,以下代码清楚地说明了这一点:

function showXMLPartBuiltId() {   Office.context.document.customXmlParts.getByIdAsync(     "{3BC85265-09D6-4205-B665-8EB239A8B9A1}", function (result) {     var xmlPart = result.value;     write(xmlPart.id);   }); } // Function that writes to a div with id='message' on the page.
function write(message){   document.getElementById('message').innerText += message; }

与 customXmlParts.addAsync 和 customXmlParts.getByNamespaceAsync 一样,除了 id 参数以外,customXml­Parts.getByIdAsync 方法也有可选参数 options 和必需参数 callback,它们的用法与在其他函数中的一样。

CustomXmlPart 对象 customXmlPart 对象表示单个自定义 XML 部件。 使用 customXmlParts 对象的方法获取对 customXmlPart 的引用后,有多个属性可用,如图 13 所示。

图 13 CustomXmlPart 属性

“名称” 说明
builtIn 获取一个值,该值指示 customXmlPart 是否为内置部件。
id 获取 customXmlPart 的 GUID。
namespaceManager 获取对当前 customXmlPart 使用的命名空间前缀映射集 (customXmlPrefixMappings)。

CustomXmlPart 也有关联的事件,如图 14 所示。

图 14 CustomXmlPart 事件

“名称” 说明
nodeDeleted 在删除节点时发生。
nodeInserted 在插入节点时发生。
nodeReplaced 在替换节点时发生。

但就本文而言,我们要重点介绍 customXmlPart 对象的几个开发者常用重要方法。 如图 15 所示。

图 15 CustomXmlPart 方法

“名称” 说明
addHandlerAsync 以异步方式为 customXmlPart 对象事件添加事件处理程序。
deleteAsync 以异步方式从集合中删除此自定义 XML 部件。
getNodesAsync 以异步方式获取此自定义 XML 部件中与指定 XPath 匹配的任何 customXmlNodes。
getXmlAsync 以异步方式获取此自定义 XML 部件中的 XML。

CustomXMLPart.addHandlerAsync customXmlPart.add­HandlerAsync 方法对于绑定用于响应自定义 XML 部件更改的事件处理程序十分重要。 customXmlPart.addHanderAsync 方法的签名如下:

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

请注意,第一个必需参数是 Office.EventType 枚举,该枚举指定要处理 Office 应用程序对象模型中的哪种事件。 下一个必需参数是事件的处理程序。 此处重要的一点在于,在调用处理程序时,JavaScript API for Office 将传入特定于所处理事件种类的事件参数(NodeDeletedEventArgs、NodeInsertedEventArgs 或 NodeReplacedEventArgs)。 这样,就像在 API 的所有异步函数中一样,也可以选择包含 options 和 callback 参数。

考虑这样一种情况,文档像数据输入窗体一样使用。 用户将数据输入窗体,然后窗体选取数据。 窗体包含一个重复节内容控件,以便用户每次输入重复项时,都会向基础自定义 XML 部件添加一个新节点。 每次添加或插入节点时,都会触发 NodeInserted 事件,你可以使用 customXmlPart.addHandlerAsync 响应事件(及所有 customXmlPart 事件)。

图 16 显示如何响应 NodeInserted 事件。

图 16 绑定 CustomXmlPart.NodeInserted 事件的事件处理程序

function addNodeInsertedEvent() {   Office.context.document.customXmlParts.getByIdAsync(     "{3BC85265-09D6-4205-B665-8EB239A8B9A1}", function (result) {     var xmlPart = result.value;     xmlPart.addHandlerAsync(Office.EventType.NodeInserted,       function (eventArgs) {         write("A node has been inserted.");     });   }); } // Function that writes to a div with id='message' on the page.
function write(message){   document.getElementById('message').innerText += message; }

CustomXMLPart.deleteAsync 当然,除了知道如何添加自定义 XML 部件以外,知道如何删除也很重要。 customXmlPart.deleteAsync 方法提供了这一功能。 CustomXmlPart.deleteAsync 是异步函数,签名如下:

customXmlPart.deleteAsync([options ,] callback);

回到发票管理器示例中,可以看到 customXMLPart.deleteAsync 的演示过程: 

$("#populate").click(function () {   var selectedOrderID = parseInt($("#orders option:selected").val());   _document.customXmlParts.getByNamespaceAsync("", function (result) {     if (result.value.length > 0) {       for (var i = 0; i < result.value.length; i++) {         result.value[i].deleteAsync(function () {         });       }     } });

在 Populate 按钮的单击事件处理程序中,程序逻辑检查是否存在任何命名空间为空白的自定义 XML 部件。 如果存在,则使用 custom­XmlPart.deleteAsync 方法全部删除。

有关使用自定义 XML 部件的知识还有很多,不过通过本文介绍的内容,你可以初步认识 JavaScript API for Office 为自定义 XML 部件提供的丰富支持功能。

接下来: 邮件应用程序

本文是系列文章的第三篇,回顾了在 Office 应用程序中使用数据的一些高级方法。 介绍了如何通过数据绑定向 Excel 中的表添加其他智能。 此外,还探讨了如何在 Word 应用程序中利用自定义 XML 部件轻松实现自动文档创建。

在本系列的下一篇和最后一篇文章中,将介绍将 JavaScript API for Office 应用于邮件应用程序。 邮件应用程序代表 JavaScript API for Office 中的一组独特功能,应用程序开发者和 Exchange 管理员可通过这些功能构建功能强大的工具来处理电子邮件。

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

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

衷心感谢以下技术专家对本文的审阅: Mark Brewster (Microsoft)、Shilpa Kothari (Microsoft) 和 Juan Balmori Labra (Microsoft)
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 担任过公共部门咨询事务的首席架构师。