24

動的インクルードを更新する ajax ナビゲーション メニューがあります。インクルード ファイルには、それぞれ独自の形式があります。

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

正しく動作しますが、インクルード ファイルのコマンド ボタンは最初のクリックでは機能しません。2回目以降のクリックでのみ機能します。

この質問commandButton/commandLink/ajax action/listener method not invoked or input value not updatedが見つかりました。私の問題はポイント 9 で説明されています。解決するに<h:form>は、インクルードにの ID を明示的に含める必要があることを理解しています。<f:ajax render>

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

ただし、私の場合、フォーム ID は事前にわかりません。また、このフォームは、最初はコンテキストで使用できません。

上記のシナリオの解決策はありますか?

4

2 に答える 2

45

次のスクリプトを使用して、Mojarra 2.0/2.1/2.2 バグを修正できます (注: これは MyFaces には現れません)。このスクリプトはjavax.faces.ViewState、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);
        }
    }
}

エラーページの<h:outputScript name="some.js" target="head">中に含めるだけです。<h:body>問題のページが 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>。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>
于 2012-07-10T11:16:19.683 に答える
5

彼の答えは本当に素晴らしいので、BalusCに感謝します(いつものように:))。ただし、RichFaces 4 からの ajax リクエストに対してはこのアプローチが機能しないことを付け加えておく必要があります。これらの ajax にはいくつかの問題があり、そのうちの 1 つは JSF-ajax-handlers が呼び出されていないことです。したがって、RichFaces-components を使用してフォームを保持しているコンテナで再レンダリングを実行すると、fixViewState 関数が呼び出されず、ViewState が失われます。

RichFaces Component Referenceでは、「自分の」ajax リクエストのコールバックを登録する方法を説明しています (実際、jQuery を利用してすべての ajax リクエストをフックしています)。しかし、これを使用すると、上記の BalusC のスクリプトで ViewState を取得するために使用される ajax-response を取得できませんでした。

したがって、BalusC の修正に基づいて、非常によく似たものを作成しました。私のスクリプトは、ajax-request がブラウザーによって処理される前に、現在のページのすべてのフォームのすべての ViewState 値をマップに保存します。DOM の更新後、以前に保存されたすべての ViewStates を復元しようとしました (ViewState が欠落しているすべてのフォームに対して)。

進む:

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 では確実に動作します。

このアプローチに対する 2 つの追加の質問:

  • 同じ ViewState 値を再度使用することは可能ですか? つまり、JSF はすべての要求/応答に対して各フォームに同じ ViewState 値を割り当てていますか? 私のアプローチはこの仮定に基づいています (関連する情報は見つかりませんでした)。

  • この JavaScript コードで何らかの問題が発生することを予想している人はいますか? または、何らかのブラウザーを使用してすでに問題に遭遇している人はいますか?

于 2014-04-11T08:56:00.013 に答える