Использование a4j: поддержка для обновления модели и вида, готова к следующему действию кнопки / отправки - PullRequest
6 голосов
/ 17 мая 2011

Проблема

У нас есть интерфейс на основе свинга для корпоративного приложения, и сейчас мы реализуем (на данный момент более простой) интерфейс 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.

Однако событие onchange не происходит, пока я не нажму где-нибудь еще на странице или не нажмувкладка или что-то.Это вызывает проблемы, когда пользователь вводит значение, скажем, age, а затем нажимает calculateButton , не нажимая где-либо еще на странице или нажимая вкладку.

Событие onchangeсначала возникает, поскольку я вижу изменение dob, но эти два значения затем возвращаются при выполнении запроса calculateButton.

Итак, наконец, к вопросу: есть ли способчтобы убедиться, что модель и представление полностью обновлены до выполнения запроса calculateButton, чтобы он не возвращал их?Почему этого не происходит, так как я использую очередь AJAX?

Обходные пути

Есть две стратегии, чтобы обойти это ограничение , но обе они требуют раздувания вкод Facelet, который может сбить с толку других разработчиков и вызвать другие проблемы.

Обходной путь 1: Использование a4j: поддержка

Эта стратегия выглядит следующим образом:

  1. ДобавьтеАтрибут ajaxSingle="true" для calculateButton.
  2. Добавьте тег a4j:support с атрибутом ajaxSingle="true" к firstName.

Первый шаг гарантирует, что calculateButton сделаетне перезаписывать значения в age или dob, так как он больше не обрабатывает их.К сожалению, у него есть побочный эффект: он больше не обрабатывает firstName.Второй шаг добавлен для противодействия этому побочному эффекту путем обработки firstName до нажатия calculateButton.

Имейте в виду, что может быть более 20 полей, например firstName.Пользователь, заполнивший форму, может затем вызвать более 20 запросов к серверу!Как я уже упоминал ранее, это также раздувание, которое может сбить с толку других разработчиков.

Обходной путь 2: Использование списка процессов

Благодаря @DaveMaple и @MaxKatz за предложение этой стратегии, это выглядит следующим образом:

  1. Добавьте атрибут ajaxSingle="true" к calculateButton.
  2. Добавьте атрибут 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;
    }
}

Ответы [ 4 ]

0 голосов
/ 30 марта 2016

Похоже, причина находится где-то в методах получения / установки.Например одна из возможностей: когда нет ajaxSingle=true и нажата кнопка расчета.Все значения установлены для модели, поэтому вызываются setAge и setDateOfBirth.И может случиться так, что setDateOfBirth вызывается до setAge.

Если присмотреться к методу setAge, он фактически сбрасывает дату в начало года, даже если дата могла иметьуже год.

Я бы рекомендовал сначала упростить логику.Например, есть отдельные отключенные поля для года и дня рождения, и проверьте, можно ли устранить проблему, чтобы найти минимально необходимые условия для воспроизведения.

Также с точки зрения пользовательского опыта, обычной практикой является выполнение чего-то вроде следующегопоэтому событие запускается, когда пользователь перестает набирать:

<a4j:support event="onkeyup"
             ajaxSingle="true"
             ignoreDupResponses="true"
             requestDelay="300"
             reRender="..."/>
0 голосов
/ 17 мая 2011

Итак, наконец, к вопросу: есть ли способ гарантировать, что модель и представление полностью обновляются до того, как будет выполнен запрос calcButton, чтобы он не возвращал их?

Что вы можете сделать, это отключить кнопку отправки с момента, когда вы инициируете свой запрос ajax, до тех пор, пока ваш запрос ajax не завершится. Это будет эффективно препятствовать пользователю нажимать кнопку отправки, пока он не разрешит:

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

Одна вещь, которая дополнительно полезна в этом подходе, - это если вы отображаете изображение «ajaxy» для конечного пользователя, когда это происходит, чтобы они интуитивно понимали, что происходит то, что скоро разрешится. Вы можете также показать это изображение в методе onsubmit и затем скрыть его при завершении.

EDIT: Возможно, проблема заключается в том, что вам нужно добавить атрибут процесса к вашему a4j: commandButton. Этот атрибут определяет компоненты по идентификатору, которые должны участвовать в фазе обновления модели:

<a4j:commandButton 
    id="calculateButton" 
    value="Calculate" 
    action="#{illustrationManager.calculatePremium()}" 
    process="firstName"        
    reRender="mainForm" />
0 голосов
/ 19 марта 2015

Полагаю, я могу предоставить другой обходной путь.

у нас должно быть два флага на уровне 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 голосов
/ 17 мая 2011

Каков объем вашего боба? Когда кнопка выполняется, это новый запрос, и если ваш бин находится в области действия запроса, тогда предыдущие значения исчезнут.

...