Office 对话框 API 最佳做法和规则
本文提供 Office 对话 API 的规则、gotchas 和最佳做法,包括设计对话框 UI 并在单页应用程序中使用该 API 的最佳做法, (SPA) 。
规则和陷阱
该对话框只能导航到 HTTPS URL,不能导航到 HTTP。
传递给 displayDialogAsync 方法的 URL 必须与加载项本身位于完全相同的域中。 它不能是子域。 但是,传递给它的页可以重定向到另一个域中的页面。
在对话框中只能调用两个 Office API,
- Office.context.ui.messageParent
-
Office.context.requirements.isSetSupported
(有关详细信息,请参阅 指定 Office 应用程序和 API 要求。)
通常应从与加载项本身完全相同的域中的页面调用 messageParent 函数,但这不是必需的。 有关详细信息,请参阅向主机运行时间跨域消息传递。
提示
在 Web 版 Office 和新 Outlook on Windows 中,如果对话框的域与加载项的域不同,并且它强制实施 跨源-Opener-Policy:同源 响应标头,则外接程序将被阻止访问来自对话框的邮件,并且将显示 错误 12006。 若要防止出现这种情况,必须将标头设置为
Cross-Origin-Opener-Policy: unsafe-none
或将加载项和对话框配置为位于同一域中。
最佳做法
避免过度使用对话框
由于不赞成重叠 UI 元素,因此除非应用场景需要,否则请勿从任务窗格打开对话框。 考虑如何使用任务窗格区域时,请注意任务窗格中可以有选项卡。 有关选项卡式任务窗格的示例,请参阅 Excel 外接程序 JavaScript SalesTracker 示例。
设计对话框 UI
有关对话框设计的最佳做法,请参阅 Office 外接程序中的对话框。
使用 Office 网页版处理弹出窗口阻止程序
在 Web 上使用 Office 时尝试显示对话框可能会导致浏览器的弹出窗口阻止程序阻止该对话框。 为了防止出现这种情况,Office 网页版会提示用户 允许 或 忽略 打开对话框。
如果用户选择 “允许”,则会打开“Office”对话框。 如果用户选择 “忽略”,则提示将关闭,并且 Office 对话框不会打开。 相反, displayDialogAsync
该方法返回错误 12009。 代码应捕获此错误,并提供不需要对话框的备用体验,或者向用户显示一条消息,告知加载项要求他们允许对话。 (有关 12009 的详细信息,请参阅 displayDialogAsync.)
如果出于任何原因想要关闭此功能,则代码必须选择退出。它使用传递给 displayDialogAsync
方法的 DialogOptions 对象发出此请求。 具体而言,对象应包括 promptBeforeOpen: false
。 当此选项设置为 false 时,Office 网页版不会提示用户允许加载项打开对话框,Office 对话框也不会打开。
请求访问 Office 网页版和 Windows 版 Outlook 中的设备功能
如果外接程序需要访问用户的设备功能,可通过 设备权限 API 使用一个对话框来请求权限。 设备功能包括用户的相机、地理位置和麦克风。 这适用于以下 Office 应用程序。
- Office 网页版 (Excel、Outlook、PowerPoint 和 Word) 在基于 Chromium 的浏览器中运行,例如 Microsoft Edge 或 Google Chrome
- 新的 Outlook on Windows
当外接程序调用 Office.context.devicePermission.requestPermissions 或 Office.context.devicePermission.requestPermissionsAsync 时,将显示一个对话框,其中包含请求的设备功能和“允许一次”或“拒绝访问”选项。 若要了解详细信息,请参阅 查看、管理和安装 Excel、PowerPoint 和 Word 加载项。
注意
- 在不基于 Chromium 的 Office 桌面客户端或浏览器中运行的加载项会自动显示一个对话框,请求用户的权限。 开发人员无需在这些平台上实现设备权限 API。
- 在 Safari 中运行的加载项被阻止访问用户的设备功能。 Safari 不支持设备权限 API。
不要使用_host_info值
Office 会自动向传递给 _host_info
的 URL 添加查询参数 displayDialogAsync
。 它会在自定义查询参数(如果有)之后追加。 它不会追加到对话框导航到的任何后续 URL。 Microsoft可能会更改此值的内容,或者将其完全删除,因此代码不应读取该值。 相同的值将添加到对话框的会话存储 (即 Window.sessionStorage 属性) 。 同样,代码不得对此值执行读取和写入操作。
关闭另一个对话框后立即打开另一个对话框
不能从给定的主机页打开多个对话框,因此代码应在打开的对话上调用 Dialog.close ,然后再调用 displayDialogAsync
以打开另一个对话框。 方法是 close
异步的。 因此,如果在调用 后立即调用 displayDialogAsync
close
,当 Office 尝试打开第二个对话框时,第一个对话框可能尚未完全关闭。 如果发生这种情况,Office 将返回 12007 错误:“操作失败,因为此加载项已具有活动对话框。”
方法 close
不接受回调参数,并且不返回 Promise 对象,因此不能使用 await
关键字或 then
方法等待它。 出于此原因,当需要在关闭对话后立即打开新对话时,建议采用以下方法:封装代码以在函数中打开新对话框,并设计函数以递归方式调用自身(如果 的 displayDialogAsync
调用返回 12007
)。 示例如下。
function openFirstDialog() {
Office.context.ui.displayDialogAsync("https://MyDomain/firstDialog.html", { width: 50, height: 50},
(result) => {
if(result.status === Office.AsyncResultStatus.Succeeded) {
const dialog = result.value;
dialog.close();
openSecondDialog();
}
else {
// Handle errors
}
}
);
}
function openSecondDialog() {
Office.context.ui.displayDialogAsync("https://MyDomain/secondDialog.html", { width: 50, height: 50},
(result) => {
if(result.status === Office.AsyncResultStatus.Failed) {
if (result.error.code === 12007) {
openSecondDialog(); // Recursive call
}
else {
// Handle other errors
}
}
}
);
}
或者,可以在代码尝试使用 setTimeout 方法打开第二个对话框之前强制暂停代码。 示例如下。
function openFirstDialog() {
Office.context.ui.displayDialogAsync("https://MyDomain/firstDialog.html", { width: 50, height: 50},
(result) => {
if(result.status === Office.AsyncResultStatus.Succeeded) {
const dialog = result.value;
dialog.close();
setTimeout(() => {
Office.context.ui.displayDialogAsync("https://MyDomain/secondDialog.html", { width: 50, height: 50},
(result) => { /* callback body */ }
);
}, 1000);
}
else {
// Handle errors
}
}
);
}
在 SPA 中使用 Office 对话框 API 的最佳做法
如果外接程序使用客户端路由,则与单页应用程序 (SPA) 通常一样,可以选择将路由的 URL 传递到 displayDialogAsync 方法,而不是单独的 HTML 页面的 URL。 建议不要这样做,原因如下。
注意
本文与 服务器端 路由无关,例如在基于 Express 的 Web 应用程序中。
SPA 和 Office 对话框 API 的问题
Office 对话框位于一个新窗口中,其中包含其自己的 JavaScript 引擎实例,因此它是自己的完整执行上下文。 如果传递路由,则基页及其所有初始化和引导代码都会在此新上下文中再次运行,并在对话框中将任何变量设置为其初始值。 因此,此方法会在框窗口中下载并启动应用程序的第二个实例,这在一定程度上违背了 SPA 的用途。 此外,更改对话框窗口中变量的代码不会更改相同变量的任务窗格版本。 同样,对话框窗口具有自己的会话存储 (Window.sessionStorage 属性) ,这无法从任务窗格中的代码访问。 所调用的对话框和主机页 displayDialogAsync
与服务器的两个不同的客户端类似。 (有关主机页的提醒,请参阅 从主机页打开对话框。)
因此,如果将路由传递给 displayDialogAsync
方法,则实际上不会有 SPA;将有 同一 SPA 的两个实例。 此外,任务窗格实例中的大部分代码永远不会在该实例中使用,对话框实例中的大部分代码永远不会在该实例中使用。 这相当于相同捆绑包中拥有两个 SPA。
Microsoft 建议
建议执行以下操作之一,而不是将客户端路由传递给 displayDialogAsync
方法:
- 如果要在对话框中运行的代码足够复杂,请显式创建两个不同的 SPA:也就是说,在同一域的不同文件夹中有两个 SPA。 一个 SPA 在对话框中运行,另一个在调用的对话框的主机页
displayDialogAsync
中运行。 - 在大多数情况下,对话框中只需要简单的逻辑。 在这种情况下,通过在 SPA 的域中托管单个 HTML 页面(嵌入或引用的 JavaScript),项目将大大简化。 将页面的 URL 传递给
displayDialogAsync
方法。 虽然这意味着你偏离了单页应用的文本概念;使用 Office 对话框 API 时,实际上没有 SPA 的单个实例。