У нас есть интерфейс на основе Swing для корпоративного приложения, и теперь мы реализуем (пока более простой) интерфейс JSF / Seam / Richfaces для него.
Некоторые страницы содержат поля, которые при редактировании должны вызывать изменение других полей. Нам нужно, чтобы это изменение было показано пользователю немедленно (т.е. им не нужно было нажимать кнопку или что-то еще).
Я успешно реализовал это, используя h: commandButton
и добавив onchange = "submit ()"
в поля, которые вызывают изменение других полей. Таким образом, отправка формы происходит, когда они редактируют поле, и в результате обновляются другие поля.
Функционально это работает нормально, но особенно когда сервер находится под значительной нагрузкой (что случается часто), отправка формы может занять долгое время, и наши пользователи тем временем продолжали редактировать поля, которые затем возвращаются, когда отображаются ответы на запросы onchange = "submit ()"
.
Чтобы решить эту проблему, я надеялся достичь чего-то, где:
Хорошо, я думаю, что проще всего показать часть моей страницы первый. Обратите внимание, что это только отрывок и что на некоторых страницах будет много полей и много кнопок.
<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.
Однако событие onchange
не происходит, пока я не щелкну в другом месте страницы или не нажму вкладку или что-то в этом роде. Это вызывает проблемы, когда пользователь вводит значение, скажем, age
, а затем нажимает calculateButton
без щелчка в другом месте страницы или нажатия вкладки.
] onchange
действительно происходит первым, поскольку я вижу изменение значения dob
, но два значения затем возвращаются, когда выполняется запрос calculateButton
.
Итак наконец, на вопрос: Есть ли способ гарантировать, что модель и представление полностью обновлены до выполнения запроса calculateButton
, чтобы они не отменялись? Почему этого еще не происходит, поскольку я использую очередь AJAX?
Есть две стратегии, чтобы обойти это ограничение , но обе они требуют раздувания кода лицевой панели, что может сбивать с толку другим разработчикам и вызвать другие проблемы.
Эта стратегия выглядит следующим образом:
ajaxSingle = "true"
в calculateButton
. a4j: support
с атрибутом ajaxSingle = "true"
в firstName
. Первый шаг гарантирует, что calculateButton
не перезапишет значения в age
или dob
, поскольку больше не обрабатывает их. К сожалению, у этого есть побочный эффект: он больше не обрабатывает firstName
. Второй шаг добавлен для противодействия этому побочному эффекту путем обработки firstName
до нажатия calculateButton
.
Имейте в виду, что может быть более 20 полей, например firstName
. Пользователь, заполняющий форму, может вызвать более 20 запросов на сервер! Как я уже упоминал ранее, это также беспорядок, который может сбить с толку других разработчиков.
Благодаря @DaveMaple и @MaxKatz за предложение этой стратегии, он выглядит следующим образом:
ajaxSingle = "
Установщики и получатели возраста и DOB (на случай, если они являются причиной проблемы)
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;
}
}