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

问题描述 投票:6回答:4

The Problem

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

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

我已经使用h:commandButton成功实现了这一点,并将onchange="submit()"添加到导致其他字段更改的字段中。这样,表单提交在他们编辑字段时发生,其他字段作为结果更新。

这在功能上运行良好,但特别是当服务器处于显着负载(经常发生)时,表单提交可能需要很长时间,并且我们的用户在此期间继续编辑字段,然后在对onchange="submit()"请求的响应时恢复被渲染。

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

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

The (Unstable) Solution

好吧,我认为首先展示一下我的页面可能是最容易的。请注意,这只是一个摘录,有些页面会有很多字段和许多按钮。

<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队列以来就没有发生过这种情况?

The Workarounds

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

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

这个策略如下:

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

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

请记住,虽然可能有20多个字段,如firstName。填写表单的用户可能会向服务器发出20多个请求!就像我之前提到的那样,这也可能让其他开发人员感到困惑。

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

感谢@DaveMaple和@MaxKatz建议这个策略,它如下:

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

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

再次,请记住,虽然可能有20多个字段,如firstName列入此列表。就像我之前提到的那样,这也可能让其他开发人员感到困惑,特别是因为列表必须包含一些字段而不包括其他字段。

Age and DOB Setters and Getters (just in case they are the cause of the issue)

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;
    }
}
jsf richfaces seam ajax4jsf
4个回答
0
投票

你的bean的范围是什么?当执行该按钮时,它是一个新请求,如果您的bean在请求范围内,那么之前的值将消失。


0
投票

最后,问题是:有没有办法确保在进行calculateButton请求之前完全更新模型和视图,以便它不会还原它们?

你可以做的是从你发起ajax请求到你的ajax请求完成之前禁用提交按钮。这将有效地阻止用户按下提交按钮,直到它解决:

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

对于这种方法另外有用的一件事是,如果您在最终用户身上显示“ajaxy”图像,以便他们直观地了解即将解决的事情。您也可以在onsubmit方法中显示此图像,然后将其隐藏在oncomplete中。

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

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

0
投票

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

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

var requestBlocked = false;
var requestShouldBeSentAfterBlock = false;

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

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

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

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

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

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

因为oncomplete块在重新渲染所有需要的元素之后工作,所以事情将以所需的顺序工作。


0
投票

这似乎是一个原因在于getter / setter中的某个地方。例如,其中一种可能性:当没有点击ajaxSingle=true和计算按钮时。所有值都设置为模型,因此调用setAgesetDateOfBirth。它可能发生在setDateOfBirth之前调用setAge

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

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

同样从用户体验的角度来看,常见的做法是执行以下操作,因此一旦用户停止输入,事件就会触发:

<a4j:support event="onkeyup"
             ajaxSingle="true"
             ignoreDupResponses="true"
             requestDelay="300"
             reRender="..."/>
© www.soinside.com 2019 - 2024. All rights reserved.