h: commandButton / h: o comandoLink não funciona no primeiro clique, funciona apenas no segundo clique

Temos um menu de navegação ajax que atualiza uma inclusão dinâmica. Os arquivos include possuem seus próprios formulários.

        

Ele funciona corretamente, mas qualquer botão de comando no arquivo de inclusão não funciona no primeiro clique. Ele funciona apenas no segundo clique e para frente.

Eu encontrei esta questão commandButton / commandLink / ajax método de ação / ouvinte não invocado ou valor de input não atualizado e meu problema é descrito no ponto 9. Eu entendo que eu preciso include explicitamente o ID do no include em o para resolvê-lo.

  

No meu caso, no entanto, o ID do formulário não é conhecido de antemão. Além disso, este formulário não estará disponível no contexto inicialmente.

Existe alguma solução para o cenário acima?

Você pode usar o seguinte script para corrigir o erro Mojarra 2.0 / 2.1 / 2.2 (nota: isso não se manifesta em MyFaces). Esse script criará o campo oculto javax.faces.ViewState para os formulários que não recuperaram nenhum estado de exibição após a atualização do 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(""); // 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); } } } 

Apenas inclua-o como dentro do da página de erro. Se você não pode garantir que a página em questão usa JSF , o que acionaria a inclusão automática de jsf.js , você pode querer adicionar uma verificação adicional if (typeof jsf !== 'undefined') antes da chamada jsf.ajax.addOnEvent() , ou incluí-lo explicitamente

  

Note que jsf.ajax.addOnEvent só cobre o padrão JSF e não ex. PrimeFaces ou como eles usam sob as capas jQuery para o trabalho. Para cobrir os pedidos de ajax do PrimeFaces, adicione o seguinte:

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

Atualize se você estiver usando a biblioteca de utilitários JSF, OmniFaces , é bom saber que o código acima já faz parte do OmniFaces. É só uma questão de declarar o seguinte script no . Veja também o mostruário .

   ...  

Obrigado ao BalusC, já que sua resposta é ótima (como de costume :)). Mas tenho que acrescentar que essa abordagem não funciona para pedidos ajax provenientes de RichFaces 4. Eles têm vários problemas com o ajax e um deles é que os manipuladores JSF-ajax não estão sendo chamados. Portanto, ao fazer um rerender em algum contêiner que contém um formulário usando RichFaces-components, a function fixViewState não é chamada e o ViewState está ausente.

Na Referência do componente RichFaces , eles informam como registrar retornos de chamada para “seus” pedidos ajax (na verdade, eles estão utilizando o jQuery para conectar-se a todos os pedidos ajax). Mas usando isso, eu não consegui obter a resposta ajax que é usada pelo script do BalusC acima para obter o ViewState.

Então, com base na correção do BalusC, eu trabalhei com um muito similar. Meu script salva todos os valores de ViewState de todos os formulários na página atual em um mapa antes de o pedido ajax ser processado pelo navegador. Após a atualização do DOM, eu tento restaurar todos os ViewStates que foram salvos antes (para todos os formulários que estão faltando o ViewState agora).

Ir em frente:

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

Desde que eu não sou um especialista em JavaScript, eu acho que isso pode ser melhorado ainda mais. Mas definitivamente funciona em FF 17, Chromium 24, Chrome 12 e IE 11.

Duas perguntas adicionais para esta abordagem:

  • É possível usar o mesmo valor de ViewState novamente? Ou seja, o JSF está atribuindo o mesmo valor de ViewState a cada formulário para cada solicitação / resposta? Minha abordagem é baseada nessa suposição (e não encontrei nenhuma informação relacionada).

  • Alguém espera algum problema com este código JavaScript ou já se deparou com algum usando qualquer navegador?