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

Attachment: smime.p7s
Description: S/MIME Cryptographic Signature

Reply via email to