Проблема
У нас есть интерфейс на основе свинга для корпоративного приложения, и сейчас мы реализуем (на данный момент более простой) интерфейс 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?
Обходные пути
Есть две стратегии, чтобы обойти это ограничение , но обе они требуют раздувания вкод Facelet, который может сбить с толку других разработчиков и вызвать другие проблемы.
Обходной путь 1: Использование a4j: поддержка
Эта стратегия выглядит следующим образом:
- ДобавьтеАтрибут
ajaxSingle="true"
для calculateButton
. - Добавьте тег
a4j:support
с атрибутом ajaxSingle="true"
к firstName
.
Первый шаг гарантирует, что calculateButton
сделаетне перезаписывать значения в age
или dob
, так как он больше не обрабатывает их.К сожалению, у него есть побочный эффект: он больше не обрабатывает firstName
.Второй шаг добавлен для противодействия этому побочному эффекту путем обработки firstName
до нажатия calculateButton
.
Имейте в виду, что может быть более 20 полей, например firstName
.Пользователь, заполнивший форму, может затем вызвать более 20 запросов к серверу!Как я уже упоминал ранее, это также раздувание, которое может сбить с толку других разработчиков.
Обходной путь 2: Использование списка процессов
Благодаря @DaveMaple и @MaxKatz за предложение этой стратегии, это выглядит следующим образом:
- Добавьте атрибут
ajaxSingle="true"
к calculateButton
. - Добавьте атрибут
process="firstName"
к calculateButton
.
Первый шаг достигает того же, что и в первом обходном пути, но имеет тот же побочный эффект. На этот раз второй шаг гарантирует, что firstName
обрабатывается с calculateButton
при нажатии.
Опять же, имейте в виду, что в этом списке может быть более 20 полей, таких как firstName
. Как я уже упоминал ранее, это также раздувание, которое может сбить с толку других разработчиков, тем более что список должен включать некоторые поля, но не другие.
Установщики и получатели возраста и 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;
}
}