jsf jsf-2 commandbutton commandlink

jsf - h: commandButton/h: commandLink no funciona con el primer clic, funciona solo con un segundo clic



jsf-2 (2)

Tenemos un menú de navegación ajax que actualiza una inclusión dinámica. Los archivos de inclusión tienen cada uno sus propios formularios.

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

Funciona correctamente, pero cualquier botón de comando en el archivo de inclusión no funciona con el primer clic. Funciona solo en el segundo clic y adelante.

Encontré esta pregunta CommandButton / commandLink / ajax action / listener method no invocado o input value no actualizado y mi problema se describe en el punto 9. Entiendo que necesito incluir explícitamente el ID de la <h:form> en el include en el <f:ajax render> para resolverlo.

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

En mi caso, sin embargo, el ID del formulario no se conoce de antemano. Además, este formulario no estará disponible en el contexto initally.

¿Hay alguna solución para el escenario anterior?


Gracias a BalusC ya que su respuesta es realmente genial (como de costumbre :)). Pero debo agregar que este enfoque no funciona para solicitudes ajax provenientes de RichFaces 4. Tienen varios problemas con ajax y uno de ellos es que los manejadores JSF-ajax no se están invocando. Por lo tanto, al realizar una redención en un contenedor que contiene un formulario que utiliza RichFaces-components, no se llama a la función fixViewState y entonces falta el ViewState.

En la Referencia de componentes de RichFaces , explican cómo registrar devoluciones de llamadas para "sus" solicitudes ajax (de hecho, están utilizando jQuery para enganchar todas las solicitudes ajax). Pero al usar esto, no pude obtener la respuesta ajax que usa el script de BalusC arriba para obtener el ViewState.

Entonces, basado en la solución de BalusC, encontré uno muy similar. Mi secuencia de comandos guarda todos los valores de ViewState de todos los formularios en la página actual en un mapa antes de que el navegador procese la solicitud ajax. Después de la actualización del DOM, trato de restaurar todos los ViewStates que se han guardado antes (para todos los formularios que ahora no tienen el ViewState).

Siga adelante:

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

Como no soy un experto en JavaScript, creo que esto se puede mejorar aún más. Pero definitivamente funciona en FF 17, Chromium 24, Chrome 12 e IE 11.

Dos preguntas adicionales a este enfoque:

  • ¿Es factible usar el mismo ViewState-value nuevamente? Es decir, ¿JSF asigna el mismo valor de ViewState a cada formulario para cada solicitud / respuesta? Mi enfoque se basa en esta suposición (y no he encontrado ninguna información relacionada).

  • ¿Alguien espera algún problema con este código JavaScript o ya se encontró con alguno usando cualquier navegador?


Puede utilizar la siguiente secuencia de comandos para corregir el error Mojarra 2.0 / 2.1 / 2.2 (nota: esto no se manifiesta en MyFaces). Este script creará el campo oculto javax.faces.ViewState para los formularios que no recuperaron ningún estado de vista después de la actualización ajax.

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

Simplemente <h:outputScript name="some.js" target="head"> como <h:outputScript name="some.js" target="head"> dentro de <h:body> de la página de error. Si no puede garantizar que la página en cuestión utilice JSF <f:ajax> , lo que desencadenaría la inclusión automática de jsf.js , entonces es posible que desee agregar un if (typeof jsf !== ''undefined'') adicional if (typeof jsf !== ''undefined'') antes de la llamada a jsf.ajax.addOnEvent() , o explícitamente incluirlo por

<h:outputScript library="javax.faces" name="jsf.js" target="head" />

Tenga en cuenta que jsf.ajax.addOnEvent solo cubre JSF estándar <f:ajax> y no p. Ej. PrimeFaces <p:ajax> o <p:commandXxx> ya que se utilizan debajo de las cubiertas jQuery para el trabajo. Para cubrir las solicitudes PrimeFaces ajax también, agregue lo siguiente:

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

Actualice si está utilizando la biblioteca de utilidades JSF OmniFaces , es bueno saber que lo anterior desde el 1.7 se ha convertido en parte de OmniFaces. Solo se trata de declarar el siguiente script en <h:body> . Ver también el showcase .

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