You can find the full source code for this website in the Seam package in the directory /examples/wiki. It is licensed under the LGPL.
| Online: | 23 Members of 3082 |
| Forum: Seam Users |
03. Apr 2008, 19:51 CET | Link |
I have two h:dataTable components on the same page (home.xhtml). The associated lists (list1 & list2) are marked with @DataModel(scope=ScopeType.PAGE) in PageTestBean. Each list has its own @Factory. Objects are displayed in the first or second table depending if the number in member variable tableNum is 1 or 2.
Clicking on an item in one of the tables takes navigates to toggle.xhtml, where I toggle the tableNum value between 1 and 2. Toggling takes us back to home.xhtml. When home.xhtml is loaded this second time only the factory for the first dataTable is called. So what ends up happening is that the items list in the first dataTable are updated while those in the second are not.
I've played around with this a fair bit and I'm thinking this might be a bug. My understanding is that the factories of both lists should be getting called when the page is reloaded. But I'm new to Seam so maybe someone else can see my problem.
//Table1.java
package com.cs.PageTest.entity;
...
@Entity
@Table(name = "table1")
public class Table1 implements java.io.Serializable {
private int id;
private int tableNum;
public Table1() {
}
@Id
@GeneratedValue
@Column(name = "id", unique = true, nullable = false)
@NotNull
public int getId() {
return this.id;
}
public void setId(int id) {
this.id = id;
}
@Column(name = "table_num", unique = false, nullable = false)
@NotNull
public int getTableNum() {
return this.tableNum;
}
public void setTableNum(int tableNum) {
this.tableNum = tableNum;
}
}
//PageTestBean.java
package com.cs.PageTest;
...
@Stateful
@Name("pageTest")
public class PageTestBean implements PageTest {
@In(create=true)
private EntityManager entityManager;
@Out(required=false)
Table1 table1;
@DataModel(scope=ScopeType.PAGE)
private List<Table1> list1;
@DataModel(scope=ScopeType.PAGE)
private List<Table1> list2;
@Factory("list1")
public void populateList1() {
list1 = entityManager.createQuery( "SELECT t " +
"FROM Table1 AS t " +
"WHERE t.tableNum = 1")
.getResultList();
}
@Factory("list2")
public void populateList2() {
list2 = entityManager.createQuery( "SELECT t " +
"FROM Table1 AS t " +
"WHERE t.tableNum = 2")
.getResultList();
}
public String viewEntryButton(Table1 table1) {
this.table1 = table1;
return "togglePage";
}
@Remove
public void destroy(){
}
}
//PageTest.java
package com.cs.PageTest;
...
public interface PageTest {
public String viewEntryButton(Table1 table1);
public void populateList1();
public void populateList2();
public void destroy();
}
//Toggle.java
package com.cs.PageTest;
...
public interface Toggle {
public String toggleButton();
public Table1 getTable1();
public void create();
public void remove();
}
//ToggleBean.java
package com.cs.PageTest;
...
@Name("toggle")
@Stateful
public class ToggleBean implements Toggle{
@In
Table1 table1;
@In
EntityManager entityManager;
@Create
@Begin(join=true)
public void create() {
}
public Table1 getTable1() {
return this.table1;
}
public String toggleButton() {
table1 = entityManager.merge(table1);
table1.setTableNum(table1.getTableNum() == 1 ? 2 : 1);
return "home";
}
@Remove
public void remove() {
}
}
<!--home.xhtml-->
<!DOCTYPE composition PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<ui:composition xmlns="http://www.w3.org/1999/xhtml"
xmlns:s="http://jboss.com/products/seam/taglib"
xmlns:ui="http://java.sun.com/jsf/facelets"
xmlns:f="http://java.sun.com/jsf/core"
xmlns:h="http://java.sun.com/jsf/html">
<body>
<h1>Test</h1>
<div id="messages">
<h:messages globalOnly="true" styleClass="message" id="globalMessages"/>
</div>
<h:form>
<h:dataTable
var="table11"
value="#{list1}"
>
<f:facet name="header">
List 1
</f:facet>
<h:column>
<f:facet name="header">
<h4></h4>
</f:facet>
<h:commandLink action="#{pageTest.viewEntryButton(table11)}">
#{table11.id}
</h:commandLink>
</h:column>
</h:dataTable>
</h:form>
<h:form>
<h:dataTable
var="table12"
value="#{list2}"
>
<f:facet name="header">
List 2
</f:facet>
<h:column>
<f:facet name="header">
<h4></h4>
</f:facet>
<h:commandLink action="#{pageTest.viewEntryButton(table12)}">
#{table12.id}
</h:commandLink>
</h:column>
</h:dataTable>
</h:form>
<h:form>
<s:link propagation="false" action="home.xhtml" value="refresh"/>
</h:form>
</body>
</ui:composition>
<!--toggle.xhtml-->
<!DOCTYPE composition PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<ui:composition xmlns="http://www.w3.org/1999/xhtml"
xmlns:s="http://jboss.com/products/seam/taglib"
xmlns:ui="http://java.sun.com/jsf/facelets"
xmlns:f="http://java.sun.com/jsf/core"
xmlns:h="http://java.sun.com/jsf/html">
<body>
<h1>Toggle</h1>
<div id="messages">
<h:messages globalOnly="true" styleClass="message" id="globalMessages"/>
</div>
<h:form>
<h:panelGrid>
<h:outputText value="id: #{toggle.table1.id}"/>
<h:outputText value="table_num: #{toggle.table1.tableNum}"/>
<h:commandButton action="#{toggle.toggleButton}" value="Toggle"/>
</h:panelGrid>
</h:form>
</body>
</ui:composition>
<!--pages.xml-->
<?xml version="1.0" encoding="UTF-8"?>
<pages xmlns="http://jboss.com/products/seam/pages"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://jboss.com/products/seam/pages http://jboss.com/products/seam/pages-2.0.xsd"
no-conversation-view-id="/home.xhtml"
login-view-id="/login.xhtml">
<page view-id="*">
<navigation>
<rule if-outcome="home">
<redirect view-id="/home.xhtml"/>
</rule>
</navigation>
</page>
<page view-id="/home.xhtml">
<navigation>
<rule if-outcome="togglePage">
<redirect view-id="/toggle.xhtml"/>
</rule>
</navigation>
</page>
<exception class="org.jboss.seam.framework.EntityNotFoundException">
<redirect view-id="/error.xhtml">
<message>Not found</message>
</redirect>
</exception>
<exception class="javax.persistence.EntityNotFoundException">
<redirect view-id="/error.xhtml">
<message>Not found</message>
</redirect>
</exception>
<exception class="javax.persistence.OptimisticLockException">
<end-conversation/>
<redirect view-id="/error.xhtml">
<message>Another user changed the same data, please try again</message>
</redirect>
</exception>
<exception class="org.jboss.seam.security.AuthorizationException">
<redirect view-id="/error.xhtml">
<message>You don't have permission to do this</message>
</redirect>
</exception>
<exception class="org.jboss.seam.security.NotLoggedInException">
<redirect view-id="/login.xhtml">
<message>Please log in first</message>
</redirect>
</exception>
<exception class="javax.faces.application.ViewExpiredException">
<redirect view-id="/error.xhtml">
<message>Your session has timed out, please try again</message>
</redirect>
</exception>
<exception>
<redirect view-id="/error.xhtml">
<message>Unexpected error, please try again</message>
</redirect>
</exception>
</pages>
I haven't looked at your code in detail, but is there a reason you're not using something like a PickList?
I'm not seeing this behavior. Are your snippets representative of your actual code?
Here's the example I whipped up using a simple application-scoped list of POJOS in lieu of an entity. Toggle works, the lists update fine.
ToggleBean.java
package com.example; import javax.ejb.Remove; import javax.ejb.Stateful; import org.jboss.seam.annotations.In; import org.jboss.seam.annotations.Name; @Name("toggle") @Stateful public class ToggleBean implements Toggle { @In Table selectedTable; public String toggle() { selectedTable.setOn(!selectedTable.isOn()); return "examplehome"; } @Remove public void destroy() { } }Toggle.java
package com.example; import javax.ejb.Remove; public interface Toggle { public abstract String toggle(); @Remove public abstract void destroy(); }TableFactory.java
package com.example; import java.util.ArrayList; import java.util.List; import org.jboss.seam.ScopeType; import org.jboss.seam.annotations.Factory; import org.jboss.seam.annotations.Name; @Name("tableFactory") public class TableFactory { @Factory(value = "allTables", scope = ScopeType.APPLICATION) public List<Table> getTables() { List<Table> list = new ArrayList<Table>(); list.add(new Table("a", true)); list.add(new Table("b", true)); list.add(new Table("c", false)); list.add(new Table("d", true)); list.add(new Table("e", false)); return list; } }TableExample.java
package com.example; import java.util.ArrayList; import java.util.List; import javax.ejb.Remove; import javax.ejb.Stateful; import org.apache.commons.collections.CollectionUtils; import org.apache.commons.collections.Predicate; import org.jboss.seam.ScopeType; import org.jboss.seam.annotations.Factory; import org.jboss.seam.annotations.In; import org.jboss.seam.annotations.Name; import org.jboss.seam.annotations.Out; import org.jboss.seam.annotations.datamodel.DataModel; @Stateful @Name("pageTest") public class TableExampleBean implements TableExample { @DataModel(scope = ScopeType.PAGE) List<Table> list1; @DataModel(scope = ScopeType.PAGE) List<Table> list2; @In(create = true) List<Table> allTables; @Out(required = false, scope = ScopeType.SESSION) Table selectedTable; @Factory("list1") public void getList1() { list1 = new ArrayList<Table>(CollectionUtils.select(allTables, new Predicate() { public boolean evaluate(Object arg0) { if (arg0 instanceof Table) { Table table = (Table) arg0; return table.isOn(); } return false; } })); } @Factory("list2") public void getList2() { list2 = new ArrayList<Table>(CollectionUtils.select(allTables, new Predicate() { public boolean evaluate(Object arg0) { if (arg0 instanceof Table) { Table table = (Table) arg0; return !table.isOn(); } return false; } })); } public String selectTable(Table table) { selectedTable = table; return "togglePage"; } @Remove public void destroy() { } }TableExample.java
package com.example; import javax.ejb.Local; @Local public interface TableExample { public abstract void getList1(); public abstract void getList2(); public void destroy(); public String selectTable(Table table); }Table.java
package com.example; public class Table { boolean on; String name; public Table(String name, boolean on) { super(); this.name = name; this.on = on; } public boolean isOn() { return on; } public void setOn(boolean on) { this.on = on; } public String getName() { return name; } public void setName(String name) { this.name = name; } }lists.page.xml
lists.xhtml
<!DOCTYPE composition PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" 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:s="http://jboss.com/products/seam/taglib" xmlns:a="https://ajax4jsf.dev.java.net/ajax" xmlns:rich="http://richfaces.org/rich"> <body> <h1>Test</h1> <div id="messages"> <h:messages globalOnly="true" styleClass="message" id="globalMessages" /> </div> <h:form> <h:dataTable var="table11" value="#{list1}"> <f:facet name="header">List 1</f:facet> <h:column> <f:facet name="header"> <h4></h4> </f:facet> <h:commandLink action="#{pageTest.selectTable(table11)}">#{table11}</h:commandLink> </h:column> </h:dataTable> </h:form> <h:form> <h:dataTable var="table12" value="#{list2}"> <f:facet name="header">List 2</f:facet> <h:column> <f:facet name="header"> <h4></h4> </f:facet> <h:commandLink action="#{pageTest.selectTable(table12)}">#{table12}</h:commandLink> </h:column> </h:dataTable> </h:form> <h:form> <s:link propagation="false" action="home.xhtml" value="refresh" /> </h:form> </body> </html>toggle.page.xml
toggle.xhtml
<!DOCTYPE composition PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" 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:s="http://jboss.com/products/seam/taglib" xmlns:a="https://ajax4jsf.dev.java.net/ajax" xmlns:rich="http://richfaces.org/rich"> <body> <h1>Test</h1> <div id="messages"> <h:messages globalOnly="true" styleClass="message" id="globalMessages" /> </div> <h:form> #{selectedTable} #{selectedTable.on} <h:commandButton action="#{toggle.toggle}" /> </h:form> </body> </html>I threw this example together in order to illustrate the problem I'm having with DataTables, so using a PickList wouldn't help with that :)
Yes, these code snippets are actual code taken directly from my project.
I'm using JBoss 4.2.2 GA and Seam 2.0
If populateList2 is not getting called, what does the context variable contain after you return from toggle.toggleButton? The old list?
The Factory will only get called if there is no value for the context variable 'list2' - is there a 'list2' defined in a higher scope?
You might try adding a 'org.jboss.seam.postSetVariable.list2' event observer to see when that context variable is getting set.
I have the exact same problem: two DataModel on scope of PAGE. The first time the page is viewed by the user, both Factory methods are called.
The second time the page is viewed, after navigating elsewhere, only the first Factory method is called. Not the second one.
PS: I'm using Seam 2.0.2-SP1, so that's as recent as I can get right now.
Alain.
Hi Alain,
I didn't have time to spend figuring this out so I abandoned the DataModel and just created a list with getters and setters. It's not elegent and likely adds extra db calls (at least in my case), but it works. My gut feeling on this is that it's probably a SEAM bug, but I don't know how to escalate things to get the dev team to look at it.
Hi,
This is annoying indeed. I guess the developers would love a testcase, but you already pretty much provided that.
The fact that two of us have the same problem is a sign that either:
1) there is a bug
2) there is a user error that is common enough that multiple people fall in the same trap, therefore it deserves the same attention as a bug
Alain.
open a JIRA issue.
Current reading list: Effective Java, 2nd ed.; Java Power Tools; Ubuntu Linux Toolbox