Обновления представления Ajax не работают должным образом в пользовательских компонентах с выделенным классом Java.

У меня есть более серьезная проблема, чем та, которую я опишу здесь, но, поскольку было бы сложно объяснить и отладить мой код, я создал меньшую (и глупую) проблему, чтобы воспроизвести ту же ошибку.

На мой взгляд, я хочу напечатать список строк, этот список рассчитывается на основе смещения. В моем классе Java у меня есть огромный статический массив строк. Чтобы рассчитать мой список строк (которые будут напечатаны на странице), я копирую значения из моего статического массива в свой список, начиная со значения «смещение».

На моей странице также есть кнопка «Увеличить смещение», которая увеличивает значение «смещения» с помощью запроса ajax и обновляет результаты (список пересчитывается на основе нового смещения).

Моя проблема: когда я нажимаю кнопку «Увеличить смещение», выполняется код на стороне сервера, смещение увеличивается, но при обновлении возникает проблема: текст, показывающий текущее смещение, обновляется новым значением «смещение». , но то же самое не происходит со списком строк, он обновляется со старыми значениями, а новые значения учитываются, только если я делаю второй запрос, значения, рассчитанные во втором запросе, учитываются только в 3-м обновлении , из-за чего список строк в представлении всегда запаздывает на один запрос.

Посмотрите это изображение (откройте его в новой вкладке, чтобы увеличить): введите здесь описание изображения

Давайте посмотрим код...

Кнопка:

<h:form>
  <h:commandButton value="Increment offsets">
    <f:ajax listener="#{test.incrementOffset()}" render=":stringList" />
  </h:commandButton>
</h:form>

Список строк:

<h:panelGroup id="stringList">
  <h1>Offsets increment = #{test.offset}</h1>
  <comp:stringPrinter offset="#{test.offset}" />
</h:panelGroup>

Вот это компонент, о котором я говорил в заголовке. Список строк не будет вычисляться и распечатываться на странице. Он будет рассчитан и напечатан в составном компоненте.

Прежде чем перейти к коду составного компонента, вот управляемый компонент «Test», используемый в представлении:

@ManagedBean(name="test")
@ViewScoped
public class Test implements Serializable {
  private int offset;

  public int getOffset() {
    return offset;
  }

  public void incrementOffset(){
     offset++;
  }
}

Составной компонент: простой, как я уже сказал, он просто получает «смещение» в качестве параметра и печатает список строк. См. код представления ниже:

<h:body>
  <composite:interface componentType="stringPrinter">
    <composite:attribute name="offset" type="java.lang.Integer" required="true" />
  </composite:interface>
  <composite:implementation>
    <ul>
      <ui:repeat value="#{cc.list}" var="string">
        <li>#{string}</li>
      </ui:repeat>
    </ul>
  </composite:implementation>
</h:body>

И вот как вычисляется список строк:

@FacesComponent(value="stringPrinter")
public class StringPrinter extends UINamingContainer implements Serializable {
  private ArrayList<String> list;

  private static String[] LIPSUM = "...".split(" "); // "..." is not the actual string, the string is irrelevant (it has more than 400 words separated by spaces).
  private static int ARRAY_SIZE = 12;

  private void generateList(){
    int offset = (Integer) getAttributes().get("offset");
    int position = offset;
    list = new ArrayList<String>();
    for (int i = 0; i < ARRAY_SIZE; i++){
      list.add(LIPSUM[position++ % LIPSUM.length]);
    }
  }

  public ArrayList<String> getList() {
    if (null == list) generateList();
      return list;
    }
  }
}

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


Некоторая отладка:

Тег ui:repeat может быть проблемой. Если бы вместо печати списка (для чего потребовался бы ui:repeat) мне нужно было напечатать первую строку в списке, все было бы правильно обновлено в нужное время. См. код составного компонента без использования ui:repeats:

<h:body>
  <composite:interface componentType="stringPrinter">
    <composite:attribute name="offset" type="java.lang.Integer" required="true" />
  </composite:interface>
  <composite:implementation>
    <h:outputText value="#{cc.list.get(0)}" />
  </composite:implementation>
</h:body>

Приведенный выше код работает, но это не то, что мне нужно делать, мне действительно нужно использовать ui:repeat. Я также пробовал c: forEach, тот же результат.

Для дальнейшей отладки я задал себе вопрос: «Вызывается ли метод getList() в компоненте после выполнения метода incrementOffset() на странице?». Это журнал, который я получил:

*** GET LIST CALLED!
*** GET LIST CALLED!
*** OFFSET INCREMENTED!
*** GET LIST CALLED!
*** GET LIST CALLED!

Я использовал System.out.flush() для предотвращения буферизации сообщений. Кажется, что getList() вызывается после выполнения метода, я не вижу причин для обновления представления старыми значениями. В любом случае, тот факт, что он вызывается два раза непосредственно перед выполнением incrementOffset() и два раза после, немного странен.

Эта проблема затрагивает Mojarra 2.1.7, 2.1.22, 2.1.23 и 2.2.0. Другие версии не проверял.

Я собираюсь обновить этот пост с дальнейшей отладкой.

Чтобы загрузить проект Maven и протестировать его самостоятельно: zip-файл< /а>

Заранее спасибо за любой ответ.


person Tiago Peres França    schedule 26.06.2013    source источник


Ответы (1)


Я обнаружил, где проблема. Я думал, что когда компонент обновляется, он полностью перерабатывается, но в некоторых случаях этого не происходит. По какой-то причине кажется, что при использовании ui:repeat JSF пытается задержать сброс компонента.

Решение состоит в том, чтобы явно пересчитать список при вызове getList():

Раньше было так:

  public ArrayList<String> getList() {
    if (null == list) generateList();
      return list;
    }
  }

Если я удалю проверку на ноль, это сработает.

На мой взгляд, именно это и происходило: когда компонент обновлялся, в списке все еще были значения, рассчитанные ранее, он не был нулевым, и пересчет не производился. В промежутке между одним запросом и другим компонент реконструировался. При сбросе компонента список был равен нулю, а пересчет был выполнен в getList(), что всегда приводило к задержке представления на один запрос.


РЕДАКТИРОВАТЬ: Гораздо лучшим решением является использование f:event в представлении компонента вместо пересчета списка при каждом получении.

<f:metadata>
  <f:event type="preRenderComponent" listener="#{cc.generateList}" />
</f:metadata>
person Tiago Peres França    schedule 26.06.2013
comment
<f:event> не обязательно должно быть внутри <f:metadata>. - person BalusC; 04.02.2014