views:

24

answers:

2

This code displays an index and performs an action using RichFaces' <a4j:commandLink> tag. It works technically fine. Only the style of previously selected letter is not been reset (altough the appropriate code part is executed). Does anybody know where the problem is and how to solve it?

The JSF page:

<?xml version="1.0" encoding="ISO-8859-1"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"&gt;
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:ui="http://java.sun.com/jsf/facelets" xmlns:h="http://java.sun.com/jsf/html" xmlns:f="http://java.sun.com/jsf/core" xmlns:c="http://java.sun.com/jstl/core" xmlns:rich="http://richfaces.org/rich" xmlns:a4j="http://richfaces.org/a4j"&gt;
 <head>
  <style>
   a.selected {
    text-decoration: underline;
   }

   a.unselected {
    text-decoration: none;
   }
  </style>
 </head>
    <body>
  <rich:panel id="result">
     hello
      <h:form id="myForm"> 
       <c:forEach var="letter" items="#{testBean.letters}" >
      <a4j:commandLink id="${letter}" value="${letter}" actionListener="#{testBean.startWith}" reRender="result" styleClass="unselected"/>&#160;
           </c:forEach>
   </h:form>
  </rich:panel>
  <a4j:keepAlive beanName="testBean" />
    </body>
</html>

Note that it is using <a4j:keepalive> to keep the backing bean alive between Ajax requests.

The backing bean (request scoped):

public class testBean
{
 private String startLetter = null; // selects from alphabetical page
 private String[] letters = { "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z" };
 private UIComponent currentStartWithLetter;

 public void startWith(ActionEvent actionEvent)
 {
  FacesContext facesContext = FacesContext.getCurrentInstance();
  ELContext elContext = facesContext.getELContext();
  Application app = facesContext.getApplication();
  ExpressionFactory elFactory = app.getExpressionFactory();
  ValueExpression valueExp;
  if (currentStartWithLetter != null) {
   valueExp = elFactory.createValueExpression(elContext, "unselected",Object.class);
   currentStartWithLetter.setValueExpression("styleClass", valueExp);
  } 
  currentStartWithLetter = actionEvent.getComponent();
  startLetter = currentStartWithLetter.getId();
  valueExp = elFactory.createValueExpression(elContext, "selected",Object.class);
  currentStartWithLetter.setValueExpression("styleClass", valueExp);
  someAction(actionEvent);    
 }


 public void setLetters(String[] letters) {
  /* nothing to do */
 }

 public String[] getLetters() {
  return letters;
 }

 public String getStartLetter() {
  return startLetter;
 }

 public void setStartLetter(String startLetter) {
  this.startLetter = startLetter;

 }
}
A: 

I modified the code changing :

if (currentStartWithLetter != null) {
 valueExp = elFactory.createValueExpression(elContext, "unselected",Object.class);
 currentStartWithLetter.setValueExpression("styleClass", valueExp);
} 

into

if (currentStartWithLetter != null) {
 valueExp = elFactory.createValueExpression(elContext, "unselected",Object.class);
 currentStartWithLetter = (UIComponent) actionEvent.getComponent().findComponent(startLetter);
 currentStartWithLetter.setValueExpression("styleClass", valueExp);
} 

and it works.

The problem seems to be that currentStartWithLetter does not refer to the same component anymore because the actionEvent is different between calls.

chrix
A: 

I always feel that if I'm referencing UIComponent in my code I'm doing something the hard way. Here's how I'd do this.

Try using <f:setPropertyActionListener> and <a4j:repeat> in place of actionListener and <c:forEach>. Also, avoid rerendering whole forms, it causes issues in some browsers:

<h:panelGroup id="results">
  <a4j:repeat var="letterBean" items="#{testBean.letters}" >
    <a4j:commandLink id="letter" value="#{letterBean.letter}" reRender="results" styleClass="#{letterBean.selected ? 'selected' : 'unselected'}">
      <f:setPropertyActionListener value="#{letterBean}" target="#{testBean.selected}"/>
    </a4j:commandLink>&#160;
  </a4j:repeat>
</h:panelGroup>

New bean class:

public class LetterBean {
  private final String letter;
  private boolean selected;

  public LetterBean (String letter) {
    this.letter = letter;
  }

  //getters and setters

}

replace startWith with:

public void setSelected(LetterBean selectedBean) {
  for (LetterBean letter : letterBeans) {
    letter.setSelected(false);
  }
  selectedBean.setSelected(true);
}
Naganalf