在 Office 加载项中使用 Office 对话框 API

使用 Office 对话框 API 打开 Office 外接程序中的对话框。 本文提供了有关如何在 Office 加载项中使用对话框 API 的指南。 请考虑从任务窗格、内容加载项或 外接程序命令 打开对话框,以执行以下操作:

  • 使用 Google、Facebook 或 Microsoft 标识等资源登录用户。 有关详细信息,请参阅 使用 Office 对话框 API 进行身份验证
  • 为加载项中的某些任务提供更多屏幕空间,或甚至整个屏幕。
  • 托管在任务窗格中显得太小的视频。

提示

由于不赞成重叠 UI 元素,因此除非应用场景需要,否则请勿从任务窗格打开对话框。 考虑如何使用任务窗格区域时,请注意任务窗格中可以有选项卡。 有关选项卡式任务窗格的示例,请参阅 Excel 外接程序 JavaScript SalesTracker 示例。

下图展示了对话框示例。

Word 中具有Microsoft 标识平台的登录对话框。

对话框始终在屏幕中心打开。 用户可以移动并重设对话框的大小。 窗口是 非模式的 -- 用户可以继续与 Office 应用程序中的文档以及任务窗格中的页面(如果有)交互。

注意

如果你正在开发在 Office web 版 或新的 Windows 版 Outlook 中运行的加载项,并且需要访问用户的设备功能,请参阅设备权限 API,了解如何提示用户输入权限。 设备功能包括用户的相机、地理位置和麦克风。

从主机页面打开对话框

Office JavaScript API 在 Office.context.ui 命名空间中包含一个 Dialog 对象和两个函数。

为了打开对话框,代码(通常是任务窗格中的一页)调用 displayDialogAsync 方法,并将要打开的资源 URL 传递到此方法。 调用方法的页面称为“主机页”。 例如在任务窗格中的 index.html 页面上使用脚本调用此方法,随后 index.html 是打开此方法对话框的主机页。

对话框中打开的资源通常是页面,,但也可以是 MVC 应用中的控制器方法、路由、Web 服务方法或其他任何资源。 在本文中,“页面”或“网站”是指对话框中的资源。 下面的代码是一个简单的示例。

Office.context.ui.displayDialogAsync('https://www.contoso.com/myDialog.html');
  • URL 使用 HTTPS 协议。 对话框中加载的所有页面都必须要遵循此要求,而不仅仅是加载的第一个页面。
  • 对话框域与宿主页的域相同,宿主页可以是任务窗格中的页面,也可以是加载项命令的函数文件。 传递到方法的页、控制器方法或其他资源 displayDialogAsync 必须与主机页位于同一域中。

重要

对话框中打开的主机页面和资源必须具有相同的完整域。 如果尝试传递 displayDialogAsync 加载项域的子域,它将不起作用。 完整域(包括任何子域)必须匹配。

加载第一个页面(或其它资源)后,用户可使用链接或其它用户界面来导航至任何使用 HTTPS 的网站(或其他资源)。 还可以将第一个页面设计为直接重定向到另一个站点。

默认情况下,对话框的高度和宽度占设备屏幕的 80%。不过,你也可以设置不同的百分比,只需将配置对象传递给方法即可,如下面的示例所示。

Office.context.ui.displayDialogAsync('https://www.contoso.com/myDialog.html', { height: 30, width: 20 });

有关执行此操作的示例加载项,请参阅 生成适用于 Excel 的 Office 外接程序。 有关使用 displayDialogAsync的更多示例,请参阅 代码示例

将这两个值都设置为 100%,以获得有效的全屏体验。 有效最大值为 99.5%,窗口仍可移动且可调整大小。

只能从主机窗口打开一个对话框。 如果尝试再打开一个对话框,就会生成错误。 例如,如果用户从任务窗格打开对话框,则无法从任务窗格中的其他页面打开另一个对话框。 不过,如果对话框是通过加载项命令打开,那么只要选择此命令,就会打开新 HTML 文件(但不可见)。 这会新建(不可见的)主机窗口,所以每个这样的窗口都可以启动自己的对话框。 有关详细信息,请参阅 displayDialogAsync 返回的错误

利用 Office 网页版中的性能选项

displayInIframe 属性是配置对象中另一个可以传递到 displayDialogAsync 的属性。 如果将此属性设置为 true,且加载项在 Office 网页版打开的文档中运行,对话框就会以浮动 iframe(而不是独立窗口)的形式打开,从而加快对话框的打开速度。 示例如下。

Office.context.ui.displayDialogAsync('https://www.contoso.com/myDialog.html', { height: 30, width: 20, displayInIframe: true });

默认值为 false,与完全省略此属性时相同。 如果加载项未在 Office web 版 中运行,displayInIframe则忽略 。

注意

如果对话框将在任何时候重定向到无法在 iframe 中打开的页面,则 不应 使用 displayInIframe: true 。 例如,许多常用 Web 服务(如 Google 和 Microsoft 帐户)的登录页面无法在 iframe 中打开。

将信息从对话框发送到主机页

对话框中的代码使用 messageParent 函数将字符串消息发送到主机页。 字符串可以是单词、句子、XML blob、字符串化的 JSON,也可以是可以序列化为字符串或转换为字符串的任何其他内容。 若要使用 messageParent 方法,对话框必须首先 初始化 Office JavaScript API

注意

为清楚起见,在本部分中,我们将消息目标称为主机 ,但严格来说,消息将发送到任务窗格中的 运行时 (或托管 函数文件的 运行时) 。 这种区别仅在跨域消息传送的情况下才有意义。 有关详细信息,请参阅向主机运行时间跨域消息传递

以下示例演示如何初始化 Office JS 并将消息发送到主机页。

Office.onReady(() => {
   // Add any initialization code for your dialog here.
});

// Called when dialog signs in the user.
function userSignedIn() {
    Office.context.ui.messageParent(true.toString());
}

下一个示例演示如何返回包含配置文件信息的 JSON 字符串。

function userProfileSignedIn(profile) {
    const profileMessage = {
        "name": profile.name,
        "email": profile.email,
    };
    Office.context.ui.messageParent(JSON.stringify(profileMessage));
}

函数 messageParent 是可在对话框中调用的 仅有 的两个 Office JS API 之一。 可在对话框中调用的另一个 JS API 是 Office.context.requirements.isSetSupported。 有关它的信息,请参阅 指定 Office 应用程序和 API 要求。 但是,在对话框中,批量许可的永久Outlook 2016 ((即 MSI 版本) )不支持此 API。

必须将主机页配置为接收消息。 为此,可以向 displayDialogAsync 的原始调用添加回调参数。 回叫会为 DialogMessageReceived 事件分配处理程序。 示例如下。

let dialog; // Declare dialog as global for use in later functions.
Office.context.ui.displayDialogAsync('https://www.contoso.com/myDialog.html', { height: 30, width: 20 },
    (asyncResult) => {
        dialog = asyncResult.value;
        dialog.addEventHandler(Office.EventType.DialogMessageReceived, processMessage);
    }
);

Office 将 AsyncResult 对象传递给回叫。 表示尝试打开对话框的结果, 不表示对话框中任何事件的结果。 若要详细了解此区别,请参阅处理错误和事件

  • asyncResultvalue 属性设置为 Dialog 对象,此对象位于主机页(而不是对话框的执行上下文)中。
  • processMessage 是用于处理事件的函数。 可以根据需要任意命名。
  • dialog 变量的声明范围比回叫更广,因为 processMessage 中也会引用该变量。

下面是一个非常简单的示例,展示了 DialogMessageReceived 事件的处理程序。

function processMessage(arg) {
    const messageFromDialog = JSON.parse(arg.message);
    showUserName(messageFromDialog.name);
}

Office 将 arg 对象传递给处理程序。 其 message 属性是由 对话框中的 调用 messageParent 发送的字符串。 在此示例中,它是服务(如 Microsoft 帐户或 Google)中用户配置文件的字符串化表示形式,因此它反序列化回具有 JSON.parse的 对象。 showUserName不会显示实现。 它可能在任务窗格上显示定制的欢迎消息。

在用户完成与对话框的交互后,消息处理程序应关闭对话框,如下面的示例所示。

function processMessage(arg) {
    dialog.close();
    // Add code to process the message here.
}

dialog 对象必须是 displayDialogAsync 调用返回的对象。 需要将 dialog 对象声明为全局变量。 或者,可以使用匿名回调函数将 dialog 对象 displayDialogAsync 限定为调用,如以下示例所示。 在此示例中, processMessage 不需要关闭对话框, close 因为在匿名回调函数中调用了 方法。

Office.context.ui.displayDialogAsync('https://www.contoso.com/myDialog.html', { height: 30, width: 20 },
    (asyncResult) => {
        const dialog = asyncResult.value;
        dialog.addEventHandler(Office.EventType.DialogMessageReceived, (arg) => {
            dialog.close();
            processMessage(arg);
        });
      }
    );

如果外接程序在收到消息后需要打开任务窗格的其他页面,可以使用 window.location.replace 方法(或 window.location.href)作为处理程序的最后一行。 示例如下。

function processMessage(arg) {
    // Add code to process the message here.
    window.location.replace("/newPage.html");
    // Alternatively, use the following:
    // window.location.href = "/newPage.html";
}

有关具有此用途的加载项示例,请参阅Insert Excel charts using Microsoft Graph in a PowerPoint add-in(在 PowerPoint 加载项中使用 Microsoft Graph 插入 Excel 图表)示例。

条件消息

由于可以从对话框发送多个 messageParent 调用,但在主机页中只有一个 DialogMessageReceived 事件处理程序,因此处理程序必须使用条件逻辑来区分不同的消息。 例如,如果对话框提示用户登录到标识提供者(如 Microsoft 帐户或 Google),则会以消息的形式发送用户的个人资料。 如果身份验证失败,对话框会将错误信息发送到主机页,如以下示例所示。

if (loginSuccess) {
    const userProfile = getProfile();
    const messageObject = { messageType: "signinSuccess", profile: userProfile };
    const jsonMessage = JSON.stringify(messageObject);
    Office.context.ui.messageParent(jsonMessage);
} else {
    const errorDetails = getError();
    const messageObject = { messageType: "signinFailure", error: errorDetails };
    const jsonMessage = JSON.stringify(messageObject);
    Office.context.ui.messageParent(jsonMessage);
}

关于上一个示例,请注意:

  • loginSuccess 变量通过读取标识提供程序返回的 HTTP 响应进行初始化。
  • 不显示 和 getError 函数的getProfile实现。 这两个函数均从查询参数或 HTTP 响应的正文获取数据。
  • 根据登录是否成功,发送不同类型的匿名对象。 两者都具有 messageType 属性,但一个 profile 具有 属性,另一个 error 具有 属性。

主机页中的处理程序代码使用 messageType 属性的值设置分支,如下面的示例所示。 请注意,showUserName 函数的用法与之前的示例相同,showNotification 函数在主机页的 UI 中显示错误。

function processMessage(arg) {
    const messageFromDialog = JSON.parse(arg.message);
    if (messageFromDialog.messageType === "signinSuccess") {
        dialog.close();
        showUserName(messageFromDialog.profile.name);
        window.location.replace("/newPage.html");
    } else {
        dialog.close();
        showNotification("Unable to authenticate user: " + messageFromDialog.error);
    }
}

showNotification不会显示实现。 它可能会在任务窗格上的通知栏中显示状态。

跨域消息传送到主机运行时

对话框打开后,对话框或父运行时可能会导航离开加载项的域。 如果发生上述任一情况,除非代码指定父运行时的域,否则调用 messageParent 将失败。 为此,可将 DialogMessageOptions 参数添加到 的 messageParent调用。 此对象具有一个 targetOrigin 属性,该属性指定消息应发送到的域。 如果未使用 参数,Office 假定目标与对话当前托管的域相同。

注意

使用 messageParent 发送跨域消息需要 Dialog Origin 1.1 要求集。 在 DialogMessageOptions 不支持要求集的旧版 Office 上忽略 参数,因此,如果传递该方法,则该方法的行为不受影响。

下面是使用 messageParent 发送跨域消息的示例。

Office.context.ui.messageParent("Some message", { targetOrigin: "https://resource.contoso.com" });

如果消息不包含敏感数据,则可以将 设置为 targetOrigin “*”,以便将其发送到任何域。 示例如下。

Office.context.ui.messageParent("Some message", { targetOrigin: "*" });

提示

  • 参数 DialogMessageOptions 在 2021 年年中作为必需参数添加到 messageParent 方法中。 使用 方法发送跨域消息的旧加载项在更新为使用新参数之前不再有效。 在加载项更新之前, 在仅限 Windows 的 Office 中,用户和系统管理员可以通过使用注册表设置指定受信任的域来允许这些加载项继续工作: HKEY_CURRENT_USER\SOFTWARE\Microsoft\Office\16.0\WEF\AllowedDialogCommunicationDomains。 为此,请创建一个 .reg 扩展名为的文件,将其保存到 Windows 计算机,然后双击它以运行它。 下面是此类文件的内容示例。

    Windows Registry Editor Version 5.00
    
    [HKEY_CURRENT_USER\SOFTWARE\Microsoft\Office\16.0\WEF\AllowedDialogCommunicationDomains]
    "My trusted domain"="https://www.contoso.com"
    "Another trusted domain"="https://fabrikam.com"
    
  • 在 Office web 版 和新 Outlook on Windows 中,如果对话框的域与加载项的域不同,并且它强制实施跨源-Opener-Policy:同源响应标头,则外接程序将阻止访问来自对话框的邮件,并且将显示错误 12006。 若要防止出现这种情况,必须将标头设置为 Cross-Origin-Opener-Policy: unsafe-none 或将加载项和对话框配置为位于同一域中。

向对话框传递信息

加载项可以使用 Dialog.messageChild消息从主机页发送到对话框。

从主机页使用messageChild()

调用 Office 对话框 API 以打开对话框时,将返回 Dialog 对象。 应将其分配给具有全局范围的变量,以便可以从其他函数引用它。 示例如下。

let dialog; // Declare as global variable.
Office.context.ui.displayDialogAsync('https://www.contoso.com/myDialog.html',
    (asyncResult) => {
        dialog = asyncResult.value;
        dialog.addEventHandler(Office.EventType.DialogMessageReceived, processMessage);
    }
);

function processMessage(arg) {
    dialog.close();

    // Add code to process the message here.

}

Dialog 对象具有 一个 messageChild 方法,该方法可将任何字符串(包括字符串化数据)发送到对话框。 这会在对话框中引发事件 DialogParentMessageReceived 。 代码应处理此事件,如下一部分所示。

假设对话框的 UI 与当前活动的 Excel 工作表以及该工作表相对于其他工作表的位置相关。 在以下示例中, worksheetPropertiesChanged 将活动工作表的属性发送到对话框。 数据经过字符串化,以便可以传递给 messageChild

await Excel.run(async (context) => {
    const worksheet = context.workbook.worksheets.getActiveWorksheet();
    worksheet.load();
    await context.sync();
    worksheetPropertiesChanged(worksheet);
});

...

function worksheetPropertiesChanged(currentWorksheet) {
    const messageToDialog = JSON.stringify(currentWorksheet);
    dialog.messageChild(messageToDialog);
}

在对话框中处理 DialogParentMessageReceived

在对话框的 JavaScript 中,使用 UI.addHandlerAsync 方法注册事件的处理程序DialogParentMessageReceived。 这通常在 Office.onReady 或 Office.initialize 函数中完成,如下所示。 (本文后面提供了一个更可靠的示例。)

Office.onReady(() => {
    Office.context.ui.addHandlerAsync(Office.EventType.DialogParentMessageReceived,onMessageFromParent);
});

然后,定义 onMessageFromParent 处理程序。 下面的代码延续了上一节中的示例。 请注意,Office 将参数传递给处理程序,参数 message 对象的 属性包含主机页中的字符串。 在此示例中,消息被重新转换为 对象,并使用 jQuery 设置对话框的顶部标题以匹配新的工作表名称。

function onMessageFromParent(arg) {
    const messageFromParent = JSON.parse(arg.message);
    document.querySelector('h1').textContent = messageFromParent.name;
}

最佳做法是验证处理程序是否已正确注册。 可以通过向 方法传递回调来 addHandlerAsync 执行此操作。 此操作会在注册处理程序的尝试完成时运行。 如果处理程序未成功注册,请使用处理程序记录或显示错误。 示例如下。 请注意, reportError 这是一个未在此处定义的函数,用于记录或显示错误。

Office.onReady(() => {
    Office.context.ui.addHandlerAsync(
        Office.EventType.DialogParentMessageReceived,
        onMessageFromParent,
        onRegisterMessageComplete
    );
});

function onRegisterMessageComplete(asyncResult) {
    if (asyncResult.status !== Office.AsyncResultStatus.Succeeded) {
        reportError(asyncResult.error.message);
    }
}

从父页到对话框的条件消息传送

由于可以从主机页进行多次 messageChild 调用,但该事件的对话框中 DialogParentMessageReceived 只有一个处理程序,因此处理程序必须使用条件逻辑来区分不同的消息。 可以采用与对话框向主机页发送消息时构造条件消息的方式完全并行的方式执行此操作,如 条件消息传送中所述。

注意

在某些情况下, messageChild 可能不支持 作为 DialogApi 1.2 要求集的一部分的 API。 例如, messageChild 在批量许可永久Outlook 2016和批量许可的永久 Outlook 2019 中不支持 。 将消息 从主机页传递到对话框的替代方法中介绍了父到对话框消息传送的一些替代方法。

重要

无法在外接程序清单的“要求>”部分指定<DialogApi 1.2 要求集。 必须按照运行时检查方法和要求集支持中所述,在运行时使用 isSetSupported 方法检查对 DialogApi 1.2 的支持。 正在开发对清单要求的支持。

跨域消息传送到对话运行时

对话框打开后,对话框或父运行时可能会导航离开加载项的域。 如果发生上述任一情况,除非代码指定对话运行时的域,否则对 messageChild 的调用将失败。 为此,可将 DialogMessageOptions 参数添加到 的 messageChild调用。 此对象具有一个 targetOrigin 属性,该属性指定消息应发送到的域。 如果未使用 参数,Office 假定目标与父运行时当前托管的域相同。

注意

使用 messageChild 发送跨域消息需要 Dialog Origin 1.1 要求集。 在 DialogMessageOptions 不支持要求集的旧版 Office 上忽略 参数,因此,如果传递该方法,则该方法的行为不受影响。

下面是使用 messageChild 发送跨域消息的示例。

dialog.messageChild(messageToDialog, { targetOrigin: "https://resource.contoso.com" });

如果消息不包含敏感数据,则可以将 设置为 targetOrigin “*”,以便将其 发送到 任何域。 示例如下。

dialog.messageChild(messageToDialog, { targetOrigin: "*" });

由于承载对话框的运行时无法访问 <清单的 AppDomains> 部分,因此无法确定 消息来自的 域是否受信任,因此必须使用 DialogParentMessageReceived 处理程序来确定这一点。 传递给处理程序的对象包含当前托管在父级中的域作为其 origin 属性。 下面是如何使用 属性的示例。

function onMessageFromParent(arg) {
    if (arg.origin === "https://addin.fabrikam.com") {
        // Process the message.
    } else {
        // Signal the parent page to close the dialog.
        const messageObject = { messageType: "untrustedDomain" };
        Office.context.ui.messageParent(messageObject);
    }
}

例如,代码可以使用 Office.onReady 或 Office.initialize 函数 将受信任域的数组存储在全局变量中。 arg.origin然后,可以针对处理程序中的该列表检查 属性。

提示

参数 DialogMessageOptions 在 2021 年年中作为必需参数添加到 messageChild 方法中。 使用 方法发送跨域消息的旧加载项在更新为使用新参数之前不再有效。 在加载项更新之前, 在仅限 Windows 的 Office 中,用户和系统管理员可以通过使用注册表设置指定受信任的域来允许这些加载项继续工作: HKEY_CURRENT_USER\SOFTWARE\Microsoft\Office\16.0\WEF\AllowedDialogCommunicationDomains。 为此,请创建一个 .reg 扩展名为的文件,将其保存到 Windows 计算机,然后双击它以运行它。 下面是此类文件的内容示例。

Windows Registry Editor Version 5.00

[HKEY_CURRENT_USER\SOFTWARE\Microsoft\Office\16.0\WEF\AllowedDialogCommunicationDomains]
"My trusted domain"="https://www.contoso.com"
"Another trusted domain"="https://fabrikam.com"

关闭对话框

可以在对话框中实现对话框关闭按钮。 为此,该按钮的单击事件处理程序应使用 messageParent 通知主机页该按钮已获得单击。 示例如下。

function closeButtonClick() {
    const messageObject = { messageType: "dialogClosed" };
    const jsonMessage = JSON.stringify(messageObject);
    Office.context.ui.messageParent(jsonMessage);
}

DialogMessageReceived 的主机页处理程序将调用 dialog.close,如以下示例所示。 (请参阅前面的示例,其中展示了 dialog 对象的初始化方式。)

function processMessage(arg) {
    const messageFromDialog = JSON.parse(arg.message);
    if (messageFromDialog.messageType === "dialogClosed") {
       dialog.close();
    }
}

即使你没有自己的关闭对话框 UI,最终用户也可以通过选择右上角的 X 关闭对话框。 此操作将触发 DialogEventReceived 事件。 如果主机窗格需要知道此事件何时发生,应为此事件声明一个处理程序。 有关详细信息,请参阅对话框中的错误和事件部分。

代码示例

以下所有示例都使用 displayDialogAsync。 有些服务器基于 NodeJS,而另一些服务器 ASP.NET/IIS-based 服务器,但无论外接程序的服务器端如何实现,使用 方法的逻辑都是相同的。

另请参阅