h:commandButton/h:commandLink在第一次单击时不起作用,仅在第二次单击时起作用

Sky*_*ays 22 jsf commandlink jsf-2 commandbutton

我们有一个更新动态包含的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>
Run Code Online (Sandbox Code Playgroud)

它工作正常,但包含文件中的任何命令按钮在第一次单击时都不起作用.它仅适用于第二次点击.

我发现这个问题commandButton/commandLink/ajax动作/监听器方法没有被调用或输入值没有更新,我的问题在第9点描述.我明白我需要明确地包含在解决它的包<h:form>中的ID <f:ajax render>.

<f:ajax render=":propertiesArea :propertiesArea:someFormId" />
Run Code Online (Sandbox Code Playgroud)

但是,在我的情况下,表单ID事先是未知的.此表格最初也不会在上下文中提供.

上述方案有什么解决方案吗?

Bal*_*usC 44

您可以使用以下脚本来修复Mojarra 2.0/2.1/2.2错误(注意:这不会在MyFaces中显示).此脚本将为javax.faces.ViewStateajax更新后未检索任何视图状态的表单创建隐藏字段.

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);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

只是把它作为<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" />
Run Code Online (Sandbox Code Playgroud)

请注意,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);
    }
}
Run Code Online (Sandbox Code Playgroud)

如果您使用的是JSF实用程序库OmniFaces,请更新,我们很高兴知道上述内容已经从1.7成为OmniFaces的一部分.这只是在声明中的以下脚本的问题<h:body>.另见展示.

<h:body>
    <h:outputScript library="omnifaces" name="fixviewstate.js" target="head" />
    ...
</h:body>
Run Code Online (Sandbox Code Playgroud)


MrD*_*MrD 5

感谢BalusC,因为他的答案真的很棒(像往常一样:)).但我必须补充一点,这种方法不适用于来自RichFaces 4的ajax请求.它们有一些与ajax有关的问题,其中一个问题是没有调用JSF-ajax处理程序.因此,当使用RichFaces组件对一个持有表单的容器进行重新渲染时,不会调用fixViewState函数,因此缺少ViewState.

RichFaces组件参考中,他们说明了如何为"他们的"ajax请求注册回调(事实上他们正在利用jQuery来挂钩所有的ajax请求).但是使用这个,我无法获得上面BalusC的脚本使用的ajax响应来获取ViewState.

因此,基于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);
}
Run Code Online (Sandbox Code Playgroud)

由于我不是JavaScript专家,我想这可能会进一步改进.但它肯定适用于FF 17,Chromium 24,Chrome 12和IE 11.

这种方法还有两个问题:

  • 是否可以再次使用相同的ViewState值?即JSF为每个请求/响应的每个表单分配相同的ViewState值?我的方法是基于这个假设(我没有找到任何相关信息).

  • 有人期望这个JavaScript代码有任何问题,或者已经使用任何浏览器遇到过一些问题?