diff --git a/spring-faces/src/main/java/org/springframework/faces/model/SelectionTrackingActionListener.java b/spring-faces/src/main/java/org/springframework/faces/model/SelectionTrackingActionListener.java index 450cd92f..6e9d1f5c 100644 --- a/spring-faces/src/main/java/org/springframework/faces/model/SelectionTrackingActionListener.java +++ b/spring-faces/src/main/java/org/springframework/faces/model/SelectionTrackingActionListener.java @@ -15,6 +15,8 @@ */ package org.springframework.faces.model; +import java.lang.reflect.Method; + import javax.faces.component.UIComponent; import javax.faces.component.UIData; import javax.faces.component.UIViewRoot; @@ -25,14 +27,16 @@ import javax.faces.event.ActionListener; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.faces.webflow.FlowActionListener; +import org.springframework.util.ReflectionUtils; /** * Custom {@link ActionListener} that inspects the {@link UIComponent} that signaled the current {@link ActionEvent} to - * determine whether it is a child of a {@link UIData} component that uses a {@link SelectionAware} data model - * implementation. If a containing SelectionAware model is found, the row containing the event-signaling component - * instance will be selected. This enables convenient access to the selected model state at any time through EL - * expressions such as #{model.selectedRow.id} without having to rely on the whether or not the current row index is - * pointing to the desired row as it would need to be to use an expression such as #{model.rowData.id} + * determine whether it is a child of any iterator type of component (such as {@link UIData}) that uses a + * {@link SelectionAware} data model implementation. If a containing SelectionAware model is found, the row containing + * the event-signaling component instance will be selected. This enables convenient access to the selected model state + * at any time through EL expressions such as #{model.selectedRow.id} without having to rely on the whether or not the + * current row index is pointing to the desired row as it would need to be to use an expression such as + * #{model.rowData.id} * * @author Jeremy Grelle */ @@ -52,23 +56,23 @@ public class SelectionTrackingActionListener implements ActionListener { } private void trackSelection(UIComponent component) { - // Find parent UIData instance if it exists - UIData table = null; + // Find parent component with a SelectionAware model if it exists UIComponent currentComponent = component; while (!(currentComponent.getParent() instanceof UIViewRoot)) { - if (currentComponent.getParent() instanceof UIData) { - table = (UIData) currentComponent.getParent(); - break; + UIComponent parent = currentComponent.getParent(); + Method valueAccessor = ReflectionUtils.findMethod(parent.getClass(), "getValue"); + if (valueAccessor != null) { + Object value = ReflectionUtils.invokeMethod(valueAccessor, parent); + if (value != null && value instanceof SelectionAware) { + ((SelectionAware) value).setSelected(true); + if (logger.isDebugEnabled()) { + logger.debug("Row selection has been set on the current SelectionAware data model."); + } + break; + } } currentComponent = currentComponent.getParent(); } - if (table != null && table.getValue() instanceof SelectionAware) { - SelectionAware selectableModel = (SelectionAware) table.getValue(); - selectableModel.setSelected(true); - if (logger.isDebugEnabled()) { - logger.debug("Row selection has been set on the current SelectionAware data model."); - } - } } } diff --git a/spring-faces/src/test/java/org/springframework/faces/model/SelectionTrackingActionListenerTests.java b/spring-faces/src/test/java/org/springframework/faces/model/SelectionTrackingActionListenerTests.java new file mode 100644 index 00000000..60e19e5a --- /dev/null +++ b/spring-faces/src/test/java/org/springframework/faces/model/SelectionTrackingActionListenerTests.java @@ -0,0 +1,114 @@ +package org.springframework.faces.model; + +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.List; + +import javax.faces.component.UIColumn; +import javax.faces.component.UICommand; +import javax.faces.component.UIData; +import javax.faces.component.UIViewRoot; +import javax.faces.event.AbortProcessingException; +import javax.faces.event.ActionEvent; +import javax.faces.event.ActionListener; + +import org.springframework.util.ReflectionUtils; + +import com.sun.facelets.component.UIRepeat; + +import junit.framework.TestCase; + +public class SelectionTrackingActionListenerTests extends TestCase { + + /** + * The JSF view to simulate + */ + private UIViewRoot viewToTest; + + /** + * The list of row data objects to + */ + private OneSelectionTrackingListDataModel dataModel; + + /** + * The delegate action listener that should be called + */ + private TestDelegateActionListener delegateListener = new TestDelegateActionListener(); + + /** + * The class under test + */ + private ActionListener selectionTrackingListener = new SelectionTrackingActionListener(delegateListener); + + public void setUp() { + viewToTest = new UIViewRoot(); + List rows = new ArrayList(); + rows.add(new TestRowData()); + rows.add(new TestRowData()); + rows.add(new TestRowData()); + dataModel = new OneSelectionTrackingListDataModel(rows); + } + + public void testProcessActionWithUIData() { + + UIData dataTable = new UIData(); + dataTable.setValue(dataModel); + UIColumn column = new UIColumn(); + UICommand commandButton = new UICommand(); + column.getChildren().add(commandButton); + dataTable.getChildren().add(column); + viewToTest.getChildren().add(dataTable); + dataTable.setRowIndex(1); + + ActionEvent event = new ActionEvent(commandButton); + + selectionTrackingListener.processAction(event); + + assertTrue(dataModel.isCurrentRowSelected()); + assertSame(dataModel.getSelectedRow(), dataModel.getRowData()); + assertTrue(delegateListener.processedEvent); + + dataModel.setRowIndex(2); + assertFalse(dataModel.isCurrentRowSelected()); + assertTrue(dataModel.getSelectedRow() != dataModel.getRowData()); + } + + public void testProcessActionWithUIRepeat() { + + UIRepeat uiRepeat = new UIRepeat(); + uiRepeat.setValue(dataModel); + UICommand commandButton = new UICommand(); + uiRepeat.getChildren().add(commandButton); + viewToTest.getChildren().add(uiRepeat); + + Method indexMutator = ReflectionUtils.findMethod(UIRepeat.class, "setIndex", new Class[] { int.class }); + indexMutator.setAccessible(true); + + ReflectionUtils.invokeMethod(indexMutator, uiRepeat, new Object[] { new Integer(1) }); + + ActionEvent event = new ActionEvent(commandButton); + + selectionTrackingListener.processAction(event); + + assertTrue(dataModel.isCurrentRowSelected()); + assertSame(dataModel.getSelectedRow(), dataModel.getRowData()); + assertTrue(delegateListener.processedEvent); + + ReflectionUtils.invokeMethod(indexMutator, uiRepeat, new Object[] { new Integer(2) }); + assertFalse(dataModel.isCurrentRowSelected()); + assertTrue(dataModel.getSelectedRow() != dataModel.getRowData()); + } + + private class TestRowData { + + } + + private class TestDelegateActionListener implements ActionListener { + + public boolean processedEvent = false; + + public void processAction(ActionEvent event) throws AbortProcessingException { + processedEvent = true; + } + } +}