使用 a4j:support 更新模型和视图,为下一步按钮/提交操作做好准备



问题

我们为企业应用程序提供了一个基于摆动的前端,现在正在为它实现一个(现在更简单的)JSF/Seam/Richfaces前端。

某些页面包含的字段在编辑时会导致其他字段因此而更改。我们需要立即向用户显示此更改(即他们不必按下按钮或任何东西)。

我已经使用 h:commandButton 和向导致其他字段更改的字段添加onchange="submit()"成功地实现了这一点。这样,当他们编辑字段时,就会进行表单提交,并因此更新其他字段。

这在功能上工作正常,但特别是当服务器处于很大负载下(经常发生)时,表单提交可能需要很长时间,我们的用户在此期间一直在继续编辑字段,然后在呈现对onchange="submit()"请求的响应时被还原。

为了解决这个问题,我希望实现以下目标:

    编辑字段时,如果需要
  1. ,仅处理字段,并且仅重新呈现它修改的字段(以便用户在此期间所做的任何其他编辑都不会丢失)。
  2. 按下按钮后,所有字段将正常处理并重新呈现。

(不稳定)解决方案

好的,我认为首先显示我的页面可能是最简单的。请注意,这只是摘录,某些页面将具有许多字段和许多按钮。

<a4j:form id="mainForm">
    ...
    <a4j:commandButton id="calculateButton" value="Calculate" action="#{illustrationManager.calculatePremium()}" reRender="mainForm" />
    ...
    <h:outputLabel for="firstName" value=" First Name" />
    <h:inputText id="firstName" value="#{life.firstName}" />
    ...
    <h:outputLabel for="age" value=" Age" />
    <h:inputText id="age" value="#{life.age}">
        <f:convertNumber type="number" integerOnly="true" />
        <a4j:support event="onchange" ajaxSingle="true" reRender="dob" />
    </h:inputText>
    <h:outputLabel for="dob" value=" DOB" />
    <h:inputText id="dob" value="#{life.dateOfBirth}" styleClass="date">
        <f:convertDateTime pattern="dd/MM/yyyy" timeZone="#{userPreference.timeZone}" />
        <a4j:support event="onchange" ajaxSingle="true" reRender="age,dob" />
    </h:inputText>
    ...        
</a4j:form>

更改 age 的值会导致模型中dob的值发生变化,反之亦然。我使用 reRender="dob"reRender="age,dob" 来显示模型中更改的值。这工作正常。

我还使用全局队列来确保 AJAX 请求的顺序。

但是,在我单击页面上的其他地方或按 tab 键或其他内容之前,onchange 事件不会发生。当用户在 age 中输入值,然后按 calculateButton 而不单击页面上的其他位置或按 Tab 时,这会导致问题。

onchange事件似乎首先发生,因为我可以看到dob的值发生了变化,但是在执行calculateButton请求时,这两个值随后被还原。

因此,最后,要问一个问题:有没有办法确保在发出calculateButton请求之前完全更新模型和视图,以便它不会还原它们?为什么自从我使用 AJAX 队列以来还没有发生这种情况?

解决方法

有两种策略可以绕过此限制,但它们都需要在facelet代码中膨胀,这可能会使其他开发人员感到困惑并导致其他问题。

解决方法 1:使用 a4j:支持

此策略如下:

  1. ajaxSingle="true"属性添加到calculateButton
  2. 将具有 ajaxSingle="true" 属性的 a4j:support 标记添加到 firstName

第一步确保 calculateButton 不会覆盖 agedob 中的值,因为它不再处理它们。不幸的是,它的副作用是它不再处理firstName。添加第二步通过在按下calculateButton之前处理firstName来抵消这种副作用。

请记住,可能有 20+ 个字段,例如 firstName .用户填写表单可能会导致 20+ 请求服务器!就像我之前提到的,这也是可能会使其他开发人员感到困惑的膨胀。

解决方法 2:使用进程列表

感谢@DaveMaple和@MaxKatz提出此策略,如下所示:

  1. ajaxSingle="true" 属性添加到 calculateButton
  2. process="firstName" 属性添加到 calculateButton

第一步实现与第一个解决方法相同的效果,但具有相同的副作用。这一次,第二步确保firstName在按下时calculateButton处理。

同样,请记住,此列表中可能包含 20+ 个字段,例如 firstName。就像我之前提到的,这也是可能会使其他开发人员感到困惑的膨胀,特别是因为列表必须包含某些字段而不是其他字段。

年龄和出生日期设置器和 Getters(以防万一它们是问题的原因)

public Number getAge() {
    Long age = null;
    if (dateOfBirth != null) {
        Calendar epochCalendar = Calendar.getInstance();
        epochCalendar.setTimeInMillis(0L);
        Calendar dobCalendar = Calendar.getInstance();
        dobCalendar.setTimeInMillis(new Date().getTime() - dateOfBirth.getTime());
        dobCalendar.add(Calendar.YEAR, epochCalendar.get(Calendar.YEAR) * -1);
        age = new Long(dobCalendar.get(Calendar.YEAR));
    }
    return (age);
}
public void setAge(Number age) {
    if (age != null) {
        // This only gives a rough date of birth at 1/1/<this year minus <age> years>.
        Calendar calendar = Calendar.getInstance();
        calendar.set(calendar.get(Calendar.YEAR) - age.intValue(), Calendar.JANUARY, 1, 0, 0, 0);
        setDateOfBirth(calendar.getTime());
    }
}
public Date getDateOfBirth() {
    return dateOfBirth;
}
public void setDateOfBirth(Date dateOfBirth) {
    if (notEqual(this.dateOfBirth, dateOfBirth)) {
        // If only two digits were entered for the year, provide useful defaults for the decade.
        Calendar calendar = Calendar.getInstance();
        calendar.setTime(dateOfBirth);
        if (calendar.get(Calendar.YEAR) < 50) {
            // If the two digits entered are in the range 0-49, default the decade 2000.
            calendar.set(Calendar.YEAR, calendar.get(Calendar.YEAR) + 2000);
        } else if (calendar.get(Calendar.YEAR) < 100) {
            // If the two digits entered are in the range 50-99, default the decade 1900.
            calendar.set(Calendar.YEAR, calendar.get(Calendar.YEAR) + 1900);
        }
        dateOfBirth = calendar.getTime();
        this.dateOfBirth = dateOfBirth;
        changed = true;
    }
}

你的豆子的范围是什么?当按钮被执行时,它是一个新请求,如果你的 Bean 在请求范围内,那么以前的值将消失。

所以,最后,关于一个问题:有没有办法确保在发出 calculateButton 请求之前完全更新模型和视图,以便它不会还原它们?

您可以做的是从启动 ajax 请求时禁用提交按钮,直到 ajax 请求完成。这将有效地防止用户按下提交按钮,直到它解决:

<a4j:support 
    event="onblur" 
    ajaxSingle="true" 
    onsubmit="jQuery('#mainForm\:calculateButton').attr('disabled', 'disabled');" 
    oncomplete="jQuery('#mainForm\:calculateButton').removeAttr('disabled');" />

这种方法另外有用的一件事是,如果您在发生这种情况时向最终用户显示"ajaxy"图像,以便他们直观地了解正在发生的事情很快就会解决。您也可以在 onsubmit 方法中显示此图像,然后在完成时将其隐藏。

编辑:问题可能只是您需要将进程属性添加到 a4j:commandButton。此属性按 id 指定应参与模型更新阶段的组件:

<a4j:commandButton 
    id="calculateButton" 
    value="Calculate" 
    action="#{illustrationManager.calculatePremium()}" 
    process="firstName"        
    reRender="mainForm" />
我想

我可以提供另一种解决方法。

我们应该在 JS 级别有两个标志:

var requestBlocked = false;
var requestShouldBeSentAfterBlock = false;

h:inputText元素阻止模糊上的 ajax 请求:

<h:inputText id="dob" onblur="requestBlocked = true;" ...

如果requestBlocked为 false,a4j:commandButton发送请求:

<a4j:commandButton id="calculateButton"
    onkeyup="requestShouldBeSentAfterBlock = requestBlocked;
        return !requestBlocked;" ...

如果requestShouldBeSentAfterBlock为 true,a4j:support发送请求:

<a4j:support event="onchange" 
    oncomplete="requestBlocked = false; 
        if (requestShouldBeSentAfterBlock) { 
            requestShouldBeSentAfterBlock = false;
            document.getElementById('calculateButton').click();
        }" ...

由于oncomplete块在所有需要的元素重新渲染后工作,因此事情将按所需的顺序工作。

看起来原因在getters/setters的某个地方。例如,一种可能性:当没有ajaxSingle=true时,单击计算按钮。所有值都设置为模型,因此将同时调用setAgesetDateOfBirth。并且可能会发生这种情况,以便在setAge之前调用setDateOfBirth.

仔细观察setAge方法,它实际上将日期重置为年初,即使该日期可能已经有正确的年份。

我建议先简化逻辑。例如,为年份和出生日期提供单独的断开连接的字段,并检查问题是否仍然可以重现,以找到重现所需的最小条件。

同样从用户体验的角度来看,常见的做法是执行以下操作,以便在用户停止键入后触发事件:

<a4j:support event="onkeyup"
             ajaxSingle="true"
             ignoreDupResponses="true"
             requestDelay="300"
             reRender="..."/>

最新更新