H: commandButton/h: commandLink не работает при первом щелчке, работает только при втором щелчке
У нас есть меню навигации 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" />
В моем случае, однако, идентификатор формы неизвестен заранее. Также эта форма будет недоступна в контексте initally.
Есть ли какое-либо решение для вышеупомянутого сценария?
Ответы
Ответ 1
Вы можете использовать следующий script, чтобы исправить ошибку Mojarra 2.0/2.1/2.2 (примечание: это не отображается в MyFaces). Этот script создаст скрытое поле 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 undefined when plain $.ajax(), $.get(), etc is used instead of PrimeFaces ajax.
fixViewState(xhr.responseXML);
}
}
Обновить, если вы используете служебную библиотеку JSF OmniFaces, хорошо знать, что выше поскольку 1.7 стали частью OmniFaces. Это просто вопрос объявления следующего script в <h:body>
. См. Также showcase.
<h:body>
<h:outputScript library="omnifaces" name="fixviewstate.js" target="head" />
...
</h:body>
Ответ 2
Благодаря BalusC, так как его ответ действительно велик (как обычно:)). Но я должен добавить, что этот подход не работает для ajax-запросов, поступающих из RichFaces 4. У них есть несколько проблем с ajax, и один из них заключается в том, что JSF-ajax-обработчики не вызываются. Таким образом, при выполнении rerender на каком-либо контейнере, содержащем форму с использованием компонентов RichFaces, функция fixViewState не вызывается, а ViewState отсутствует.
В Справочник по компонентам RichFaces говорится, как регистрировать обратные вызовы для своих "ajax-запросов" (на самом деле они используют jQuery для перехватить все запросы ajax).
Но, используя это, я не смог получить ajax-response, который используется BalusC script выше, чтобы получить ViewState.
Итак, основанный на исправлении BalusC, я разработал очень похожий. Мой script сохраняет все значения 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-кодом или уже запутался в некоторых с помощью любого браузера?