我们有一个ajax导航菜单,用于更新动态include。include文件各有自己的形式。
<h:form>
<h:commandButton value="Add" action="#{navigator.setUrl('AddUser')}">
<f:ajax render=":propertiesArea" />
</h:commandButton>
</h:form>
<h:panelGroup id="propertiesArea" layout="block">
<ui:include src="#{navigator.selectedLevel.url}" />
</h:panelGroup>
它工作正常,但include文件中的任何命令按钮在第一次单击时都不起作用。它只适用于第二次点击和来回点击。
我发现这个问题commandButton/commandLink/ajax操作/监听器方法没有被调用,或者输入值没有更新,我的问题在第9点中描述。我知道我需要在<f:ajax render>
的include中显式地包含<h:form>
的ID来解决它
<f:ajax render=":propertiesArea :propertiesArea:someFormId" />
然而,在我的情况下,表单ID事先是未知的。此外,此表单最初在上下文中不可用。
对于上述情况,有什么解决方案吗?
您可以使用以下脚本来修复Mojarra 2.0/2.1/2.2错误(注意:这在MyFaces中并不明显)。此脚本将为ajax更新后未检索到任何视图状态的表单创建javax.faces.ViewState
隐藏字段。
jsf.ajax.addOnEvent(function(data) {
if (data.status == "success") {
fixViewState(data.responseXML);
}
});
function fixViewState(responseXML) {
var viewState = getViewState(responseXML);
if (viewState) {
for (var i = 0; i < document.forms.length; i++) {
var form = document.forms[i];
if (form.method == "post") {
if (!hasViewState(form)) {
createViewState(form, viewState);
}
}
else { // PrimeFaces also adds them to GET forms!
removeViewState(form);
}
}
}
}
function getViewState(responseXML) {
var updates = responseXML.getElementsByTagName("update");
for (var i = 0; i < updates.length; i++) {
var update = updates[i];
if (update.getAttribute("id").match(/^([w]+:)?javax.faces.ViewState(:[0-9]+)?$/)) {
return update.textContent || update.innerText;
}
}
return null;
}
function hasViewState(form) {
for (var i = 0; i < form.elements.length; i++) {
if (form.elements[i].name == "javax.faces.ViewState") {
return true;
}
}
return false;
}
function createViewState(form, viewState) {
var hidden;
try {
hidden = document.createElement("<input name='javax.faces.ViewState'>"); // IE6-8.
} catch(e) {
hidden = document.createElement("input");
hidden.setAttribute("name", "javax.faces.ViewState");
}
hidden.setAttribute("type", "hidden");
hidden.setAttribute("value", viewState);
hidden.setAttribute("autocomplete", "off");
form.appendChild(hidden);
}
function removeViewState(form) {
for (var i = 0; i < form.elements.length; i++) {
var element = form.elements[i];
if (element.name == "javax.faces.ViewState") {
element.parentNode.removeChild(element);
}
}
}
只需将其作为<h:outputScript name="some.js" target="head">
包含在错误页面的<h:body>
中即可。如果您不能保证有问题的页面使用JSF <f:ajax>
,这将触发jsf.js
的自动包含,那么您可能需要在jsf.ajax.addOnEvent()
调用之前添加一个额外的if (typeof jsf !== 'undefined')
检查,或者通过显式包含它
<h:outputScript library="javax.faces" name="jsf.js" target="head" />
请注意,jsf.ajax.addOnEvent
仅涵盖标准JSF <f:ajax>
,而不包括PrimeFaces <p:ajax>
或<p:commandXxx>
,因为它们在作业的jQuery中使用。为了覆盖PrimeFaces ajax请求,添加以下内容:
$(document).ajaxComplete(function(event, xhr, options) {
if (typeof xhr.responseXML != 'undefined') { // It's undefined when plain $.ajax(), $.get(), etc is used instead of PrimeFaces ajax.
fixViewState(xhr.responseXML);
}
}
Update如果您正在使用JSF实用程序库OmniFaces,那么很高兴知道,自1.7以来,上面的内容已经成为OmniFaces的一部分。这只是在<h:body>
中声明以下脚本的问题。另请参阅展示。
<h:body>
<h:outputScript library="omnifaces" name="fixviewstate.js" target="head" />
...
</h:body>
感谢BalusC,因为他的回答真的很棒(和往常一样:)。但我必须补充一点,这种方法不适用于来自RichFaces4的ajax请求。他们在ajax方面有几个问题,其中一个问题是没有调用JSF ajax处理程序。因此,当使用RichFaces组件对某个包含表单的容器进行重新发送时,fixViewState函数不会被调用,ViewState也会丢失。
在RichFaces组件参考中,他们说明了如何为"他们的"ajax请求注册回调(事实上,他们正在利用jQuery挂接所有ajax请求)。但使用它,我无法获得上面BalusC脚本用于获取ViewState的ajax响应。
因此,基于BalusC的修复,我找到了一个非常相似的修复。在浏览器处理ajax请求之前,我的脚本将当前页面上所有表单的所有ViewState值保存在映射中。DOM更新后,我尝试恢复以前保存的所有ViewState(对于现在缺少ViewState的所有表单)。
继续:
jQuery(document).ready(function() {
jQuery(document).on("ajaxbeforedomupdate", function(args) {
// the callback will be triggered for each received JSF AJAX for the current page
// store the current view-states of all forms in a map
storeViewStates(args.currentTarget.forms);
});
jQuery(document).on("ajaxcomplete", function(args) {
// the callback will be triggered for each completed JSF AJAX for the current page
// restore all view-states of all forms which do not have one
restoreViewStates(args.currentTarget.forms);
});
});
var storedFormViewStates = {};
function storeViewStates(forms) {
storedFormViewStates = {};
for (var formIndex = 0; formIndex < forms.length; formIndex++) {
var form = forms[formIndex];
var formId = form.getAttribute("id");
for (var formChildIndex = 0; formChildIndex < form.children.length; formChildIndex++) {
var formChild = form.children[formChildIndex];
if ((formChild.hasAttribute("name")) && (formChild.getAttribute("name").match(/^([w]+:)?javax.faces.ViewState(:[0-9]+)?$/))) {
storedFormViewStates[formId] = formChild.value;
break;
}
}
}
}
function restoreViewStates(forms) {
for (var formIndexd = 0; formIndexd < forms.length; formIndexd++) {
var form = forms[formIndexd];
var formId = form.getAttribute("id");
var viewStateFound = false;
for (var formChildIndex = 0; formChildIndex < form.children.length; formChildIndex++) {
var formChild = form.children[formChildIndex];
if ((formChild.hasAttribute("name")) && (formChild.getAttribute("name").match(/^([w]+:)?javax.faces.ViewState(:[0-9]+)?$/))) {
viewStateFound = true;
break;
}
}
if ((!viewStateFound) && (storedFormViewStates.hasOwnProperty(formId))) {
createViewState(form, storedFormViewStates[formId]);
}
}
}
function createViewState(form, viewState) {
var hidden;
try {
hidden = document.createElement("<input name='javax.faces.ViewState'>"); // IE6-8.
} catch(e) {
hidden = document.createElement("input");
hidden.setAttribute("name", "javax.faces.ViewState");
}
hidden.setAttribute("type", "hidden");
hidden.setAttribute("value", viewState);
hidden.setAttribute("autocomplete", "off");
form.appendChild(hidden);
}
由于我不是JavaScript专家,我想这可能会得到进一步的改进。但它确实适用于FF 17、Chromium 24、Chrome 12和IE 11。
这种方法的另外两个问题:
再次使用相同的ViewState值是否可行?也就是说,JSF是否为每个请求/响应为每个表单分配相同的ViewState值?我的方法是基于这一假设(我没有找到任何相关信息)。
有人认为这个JavaScript代码会有任何问题吗?或者在使用任何浏览器时已经遇到了一些问题吗?