h: commandButton / h: commandLink non funziona al primo clic, funziona solo al secondo clic

Abbiamo un menu di navigazione ajax che aggiorna una dynamic include. I file include hanno ciascuno le proprie forms.

        

Funziona correttamente, ma qualsiasi pulsante di comando nel file include non funziona al primo clic. Funziona solo al secondo clic e avanti.

Ho trovato questa domanda commandButton / commandLink / ajax metodo action / listener non invocato o valore di input non aggiornato e il mio problema è descritto al punto 9. Capisco che ho bisogno di includere esplicitamente l’ID del nell’include in il per risolverlo.

  

Nel mio caso, tuttavia, l’ID del modulo non è noto in anticipo. Anche questo modulo non sarà disponibile inizialmente nel contesto.

C’è qualche soluzione per lo scenario sopra?

È ansible utilizzare il seguente script per correggere il bug Mojarra 2.0 / 2.1 / 2.2 (nota: questo non si manifesta in MyFaces). Questo script creerà il campo nascosto javax.faces.ViewState per i moduli che non hanno recuperato nessuno stato di visualizzazione dopo l’aggiornamento di 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); } } } 

come all'interno di della pagina di errore. Se non puoi garantire che la pagina in questione utilizzi JSF , che triggers l' jsf.js di jsf.js , allora potresti voler aggiungere un ulteriore controllo if (typeof jsf !== 'undefined') prima della chiamata a jsf.ajax.addOnEvent() o per includerla esplicitamente da

  

Si noti che jsf.ajax.addOnEvent copre solo JSF standard e non ad es. PrimeFaces o poiché vengono utilizzati sotto le copertine jQuery per il lavoro. Per coprire anche le richieste Ajax di PrimeFaces, aggiungere quanto segue:

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

Aggiorna se stai utilizzando la libreria di utilità JSF OmniFaces , è bene sapere che quanto sopra è diventato parte di OmniFaces a partire da 1.7. È solo questione di dichiarare il seguente script in . Vedi anche la vetrina .

   ...  

Grazie a BalusC poiché la sua risposta è davvero eccezionale (come al solito :)). Ma devo aggiungere che questo approccio non funziona per richieste Ajax provenienti da RichFaces 4. Hanno diversi problemi con ajax e uno di questi è che i gestori JSF-ajax non vengono invocati. Pertanto, quando si esegue un rerender su un contenitore che contiene un modulo utilizzando i componenti RichFaces, la funzione fixViewState non viene chiamata e quindi manca il ViewState.

Nel Richent Reference di RichFaces , indicano come registrare i callback per le “loro” richieste di ajax (in realtà stanno utilizzando jQuery per collegarsi a tutte le richieste di ajax). Ma usando questo, non ero in grado di ottenere la risposta ajax che è usata dallo script di BalusC sopra per ottenere ViewState.

Quindi, in base alla correzione di BalusC, ho elaborato una soluzione molto simile. Il mio script salva tutti i valori di ViewState di tutti i moduli sulla pagina corrente in una mappa prima che la richiesta di ajax venga elaborata dal browser. Dopo l’aggiornamento del DOM, provo a ripristinare tutti i ViewState che sono stati salvati prima (per tutti i moduli che mancano ora il ViewState).

Vai avanti:

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

Dal momento che non sono un esperto di JavaScript, immagino che questo possa essere ulteriormente migliorato. Ma sicuramente funziona su FF 17, Chromium 24, Chrome 12 e IE 11.

Due ulteriori domande a questo approccio:

  • È ansible utilizzare nuovamente lo stesso valore di ViewState? Cioè JSF che assegna lo stesso valore di ViewState a ciascun modulo per ogni richiesta / risposta? Il mio approccio si basa su questa ipotesi (e non ho trovato alcuna informazione correlata).

  • Qualcuno si aspetta qualche problema con questo codice JavaScript o se ne è già imbattuto in qualche browser?