h: commandButton / h: commandLink не работает при первом нажатии, работает только при втором нажатии

33776 просмотра

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>

Он работает правильно, но любая кнопка в файле включения не работает при первом нажатии. Работает только по второму клику и вперед.

Я обнаружил этот вопрос: метод commandButton / commandLink / ajax action / listener не был вызван или входное значение не обновлено, и моя проблема описана в пункте 9. Я понимаю, что мне нужно явно включить идентификатор <h:form>в include <f:ajax render>для его решения.

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

В моем случае, однако, идентификатор формы заранее неизвестен. Также эта форма не будет доступна в контексте изначально.

Есть ли решение для вышеупомянутого сценария?

Автор: Skyler Hays Источник Размещён: 12.11.2019 09:11

Ответы (2)


43 плюса

Решение

Вы можете использовать следующий скрипт, чтобы исправить ошибку 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>которые они используют под крышками jQuery для работы. Чтобы покрыть ajax-запросы PrimeFaces, добавьте следующее:

$(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>
Автор: BalusC Размещён: 10.07.2012 11:16

5 плюса

Спасибо BalusC, так как его ответ действительно хорош (как обычно :)). Но я должен добавить, что этот подход не работает для запросов ajax, поступающих от RichFaces 4. У них есть несколько проблем с ajax, и одна из них заключается в том, что JSF-ajax-обработчики не вызываются. Таким образом, при выполнении повторного рендеринга в некотором контейнере, содержащем форму с использованием RichFaces-компонентов, функция fixViewState не вызывается, и ViewState тогда отсутствует.

В справочнике по компонентам RichFaces они заявляют, как регистрировать обратные вызовы для «своих» ajax-запросов (фактически они используют jQuery для подключения ко всем ajax-запросам). Но, используя это, я не смог получить ajax-ответ, который используется вышеописанным сценарием BalusC для получения ViewState.

Итак, основываясь на исправлении BalusC, я разработал очень похожий. Мой скрипт сохраняет все ViewState-значения всех форм на текущей странице карты до того, как браузер обработает ajax-запрос. После обновления 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.

Два дополнительных вопроса к этому подходу:

  • Возможно ли снова использовать то же значение ViewState? Т.е. присваивает ли JSF одно и то же значение ViewState каждой форме для каждого запроса / ответа? Мой подход основан на этом предположении (и я не нашел никакой связанной информации).

  • Кто-то ожидает каких-либо проблем с этим JavaScript-кодом или уже сталкивался с какими-то браузерами?

Автор: MrD Размещён: 11.04.2014 08:56
Вопросы из категории :
32x32