如何使用 JSF 打开弹出窗口而不被浏览器阻止

How to open a pop-up using JSF without it being blocked by the browser

我正在开发 PrimeFaces 6.0、JSF 2.2 (Mojarra 2.2.7) 应用程序。

我需要从外部站点加载网页并突出显示 DOM 节点。我的做法是创建一个JavaScript函数来打开一个弹出窗口window,通过servlet加载网页(避免跨域问题)并突出显示该节点。我发送到函数的参数是在托管 bean 中生成的。

我尝试了两种不同的方式:

  1. 在我的操作中使用 RequestContext.getCurrentInstance().execute("myFunction(...)")(是的,我正在使用 PrimeFaces)。
  2. 在我的命令按钮上使用 oncomplete="#{myBean.myJsCall}"

两种方式调用都已执行且调用正确,但我 运行 进入浏览器 (Chromium) 的弹出窗口拦截器:

有没有办法在 JSF 或 PrimeFaces 中打开弹出窗口而不被阻止?

这不是真正相关的,但这是我的 JavaScript 函数的简化版本。

我使用纯 HTML 和 JS 开发了这个脚本。它在那里打开弹出窗口而没有拦截器干扰。此外,在 运行 运行 JSF 应用程序时将调用粘贴到控制台时,会打开弹出窗口。

function myFunction(url, selector) {
    var popup = window.open("", "popup", "height=500,width=700");
    var req = new XMLHttpRequest();
    req.open("GET", url, true);
    req.onreadystatechange = function() {
        if (req.readyState === XMLHttpRequest.DONE) {
            popup.document.open();
            popup.document.write(req.responseText);
            popup.document.close();
            popup.document.addEventListener(
                "DOMContentLoaded",
                function() { /* Some code highlighting the selector */ },
                false
            );
        }
    }
    req.send();
}

当您尝试在 JavaScript 中打开一个新的 window 时,这不是由用户操作(如点击)直接触发的,而是在例如执行 JSF 后触发的回调 ajax请求,被浏览器拦截。

此行为在以下问题中有所描述:jquery window.open in ajax success being blocked

作为解决方法,您可以在 JSF ajax 请求之前打开一个 window。在使用 JSF HTML 标签的命令按钮的情况下,这可以通过使用来完成:

<h:commandButton ... onclick="prepareWindow()"/>

onclick 将按原样呈现。但是,当您使用 PrimeFaces 时,您 不能 使用:

<p:commandButton ... onclick="prepareWindow()"/>

PrimeFaces 包装 onclick 导致间接执行,因此弹出窗口被阻止。

无论如何,通过一些额外的技巧,您可以使用某种看起来像 PrimeFaces 按钮的按钮来实现此功能。但是黑客的数量越来越多了。

我选择使用 p:dialogiframe。首先,如果您使用 p:layout不要 将对话框放置在任何布局单元中。

对话框:

<p:dialog header="Preview"
          widgetVar="previewDlg" modal="true" width="1000" height="600"
          dynamic="true">
    <iframe src="about:blank"
            class="previewIframe"
            style="position:absolute;top:0;left:0;right:0;bottom:0;width:100%;height:100%;border:0"></iframe>
</p:dialog>

按钮:

<p:commandButton value="Show"
                 ...
                 onclick="PF('previewDlg').show()"
                 action="#{myBean.showPreview('previewIframe')}"/>

动作:

public void showPreview(String iframeClass) {
    ...
    RequestContext.getCurrentInstance().execute(js);
}

JavaScript函数:

function myFunction(iframeClass, url, selector) {
    var iframe = $("iframe[class=" + iframeClass + "]")[0];
    iframe.contentDocument.location = "about:blank";
    var req = new XMLHttpRequest();
    req.open("GET", url, true);
    req.onreadystatechange = function() {
        if (req.readyState === XMLHttpRequest.DONE) {
            iframe.contentDocument.open();
            iframe.contentDocument.write(req.responseText);
            iframe.contentDocument.close();
            iframe.contentDocument.addEventListener(
                "DOMContentLoaded",
                function() { /* Some code highlighting the selector */ },
                false
            );
        }
    }
    req.send();
}

有一个简单的出路。在 ajax 数据提交后,打开一个空的弹出式 onclick(直接由用户诱导,因此不会被阻止)并重定向 window 到更新的模型:

<p:commandButton value="open popup" process="@form"
                     onclick="myNamespace.openPreview()"
                     oncomplete="myNamespace.relocatePreview()"/>

你的 js 应该是这样的:

myNamespace.openPreview = function () {
    myNamespace.prev = window.open('', '_blank');
}

myNamespace.relocatePreview = function () {
    myNamespace.prev.location = 'preview.xhtml?faces-redirect=true';
}