web-dev-qa-db-ja.com

h:commandButton / h:commandLinkは最初のクリックでは機能せず、2回目のクリックでのみ機能します

動的インクルードを更新する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アクション/リスナーメソッドが呼び出されなかったか、入力値が更新されていません と私の問題はポイント9で説明されています<h:form>のIDを明示的に含める必要があることを理解していますそれを解決するために<f:ajax render>にインクルードします。

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

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

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

23
Skyler Hays

次のスクリプトを使用して、Mojarra 2.0/2.1/2.2バグを修正できます(注:これはMyFacesでは発生しません)。このスクリプトは、ajaxの更新後にビューステートを取得しなかったフォームの_javax.faces.ViewState_非表示フィールドを作成します。

_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>_は、ジョブのjQueryのカバーの下で使用します。 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);
    }
}
_

UpdateJSFユーティリティライブラリを使用している場合 OmniFaces は、1.7からOmniFacesの一部になったことを知っておくとよいでしょう。 。 _<h:body>_で次のスクリプトを宣言するだけです。 showcase も参照してください。

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

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

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

BalusCの修正に基づいて、私は非常によく似た修正を行いました。私のスクリプトは、ブラウザーがajaxリクエストを処理する前に、現在のページのすべてのフォームのすべてのViewState値をマップに保存します。 DOMの更新後、以前に保存されたすべてのViewStateを復元しようとします(現在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-valueを再び使用することは可能ですか?つまりJSFはすべてのリクエスト/レスポンスの各フォームに同じViewState値を割り当てていますか?私のアプローチはこの仮定に基づいています(そして私は関連情報を見つけていません)。

  • 誰かがこのJavaScriptコードの問題を予期しているのでしょうか、それとも、何らかのブラウザを使用してすでに何かに遭遇しましたか?

5
MrD