Jasper de Vries Jasper de Vries - 1 month ago 22
Javascript Question

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

I'm working on a PrimeFaces 6.0, JSF 2.2 (Mojarra 2.2.7) application.

I need to load a web page from an external site and highlight a DOM node. My approach is to create a JavaScript function to open a pop-up window, load the web page through a servlet (to avoid cross domain issues) and highlight the node. The parameters I send to my function are generated in a managed bean.

I tried to do so in two different ways:


  1. Using
    RequestContext.getCurrentInstance().execute("myFunction(...)")
    in my action (yes, I'm using PrimeFaces).

  2. Using
    oncomplete="#{myBean.myJsCall}"
    on my command button.



Both ways the call is executed and the call is correct, but I run into my browser's (Chromium) pop-up blocker:

The following pop-ups were blocked on this page

Is there a way to open pop-ups in JSF or specifically in PrimeFaces without them being blocked?

This is not really relevant, but this is the simplified version of my JavaScript function.

I developed this script using plain HTML and JS. There it was opening the pop-up without the blocker interfering. Also, when pasting the call into the console when running the JSF application, the pop-up is opened.

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();
}

Answer

When you try to open a new window in JavaScript, which is not directly triggered by a user action (like a click), but in for example a callback triggered after executing JSF ajax requests, it is blocked by the browser.

This behavior is described in the following question: jquery window.open in ajax success being blocked.

As a workaround you could open a window before the JSF ajax request. In case of a command button using the JSF HTML tags this can be done by using:

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

The onclick will be rendered as is. However, if you are using PrimeFaces, you cannot use:

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

PrimeFaces wraps the onclick resulting in indirect execution, so the pop-up is blocked.

Anyhow, with some extra hacks you could get this working with some sort of button which looks like a PrimeFaces button. But the number of hacks was getting too much.

I opted to use a p:dialog with an iframe instead. To start of, if you are using p:layout, don't place the dialog in any of the layout units.

The dialog:

<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>

The button:

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

The action:

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

The JavaScript function:

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();
}