Solution
Use Windchill Client Architecture Tree to display Windchill business objects in Tree format.
Prerequisite knowledge
To achieve this objective, you need to have an understanding of the following:
• The actions framework in the Windchill client architecture.
• JCA Table component
• Data acquisitions, Data Utility, GUI component.
• JSP, JavaScript and Custom taglibs.
• Java annotations
Solution Elements
Element
|
Type
|
Description
|
<your_ConfigBuilder>
|
java
|
TreeConfiguration is defined here
|
<your_DataBuilder>
|
java
|
Data fetching service/commands are defined here
|
<your_view_page>.jsp
|
jsp
|
View rendering logic is defined here
|
<your>service.properties.xconf
|
xconf
|
DataUtilities and services are defined here.
|
<your>action.rbInfo
|
rbInfo
|
Action attributes defined here.
|
<your>actions.xml
|
xml
|
Actions can be defined here.
|
<your>actionModels.xml
|
xml
|
Action models can be defined here.
|
Implementing Tree Component using Asynchronous DataSource
To implement a tree component using Asynchronous DataSource, TreeDataBuilderAsync interface needs to be implemented. It has a single method to be implemented by the concrete class:
void buildNodeData(Object node, ComponentResultProcessor resultProcessor) throws Exception
It Fetches children for each node.The first call to this method from infrastructure will always pass first argument as TreeNode.RootNode.This first call ensures that all rootNodes are fetched for subsequent calls. The subsequent calls will always pass a treeNode for which child nodes are required to be fetched.
E.g. The code should handle this case similar to following code snippet:
List nodes;
if (node == TreeNode.RootNode){
nodes = getRootNodes();
resultProcessor.addElements(nodes);
}else {
getNodes(node);
nodes = resultProcessor.addElements(nodes);
}
Implementing Tree Component without using DataSources
To implement Tree component without DataSources, TreeHandler interface needs to be implemented. TreeHandlerAdapter provides default implementation. TreeHandler is a JAVA interface ,which is responsible for populating the tree content.Following methods are provided by TreeHandler :
• ModelContext getModelContext() :- Get the handler's model context
• void setModelContext(ModelContext mc) throws WTException :- Sets the contextual information about the tree that is being built, like the descriptor, command bean etc. This should be initialized before data is requested from the handler.
• List getRootNodes() throws WTException :- Get the list of root nodes for the tree.
• Map<Object,List> getNodes(List parents) throws WTException :- Get a mapping of the child nodes for each of the parent nodes in the given list This is the only method that will be called for the expand action, so this method must be able to initialize the handler properly so that it can answer the question of what are the children of the parent.
• boolean isExpandNeeded(Object node, int level) throws WTException :- Determines whether or not the given node needs to be expanded. The default implementation looks at the session state of the list of nodes already expanded or configured by the tag.
• boolean hasChildren(Object node) throws WTException :- Determines whether or not the given node should show the expand norgie if its collapsed. Override this method to improve performance or to get custom behavior. The default implementation calls the getNodes and sees if the size is larger than 0.
• void addExpandedNode(Object node) throws WTException :- Add a node to the expanded node list. This list is returned and used in session state to remember what the tree expansion is.
Trees need to be carefully created because of their recursive nature that causes many queries to the database. The Treehandler class can save tree state information as private attributes on the class and reused for each getNodes call. In addition, you can take advantage of knowing which tree rows are expanded by the user with the isExpandNeeded method.
TreeHandlerAdapter is an abstract class which implements TreeHandler which you can extent. Following is an example
getRootNodes() determine what the root nodes should be. The TreeHandler has access to the ModelContext, which can be used to access required information.
public List getRootNodes() throws WTException {
NmCommandBean cb = getModelContext().getNmCommandBean();
WTPart part;
NmOid oid = cb.getPageOid();
if (oid == null) {
log.debug("No oid found in request, trying GOLF_CART");
part = getGolfCart();
}else {
if (!oid.isA(WTPart.class)) {
throw new ClassCastException("Expected part, but was: " + oid);
}
part = (WTPart)oid.getRef();
}
if (part == null) {
log.debug("Couldn't get part");
return null;
}
configSpec = ConfigHelper.service.getConfigSpecFor(part);
return Collections.singletonList(part);
}
The method first retrieves the NmCommandBean from the model context and extracts the primary oid from it. If no oid was requested from the user, then it uses the GOLF_CART part instead by calling the getGolfCart method. The getGolfCart method simply queries and returns the GOLF_CART. If the user did specify an oid in the request parameter, it checks to make sure the oid is a WTPart. Otherwise, it throws a ClassCastException. If it’s the expected oid, it continues by retrieving the part from the oid via the getRef method. This method inflates the referenced object, which then can be cast into a WTPart. The last thing it checks is if the part retrieved either from the oid or the GOLF_CART is null and if so return null. The configSpec variable is assigned the ConfigSpec of the part using the ConfigHelper class. This helper class contains a service that helps obtain Iterated objects from Mastered objects. Lastly, an immutable List containing the part is returned.
getNodes(List parents) Get a mapping of the child nodes for each of the parent nodes in the given list. It can be called directly without calling the getRootNodes() method first. E.g. (expand action). This means that this method must be able to initialize the handler properly so that it can answer the question of what are the children of the parent.
The given example, generates a hierarchy of WTPart based on the WTPartUsageLinks.
public Map<Object,List> getNodes(List parents) throws WTException {
if (configSpec == null) {
configSpec = getDefaultConfigSpec();
}
Map<Object,List> result = new HashMap<Object,List>();
//API returns a 3D array where the 1st dim is the parent parts,
//the 2nd dim is the list of children for a given parent,
//and the 3rd dim is 2 element array w/the link obj at 0 and the child part at 1
Persistable[][][] all_children = WTPartHelper.service.getUsesWTParts(
new WTArrayList(parents), configSpec);
for (ListIterator i = parents.listIterator(); i.hasNext();) {
WTPart parent = (WTPart)i.next();
Persistable[][] branch = all_children[i.previousIndex()];
if (branch == null) {
continue;
}
List children = new ArrayList(branch.length);
result.put(parent,children);
for (Persistable[] child : branch) {
children.add(child[1]);
}
}
log.debug("ParentsToChildren: " + result);
return result;
}
private ConfigSpec getDefaultConfigSpec() throws WTException {
return ConfigHelper.service.getDefaultConfigSpecFor(WTPart.class);
}
Custom tree handlers need to be registered. Please refer Refererence to <DataUtilities Document> for information on how to register services. Following is a sample tree handler entry.
<Service context="default" name="com.ptc.core.components.beans.TreeHandler">
<Option requestor="your_object" selector="your_treeHandler"
serviceClass="your_treeHandler_class"
cardinality="duplicate"/>
</Service>
If you want to show non-persistables in a Tree, you are recommended to return Element for getRootNodes & getNodes methods. If your element doesn’t have a valid “ufid” value available or “obid” attribute, you need to provide NmObject for each row object. This can be done in two ways.
1. Author a DataUtility which extends DefaultNmObjectUtility and override its getTargetObject() method which will return a NmObject for the row object. Please refer to
NmObject Utilities for more information.
2. Your Element will have an attribute named "nmObject" with its value an instanceof NmObject
Otherwise, to show non-persistables, you can write a wrapper class extending from NmObject and override getOid() method to return proper NmOid.
Procedure — Configuring JCA Tree
• Implementing ComponentConfigBuiler and ComponentDataBuilder
• Defining View
Implementing ComponentConfigBuiler and ComponentDataBuilder
There are two approaches:
• Separate builder approach
• Single builder approach
Separate Builder Approach
In this case separate classes are written for ComponentConfigBuiler and ComponentDataBuilder.
Using Asynchronous DataSource
Implementing ComponentConfigBuilder
Example:
@ComponentBuilder(value = "custom.treeExample.seperate", type = ComponentBuilderType.CONFIG_ONLY)
public class TreeExampleConfigBuilder extends AbstractComponentConfigBuilder {
private static final String RESOURCE = "com.ptc.carambola.carambolaResource";
@Override
public ComponentConfig buildComponentConfig(ComponentParams params)
throws WTException {
ComponentConfigFactory factory = getComponentConfigFactory();
//Create TreeConfig
TreeConfig tree = factory.newTreeConfig();
// Need to set DataSOurceModes explicitely to DataSourceMode.ASYNCHRONOUS
((JcaTreeConfig) tree).setDataSourceMode(DataSourceMode.ASYNCHRONOUS);
// Set expansion level . Default is TableTreeProperties.ONE_EXPAND
(expand by one level)
((JcaTreeConfig) tree).setExpansionLevel(TableTreeProperties.FULL_EXPAND);
tree.setLabel((new ResourceBundleClientMessageSource(RESOURCE)).getMessage
("PART_TREE_LABEL"));
//Add Columns to the config
tree.addComponent(factory.newColumnConfig(NAME, true));
tree.addComponent(factory.newColumnConfig(NUMBER, true));
//Set column to which expand/collapse norgie should appear
tree.setNodeColumn(NUMBER);
return tree;
}
}
Important Points:
1. To use Asynchronous DataSource, DataSource mode should be explicitely set to DataSourceMode.ASYNCHRONOUS.
2. setNodeColumn method, allows to choose the column for which expand/collapse norgie is required. Default is “name ” column.
Implementing ComponentDataBuilder
The concrete class should implement interface com.ptc.mvc.components.TreeDataBuilderAsync. Please check Java API documentation for more details.
Example:
@ComponentBuilder(value = "custom.treeExample.seperate", type =
ComponentBuilderType.DATA_ONLY)
public class TreeExampleComponentDataBuilder
implements TreeDataBuilderAsync {
@Override
public void buildNodeData(Object node,
ComponentResultProcessor resultProcessor)
throws Exception {
if (node == TreeNode.RootNode) {
List<Object> objects = getRootNodes();
resultProcessor.addElements(objects);
} else {
List nodeList = new ArrayList();
nodeList.add(node);
Map<Object, List> map = getNodes(nodeList);
Set keySet = map.keySet();
for (Object key : keySet) {
resultProcessor.addElements(map.get(key));
}
}
}
private List<Object> getRootNodes(){ // Add code to find RootNodes}
private List<Object> getNodes(List<Object> nodeList){ // Add code to find ChildNodes}
}
Advanced Configurations by implementing TreeExpansionStateManager
TreeExpansionStateManager provides following APIs
boolean isExpandNeeded(DefaultMutableTreeNode node, ComponentConfig config,
ComponentParams params) throws WTException;
void addExpandedNode(Object node, ComponentConfig config,
ComponentParams params)
throws WTException;
Set getExpandedOids(ComponentConfig config, ComponentParams params)
throws WTException;
boolean hasChildren(Object node, ComponentResultProcessor resultProcessor,
TreeDataBuilderAsync builder) throws Exception;
List<DefaultMutableTreeNode> getDynamicExpandedNodes(ComponentConfig config,
ComponentParams params) throws WTException;
Please refer to JavaDoc for details.
Concrete implmenetation of TreeExpansionStateManager is provided by DefaultTreeExpansionStateManager. This implementation can be extended to customize the APIs as per requirement or TreeExpansionStateManager can be implemented directly. The concrete implementation can be injected into the builder by using ExpansionStateManager annotation.
Example:
@ComponentBuilder ("folderbrowser_tree")
@ExpansionStateManager (FolderTreeExpansionStateHandler.class)
//
public class FolderTreeBuilder extends AbstractComponentConfigBuilder implements TreeDataBuilderAsync{…}
Here FolderTreeExpansionStateHandler implements DefaultTreeExpansionStateManager and is injected into the FolderTreebuilder
These APIs are useful for customizing the tree expansion state behavior. E.g. IsExpandNeeded API can be used to override the default logic which decides whether the node should be expanded.
Without using DataSource
In this case the either setDataSourceMethod should not be called or should set dataSourceMode to DataSourceMode.SYNCHRONOUS. All other configurations are same as explained in the “Implementing ComponentConfigBuilder” section.
Implementing ComponentConfigBuilder
Implementing ComponentDataBuilder
In this case the databuilder should implement ComponentDataBuilder interface.
Example:
@ComponentBuilder(value = "custom.treeExample.seperate", type = ComponentBuilderType.DATA_ONLY)
public class TreeExampleComponentDataBuilder implements ComponentDataBuilder {
@Override
public TreeHandler buildComponentData(ComponentConfig config, ComponentParams params) throws WTException {
return new customTreeHandler();// customTreeHandler should implement TreeHandler or extend TreeHandlerAdapter
}
}
Single Builder Approach
In this case both ComponentConfigBuilder and ComponentDataBuilder are implemented by a single class.
Using Asynchronous DataSource
Example:
@ComponentBuilder(value = "custom.treeExample")
public class TreeExampleBuilder implements TreeDataBuilderAsync, ComponentConfigBuilder,ComponentConfigFactoryAware {....}
Without Using DataSource
Example:
@ComponentBuilder(value = "custom.treeExample")
public class TreeExampleComponentDataBuilder extends AbstractConfigurableTableBuilder{….}
Defining View
setView method on componentConfig enables setting of view for a particular builder The view should be set relative to /WEB-INF/jsp folder. The default view points to uses WEB-INF/jsp/components/tree.jsp
The default view can be customized using the custom tag definitions provided in file <Windchill>\codebase\WEB-INF\tlds\jcaMvc.tld
Parent topic