h:commandButton/h:commandLink ne fonctionne pas sur cliquez d'abord, ne fonctionne que sur le deuxième clic

nous avons un menu de navigation ajax qui met à jour une inclusion dynamique. Les fichiers ont chacun leurs propres formes.

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

cela fonctionne correctement, mais n'importe quel bouton de commande dans le fichier include ne fonctionne pas sur le premier clic. Il fonctionne seulement sur le deuxième clic et avant.

j'ai trouvé cette question commandButton / commandLink / ajax action / listener méthode non invoquée ou valeur d'entrée non mise à jour et mon problème est décrit au point 9. Je comprends que j'ai besoin d'inclure explicitement L'ID du <h:form> dans l'inclusion dans le <f:ajax render> pour le résoudre.

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

dans mon cas, cependant, le formulaire ID n'est pas connu à l'avance. Aussi le présent formulaire ne sera pas disponible dans le contexte où elles.

y a-t-il une solution au scénario ci-dessus?

21
demandé sur Community 2012-07-10 10:58:28

2 réponses

vous pouvez utiliser le script suivant pour corriger le Mojarra 2.0/2.1/2.2 bug (note: Ceci ne se manifeste pas dans MyFaces). Ce script va créer le champ caché javax.faces.ViewState pour les formulaires qui n'ont pas récupéré d'état de vue après la mise à jour d'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);
        }
    }
}

il suffit de l'inclure comme <h:outputScript name="some.js" target="head"> dans le <h:body> de la page d'erreur. Si vous ne pouvez pas garantir que la page en question utilise JSF <f:ajax> , ce qui déclencherait l'auto-inclusion de jsf.js , alors vous pouvez ajouter un contrôle if (typeof jsf !== 'undefined') supplémentaire avant jsf.ajax.addOnEvent() appel, ou l'inclure explicitement par

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

noter que jsf.ajax.addOnEvent ne couvre que les JSF standard <f:ajax> et non pas les PrimeFaces <p:ajax> ou <p:commandXxx> comme ils utilisent sous les couvertures jQuery pour le travail. Pour couvrir également les requêtes ajax PrimeFaces, ajouter ce qui suit:

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

mise à jour si vous utilisez JSF utility library OmniFaces , il est bon de savoir que ce qui précède a depuis 1.7 fait partie D'OmniFaces. Il s'agit simplement de déclarer le script suivant dans le <h:body> . Voir aussi le vitrine .

<h:body>
    <h:outputScript library="omnifaces" name="fixviewstate.js" target="head" />
    ...
</h:body>
41
répondu BalusC 2016-03-03 14:11:10

merci à BalusC puisque sa réponse est vraiment grande (comme d'habitude :) ). Mais je dois ajouter que cette approche ne fonctionne pas pour les requêtes ajax provenant de RichFaces 4. Ils ont plusieurs problèmes avec ajax et l'un d'eux est que les gestionnaires JSF-ajax ne sont pas invoqués. Ainsi, lors d'une redirection sur un conteneur tenant une forme à L'aide de RichFaces-components, la fonction fixViewState-function n'est pas appelée et le ViewState est manquant alors.

dans le RichFaces Référence du composant , ils indiquent comment enregistrer les callbacks pour" leurs " requêtes ajax (en fait, ils utilisent jQuery pour hook sur toutes les requêtes ajax). Mais en utilisant ceci, je n'ai pas pu obtenir la réponse ajax qui est utilisée par le script de BalusC ci-dessus pour obtenir le ViewState.

donc basé sur la solution de BalusC, j'en ai trouvé une très similaire. Mon script enregistre toutes les valeurs de ViewState de toutes les formes sur la page courante dans une carte avant que la requête ajax ne soit traitée par le navigateur. Après la mise à jour du DOM, j'essaie de restaurer toutes les vues qui ont été sauvegardées auparavant (pour tous les formulaires qui manquent le ViewState maintenant).

passer:

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

comme je ne suis pas expert en JavaScript, je pense que cela pourrait être encore amélioré. Mais il fonctionne certainement sur FF 17, Chrome 24, Chrome 12 et IE 11.

deux questions supplémentaires à cette approche:

  • est-il possible d'utiliser à nouveau la même valeur ViewState? C'est-à-dire: JSF attribue-t-elle la même valeur de ViewState à chaque formulaire pour chaque demande/réponse? Mon approche est basée sur cette hypothèse (et je n'ai trouvé aucune information connexe).

  • est-ce que quelqu'un s'attend à des problèmes avec ce code JavaScript ou a déjà rencontré certains problèmes en utilisant un navigateur?

5
répondu MrD 2014-04-11 08:56:00