It seems that you are not using the latest CVS Head. So perharps you can try that, or wait some days for beta-1 release. :)
On 12/3/05, Dennis Fleurbaaij <[EMAIL PROTECTED]> wrote:
Dear Tacos developers,
First of all thanks for the very nice tapestry plug in. It solves all my
needs for AJAX driven websites in T4 :)
Unfortunately there is one thing (atm ;)) I cannot get to work and that
is the advanced tree. I've noticed some more ppl that have problems with
it and therefore I'm posting this email. I'd like to make a very
elaborate case here and maybe this (in some modified form or another)
can be added to the examples so that this is more trivial to implement
for everybody.
Therefore this is going to be a _long_ message, filled with files and
examples.
In anycase, first off: the story. I'm digging trough a data collection
filled with locations. Locations have an id, name and a parent id. These
locations happen to be ideally suited for tree work as they represent
locations on earth! The first layer are countries, the second layer are
states and so on (cities, streets, houses, rooms).
This will offcourse be a very large amount of data that you do not want
to load all at once. To keep in line with the largeness of this database
I've decided to use Hibernate (and ejb3, but that's not important).
Locations are defined in an Hibernate entity file:
------ Location.java ------
@Entity
@Table(name="Locations")
public class Location implements Serializable {
private static final long serialVersionUID = -109168154630091533L;
long id;
String name;
Location parentLocation;
Set<Location> childLocations = new HashSet<Location>();
@Id(generate=GeneratorType.AUTO)
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
@Column(nullable=false)
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@OneToOne
@JoinColumn(name="parentId")
public Location getParentLocation() {
return parentLocation;
}
public void setParentLocation(Location parentLocation) {
this.parentLocation = parentLocation;
}
@OneToMany(mappedBy="parentLocation")
public Set<Location> getChildLocations() {
return childLocations;
}
public void setChildLocations(Set<Location> childLocations) {
this.childLocations = childLocations;
}
}
------ EOF Location.java ------
Just very simply all the needed getters and setters for the Id, Name,
Parent and Children. Nothing too fancy here yet. This data will be shown
in a tapestry page that is surrounded by a border (not really important
here). The HTML code for the tapestry page is:
------ LocationTree.html ------
<body jwcid="@Border" title="Home">
<div id="treeArea">
<div jwcid="tree" id="tree">
<span jwcid="nodeLink">
<span jwcid="nodeLabel" />
</span>
</div>
</div>
</body>
------ EOF LocationTree.html ------
Please not that I don't use an icon here, maybe loading it from the
database will be a version 2 of this example. With this page there also
must be a tapestry page file, defining all the nice components that are
used in the HTML.
------ LocationTree.page ------
<page-specification class="<fill in tacos classpath here>.LocationTreePage">
<description>Location Tree</description>
<property name="location" />
<property name="selectedLocation" persist="session"/>
<property name="locationState" persist="session"
initial-value="ognl: new java.util.HashSet()" />
<bean name="evenOdd" class="org.apache.tapestry.bean.EvenOdd" />
<component id="nodeLink" type="tacos:AjaxDirectLink">
<binding name="listener"
value="ognl:components.tree.listeners.contentExpansion"/>
<binding name="parameters"
value="ognl:{keyProvider.getKey(components.tree.value ), not
components.tree.isExpanded(components.tree.value),location.parentLocation}"/>
<binding name="updateComponents"
value="ognl:{location.parentLocation, 'selectedLocation' }"/>
<binding name="effects" value="template:%nodeLink.effects"/>
</component>
<component id="nodeLabel" type="Insert">
<binding name="value"
value="ognl:@[EMAIL PROTECTED](location.name)"/>
<binding name="class" value="ognl:location == selectedLocation ?
'selected' : null"/>
</component>
<component id="tree" type="tacos:Tree">
<binding name="contentProvider" value="ognl:contentProvider"/>
<binding name="keyProvider" value="ognl:keyProvider"/>
<binding name="value" value="ognl:location"/>
<binding name="state" value="ognl:locationState"/>
<binding name="sorter"
value="ognl:@[EMAIL PROTECTED]"/>
<binding name="rowStyle" value="ognl:beans.evenOdd" />
<binding name="partialBlockClass" value="literal:treeBlock" />
<binding name="linkListener" value="listener:select" />
</component>
<inject property="ajaxEngine" object="service:tacos.ajaxdirect" />
</page-specification>
------ EOF LocationTree.page ------
As shown in the page file, we use the LocationTreePage.java class for
the code-part of this page. As you can see in the
select() call I'm kinda stuck here, but in anycase tapestry explodes
before this is ever called.
------ LocationTreePage.java ------
public abstract class LocationTreePage extends BasePage {
Logger logger = Logger.getLogger( this.getClass().getName() );
private final static ITreeContentProvider CONTENT_PROVIDER = new
LocationTreeContentProvider(null);
public abstract Location getSelectedLocation();
public abstract void setSelectedLocation(Location location);
public abstract Set getLocationState();
public abstract AjaxDirectService getAjaxEngine();
public ITreeContentProvider getContentProvider() {
return CONTENT_PROVIDER;
}
public IKeyProvider getKeyProvider() {
return LocationKeyProvider.INSTANCE;
}
public ITreeManager getTreeManager() {
return new TreeManager(getLocationState(), getContentProvider(),
getKeyProvider() );
}
public void select(IRequestCycle cycle) {
// I'm stuck here
}
public void revealSelected(IRequestCycle cycle) {
getTreeManager().reveal(getSelectedLocation());
}
}
------ EOF LocationTreePage.java ------
The comparation is done by the following class. Please note that we
should do something better here, but I havn't gotten around to thinking
about that.
------ LocationComparator.java ------
public class LocationComparator implements Comparator {
public final static Comparator INSTANCE = new LocationComparator();
public int compare(Object o1, Object o2)
{
Location location1 = (Location)o1;
Location location2 = (Location)o2;
if( location1.equals(location2) ) {
return 0;
}
return 1;
}
}
------ EOF LocationComparator.java ------
The keys are provided with the following class:
------ LocationKeyProvider.java ------
public class LocationKeyProvider implements IKeyProvider {
public final static LocationKeyProvider INSTANCE = new
LocationKeyProvider();
public Serializable getKey(Object value) {
return ((Location) value).getId();
}
}
------ EOF LocationKeyProvider.java ------
Finally the last class that is used provides the actual data, there is a
but of custom datahandling in here. I use JNDI calls to get a EJB3bean
that knows about CRUD functions.
------ LocationKeyContentProvider.java ------
public class LocationTreeContentProvider implements ITreeContentProvider {
Logger logger = Logger.getLogger( this.getClass().getName() );
private final Location rootLocation;
/**
* Constuctor that starts to build the tree somewhere
* @param rootLocation
*/
public LocationTreeContentProvider(Location rootLocation) {
this.rootLocation = rootLocation;
}
public Collection getChildren(Object parentElement) {
return ((Location)parentElement).getChildLocations();
}
public boolean hasChildren(Object parentElement) {
/**
if( ((Location)parentElement).getChildLocations().size() > 0 ) {
return true;
}
return false;
*/
return false;
}
public Object getParent(Object childElement) {
return ((Location)childElement).getParentLocation();
}
public List getElements()
{
// Get initial context
LocationBean locationBean=null;
try{
locationBean = (LocationBean)(new
InitialContext()).lookup(LocationBean.class.getName());
} catch (Exception e){
logger.error( "Unable to get location bean: " +
e.getMessage() );
return Collections.EMPTY_LIST ;
};
// Return the list of rootlocations
List<Location> list =
locationBean.findByParentLocation(rootLocation);
return list;
}
}
------ EOF LocationKeyContentProvider.java ------
Just to be complete here is the listing for the EJB:
------ LocationBeanImpl.java ------
@Stateless
public class LocationBeanImpl extends GenericEJBDAO<Location, Long>
implements LocationBean {
/**
* Use constructor to tell GenericEJBDAO our class
*/
public LocationBeanImpl() {
super(Location.class);
}
/* (non-Javadoc)
* @see
com.stocketeer.ejb.impl.LocationBean#findByParentLocation (com.stocketeer.par.Location)
*/
@SuppressWarnings("unchecked")
public List<Location> findByParentLocation(Location parentLocation)
{
if( parentLocation == null ) {
return this.em.createQuery("from Location as location where
location.parentLocation = null").
getResultList();
}
else {
return this.em.
createQuery("from Location as location where
location.parentLocation = ?"
).setParameter(0, parentLocation.getId()
).getResultList();
}
}
}
------ EOF LocationBeanImpl.java ------
Oof, that was a lot of c/p and a bit of typing, but I'm hoping that you
can point me to my errors and include something that makes it easier for
us noob users to use all this fancy functionality. In anycase keep up
the good work and thanks in advance.
With this I'm getting an error:
org.apache.tapestry.BindingException
Unable to read OGNL _expression_ '<parsed OGNL _expression_>' of
[EMAIL PROTECTED]: source is null for
getProperty(null, "name")
--
Kind regards,
Dennis Fleurbaaij
E: [EMAIL PROTECTED]
T: (0031) (0) 6 54 21 53 65
