h:commandButton / h:commandLink不能在第一次点击时使用,只能在第二次点击时使用

我们有一个ajax导航菜单,更新一个dynamic的包括。 包含文件有各自的forms。

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

它工作正常,但包含文件中的任何命令button不起作用的第一次点击。 它只能第二次来回运行。

我发现这个问题commandButton / commandLink / ajax动作/侦听器方法没有被调用或input值没有更新 ,我的问题是在第9点描述。我明白,我需要明确包括<h:form>的ID在include <f:ajax render>来解决它。

 <f:ajax render=":propertiesArea :propertiesArea:someFormId" /> 

然而在我的情况下,表单ID是事先不知道的。 此外,这种forms将不会在最初的情况下可用。

有没有解决上述情况?

您可以使用以下脚本来修复Mojarra 2.0 / 2.1 / 2.2错误(注意:这不会在MyFaces中显示)。 这个脚本将为ajax更新后没有检索任何视图状态的表单创buildjavax.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:body>内包含<h:outputScript name="some.js" target="head"> 。 如果您不能保证有问题的页面使用JSF <f:ajax> ,这会触发自动包含jsf.js ,那么您可能需要添加一个额外的if (typeof jsf !== 'undefined')检查在调用jsf.ajax.addOnEvent()之前,或者明确地包含它

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

如果您使用的是JSF实用程序库OmniFaces ,那么更新它,这是很好的知道1.7以上已经成为OmniFaces的一部分。 这只是在<h:body>中声明以下脚本的问题。 另见展示 。

 <h:body> <h:outputScript library="omnifaces" name="fixviewstate.js" target="head" /> ... </h:body> 

感谢BalusC,因为他的回答非常好(像往常一样:))。 但是我必须补充一点,这个方法不适用于来自RichFaces 4的Ajax请求。他们在Ajax方面有几个问题,其中一个是JSF-ajax处理程序没有被调用。 因此,当使用RichFaces组件对某个容器进行重新渲染时,不会调用fixViewState函数,并且ViewState会丢失。

在RichFaces组件参考中 ,他们声明了如何为“他们的”ajax请求注册callback(实际上他们利用jQuery来挂接所有的Ajax请求)。 但是使用这个,我无法获得上面BalusC脚本使用的ajax-response来获取ViewState。

所以基于BalusC的解决scheme,我制定了一个非常相似的。 在浏览器正在处理ajax请求之前,我的脚本将所有窗体的所有ViewState值保存在地图上的当前页面上。 在DOM更新之后,我尝试恢复之前保存的所有ViewState(对于所有现在缺lessViewState的表单)。

继续:

 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-值到每个表单? 我的方法是基于这个假设(我还没有find任何相关的信息)。

  • 有人期望这个JavaScript代码有任何问题,或已经遇到了一些使用任何浏览器?