Версия для печати темы
Нажмите сюда для просмотра этой темы в оригинальном формате
Форум программистов > Java EE (J2EE) и Spring > JSF/HtmlCommandLink - программно установить action


Автор: Dims 3.6.2008, 20:47
У меня в задаче есть "категории", к каждой категории принадлежат "статьи". 

У меня есть главная страница, на которой я вывожу список категорий. Каждая категория представляет собой ссылку. Все ссылки должны вести на вторую страницу, на которой должен выводиться список статей выбранной категории.

Для обеспечения этого, на главной странице я программно формирую HtmlPanelGrid и заполняю его объектами HtmlCommandLink.

Каждый HtmlCommandLink, по моему представлению, должен сделать две вещи:

1) запомнить выбранную категорию в бине
2) вернуть системе JSF строку, которая указывает перемещение между страницами.

Вот так у меня описано правило перехода:

Код

<navigation-rule>
      <from-view-id>/content.jsp</from-view-id>
      <navigation-case>
         <from-outcome>select</from-outcome>
         <to-view-id>/categ.jsp</to-view-id>
      </navigation-case>
      <navigation-case>
         <from-outcome>edit</from-outcome>
         <to-view-id>/admin/editContent.jsp</to-view-id>
      </navigation-case>
   </navigation-rule>


Вот так я пытаюсь оформить код:

Код

   private void fillCategsPanel() {

      if (categsPanel != null) {
         categsPanel.getChildren().clear();
         if (categs != null && categs.size() > 0) {
            Application application = FacesContext.getCurrentInstance().getApplication();
            HtmlCommandLink link;

            for (final Categ herecateg : categs) {
               link = (HtmlCommandLink) application.createComponent(HtmlCommandLink.COMPONENT_TYPE);
               link.addActionListener(new ActionListener() {

                  public void processAction(ActionEvent arg0) throws AbortProcessingException {
                     try {
                        setCateg(herecateg);
                     } catch (SQLException ex) {
                        Logger.getLogger(SiteController.class.getName()).log(Level.SEVERE, null, ex);
                        throw new AbortProcessingException();
                     }
                  }
               });
               link.setValue(herecateg.getSeq()); 
               link.setActionExpression(application.getExpressionFactory().createMethodExpression(FacesContext.getCurrentInstance().getELContext(), "select", String.class, new Class[0]));
               link.setId("linkto" + herecateg.getSeq());
               categsPanel.getChildren().add(link);
            }
         }
      }
   }


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

type Exception report

message

description The server encountered an internal error () that prevented it from fulfilling this request.

exception

javax.servlet.ServletException: com.telesmi.SiteController$1
    javax.faces.webapp.FacesServlet.service(FacesServlet.java:256)
    org.netbeans.modules.web.monitor.server.MonitorFilter.doFilter(MonitorFilter.java:390)

root cause

java.lang.IllegalStateException: com.telesmi.SiteController$1
    javax.faces.component.StateHolderSaver.restore(StateHolderSaver.java:98)
    javax.faces.component.UIComponentBase.restoreAttachedState(UIComponentBase.java:1406)
    javax.faces.component.UIComponentBase.restoreState(UIComponentBase.java:1272)
    javax.faces.component.UICommand.restoreState(UICommand.java:332)
    javax.faces.component.html.HtmlCommandLink.restoreState(HtmlCommandLink.java:876)
    javax.faces.component.UIComponentBase.processRestoreState(UIComponentBase.java:1141)
    javax.faces.component.UIComponentBase.processRestoreState(UIComponentBase.java:1157)
    javax.faces.component.UIComponentBase.processRestoreState(UIComponentBase.java:1157)
    javax.faces.component.UIComponentBase.processRestoreState(UIComponentBase.java:1157)
    com.sun.faces.application.StateManagerImpl.restoreState(StateManagerImpl.java:581)
    com.sun.faces.application.StateManagerImpl.restoreView(StateManagerImpl.java:89)
    com.sun.faces.application.ViewHandlerImpl.restoreView(ViewHandlerImpl.java:290)
    com.sun.faces.lifecycle.RestoreViewPhase.execute(RestoreViewPhase.java:141)
    com.sun.faces.lifecycle.LifecycleImpl.phase(LifecycleImpl.java:251)
    com.sun.faces.lifecycle.LifecycleImpl.execute(LifecycleImpl.java:117)
    javax.faces.webapp.FacesServlet.service(FacesServlet.java:244)
    org.netbeans.modules.web.monitor.server.MonitorFilter.doFilter(MonitorFilter.java:390)

Автор: v2v 3.6.2008, 23:02
покажи код jsp страницы, где это происходит.

Автор: Dims 3.6.2008, 23:19
Вот код:

Код

         <h:form id="contentForm">
            <%--
            <h:commandLink id="testLink" action="select" value="TESTLINK" actionListener="#{controller.debugEvent}"/>
            --%>

            <h:panelGrid id="categsPanel" binding="#{controller.categsPanel}" columns="20" border="1" cellspacing="0"/>
         </h:form>


Автор: v2v 4.6.2008, 12:44
Цитата(Dims @  3.6.2008,  23:19 Найти цитируемый пост)
 <h:commandLink id="testLink" action="select"

замени на 
Код

<h:commandLink id="testLink" action="#{controller.select}"

а в функции возвращай строку "select".

Автор: Dims 4.6.2008, 23:43
Так не получится. У меня каждая ссылка -- это категория. Она должна как-то передавать выбранную категорию на следующую страницу. Для этого надо как-то запомнить в бине эту категорию, а команда action="#{controller.select}" этого не обеспечивает.

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

Пример, который ты исправил --  закомментирован и не используется. Это я пробовал, работают ли вообще переходы.

Автор: ivg 5.6.2008, 12:58
Цитата(Dims @  3.6.2008,  23:47 Найти цитируемый пост)
но при нажатии ссылки возникает исключение, которое я не понимаю

JSF по окончании обработки запроса сохраняет состояние View, а в начале обработки следующего восстанавливает его. Под View в данном случае понимается дерево JSF компонентов расположенных на странице. Для обеспечения этой функциональности каждый компонент реализует интерфейс javax.faces.component.StateHolder, а точнее реализован он в базовом классе, а в классах компонентов переопределяются его методы saveState() и restoreState(). Код этих методов определяет набор полей достаточных для сохранения и восстановления состояния компонента. В этот набор входит список слушателей (Listeners), а вызов HtmlCommandLink.addActionListener() пополняет этот список. Для успешного сохранения и восстановления состояния компонента каждый такой Listener должен "имплементировать" либо StateHolder интерфейс, либо Serializable (подробнее: спецификация JSF, "State Management"). Для превращения объектов, хранящих информацию о состоянии компонента, в последовательность байт используется сериализация. Для анонимного класса определённого внутри метода сериализация "весьма не рекомендуется", если вообще возможна, ибо потянет за собой не только объект бина SiteController, но и потребует сериализовать фрэйм локальных переменных метода.
Вариантов решения ситуации много. Во первых предлагаю избавиться от динамического формирования списка категорий в коде. Для этого можно использовать итерационный тег на странице, например <c:forEach var="cat" items="#{controller.categs}" ... >. А во вторых, что касается передачи значения выбранной категории, то например использовать обычные ссылки в виде:
Код

<h:outputLink ... value="categ.jsp?id=#{cat.id}">#{cat.name}</h:outputLink>

либо
Код

<h:commandLink id="cat_#{cat.id}" value="#{cat.name}" action="select" actionListener="#{controller.getCategory}" />

а в методе getCategory() получаете ID категории:
Код

public void getCategory(ActionEvent event) {
  String idstr = (String) event.getComponent().getId();
  Long id = Long.valueOf(idstr.substring(idstr.lastIndexOf("_")+1));    
  ...
}

Если формирование списка категорий в коде - условие неизменное то код аналогичный
Код

<h:commandLink actionListener="#{controller.getCategory}" />

выглядит так
Код

private void fillCategsPanel() {
  ...
  FacesContext fctx = FacesContext.getCurrentInstance();
  javax.faces.application.Application app = fctx.getApplication();
  ELContext elctx = fctx.getELContext();
  ...
    MethodExpression me = app.getExpressionFactory().createMethodExpression(elctx,
      "#{controller.getCategory}", null, new Class[]{ActionEvent.class});
    link.addActionListener(new MethodExpressionActionListener(me));
  ...
}

Ну и в конце концов можно сделать как вы хотели, только создать отдельный класс, который implements ActionListener, Serializable, в конструктор его передавать категорию, а лучше ID, в методе processAction() достаете из сессии объект бина SiteController и устанавливаете у него выбранную категорию.
Следует заметить, что явно прописывать в коде имя бина, которое является настраиваемым параметром, - не есть хорошо. 

Powered by Invision Power Board (http://www.invisionboard.com)
© Invision Power Services (http://www.invisionpower.com)