Javascript 卸载前的Omnifaces在某些情况下不起作用

Javascript 卸载前的Omnifaces在某些情况下不起作用,javascript,jsf,dom-events,cdi,omnifaces,Javascript,Jsf,Dom Events,Cdi,Omnifaces,我将JSF与omnifaces和cdi一起使用。 在xhtml页面中,我使用了一些类似于的js(在下面的jsf页面中有更多详细信息) 如果窗口将要卸载且剩余未保存的数据,则激活浏览器的默认确认对话框 填写表单并重定向到同一页面时,会弹出警告。如果我决定“保持在页面上”,我将能够继续我的工作,然后提交我的表格,就像我预期的那样 问题:如果我导航到其他页面或尝试关闭选项卡,警告也会按预期显示。但在本例中,http POST提交“omnifaces.event:unload”被发送到服务器。就我所见,

我将JSF与omnifaces和cdi一起使用。 在xhtml页面中,我使用了一些类似于的js(在下面的jsf页面中有更多详细信息)

如果窗口将要卸载且剩余未保存的数据,则激活浏览器的默认确认对话框

填写表单并重定向到同一页面时,会弹出警告。如果我决定“保持在页面上”,我将能够继续我的工作,然后提交我的表格,就像我预期的那样

问题:如果我导航到其他页面或尝试关闭选项卡,警告也会按预期显示。但在本例中,http POST提交“omnifaces.event:unload”被发送到服务器。就我所见,这会导致调用Bean的
onDestroy()
。如果我选择留下来,那么表单中的所有值仍然存在于页面上,但是在提交表单时,会抛出NPE来获取这些值(我猜是因为bean已经被销毁了,不考虑我在确认对话框中的决定)

经过数小时的研究,我无法理解为什么第二种情况会导致bean卸载,而第一种方法等待确认对话框的结果。。。有什么想法吗

我已经注意到,
window.onbeforeunload
应该用纯js调用,如中所述。这适用于第一种情况,但不适用于第二种情况

编辑

复制步骤位于jsf页面上。如果您遵循这些,您应该能够理解我的问题。

菜豆

import org.omnifaces.cdi.ViewScoped;    
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import javax.inject.Named;
import java.io.Serializable;

@Named
@ViewScoped
public class SomeBean implements Serializable {

    private String fieldA;
    private String fieldB;
    private String info;

    @PostConstruct
    public void init(){
        this.fieldA = null;
        this.fieldB = null;
        this.info = "bean = " + this;
    }

    @PreDestroy
    public void preDestroy() {
        /* triggered when performing a navigation to other resources or
           closing the browser tab (unwanted), but not invoked if navigation is done
           within the same resource, e.g by using templates and compositions (wanted) */
        this.info = "destroy will be invoked for bean " + this;
    }

    public void submit(){
        // do smth. with the fields
    }

    public String getFieldA() {
        return fieldA;
    }

    public void setFieldA(String fieldA) {
        this.fieldA = fieldA;
    }

    public String getFieldB() {
        return fieldB;
    }

    public void setFieldB(String fieldB) {
        this.fieldB = fieldB;
    }

    public String getInfo() {
        return info;
    }

    public void setInfo(String info) {
        this.info = info;
    }
}
JSF页面

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html
        PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
        "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:h="http://java.sun.com/jsf/html" xmlns:p="http://primefaces.org/ui"
      xml:lang="en" lang="en">
<h:head>
    <title>reproduce example</title>

    <script type="application/javascript">
        $(document).ready(function () {
            var unsavedData = false;

            function setUnsavedData(flag) {
                unsavedData = flag;
            }

            window.onbeforeunload = function (e) {
                e = e || window.event;
                if (unsavedData) {
                    if (e) {
                        e.preventDefault();
                        e.returnValue = 'Any string';
                    }
                    return 'Any string';
                } else {
                    return undefined;
                }
            };

            $(document).on("change", ":input:not(.stateless)", function () {
                setUnsavedData(true);
            });

            $(document).on("click", "button.stateless", function () {
                setUnsavedData(false);
            });
        });
    </script>
</h:head>
<h:body>
    <h:form id="myform">
        <h3>steps to reproduce:</h3><br/>
        1) enter some value for fieldA<br/>
        2) click the 'navigate via navigationBean' link > confirm the dialog and 'stay on page'<br/>
        3) press the submit button > as you can see, the bean instance is still the same and value is passed.<br/>
        4) enter some value for fieldB<br/>
        5) click somewhere else on the page to lose the input's focus (otherwise the confirm dialog won't show up!)<br/>
        7) close the browser tab or use the browser's nav buttons > confirm the dialog and 'stay on page'<br/>
        8) press the submit button again > as you can see now, submit has not been called! Pressing submit shows the bean instance has changed!<br/><br/>
        <h4> > For this example, values are still usable after the new bean was initialized. Because the original page is way more complex then this example, <br/>
            I really need to prevent onmnifaces from initializing a new bean, when a user confirms to stay on the page, even if he tries to close the tab!</h4><br/>
        <br/>
        fieldA
        <p:inputText value="#{someBean.fieldA}"/>

        <!-- note: in real code this is represented by a NavigationBean logic!-->
        <p:commandLink value="navigate via navigationBean"
                       action="#"
                       ajax="false">
        </p:commandLink>
        <br/>
        fieldB
        <p:inputText value="#{someBean.fieldB}"/>
        <br/>
        <p:commandButton
                id="submitBtn"
                value="submit"
                action="#{someBean.submit()}"
                process="@form"
                update="@form" styleClass="stateless">
        </p:commandButton>
        <br/>
        <br/>
        bean info: <p:outputLabel id="output_1" value="#{someBean.info}"/>
        <br/>
        value info for fieldA: <p:outputLabel id="output_2" value="#{someBean.fieldA}"/>
        <br/>
        value info for fieldB: <p:outputLabel id="output_3" value="#{someBean.fieldB}"/>
    </h:form>
</h:body>
</html>

复制示例
$(文档).ready(函数(){
var unsavedData=false;
函数setUnsavedData(标志){
未保存的数据=标志;
}
window.onbeforeunload=函数(e){
e=e | | window.event;
如果(未保存的数据){
如果(e){
e、 预防默认值();
e、 returnValue='任何字符串';
}
返回'anystring';
}否则{
返回未定义;
}
};
$(文档).on(“更改”,“输入:非(.stateless)”,函数(){
setUnsavedData(真);
});
$(文档).on(“单击”,“按钮.无状态”,函数(){
setUnsavedData(假);
});
});
复制步骤:
1) 为字段A输入一些值
2) 单击“通过navigationBean导航”链接>确认对话框并“停留在页面上”
3) 按submit按钮>可以看到,bean实例仍然是相同的,并且传递了值。
4) 为字段B输入一些值
5) 单击页面上的其他位置可失去输入的焦点(否则确认对话框将不会显示!)
7) 关闭浏览器选项卡或使用浏览器的导航按钮>确认对话框并“停留在页面上”
8) 再次按下submit按钮>如您现在所见,submit未被调用!按submit显示bean实例已更改

>对于本例,在初始化新bean之后,值仍然可用。因为原始页面比本例复杂得多,
我真的需要防止onnifaces初始化一个新bean,当用户确认要留在页面上时,即使他试图关闭选项卡

菲尔德
字段B


bean信息:
字段A的值信息:
字段B的值信息:
注意:这是一个最小化版本,原始版本有更多细节,但由于我们公司的一些限制,我无法显示完整的代码堆栈。我希望这还是足够的,imo将显示与问题相关的部分


Omnifaces版本为2.7

卸载脚本在HTML
末尾初始化。此时,它将在装饰之前检查现有的
window.onbeforeunload
函数

您的
窗口.onbeforeunload
函数是在
$(document.ready()
期间定义的。但这还没有在HTML的末尾执行。它仅在
结束后执行。因此,
@ViewScoped
卸载脚本将无法正确修饰它


您需要确保在初始化
@ViewScoped
卸载脚本之前定义了
窗口.onbeforeunload
。您可以将它放在
$(document).ready()之外,然后通过
导入包含定义的JavaScript文件。将脚本内联到
中也会起作用,但不建议这样做,因为这只会使HTML文档变得更大,而且不会为浏览器提供缓存脚本的机会。

您好,这是OmniFaces 0.9中的一个已知问题。请打个招呼,谢谢你的信息。我将很快更新我的问题,提供更详细的示例。@Kukeltje更新了我的问题以提供更多信息。我在你的0.9手册中找不到关于这个问题的任何信息,你能给我一个链接吗?因为我使用的是2.7,所以我必须检查它是否仍然存在。非常感谢。您仅在
$(document.ready()
期间定义
窗口.onbeforeunload
。移动到块外,以便立即定义它,然后重试。@BalusC你是对的,这就成功了。我以前用过你的方法,但在那里不起作用。我将在原始代码中尝试这个,看看它是否仍然有效。谢谢
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html
        PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
        "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:h="http://java.sun.com/jsf/html" xmlns:p="http://primefaces.org/ui"
      xml:lang="en" lang="en">
<h:head>
    <title>reproduce example</title>

    <script type="application/javascript">
        $(document).ready(function () {
            var unsavedData = false;

            function setUnsavedData(flag) {
                unsavedData = flag;
            }

            window.onbeforeunload = function (e) {
                e = e || window.event;
                if (unsavedData) {
                    if (e) {
                        e.preventDefault();
                        e.returnValue = 'Any string';
                    }
                    return 'Any string';
                } else {
                    return undefined;
                }
            };

            $(document).on("change", ":input:not(.stateless)", function () {
                setUnsavedData(true);
            });

            $(document).on("click", "button.stateless", function () {
                setUnsavedData(false);
            });
        });
    </script>
</h:head>
<h:body>
    <h:form id="myform">
        <h3>steps to reproduce:</h3><br/>
        1) enter some value for fieldA<br/>
        2) click the 'navigate via navigationBean' link > confirm the dialog and 'stay on page'<br/>
        3) press the submit button > as you can see, the bean instance is still the same and value is passed.<br/>
        4) enter some value for fieldB<br/>
        5) click somewhere else on the page to lose the input's focus (otherwise the confirm dialog won't show up!)<br/>
        7) close the browser tab or use the browser's nav buttons > confirm the dialog and 'stay on page'<br/>
        8) press the submit button again > as you can see now, submit has not been called! Pressing submit shows the bean instance has changed!<br/><br/>
        <h4> > For this example, values are still usable after the new bean was initialized. Because the original page is way more complex then this example, <br/>
            I really need to prevent onmnifaces from initializing a new bean, when a user confirms to stay on the page, even if he tries to close the tab!</h4><br/>
        <br/>
        fieldA
        <p:inputText value="#{someBean.fieldA}"/>

        <!-- note: in real code this is represented by a NavigationBean logic!-->
        <p:commandLink value="navigate via navigationBean"
                       action="#"
                       ajax="false">
        </p:commandLink>
        <br/>
        fieldB
        <p:inputText value="#{someBean.fieldB}"/>
        <br/>
        <p:commandButton
                id="submitBtn"
                value="submit"
                action="#{someBean.submit()}"
                process="@form"
                update="@form" styleClass="stateless">
        </p:commandButton>
        <br/>
        <br/>
        bean info: <p:outputLabel id="output_1" value="#{someBean.info}"/>
        <br/>
        value info for fieldA: <p:outputLabel id="output_2" value="#{someBean.fieldA}"/>
        <br/>
        value info for fieldB: <p:outputLabel id="output_3" value="#{someBean.fieldB}"/>
    </h:form>
</h:body>
</html>