我正在将JSF与omnifaces和cdi一起使用。 在xhtml页面中,我使用了一些类似的js(在下面的jsf页面上有更详细的说明)
window.onbeforeunload = function(e){
// some other stuff
return 'unsaved data';
};
如果要卸载该窗口且剩余未保存的数据,则激活浏览器的默认确认对话框。
填写表格并重定向到同一页面,会弹出警告。如果我决定“停留在页面上”,则可以继续我的工作,然后再提交表单,就像我期望的那样。
问题:如果我导航到其他页面或尝试关闭标签页,警告也会如期出现。但是在这种情况下,提交“ omnifaces.event:unload”的http POST发送到服务器。据我所知,这将导致Bean的onDestroy()被调用。如果我选择留下,那么表单中的所有值仍会显示在页面上,但是在提交表单时,会为这些值抛出NPE(我猜是因为bean已经被破坏了,不尊重我在确认对话框上的决定)
经过数小时的研究,我无法弄清为什么第二种情况导致Bean卸载,而第一种方式正在等待确认对话框的结果……有什么想法吗? >
我已经注意到window.onbeforeunload
应该用普通的js调用,就像ViewScoped中的mentoined。这适用于第一种情况,但不适用于第二种情况。
编辑:
要重现的步骤位于jsf页面上。如果您遵循这些说明,则应该能够理解我的问题。
SomeBean
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>
注意:这是最小化版本,原始版本有更多详细信息,但是由于我们公司的某些限制,我无法显示完整的代码堆栈。我希望这仍然足够,并且显示与问题相关的部分。
Omnifaces版本为2.7
答案 0 :(得分:2)
@ViewScoped
卸载脚本在HTML <body>
的末尾初始化。此时,它将在装饰之前检查现有的window.onbeforeunload
函数。
您的window.onbeforeunload
函数是在$(document).ready()
期间定义的。但这尚未在HTML <body>
的末尾执行。它仅在<html>
结束后的 中执行。因此,@ViewScoped
卸载脚本将无法正确装饰它。
您需要确保在初始化window.onbeforeunload
卸载脚本之前 定义了@ViewScoped
。为此,您可以将其放置在外部 $(document).ready()
中,然后通过<h:outputScript target="head">
或<h:outputScript target="body">
导入包含定义的JavaScript文件。将脚本内联到<head>
中也可以,但是不建议这样做,因为这样做只会使HTML文档变大,什么也没做,并且不会为浏览器提供缓存脚本的机会。