Windchill Customization Document
Windchill Customization Document
1. Install Eclipse(Mars 2 for WC 11).
2. Start the Windchill.
3. Goto windchill shell.
4. Run the following command. Follow the procedure as it is in the windchill shell.
ant -f bin/tools.xml eclipse_project.help
Creates an Eclipse workspace and project for C:\ptc\Windchill_11.0\Windchill/src. Works with Eclipse 3.6.x (Helios)
Requires FileSync plugin (http://andrei.gmxhome.de/filesync/index.html)
To use:
0) Install FileSync
a) Start Eclipse
b) Accept the default workspace (we'll, later, assign a different one)
c) Select Help -> Install new software...
d) Click Add... (next to "Work with:")
e) Set Name to FileSync and Location to http://andrei.gmxhome.de/eclipse/
f) Click OK, then wait until the "Pending..." text disappears
g) Type FileSync in the text are with the grayed-out text reading "type filter text"
h) Select the FileSync for Eclipse 3.6 (currently under Eclipse 3.5 - 3.7 plugins)
i) Click Next >, wait, then click Next > again
j) Select the "I accept the terms of the license agreement" radio button
k) Click Finish
l) OK the Security Warning
m) Click Not Now, then exit Eclipse.
1) Run the eclipse_project task
2) Launch Eclipse, specifying C:\ptc\Windchill_11.0\Windchill/../eclipse as the workspace
3) Import the cust_Windchill_src project
a) File -> Import... -> General -> Existing Projects into Workspace
b) Select Browse (next to "Select root directory") and click OK
c) Your project will be listed and checked; click Finish.
Custom Tab Action Models and Object Action Models
Prerequisite for Custom Actions and Models
1. Understanding xml file and formats
2. Basic Windchill file structure
• If you have an <objecttype> element in the custom-actions.xml file, and that type exists within another actions.xml file, then actions from
your custom file will be added to the full set of actions supported for that type (defined in the actions.xml file).
• This custom-actions.xml is read last of all the actions.xml files, so if action names are duplicated, the one in this file will be used
If you have a <model> element in the custom-actionmodels.xml file, and a model by that name already exists, your model will completely
override the ones processed before your custom action models file. Be very careful in the naming of your models so that you do not wipe
out others from files read in prior unless that is the intent.
Action Framework for Windchill Client Architecture
The action framework for the Windchill client architecture supports the ability to configure new actions and action models in the system.
This section describes the action framework for the Windchill Client Architecture. It does not include information on how to control the display
of the actions. For more information on controlling the display
Intended Outcome and Objectives
When you are finished reading this, you should understand how the action framework works, and how to register actions and action models into
the action framework. You should also be familiar with the tools and debug settings that can help in your development.
These topics cover the following objectives:
• You want to add a new action that will be exposed in the user interface.
• You want to add a new action model to the system.
• You want to remove an action from an action model.
Prerequisite knowledge
Related Documentation
Actions in the system are uniquely identified by the name of the action and the object type. They should be defined in an XML file that follows
the structure based on codebase/config/actions/actions.dtd.
Here is an example of an action definition for the New Document window.
<objecttype name="document" class="wt.doc.WTDocument"
resourceBundle="com.ptc.windchill.enterprise.doc.documentResource">
<action name="create" uicomponent="CREATE_DOC" dtiUpload="true">
<command
class="com.ptc.windchill.enterprise.doc.forms.CreateDocFormProcessor"
method="execute" windowType="popup" onClick="validateCreateLocation(event)"/>
<includeFilter name="projectM4D" />
<nonSupportedTypes value="wt.part.WTPart"/>
<supportedTypes value="wt.doc.WTDocument"/>
</action>
</objecttype>
objecttype Tag
The objecttype is a way to create a name space as well as packaging for actions related to a specific object or functional area. In the above
example, the name “document” creates a unique name space for actions that apply to wt.doc.WTDocuments.
Naming conventions for the name of an objecttype can be any combination of alpha-numeric characters. Most objecttypes are an alias for the
persistable object to which the actions relate. Actions that apply to any object type, such as copy, can be put within the objecttype of “object”.
PTC recommends that all custom objecttypes have a prefix specific to the company to prevent collisions with object types delivered with the
product.
The table below describes the valid parameters for objecttype. Details about these parameters can also be found in
codebase/config/actions/actions.dtd.
name any combination of alpha-numeric Yes The name used to reference this object type.
characters
class A valid Java class Yes Object class for the enclosed actions
resourceBundle Any valid resource bundle class name No Class name for the default resource bundle to use for the
properties of the actions that are to be localized
action Tag
The action name is a unique identifier for an action within the context of the object type. The object type in conjunction with the action name
make the action unique within the system.
By default, the action name corresponds to the name of a JSP within the package named for the object type. The packaging is relative to
codebase/netmarkets/jsp. For example, the action name in the XML example above is “create.” Within the document object type, this
corresponds to codebase/netmarkets/jsp/document/create.jsp.
Naming conventions for the name of an action can be any combination of alpha-numeric characters.
PTC recommends that all your custom actions have a prefix specific to your company to prevent collisions with names of actions
delivered with the product.
The table below describes the valid parameters for action. Details about these parameters can also be found in
codebase/config/actions/actions.dtd:
Create a Tab Action Model in the Navigator in Windchill
Steps
1. Login in to Windchill as site admin and enable the client customization preference from the site admin level
2. Check if the client customization tool is visible
3. Click on tools and enable the jcaDebug so that in the navigation tab the xml file responsible for the customization is revealed, if the
customization was already done the custom xml file would be custom-actionModels.xml
4. In our case the files required for customization are,
navigation-actionModels.xml
Navigation-actions.xml located at %WT_HOME%\codebase\config\actions.
5. Open both the files and check for provision for adding custom models and actions for users
6. Copy the model from the parent files and add it to the corresponding custom*.xml files
7.
8. See the custom submodel added to the one copied from the navigation-actionModels.xml
9. Now create the submodel definitions and actions in the custom-actions.xml
10.
11. Create the contents for sub actions with a jsp file, see below a sample file,
12. Restart the method server and check if the actions appear.
When creating a new action, there might be logic that you want executed to determine if that action should be enabled, disabled, or hidden in
the user interface. You can write this logic in a class called a validator. See UI Validation for details about the validation service, writing a
validator, and registering the validator for your action.
nonSupportedTypes and supportedTypes Attributes
By specifying supportedTypes or nonSupportedTypes, the action framework either enables or disables the action depending on the object
types mentioned. This filtering is applied only for a customized menu.
The supportedTypes or nonSupportedTypes filtering is applied before any other validation service class defined
by application developers. In other words, it is a universal filter.
• If an action is disabled using the nonSupportedTypes attribute, then any custom validators that have been written are not executed.
• If an action is enabled through the supportedTypes attribute, then any custom validators that have been written are invoked.
For example:
<objecttype name="object" class="java.lang.Object" >
<action name="reports" enabledwhensuspended="true">
<command
url="netmarkets/jsp/carambola/customization/reports/base.jsp"/>
<nonSupportedTypes
value="wt.doc.WTDocument,wt.part.WTPart"/>
</action>
</objecttype>
Alternatively you can also use:
<objecttype name="object" class="java.lang.Object" >
<action name="reports" enabledwhensuspended="true">
<command
url="netmarkets/jsp/carambola/customization/reports/base.jsp"/>
<nonSupportedTypes>
<type value="wt.doc.WTDocument" />
<type value=" wt.part.WTPart " />
</nonSupportedTypes>
</action>
</objecttype>
You can specify whether this filtering is applied to subtypes of an object. By default, subtypes inherit this attribute. You can control this
functionality by adding the additional attribute applyToDescendants:
<supportedTypes>
<type value="wt.foo.Bar" applyToDescendants=”false” />
</supportedTypes>
MCV
Custom Action Menu for MVC Table Builder
Display the table from the database
Custom-actionModels.xml
<action name="show_MVC_Table" type="object" resourceBundle="ext.test.hello.objectActionsRB"/>
Custome-actions.xml
<objecttype name="object" class="wt.fc.Persistable" >
<action name="show_MVC_Table" resourceBundle="ext.test.hello.objectActionsRB">
<component name="ext.test.hello.MVCTableBuilder" windowType="page"/>
</action>
</objecttype>
objectActionsRB.java
package ext.test.hello;
import wt.util.resource.NestableListResourceBundle;
import wt.util.resource.RBEntry;
import wt.util.resource.RBPseudo;
import wt.util.resource.RBUUID;
import wt.util.resource.RBComment;
import wt.util.resource.*;
@RBUUID("ext.test.resourceBundle.objectActionsRB")
public class objectActionsRB extends WTListResourceBundle {
@RBEntry("Show MVC Table")
public static final String PRIVATE_CONSTANT_1="object.show_MVC_Table.description";
}
MVC Builders
There are two types of Builders. The one that provides ComponentConfig are called ComponentConfigBuilder and the one that provides the
ComponentData are called ComponentDataBuilder.
● Customization Guide• ComponentConfigBuilder : beans that implement com.ptc.mvc.components.ComponentConfigBuilder
● ComponentDataBuilder : beans that implement any of the following interface
➔ com.ptc.mvc.components.ComponentDataBuilder
➔ com.ptc.mvc.components.ComponentDataBuilderAsync
➔ com.ptc.mvc.components.TreeDataBuilderAsync
Specifying the componentid
Typically the component ID that a builder maps to is declared using the @ComponentBuilder annotation in your builder class declaration.
@ComponentBuilder(value = “{<componetId1>, <componetId2>}”)
public class MyBuilder extends ... {}
If you wish to implement your config and data builders in separate classes, then you must supply an additional ComponentBuilderType
parameter to the @ComponentBuilder annotation.
For config builders, this looks like:
@ComponentBuilder(value=“<componetId>", type=ComponentBuilderType.CONFIG_ONLY) public class MyConfigBuilder implements
ComponentConfigBuilder .... {}
For data builders, this looks like:
@ComponentBuilder(value=“<componetId>", type=ComponentBuilderType.DATA_ONLY) public class MyDataBuilder implements
ComponentDataBuilder... {}
Note that framework will throw an error if two builders will have the same component id. Spring initialization will fail in the MethodServer
startup phase. If you need to override an OOTB builder, please use OverrideComponentBuilder annotation.
TypeBased
To build certain components, you may need the Windchill Type of the context object playing a role in finding the appropriate builder. For
example you have an info page and you want to populate the content based on the Windchill Type. For these scenarios, we have introduced an
annotation “TypeBased” that can be used in the builder, to attach the builder to a specific Windchill Type. This works with ComponentBuilder.
@ComponentBuilder("compIdA")
@TypeBased(value="{WTPart, WTDocument}")
public class OOTBBuilder1 extends ......{
}
@ComponentBuilder("compIdA")
@TypeBased(“myPart”)
public class OOTBBuilder2 extends ......{
}
• If the context object’s Windchill Type is WTPart and the componentId = “compIdA”, the best match builder for that component is OOTBBuilder1
• If the context object’s Windchill Type is a sub-type of WTPart(myPart) and the componentId = “compIdA”, the best match builder for that
component is OOTBBuilder2
• If the context object’s Windchill Type is a sub-type of WTDocument(myDoc) and the componentId = “compIdA”, the best match builder for that
component is OOTBBuilder1 While resolving to find the suitable builder, the context object’s Windchill type hierarchy is respected. The attribute
value can take
• Internal Name of the Windchill Type
• If the representation has the domain name of the exchange container involved, then can use ${internet_domain_name} for its representation.
e.g
${internet_domain_name}.DynamicDocument
Registering Builders
Once the builders are created, you need to let Spring infrastructure know about it. Registration can be done either via explicit configuration or
by automated scanning. Automated scanning is the preferred approach for typical use cases.
1. Automated scanning: You can configure to automatically pick up all builders within a certain package
hierarchy. To do this, add the <mvc:builder-scan/> configuration element to <Windchill>\codebase\config\mvc\custom.xml.
<beans xmlns="xmlns:mvc="http://www.ptc.com/schema/mvc" xsi:schemaLocation="http://www.ptc.com/schema/mvc
http://www.ptc.com/schema/mvc/mvc-10.0.xsd">
<mvc:builder-scan base-package="com.ptc.windchill.enterprise.preference.mvc.builders"/>
</beans>
The builder-scan implementation scans the entire classpath, so you should take care to be specific with the package name you declare if
using scanning. (don’t scan com.ptc.* for example). In addition, this means that classes that are outside your interest, but that are in the
classpath and match the package hierarchy, will also get scanned. From a performance standpoint (MethodServer startup) each scan adds
up time. You are requested to take advantage of the OOTB scan provided on “com.ptc.mvc.builders” base package. This means that all the
builders that you author should be under com.ptc.mvc.builders package. If you are not using the OOTB scan, the rule of thumb is to use the
scan if there are more than 10 builders available in the package.
Explicit configuration : To do this, simply add a bean declaration for the builder to the <Windchill>\codebase\config\mvc\custom.xml
e.g <bean class=" my.builder.class.name "/>
You are encouraged not to specify the bean name while declaring the bean and use @ComponentBuilder annotation in the builder for
proper registration of it in the Spring bean factory.
Example Program
MVCTableBuilder.java
package ext.test.hello;
import java.lang.Object;
import com.ptc.mvc.components.AbstractComponentConfigBuilder;
import com.ptc.mvc.components.AbstractComponentBuilder;
import com.ptc.mvc.components.ComponentBuilder;
import com.ptc.mvc.components.ComponentConfig;
import com.ptc.mvc.components.ComponentConfigFactory;
import com.ptc.mvc.components.ComponentParams;
import com.ptc.mvc.components.TableConfig;
import com.ptc.windchill.annotations.metadata.SupportedAPI;
import wt.fc.PersistenceHelper;
import wt.part.WTPart;
import wt.query.QuerySpec;
import wt.query.SearchCondition;
import wt.util.WTException;
@ComponentBuilder("ext.test.tables.MVCTableBuilder")
public class MVCTableBuilder extends AbstractComponentBuilder {
@Override
public Object buildComponentData(ComponentConfig config, ComponentParams params) throws Exception {
QuerySpec query = new QuerySpec(WTPart.class);
// You can specify the search condition according to you
query.appendWhere(new SearchCondition(WTPart.class,WTPart.NAME,SearchCondition.LIKE,"%P%"), null);
return PersistenceHelper.manager.find(query);
}
@Override
public ComponentConfig buildComponentConfig(ComponentParams arg0) throws WTException {
ComponentConfigFactory factory = getComponentConfigFactory();
TableConfig table = factory.newTableConfig();
table.setLabel("Selected Parts");
table.setSelectable(true);
table.addComponent(factory.newColumnConfig("name", true));
table.addComponent(factory.newColumnConfig("number", true));
table.addComponent(factory.newColumnConfig("type", true));
table.addComponent(factory.newColumnConfig("thePersistInfo.modifyStamp", true));
table.addComponent(factory.newColumnConfig("thePersistInfo.createStamp", true));
return table;
}
}
Custom.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.ptc.com/schema/mvc"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
http://www.ptc.com/schema/mvc http://www.ptc.com/schema/mvc/mvc-10.0.xsd">
<!-- Configurations in this file override all other configurations -->
<mvc:builder-scan base-package="ext.test.hello"/>
<bean class="ext.test.hello.MVCTableBuilder" />
<bean class="ext.test.hello.CSMApproverMatrixStepTableBuilder" />
</beans>
Another MVC Table Builder
Custom-actionModels.xml
<action name="Matrix_Step" type="object" resourceBundle="ext.test.hello.matrixActionsRB"/>
Custom-action.xml
<objecttype name="object" class="wt.fc.Persistable" >
<action name="Matrix_Step" resourceBundle="ext.test.hello.matrixActionsRB">
<component name="ext.test.hello.CSMApproverMatrixStepTableBuilder" windowType="page"/>
</action>
</objecttype>
CSMApproverMatrixStepTableBuilder.java
package ext.test.hello;
import com.ptc.jca.mvc.components.JcaTableConfig;
import com.ptc.mvc.components.AbstractComponentBuilder;
import com.ptc.mvc.components.ColumnConfig;
import com.ptc.mvc.components.ComponentBuilder;
import com.ptc.mvc.components.ComponentConfig;
import com.ptc.mvc.components.ComponentConfigFactory;
import com.ptc.mvc.components.ComponentParams;
import wt.util.WTException;
@ComponentBuilder("ext.test.hello.CSMApproverMatrixStepTableBuilder")
public class CSMApproverMatrixStepTableBuilder extends AbstractComponentBuilder {
@Override
public Object buildComponentData(ComponentConfig arg0, ComponentParams arg1) throws Exception {
return null;
}
@Override
public ComponentConfig buildComponentConfig(ComponentParams arg0) throws WTException {
ComponentConfigFactory factory = getComponentConfigFactory();
JcaTableConfig table = (JcaTableConfig) factory.newTableConfig();
table.setLabel("Approver Matrix");
table.setSelectable(true);
ColumnConfig columnConfig = factory.newColumnConfig("Approval Matrix Description", true);
columnConfig.setInfoPageLink(false);
//columnConfig.setDataUtilityId("");
columnConfig.setDefaultSort(true);
table.addComponent(columnConfig);
columnConfig = factory.newColumnConfig("Reviewer", true);
columnConfig.setInfoPageLink(false);
//columnConfig.setDataUtilityId("");
columnConfig.setDefaultSort(true);
table.addComponent(columnConfig);
columnConfig = factory.newColumnConfig("Content Approver", true);
columnConfig.setInfoPageLink(false);
//columnConfig.setDataUtilityId("");
columnConfig.setDefaultSort(true);
table.addComponent(columnConfig);
columnConfig = factory.newColumnConfig("Customer Approver", true);
columnConfig.setInfoPageLink(false);
//columnConfig.setDataUtilityId("");
columnConfig.setDefaultSort(true);
table.addComponent(columnConfig);
columnConfig = factory.newColumnConfig("Process Compliance Approver", true);
columnConfig.setInfoPageLink(false);
//columnConfig.setDataUtilityId("");
columnConfig.setDefaultSort(true);
table.addComponent(columnConfig);
return table;
}
}
Custom.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.ptc.com/schema/mvc"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
http://www.ptc.com/schema/mvc http://www.ptc.com/schema/mvc/mvc-10.0.xsd">
<!-- Configurations in this file override all other configurations -->
<mvc:builder-scan base-package="ext.test.hello"/>
<bean class="ext.test.hello.MVCTableBuilder" />
<bean class="ext.test.hello.CSMApproverMatrixStepTableBuilder" />
</beans>
Listener Service to capture a check-in Event
Creating Non-Modeled Services for Listening
Here are the steps necessary to create a non-modeled service for listening:
1. Create a service interface
2. Create a standard service class to implement your service interface and extend the StandardManager class.
3. Compile your interface and class into your Windchill codebase.
4. Register your new service in the codebase with xconfmanager.
Below is an example to create a service that listens for pre- and post-checkin events. When it "hears" one of these events, it prints a simple
message to standard output. The example assumes the service is in a package called com.acme.listen, and that the service is called the "Listen"
service.
Use below steps to create new listener
1. Create new interface as xxxxxService
package PTC.Listener;
}
1. Create new class for Standardservice class StandardxxxxService which implements service interface created in step1 and extends
StandardManager class
package PTC.Listener;
3. Create internal class for Listening event StandardXXXXXEventListener which extends ServiceEventListenerAdapter class
xconfmanager –s
”wt.services.service.65001=com.ptc.windchill.enterprise.datamonitor.DataMonitorService/com.ptc.windchill.enterprise.datamonitor.StandardData
MonitorService” -t codebase\wt.properties -p
Note: You can select any random number instead on 65001 for registering service/Listener make sure that number is not already in used
you can set below verbosity to true check event keys
wt.services.verbose
wt.services.verboseEvents
There is no restriction to copied/place listener in specific folder. You can store listener in any folder you want in Windchill you can place
source java file of listener in windchill/src/ directory and use below command to compile
ant -f bin/tools.xml class -Dclass.includes=/ PTC/Listener/**
Example listener Program
StandardListenService.java
package ext.cust.services;
import ext.cust.services.ListenService;
import wt.services.ManagerException;
import wt.services.ServiceEventListenerAdapter;
import wt.services.StandardManager;
import wt.session.SessionContext;
import wt.session.SessionHelper;
import wt.util.DebugProperties;
import wt.util.DebugWriter;
import wt.util.WTException;
import wt.vc.wip.*;
import java.lang.String;
public final class StandardListenService extends StandardManager implements ListenService {
private static final String CLASSNAME = StandardListenService.class.getName();
private static final boolean DEBUG = DebugProperties.isDebugOn(CLASSNAME);
private static final DebugWriter LOG = (DEBUG ? DebugProperties.getWriter(CLASSNAME) : null);
public String getConceptualClassname() {
return CLASSNAME;
}
public static StandardListenService newStandardListenService() throws WTException {
StandardListenService instance = new StandardListenService();
instance.initialize();
return instance;
}
protected void performStartupProcess() throws ManagerException {
if (DEBUG && DebugProperties.isTrace(this))
LOG.enter(CLASSNAME, "performStartupProcess");
SessionContext prev = SessionContext.newContext();
try {
SessionHelper.manager.setAdministrator();
}
catch (WTException wte) {
System.err.println("StandardListenService: failed to set Administrator (ok if Installation)");
return;
}
finally {
SessionContext.setContext(prev);
}
getManagerService().addEventListener(new ServiceEventListenerAdapter(this.getConceptualClassname()) {
public void notifyVetoableEvent(Object event) throws WTException {
final Workable target = ((WorkInProgressServiceEvent)event).getOriginalCopy();
System.out.println("Listen Hears Pre-Checkin:");
System.out.println(" target:");
System.out.println(target.toString());
}
},
WorkInProgressServiceEvent.generateEventKey (WorkInProgressServiceEvent.POST_CHECKIN));
if (DEBUG && DebugProperties.isTrace(this))
LOG.exit(CLASSNAME, "performStartupProcess");
}
}
Create a interface called ListenService (let it be empty)
package ext.cust.services;
public interface ListenService {
}
Create a service in wt.properties as below,
<Property name="wt.services.service.4160" overridable="true"
targetFile="codebase/wt.properties"
value="ext.cust.services.ListenService/ext.cust.services.StandardListenService"/>
Add the above property in site.xconf and run the command xconfmanager -p in the windchill shell
Restart the method server
Create a Custom Form Wizard
Wizard Processing
Objective
You have created a JSP wizard to gather information from a user about one or more object(s). You now need to create the code to process that
information and perform a database operation(s) on the object(s).
Background
If your wizard uses one of the built-in button sets, when a user clicks the Finish, Apply, Save, or Check In button to submit the form, a javascript
function is called that invokes the processRequest() method of the ActionController. The WizardServlet loads the HTTP form data and other
wizard context information, such as where the wizard was launched, into a NmCommandBean. It then passes the
NmCommandBean to the FormDispatcher class. The FormDispatcher calls the F ormProcessorController.
The FormProcessorController partitions the form data into ObjectBeans. One ObjectBean is created for each target object of the wizard. It
contains all the form data specific to that object and any form data that is common to all objects. The FormProcessorController then passes the
ObjectBeans to classes called ObjectFormProcessors that perform the tasks appropriate to the wizard --- for example, creating a new object in
the database, updating an object in the database, or checking in an object ObjectFormProcessors, in turn, can call classes called
ObjectFormProcessorDelegates to perform one or more subtasks.
If your wizard is performing an operation on a single object you may need to create your own ObjectFormProcessor and/or
ObjectFormProcessorDelegates to perform the tasks specific to your wizard. However, if your wizard is creating or editing an object, you may be
able to take advantage of some processors that are delivered with the product for those purposes.
If your wizard has multiple target objects you may or may not also need to create your own FormProcessorController to control the order in
which objects are processed.
Scope/Applicability/Assumptions
Assumes that you have already created the necessary JSPs, data utilities, GUI components, and renderers to display your wizard. Also assumes
that you have created the necessary actions to hook up your wizard to the UI.
Intended Outcome
Solution
Use the JSP client architecture framework and common components to process wizard form data and perform the appropriate database tasks
for one or more object(s).
Prerequisite knowledge
Term Definition
target Object(s) for which you are gathering data in your wizard. Some operation(s) will typically be performed on these
object objects in your wizard processing.
Solution Elements
ActionController Java class This is the class to which wizard form data gets posted and which sends the
response page sent back to the browser after processing completes.
FormProcessorController Java Classes implementing this interface instantiate and call ObjectFormProcessor(s)
interface to execute wizard tasks.
Wizards with multiple target objects may need to extend this class to control the
order in which objects are processed.
DefaultObjectFormProcessor Java class A default implementation of ObjectFormProcessor that contains the logic to
execute ObjectFormProcessorDel egates and perform several other common
tasks. This is the base class that should be extended by wizard-specific
processors.
ObjectFormProcessrDelegate Java Classes implementing this interface are called by ObjectFormProcessors to
interface perform processing subtasks. Multiple ObjectFormProcessorDel egates may be
called by one processor and the same delegate may be used by multiple
processors to handle a task common to multiple wizards. These are optional.
Runtime location:<Windchill>/srclib/CommonComponents.jar
ProcessorBean Java class A container for ObjectBeans that knows which ObjectBeans should be processed
by the same processor instance and the order in which they should be processed.
FormResult Java class A class used to pass method results between server methods and from the server
to the WizardServet.
The relationship between the main Java classes in the wizard processing framework is shown in the UML diagram below.
ObjectFormProcessors should not open/commit additional transaction blocks in steps 3 or 4 as nesting of transactions is not
recommended.
Each wizard step must display one of these types of data:
• data in tabular format where each row represents a different object
• data that is specific to one and only one of the objects created
• data that is common to all objects created
A step cannot contain object-specific data for multiple objects unless it is in tabular format.
In multiple-object wizards, the object to which an input field applies is identified by an "objectHandle" embedded in the name attribute of the
HTML input field. For example:
<input id="null1188140328133"
name="<someFieldIdString>!~objectHandle~newRowObj_430512131997223~!
<someAdditionalText>" value="" size="60" maxlength="60"
type="text">
In the example above, "newRowObj_430512131997223" is the object handle, "!~objectHandle~" is the required prefix, and "~!" is the required suffix.
The HTML name attribute in which the object handle is embedded can be any string and the object handle may appear anywhere within the
string.
When the DefaultFormProcessorController loads the form data in to ObjectBeans, it will strip off the object handle (including the required prefix
and suffix) from the name attribute and use the resulting string as the key for the value in the form data parameter maps. For example, to
retrieve the form value for the input field above you would call ObjectBean.getTextParameter() with the following key:
<someFieldIdString><someAdditionalText>
The framework generates object handles on name attributes for you in one of two ways:
• If data for the objects is being captured in tabular format, where each row represents an object and each column an attribute of the
object, the handle will be dynamically generated for you if you set rowBasedObjectHandle=true on the table config in the builder::
table.setRowBasedObjectHandle(true);
The object handle for each row will be based on the row’s OID.
objectHandle = CreateAndEditWizBean.getNewObjectHandle(next.getOid().toString());
• Where all the data on a given wizard step is for the same object, you specify the object handle for that object on the wizard step action:
<jca:wizardStep action="setContextWizStep" type="object"
objectHandle="<your object handle string>" …
If data on the wizard step is common to all objects created, no object handle is needed on the input fields. The object handle associated to the
data in an ObjectBean can be accessed by the ObjectBean.getObjectHandle() method. The FormProcessorController controls the order in
which processors are called to process the ObjectBeans, as described in the sections below. Note that in the illustrations below, circles are
used to represent ObjectBeans. Circles representing objects of the same type will have the same shading.
Typically, when a wizard has multiple unrelated target objects, the objects are the same type:
An example of such a wizard is that in which you can create multiple parts. This wizard has three steps:
1. Define Part
User enters the type of parts to be created and other attributes common to all the parts being created.
2. Set Attributes
User enters the name and number of each part to be created in tabular format. This table is dynamic in that the user can enter any number
of parts.
3. Set Additional Attributes
User enters some additional attributes common to all of the parts.
If the user enters data for five parts, the DefaultObjectFormProcessorController will create five ObjectBeans. Each ObjectBean will contain the
data from step 2 specific to the part it represents and the data from steps 1 and 3. Because there is no relationship between the parts and they
can be created independently, none of the ObjectBeans will have parents or children. Since the same ObjectFormProcessor and
ObjectFormProcessorDelegates will be used to process all the ObjectBeans and all the objects are of the same
type, they will all be placed in the same ProcessorBean.
Other wizards may have multiple related target objects. For example, you might have a wizard that creates a change notice and the change
tasks related to that change notice. To create the associations between the change notice and the change tasks, the processor for the change
notice will need to know how the objects relate to each other.
The change notice ObjectBean has three child ObjectBeans. The change task ObjectBeans have a parent ObjectBean and no children.
In this case, you would need to write your own FormProcessorController to create the structure of ObjectBeans. This can be a subclass of the
DefaultFormProcessorController.
The default controller will create the ObjectBeans for you. You would override its createObjectBeanStructure() method, which is given a flat list
of all the ObjectBeans. In that method you would set the parents and children of the ObjectBeans. You pass back a list of all the root
ObjectBeans. After you have created the ObjectBean structure, the DefaultFormProcessorController will call the ProcessorBean.newCollection()
method which will group the ObjectBeans into ProcessorBeans as follows:
In the diagram above the circles represent the ObjectBeans and the solid lines the relationships between them. The rectangles represent the
two ProcessorBeans and the dotted line the relationship between them.
Each ProcessorBean will have its own instances of the ObjectFormProcessor and the ObjectFormProcessorDelegates needed for the objects in
it. If the processor for the root ProcessorBean is called "ProcessorInstance1" and the processor for the child ProcessorBean is called
"ProcessorInstance2", the processor methods would be called as follows:
2 ProcessorInstance2.preProcess(ObjectBeans in Processor create three instances of WTChangeActivity2 and store them in the
Bean 2) "object" attributes of the beans
8 ProcessorInstance2.postTransactionProcess(ObjectBeans none
in Processor Bean 2)
The tasks could be arranged differently. For example, you could create the associations in method 6 instead of method 5 with the same effect.
Or, you could create an ObjectFormProcessorDelegate to create the associations in its postProcess() method. The framework offers the
flexibility to modularize your code as best fits your wizard. Just be sure to arrange your tasks correctly relative to the start and end of the main
transaction.
Your structure of ObjectBeans could be more complex. For example:
As you can see from the diagram above, ObjectBeans will be placed in different ProcessorBeans if any of the following is true:
• the object in the ObjectBeans are different types
• the ObjectBeans have a different ObjectFormProcessor
(Note: at this time all the ObjectBeans in the wizard must have the same ObjectFormProcessor.)
• the ObjectBeans have a different list of ObjectFormProcessorDelegates
• the ObjectBeans have a different parent ObjectBean
The DefaultFormProcessorController will call the processors associated with each ProcessorBean starting at the root ProcessorBean and
proceeding down the tree to the leaf ProcessorBeans.
Three processors for handling object creation and editing wizards are delivered with the product:
• com.ptc.core.components.forms.CreateObjectFormProcessor
• com.ptc.core.components.forms.DefaultEditFormProcessor
• com.ptc.core.components.forms.EditWorkableFormProcessor
These processors may be used as is or extended for your own purposes.
If your wizard is not an object creation or editing wizard you will need to create your own ObjectFormProcessor. ObjectFormProcessors should
extend the DefaultObjectFormProcessor class.
You should place your form processing logic into the preProcess(), doOperation(), postProcess(), and postTransactionProcess() methods of the
processor, as appropriate. Your methods should call the corresponding super method of the DefaultObjectFormProcessor, which will handle
the calling of ObjectFormProcessorDelegates. These methods will be passed a single ObjectBean, which will contain all the form data from the
wizard. The form data can be accessed using the getter methods of that object. The following getter methods are commonly used:
public Map<String,List<String>> getChangedComboBox()
public Map<String,String> getChangedRadio()
public Map<String,String> getChangedText()
public Map<String,String> getChangedTextArea()
public Map<String,List<String>> getChecked()
public Map<String,List<String>> getUnChecked()
public List getRemovedItemsByName(String paramName)
public List getAddedItemsByName(String paramName)
public String getTextParameter(String key)
public String[] getTextParameterValues String key)
See the javadoc for more information.
The "object" attribute of the ObjectBean represents an instance of the target object . Where appropriate, it should be set by one of your
processor methods (most likely preProcess()) for use by downstream methods. Other information can be passed from one method to another
using processor instance variables.
The NmCommandBean object passed to these methods contains information about the page from which the wizard was launched and the
object selected on the parent page when the wizard was launched. It also contains all the HTML form data but you should use the methods on
the ObjectBean rather than the NmCommandBean to access that data. See the javadoc for NmCommandBean for more information.
You pass the outcome of the preProcess(), doOperation(), postProcess(), and p
ostTransactionProcess() methods back to the
DefaultFormProcessorController using the com.ptc.core.component.FormResult object. Before returning, each of these methods should call
FormResult.setStatus() to return the processing status. Three options are available:
• FormProcessingStatus.SUCCESS - if the method executed without error
• FormProcessingStatus.FAILURE - if the method encountered a fatal errors FormProcessingStatus.NON_FATAL_ERROR - if the method
succeeded but encountered one or more problems that should be reported to the user.
The DefaultFormProcessorController passes the returned FormResult to its continueExecuting() method to determine whether processing
should continue to the next phase or be aborted. By default, it will abort only if the status is FormProcessingStatus.FAILURE. If processing is to
be aborted or after all processing competes successfully, the controller will call the setResultNextAction() method of the ObjectFormProcessor
to set information in the FormResult needed to construct the response page sent back to the browser.
This method should convey the following information:
• The feedback messages should be displayed to the user, if any.
Determined from the feedbackMessages and exceptions variables.
Feedback messages, if any, are displayed before executing any window operations or provided javascript.
Exception messages are only displayed if status is FormProcessingStatus.FAILURE or FormProcessingStatus.NON_FATAL_ERROR.
See the javadoc for the FormResult and FormProcessingStatus classes for more information.
It is also possible for your ObjectFormProcessor's preProcess(), doOperation(), postProcess(), and postTransactionProcess() methods to throw
exceptions. In that case, control will be returned to the ActionController which will set the variables of the FormResult as follows:
• status - FormProcessingStatus.FAILURE
• exceptions - the thrown Exception
This will cause the response page to display the exception message in an alert window and then close the wizard
window.
There are four different form processing scenarios and to get the selected oid in each scenario there is different API on NmCommandBean that
can be used. See the picture below for more details.
Specify the processor class for the wizard on the wizard action
You specify the ObjectFormProcessor class in the <command> subtag of the <action> tag for the wizard. Your action tag will be contained in a
*actions.xml file.
Here is an example of how you would specify the CreateDocFormProcessor class as your processor.
<action name="create">
<command
class="com.ptc.core.components.forms.CreateObjectFormProcessor"
windowType="popup" />
</action>
Create any necessary ObjectFormProcessorDelegate classes for your wizard
ObjectFormProcessorDelegates may be used to carry out one or more subtasks in your wizard processing. Because the same delegate class
may be called by multiple ObjectFormProcessors, they are typically used for tasks that are needed by multiple wizards. Here are some examples
of how ObjectFormProcessorDelegates are used in the delivered product:
• Several object creation wizards have a checkbox named "Keep checked out after checkin." The ObjectFormProcessors for all these wizards
call the same ObjectFormProcessorDelegate class to handle this checkbox and checkout the object after it is created if the box is
checked.
• Wizards to create objects that are ContentHolders typically have a Set Attachments step to specific the documents that should be
attached to the object. All these wizards call the same ObjectFormProcessorDelegate class to handle the persistence of the attachments.
• Many object creation wizards have an input field for a Location attribute to specify the object's folder. Because the processing of this input
field is complex all these wizards call the same ObjectFormProcessorDelegate to set the folder on the object being created.
As shown in the examples above, a ObjectFormProcessorDelegate can handle the processing of one or many input fields. If your wizard does
not have any HTML elements that are used in multiple wizards you may not need any delegates. However, they can be useful for modularizing
your processing code also.
ObjectFormProcessorDelegates should extend the class DefaultObjectFormProcessorDelegate. ObjectFormProcessorDelegates are
instantiated by the DefaultFormProcessorController and passed to the ObjectFormProcessor.
ObjectFormProcessorDelegates have preProcess(), doOperation(), postProcess(), and postTransactionProcess() methods just like
ObjectFormProcessors. The delegates registered for the wizard will be called by the DefaultObjectFormProcessor during the different
processing phases.
The outcome of an ObjectFormProcessorDelegate method is passed back to the ObjectFormProcessor using a FormResult, just as the
ObjectFormProcessor methods pass back their results to the FormProcessorController.
Like ObjectFormProcessor methods, ObjectFormProcessorDelegate methods can throw exceptions. How these are handled depends on the
ObjectFormProcessor calling it.
The names of the ObjectFormProcessorDelegate classes to be instantiated by the DefaultObjectFormProcessor, if any, are communicated in
hidden input fields as follows:
<input name="FormProcessorDelegate"
value="com.ptc.core.components.forms.NumberPropertyProcessor"
type="hidden">
These hidden input fields can be created in several ways:
• If you have a delegate associated with a specific wizard step, the delegate can be specified in the command
subtag of in the wizard step action. For example:
<action name="attachmentsWizStep" postloadJS="preAttachmentsStep"
preloadWizardPage="false"
<command
class="com.ptc.windchill.enterprise.attachments.forms.Second
aryAttachmentsSubFormProcessor" windowType="wizard_step"/>
</action>
The wizard framework will then generate a hidden FormProcessorDelegate field in the wizard for any wizard using this step. If you have
specified an object handle on the wizard step action, the name attribute of the hidden field will include the object handle so that the
delegate will be called only for the target object associated with the step.
• If you have a specific input field that requires a delegate you can generate the hidden field in the data utility that creates the GUI
component for the input field. It is recommended that the data utility return a subclass of AbstractGuiComponent to take advantage of
the addHiddenField() method and the AbstractRenderer. After creating the GUI component in the data utility, call the method
addHiddenField() in the AbstractGuiComponent class. For example:
LocationInputGuiComponent guiComponent = new
LocationInputComponent(…);
guiComponent.addHiddenField
(CreateAndEditWizBean.FORM_PROCESSOR_DELEGATE, "com.
ptc.windchill.enterprise.folder.LocationPropertyProcess
or");
The hidden input field will be generated for you by the AbstractRenderer automatically. If the field is associated with a step or table row that
has an object handle, that object handle will be embedded in the HTML name attribute of the hidden field. If you choose to return a GUI
component that does not extend AbstractGuiComponent, your GUI component and renderer would have to know how to render the
necessary hidden field.
• You can include a hidden field for your delegate directly in your jsp file. However, one of the first two methods is preferred because it
encapsulates the hidden field with its associated HTML input fields.
The same as for a wizard with a single target object. See Create your processor class.
Specify the processor class for the wizard on the wizard action
The same as for a wizard with a single target object. See Specify the processor class for the wizard on the wizard action.
The same as for a wizard with a single target object. See Create any necessary ObjectFormProcessorDelegate classes for your wizard.
The same as for a wizard with a single target object. See Specifying the ObjectFormProcessorDelegate(s) to be used in your wizard. Remember
that an object handle must be embedded in the name attributes of the hidden fields specifying the delegates if you want the delegate to be
used only for a given object.
If the target objects of your wizard form a structure of related objects, you will need to create your own FormProcessorController to create a
structure of the ObjectBeans. You should subclass the DefaultFormProcessorController to leverage its ability to partition the form data into
ObjectBeans and ProcessorBeans and call the form processors. Typically, the only method you will need to override is the
createObjectBeanStructure() method.
If you have created your own FormProcessorController, you should specify the controller to be used for your wizard on the wizard tag in your
main JSP file for the wizard. For example:
<jca:wizard helpSelectorKey="change_createProblemReport"
buttonList="DefaultWizardButtonsWithSubmitPrompt"
formProcessorController="com.ptc.windchill.enterprise.change2.forms.
controllers.ChangeItemFormProcessorController">
Starting with the Windchill 10.1 MR010 release, the form processing and the action response handling have been separated. This means that the
form processor will still perform the work to update/add/delete the selected/affected objects, but there is no need to specify the next action on
the FormResult. The FormResult will contain the OIDs of the objects that were updated/added/deleted. These OIDs will be passed back to the
client and the client will update the components that are currently displaying those objects.
Starting with the Windchill 10.1 MR010 release there is no need to set the next action (i.e. setNextAction(FormResultAction.REFRESH_OPENER)).
Only the OIDs the objects added/updated/removed should be added to the FormResult object.
Example: formResult.addDynamicRefreshInfo new DynamicRefreshInfo(newOid, oldOid, NmCommandBean.DYNAMIC_UPD))
The ability to set a URL or Javascript function on the FormResult is still valid. However, this should only be done when no other options are
available. Because multiple clients are potentially invoking these actions the URL javascript returned may not be appropriate or may not
reference valid code. Ideally, you want the components to update themselves and not have individual actions update the components on the
page. If a URL is set on the FormResult, the afteraction listener will not be called on the component where the action was launched from.
However, the objectsaffectedlistener will be called.
There are two listeners that can be attached to components for objects updated via actions. Most of the components (i.e. table, tree, info page,
etc.) have a default listener for both of the events.
• afteraction : This listener is responsible for updating the objects in the component where the action was launched/executed from. For
example, if the checkout action was executed from the information page, the information page afteraction listener is responsible for
refreshing itself. Likewise, if the checkout action was launched from a table, the table afteraction listener will be responsible for updating
the row in the table. This listener can be attached to any Ext component by adding the afteraction event listener. The listener will receive
the FormResult JS object as a parameter.component.on(‘afteraction’, someAfterActionListener);
• objectsaffected : This listener is responsible for updating the objects in a component when the action is executed from a different
component. For example, if the checkout action was performed from the information page and the search results table contained that
same object, the objectsaffected listener for the [search results] table will be responsible for updating the row in the table. This listener is
attached to the global PTC.action object: component.mon(PTC.action, ‘objectsaffected’,someListener);
me.on('afteraction', me.onAfterAction);
me.onAfterAction = function(formResult) {
me.refresh();
return true;//Stop further processing
};
Different components have different behavior and different client platforms have different URL patterns so it is not supported to generically try
and specify a URL on the FormResult. Each component can decide how to do it individually and the afterAction listeners on common
components such as the infopage and miniNavigator already have generic logic to refresh themselves. The miniNavigator for example does not
want to forward to the new URL because that would break the user out of the current context and so it refreshes itself instead.
See the checkout example below for how to have a component specific event listener do something unique. See the
PTC.miniNavigator.onAfterAction and the PTC.infoPage.onAfterAction code in the js files. If extra URL parameters are needed to be passed down
to construct the url on the client side then the FormResult.extraData map can be used to pass down extra information.
You can change how an object is handled for a specific table. For example add a row to the checkouts table instead of adding a checkout glyph
to an existing row (from wip.jsfrag):
PTC.wip = {};
/**
* add/remove rows from the checkouts table for the wip actions
*/
PTC.wip.checkoutsTableObjectsAffectedWrapper = function(original, formResult) {
if (formResult.actionName === 'checkout') {
var added_new_rows = formResult.getUpdatedOids();
if(added_new_rows.length > 0) {
clearActionFormData();
rowHandler.addRows(added_new_rows, this.id, null, {
doAjaxUpdate : true,
addSorted : true
});
}
return true;
}
return original.call(this, formResult);
};
PTC.wip.addCheckoutTableListeners = function (table) {
table.onObjectsAffected = table.onObjectsAffected.wrap
(PTC.wip.checkoutsTableObjectsAffectedWrapper);
};
Ext.ComponentMgr.onAvailable('checkedout.work.table',
PTC.wip.addCheckoutTableListeners);
In cases where extra javascript was needed to run to update specific components in the page as the result of an action, it is better to add
component specific objectsaffected listeners instead that react to the specific action, such as the checkout action example below. Some
wizards may want to add a listener specific to the wizard because the javascript is often times not needed in other pages.
Actions that need to perform custom or specific code as the result of a wizard can use a callback function for handling a successful submission.
This successFunc configuration parameter is passed to the PTC.wizard.submitWizard function. This successFunc is then called when the wizard
receives the FormResult from the server and begins to fire the afteraction and objectsaffected events. This allows an action owner to control the
flow and behavior of the wizard more specifically than needing to modify how individual components handle the event name. For Example, the
checkin Button on the edit wizard has a success handler to launch the checkin wizard once the attributes are successfully saved to the
database.
In the sample code below, notice that the function passed to the wizard code:
<command onClick="onEditSubmit('checkinButton')"/>
function onEditSubmit(actionName){
var params = {successFunc: PTC.wizard.launchCheckinWizard,
finished: actionName=='saveButton'};
PTC.wizard.submitWizard(params);
}
For example, you can add a row to the checkouts table instead of adding a checkout glyph to an existing row (from wip.jsfrag):
PTC.wip = {};
/**
* add/remove rows from the checkouts table for the wip actions
*/
PTC.wip.checkoutsTableObjectsAffectedWrapper =
function(original, formResult) {
if (formResult.actionName === 'checkout') {
var added_new_rows = formResult.getUpdatedOids();
if(added_new_rows.length > 0) {
clearActionFormData();
rowHandler.addRows(added_new_rows, this.id, null, {
doAjaxUpdate : true,
addSorted : true
});
}
return true;
}
return original.call(this, formResult);
};
PTC.wip.addCheckoutTableListeners = function (table) {
table.onObjectsAffected = table.onObjectsAffected.wrap
(PTC.wip.checkoutsTableObjectsAffectedWrapper);
};
PTC.onAvailable('checkedout.work.table',PTC.wip.addCheckoutTableListeners);
Different components have different behavior and different client platforms have different URL patterns so it’s not supported to generically try
and specify a URL on the FormResult. Each component can decide how to do it individually and the afterAction listeners on common
components such as the infopage and miniNavigator already have generic logic to refresh themselves. The miniNavigator for example does not
want to forward to the new URL because that would break the user out of the current context and so it refreshes itself instead.
See the checkout example below for how to have a component specific event listener do something unique. Also see the
PTC.miniNavigator.onAfterAction and the PTC.infoPage.onAfterAction code in the js files. If extra URL parameters are needed to be passed down
to construct the url on the client side then the FormResult.extraData map can be used to pass down extra information. In most cases it is not
necessary to auto-forward to the info page of an object just created or modified. After create actions, an inline message appears and contains
a link to allow someone to go to the new objects info page. This is how one might auto-forward after an action. Modify the component’s
onAfterAction method to include this code sequence:
if (formResult.actionName === 'checkout') {
PTC.infoPage.goTo(formResult.getUpdatedOids()[0]);
}
return true;
You can add a generic action handler to all possible components that will auto-forward for a specific action. The following code example details
this. However, this is not necessarily recommended because some pages and components might lose in-progress data.
PTC.action.on('objectsaffected', function (formResult) {
if (formResult.actionName === 'checkout') {
PTC.infoPage.goTo(formResult.getUpdatedOids()[0]);
}
return true;
});
Limitations
Additional Resources
• com.ptc.core.components.forms.FormDispatcher
• com.ptc.core.components.forms.FormProcessorController
• com.ptc.core.components.forms.DefaultFormProcessorController
• com.ptc.core.components.forms.ObjectFormProcessor
• com.ptc.core.components.forms.DefaultObjectFormProcessor
• com.ptc.core.components.forms.CreateObjectFormProcessor
• com.ptc.core.components.forms.DefaultEditFormProcessor
• com.ptc.core.components.forms.EditWorkableFormProcessor
• com.ptc.core.components.forms.ObjectFormProcessorDelegate
• com.ptc.core.components.forms.DefaultObjectFormProcessorDelegate
• com.ptc.core.components.forms.FormResult
• com.ptc.core.components.forms.DynamicRefreshInfo
• com.ptc.core.components.forms.FormProcessingStatus
• com.ptc.core.components.util.FeedbackMessage
• com.ptc.core.ui.resources.FeedbackType
Example Formwizard
Custom-action.xml
<objecttype name="customdoc" resourceBundle="null">
<action name="createdoc" type="customdoc" >
<command url="/ext/jsp/customwizard/createdoc.jsp" class="ext.cust.myprocessor.SelectedObjectFormProcessor" windowType="popup" />
<label>Create Document</label>
</action>
<action name="selectObject" type="customdoc" >
<command url="/ext/jsp/customwizard/selectObject.jsp" windowType="wizard_step" />
<label>Select Object</label>
</action>
<action name="setAttributes" type="customdoc" >
<command url="/ext/jsp/customwizard/setAttributes.jsp" windowType="wizard_step" />
<label>Set Attributes</label>
</action>
</objecttype>
Custom-actionModel.xml
<action name="createdoc" type="customdoc" />
Createdoc.jsp
<%@ taglib prefix="jca" uri="http://www.ptc.com/windchill/taglib/components"%>
<%@ taglib uri="http://www.ptc.com/windchill/taglib/fmt" prefix="fmt"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
<%@ include file="/netmarkets/jsp/components/beginWizard.jspf" %>
<%@ include file="/netmarkets/jsp/components/includeWizBean.jspf" %>
<jca:wizard title="Create object">
<jca:wizardStep action="selectObject" type="customdoc"/>
<jca:wizardStep action="setAttributes" type="customdoc"/>
</jca:wizard>
<%@ include file="/netmarkets/jsp/util/end.jspf"%>
selectObject.jsp
<html>
<body>
Select Object Type: <select name="Type">
<option>Select a Type</option>
<option>part</option>
<option>document</option>
</select>
</body>
</html>
setAttributes.jsp
<%@ include file="/netmarkets/jsp/components/beginWizard.jspf" %>
<%@ taglib uri="http://www.ptc.com/windchill/taglib/wrappers" prefix="w" %>
<table>
<tr>
<td scope="row" width="200” align="right">
*Number:
</td>
<td align="left">
<input type="textbox" name="null___number___textbox" id="number" />
</td>
</tr>
<tr>
<td scope="row" width="200” align="right">
*Name:
</td>
<td align="left">
<w:textBox name="name" id="name" maxlength="30" size="10"/>
</td>
<! -- Both input type textbox and w:textBox will create a text box for user input.
One is using normal HTML tag another is from Windchill Tag Library that’s the
only difference -->
</tr>
<tr>
<td scope="row" width="200” align="right">
Location:
</td>
<td align="left">
<input type="text" name="loc_displayLocation" id="loc_displayLocation">
<button onclick="ITC.launchFolderPicker(event)" type="button">
Browse
</button>
</td>
</tr>
</table>
<input type="hidden" name="null___loc_folder___textbox" id="loc_folder" />
<input type="hidden" name="null___loc_container___textbox" id="loc_container" />
<script type="text/javascript">
<!--//namespace for paste example to avoid colliding with any globally defined functions -->
ITC = {};
ITC.launchFolderPicker = function(event) {
var url = getBaseHref() +
'servlet/WindchillAuthGW/wt.enterprise.URLProcessor/invokeAction?action=cadxBrowseLocations&containerVisibilityMask=PDMLink&accessPer
mission=modify&displayHotlinks=false&displayCreateFolder=true';
launchBrowseFolders(url, $('loc_displayLocation'), ITC.locationCallback);
Event.stop(event);
return false;
}
ITC.locationCallback = function(contextValues) {
var containerOID = contextValues.containerOID; // 'OR:wt.pdmlink.PDMLINKProduct:123'
var folderOID = contextValues.folderOID; // 'OR:wt.folder.SubFolder:123'
$('loc_folder').value = folderOID;
$('loc_container').value = containerOID;
}
</script>
<%@include file="/netmarkets/jsp/util/end.jspf"%>
SelectedObjectFormProcessor.java
package ext.cust.myprocessor;
import java.util.HashMap;
import java.util.List;
import javax.servlet.http.HttpServletRequest;
import com.ptc.core.components.beans.ObjectBean;
import com.ptc.core.components.forms.DefaultObjectFormProcessor;
import com.ptc.core.components.forms.FormResult;
import com.ptc.netmarkets.util.beans.NmCommandBean;
import wt.doc.WTDocument;
import wt.fc.ObjectReference;
import wt.fc.Persistable;
import wt.fc.PersistenceHelper;
import wt.fc.PersistenceServerHelper;
import wt.fc.ReferenceFactory;
import wt.folder.Folder;
import wt.folder.FolderEntry;
import wt.folder.FolderHelper;
import wt.inf.container.WTContainer;
import wt.part.WTPart;
import wt.part.WTPart.*;
import wt.util.WTException;
import wt.util.WTPropertyVetoException;
public class SelectedObjectFormProcessor extends DefaultObjectFormProcessor {
@SuppressWarnings("rawtypes")
@Override
public FormResult doOperation(NmCommandBean commandbean ,List<ObjectBean> list)throws WTException
{
HttpServletRequest request = commandbean.getRequest();
String type = request.getParameter("Type");
HashMap map = commandbean.getText();
Object[] keys = map.keySet().toArray();
String name = null;
String number = null;
String containerOid = null;
String folderOid = null;
String desCripTion = null;
String viewName = null;
if (type.toLowerCase().contains("wtpart")) {
System.out.println("Getting Type:"+type);
name = ((String) map.get("name")).toUpperCase();
number = ((String) map.get("number")).toUpperCase();
folderOid = ((String) map.get("loc_folder"));
containerOid = ((String) map.get("loc_container"));
viewName = ((String) map.get("view"));
} else {
name = ((String) map.get("name")).toUpperCase();
number = ((String) map.get("number")).toUpperCase();
folderOid = ((String) map.get("loc_folder"));
containerOid = ((String) map.get("loc_container"));
}
HashMap mapArea = commandbean.getTextArea();
//desCripTion = ((String) mapArea.get("description"));
Persistable per = null;
if (type.toLowerCase().contains("part")) {
System.out.println("Getting Name:"+name);
System.out.println("Getting Number:"+number);
System.out.println("Getting folder:"+folderOid);
//System.out.println("Getting description:"+desCripTion);
per = createPart(name, number, containerOid, folderOid, viewName);
} else if (type.toLowerCase().contains("document")) {
per = createDocument(name, number, desCripTion, containerOid,
folderOid);
try {
((WTDocument)per).setDescription("UPDATED FROM METHOD");
System.out.println("after persisting the value");
PersistenceServerHelper.manager.update(per);
PersistenceHelper.manager.refresh(per);
} catch (WTPropertyVetoException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
ObjectBean ob = ObjectBean.newInstance();
ob.setObject(per);
list.add(ob);
/* Adding the Persistable object in the List */
return super.doOperation(commandbean, list);
}
private Persistable createPart(String name, String number,String containerOid, String folderOid, String viewName) throws WTException
{
WTPart part = WTPart.newWTPart();
try {
part.setName(name);
part.setNumber(number);
part.setContainer((WTContainer)fromReferenceToObject(containerOid));
part=(WTPart)PersistenceHelper.manager.save(part);
} catch (wt.util.WTPropertyVetoException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}catch(WTException e)
{
e.printStackTrace();
}
return part;
}
private Persistable fromReferenceToObject(String objectReference)
{
ObjectReference objRef = null;
try {
final ReferenceFactory factory=new ReferenceFactory();
objRef = (ObjectReference)factory.getReference(objectReference);
} catch (WTException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return (objRef.getObject());
}
private Persistable createDocument(String name, String number,String desCripTion, String containerOid, String folderOid) throws
WTException
{
WTDocument doc = WTDocument.newWTDocument();
try {
doc.setNumber(number);
doc.setName(name);
if(desCripTion!=null)
{
doc.setDescription(desCripTion);
}
doc.setContainer((WTContainer) fromReferenceToObject(containerOid));
assignFolder(doc,folderOid);
doc=(WTDocument)PersistenceHelper.manager.save(doc);
} catch (WTPropertyVetoException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return doc;
}
private void assignFolder(FolderEntry folderEntry,String folderoid)
{
final Folder folder=((Folder)fromReferenceToObject(folderoid));
try {
FolderHelper.assignLocation(folderEntry, folder);
} catch (WTException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
Load From Files Utility
General,user,name,title,number,type,description,department,saveIn,teamTemplate,domain,lifecycletemplate,typedef,primarycontenttype,path,fo
rmat,contdesc,version,iteration
o Example:
o Notes: title does not show up in the UI; department does not show up in the UI; you can leave typedef blank if not a soft type
o Note: for a Library, use wt.inf.library.WTLibrary=”<library>”. Please check the file csvmapfile.txt for other object type bulk upload
Resolut {GEN:wt.enterprise.SequenceGenerator:WTPARTID_seq:10:0} is basically a code snippet in rule.xml which is used to describe the
ion sequence to be used for new object creation for a particular object type.
● wt.enterprise.SequenceGenerator is a class that maps the name of the sequence to the Sequence pool present in
Oracle database using the NAME_TO_SEQUENCE_MAP.
● WTPARTID_seq is the name of the sequence specified in Windchill database which generates numbers in a particular
sequence/pattern.
● 10 is the length of the number to be generated.
● 0 is the padding character which will be padded in the beginning of the number if the length of the number is less than
10.
Turn off auto-numbering of object types in Windchill using OIR
To turn off auto-numbering of object types in Windchill, edit the OOTB rule.xml file.
Resolution If GatherAttributeConstraints is not present in rule.xml for number attribute, then add the below code snippet.
<AttributeValues objType="ext.part.CustomPart">
<AttrConstraint i d="number" a
lgorithm="com.ptc.core.rule.server.impl.GatherAttributeConstraints">
</AttrConstraint>
</AttributeValues>
But, if GatherAttributeConstraints is already present in the rule.xml file with some additional constraints, then comment
out the other constraints within GatherAttributeConstraints as shown in the code snippet below:
<AttrConstraint id="number" algorithm="com.ptc.core.rule.server.impl.GatherAttributeConstraints">
<!--
<Value algorithm="com.ptc.core.rule.server.impl.GetServerAssignedConstraint"/>
<Value algorithm="com.ptc.core.rule.server.impl.GetImmutableConstraint"/>
-->
<!-- Other algorithms -->
</AttrConstraint>
After doing this, upload the rule.xml file for that object type. Now while creating a new object, the user will be asked to enter
the number manually.
How to create and use custom RuleAlgorithm in Windchil
Proble ● How to set the number of object type to a number coming from an external number generating system.
m ● How to create and implement a custom RuleAlgorithm.
● How to create a custom NumberGenerator.
Resolution At first, create a class that either implements RuleAlgorithm or extends NumberGenerator class.
Override the calculate() method and write the logic for generating random number as shown below:
package ext.custom.rules;
import java.util.Random;
import wt.inf.container.WTContainerRef;
import wt.rule.algorithm.RuleAlgorithm;
import wt.util.WTException;
public class CustomNumberGenerator implements RuleAlgorithm {
@Override
public Object calculate(Object[] arg0, WTContainerRef arg1) throws WTException {
Random random = new Random();
int randomValue = random.nextInt(99999);
return randomValue;
}
}
Register the class within OIR for generation of customized random numbers as shown below:
<AttributeValues objType="ext.part.CustomPart">
<VarDef id="randomNumber" a lgorithm="ext.custom.rules.CustomNumberGenerator">
</VarDef>
<AttrValue id="number" algorithm="com.ptc.windchill.enterprise.revisionControlled.server.impl.NumberGenerator">
<VarRef id="randomNumber" / >
</AttrValue>
</AttributeValues>
Change the numbering pattern of an object based on another attribut
Proble Based on an attribute value, change the numbering pattern of an object type.
m
[How to use Switch-Case in OIR] [How to put conditions in OIR]
Resolution If we want to customize the naming pattern of an object based on business conditions, we can do it by making use of
CaseBranch, BooleanBranch or EqualsTest.
CaseBranch algorithm in OIR, provides the functionality similar to Switch-Case used in Java. Similarly, EqualsTest is used
to compare two values for equality.
For eg.
<AttrValue id="number" algorithm="com.ptc.windchill.enterprise.revisionControlled.server.impl.NumberGenerator">
<!-- CaseBranch starts here -->
<Value algorithm="wt.rule.algorithm.CaseBranch">
<!-- If name entered by user equals to One, then number is assigned the value of Source attribute -->
<Value algorithm="wt.rule.algorithm.EqualsTest">
<Attr id="name" />
<Arg>One</Arg>
</Value>
<Attr id="source" / >
< !-- If name entered by user equals to Two, then number is assigned the value of Container Name -->
<Value algorithm="wt.rule.algorithm.EqualsTest">
<Attr id="name" />
<Arg>Two</Arg>
</Value>
<Attr id="containerName" / >
< !-- If none of the above cases are true then the default value is assigned to the number attribute -->
<Arg>{GEN:wt.enterprise.SequenceGenerator:WTPARTID_seq:10:0}</Arg>
</Value>
</AttrValue>
Prefix or Postfix an argument to auto-generated number in Windchill
Proble How to prefix or postfix an argument to the auto-generated number in Windchill?
m
Resolution To prefix or postfix an argument to the number of an object type, we can use <Arg> tag. Below is an example:
<AttrValue id="number" algorithm="com.ptc.windchill.enterprise.revisionControlled.server.impl.NumberGenerator">
<Arg>PREFIX-</Arg>
<Arg>{GEN:wt.enterprise.SequenceGenerator:WTPARTID_seq:4:0}</Arg>
<Arg>-POSTFIX</Arg>
</AttrValue>
PREFIX-0123-POSTFIX
Assign an attribute value to the number of an object using OIR in Windchill
Proble How to assign an attribute value to the number of an object using Object Initialization Rule (OIR) in Windchill?
m
Resolution To set the number of an object to any attribute value, we can simply use <Attr> tag along with the internal name of the attribute to which
you want to map the number.
For eg.
We can set the number of an object same as its name which is entered by the user.
Just as the name attribute, we can use value of other attributes simply by putting the internal name of the attribute in the <Attr> tag.
Creating a new number sequence in Oracle for OIR in Windchill
Proble How to create a new sequence in Oracle that can be used to generate number of an object type and how to hook it up in
m OIR.
Resolution Below are the steps to create new sequence:
<Windchill_Home>/db/execute_sql_script.bat create_newseq_sequence.sql
This will create a new sequence with name PartSequence_seq in the Oracle database. After the sequence is created, we can use the
generated sequence in our Object Initialization Rule (OIR).
Edit the OOTB rule.xml file and use the newly created sequence.
<AttrValue id="number" algorithm="com.ptc.windchill.enterprise.revisionControlled.server.impl.NumberGenerator">
<Arg>{GEN:wt.enterprise.SequenceGenerator:PartSequence_seq:10:0}</Arg>
</AttrValue>
Explain {GEN:wt.enterprise.SequenceGenerator:WTPARTID_seq:10:0}
Resolut {GEN:wt.enterprise.SequenceGenerator:WTPARTID_seq:10:0} is basically a code snippet in rule.xml which is used
ion to describe the sequence to be used for new object creation for a particular object type.
● wt.enterprise.SequenceGenerator is a class that maps the name of the sequence to the Sequence pool present in
Oracle database using the NAME_TO_SEQUENCE_MAP.
● WTPARTID_seq is the name of the sequence specified in Windchill database which generates numbers in a particular
sequence/pattern.
● 10 is the length of number to be generated.
● 0 is the padding character which will be padded in the beginning of the number if the length of the number is less
than 10.
Resolution Consider a new WTPart is created, below is the VR and OR for difference revisions and iterations created:
From the table, it is clear that Version Reference(VR) refers to a particular revision of a part i.e. VR will be same for all the
iterations of a particular revision
The VR number is repeated and shared on a per iteration basis, meaning that all iterations to that mastered/revision
controlled object will be the same. VR also redirects you to the latest iteration of the latest revision
On the other hand, Object Reference(OR) refers to the actual object i.e. every iteration will have a different OR
}
Difference between Version Reference(VR) and Object Reference(OR)
Resolution Consider a new WTPart is created, below is the VR and OR for difference revisions and iterations created:
From the table, it is clear that Version Reference(VR) refers to a particular revision of a part i.e. VR will be the same for all the iterations of a particular
revision.
The VR number is repeated and shared on a per iteration basis, meaning that all iterations to that mastered/revision controlled object will be the same.
VR also redirects you to the latest iteration of the latest revision.
On the other hand, Object Reference(OR) refers to the actual object i.e. every iteration will have a different OR.
}
Type of attributes in Windchill
Resolution There are 5 types of attributes used in Windchill:
● Modeled attributes
● Local/Standard attributes
● Global attributes
● Alias attributes
● Calculated attributes
Modeled attributes:
● These are programmed in Java and cannot be created using Type and Attribute Manager tool.
● You can create your own modeled attributes by using @GeneratedProperty annotation within the modeled
type(hard type).
● PTC doesn’t recommend the usage of modeled attributes because of the development time and effort it takes and it
also makes the Windchill upgrade more complex. It also requires the purchase of Windchill Information Modeler, an
optional Windchill component.
● It is basically used for statistical analysis or validating itself against third-party system.
Local/Standard attributes:
Global attributes:
● Global attributes are configured without Java code. These are stored in a table separate from its parent type.
● For eg. We can create a global attribute “height” and can use it for various object types.
● Global attributes have definition and value tables to store the data in database.
Defnitions alues
V
BooleanDefinition BooleanValue
FloatDefinition FloatValue
IntegerDefinition IntegerValue
StringDefinition StringValue
RatioDefinition RatioValue
ReferenceDefinition ReferenceValue
TimestampDefinition TimestampValue
UnitDefinition UnitValue
URLDefinition URLValue
● Global attributes can be multi-valued as it can be used by different object types.
● They are comparatively slower than local attributes.
● Calculated and Alias attributes are not stored in Windchill database directly. Instead, they are derived from other
attributes.
● Calculated attributes create new attributes from existing ones, either by concatenating String together or by using
mathematical formula.
For eg. If a part have “height” and “width” attributes for measurement, then we can create a calculated attribute to
compute its area having
Internal Name = area
Display Name = Area
Formula = height * width
● Alias attributes navigate links to other objects.For eg. An alias attribute for a part would be the name of the
document describing the part.
Add a custom role in Windchill
Problem How to add a custom role in Windchill.
Method 1:
Method 2:
Add a custom state in Windchill
Method 1:
Method 2:
Resolution Below are the link objects in Windchill.
How to create Data Utility in Windchi
Details Data Utility Example
1. Create a class that extends any OOTB data utility class that falls in your context. Also override the
Resolut
method getDataValue according to your requirement.
ion
public class TextColorDataUtility extends TypeDataUtility {
@Override
public Object getDataValue(String comp_id, Object datum, ModelContext mc) throws WTException {
WTPart part = null;
TextDisplayComponent tdp = null;
if (datum instanceof WTPart) {
part = (WTPart) datum;
tdp = new TextDisplayComponent(part.getName());
tdp.setLabel(part.getName());
tdp.setValue(part.getName());
tdp.addStyleClass("overdueDate");
}
return tdp;
}
}
2. Register your data utility in <windchill>/codebase/service.properties.xconf and run xconfmanager -p to propagate
the changes.
<Service name="com.ptc.core.components.descriptor.DataUtility">
<Option serviceClass="ext.custom.datautilities.TextColorDataUtility" requestor="java.lang.Object"
selector="textColorDataUtility"/>
</Service>
3. There are two ways you can call your data utility. Any of the two listed methods can be used to call the data utility.
First: You can call it for attributes using Type and Attribute Management.
For eg.
Go to Type and Attribute Manager ->
Part(Get into Edit Mode) ->
Layouts(Choose a layout) ->
Add Data Utility Id to the desired attribute ->
Save the changes and exit.
Second: You can call it in Table Builder
For eg.
@ComponentBuilder("ext.custom.tables.MVCTableBuilder")
public class MVCTableBuilder extends AbstractComponentBuilder {
@Override
public Object buildComponentData(ComponentConfig config, ComponentParams params) throws Exception {
QuerySpec query = new QuerySpec(WTPart.class);
// You can specify the search condition according to you
query.appendWhere(new SearchCondition(WTPart.class,WTPart.NAME,SearchCondition.LIKE,"%Part%"), null);
return PersistenceHelper.manager.find(query);
}
@Override
public ComponentConfig buildComponentConfig(ComponentParams arg0) throws WTException {
ComponentConfigFactory factory = getComponentConfigFactory();
TableConfig table = factory.newTableConfig();
table.setLabel("Selected Parts");
table.setSelectable(true);
ColumnConfig column = factory.newColumnConfig("name",true);
column.setDataUtilityId("textColorDataUtility"); // set the data utility id
column.setLabel("Name");
table.addComponent(column);
table.addComponent(factory.newColumnConfig("number", true));
table.addComponent(factory.newColumnConfig("type", true));
table.addComponent(factory.newColumnConfig("thePersistInfo.modifyStamp", true));
table.addComponent(factory.newColumnConfig("thePersistInfo.createStamp", true));
return table;
}
}
How to specify the folder where an object will be created in Windchill PDMLink
Description
● How to specify a folder selected as default where new objects will be created?
● How to create a Part in a specific folder?
● How to set folder location while Check-In WTPart from Pro/ENGINEER?
● How to Set up a Default Folder for all New Baselines Created?
● How to change the save location of Change Request(ECR), Change Notice(ECN) and Problem Report(PR)
● How to save ECR, ECN and PR to a different folder, not default root folder?
● How to create OIR which will always create part in a specific folder of a particular library
● Is it possible to mention the folder location in a different context in Object Initialization Rule?
● How to force instances saving at the same location as their generic file?
Resolution
● Set the default folder location via Object Initialization Rules (OIR)
1. Navigate to appropriate context such as Site / Organization / Product / Library > Utilities > Object Initialization Rules
2. Download OIR of the appropriate object type such as Part, Document, Change Notice etc.
3. Open XML file in a text editor such as Notepad etc.
4. Modify <AttrValue id="folder.id"> element, add the name of the desired folder after /Default, for example:
6. Search for AttrConstraint id="folder.id" element, which is located in the same file to specify how default folder location should display when
creating or editing new objects.
Notes: The constraint details specified above such as algorithms can be changed in order to change the display of the components in GUI in
Create New wizard.
A. Workspace Preferences:
Select the Workspace > Edit Preferences action
1. Change Part Target Folder and CAD Document Target Folder by clicking on Browse button
2. Click OK
OR
B. Workspace Action
UI Validation
Objective
You want to hide an action or attribute in the UI based on some context information.
● You want to determine whether or not an action selected in the UI should be allowed to proceed based on some context
information.
● You want to determine whether or not a user can proceed to the next step in a wizard or whether the entire wizard may be
submitted, based on the data entered by the user in that wizard.
Background
UI Validation is intended to simplify the experience of the Windchill end-user. There are three categories of UI Validation that will each
be discussed in further detail in this document.
● Pre-Validation
● Post-Select-Validation
● Post-Submit Validation
Pre-Validation
The first category of UI Validation is referred to as Pre-Validation. This is the category of validation that most people will first associate
with UI Validation. Pre-Validation is a term that describes the process of determining whether or not a UI Component should be
available in the UI. An example of Pre-Validation would be disabling the “check-in” action for an object that is not checked-out.
Pre-Validation can be applied to both actions and attributes in the UI. Of the three types of UI Validation, this type is the most
often-used.
Post-Select Validation
A second category of UI Validation is Post-Select Validation. Post-Select Validation is the process of determining whether or not an
action should be allowed to proceed once it is selected in the UI. An example of post-select validation would be displaying an error
message and not allowing the checkout to proceed if a user tries to perform a checkout on an object that is already checkedout.
Post-Select Validation applies only to actions.
Post-Submit Validation
The final category of UI Validation is Post-Submit Validation. This type of validation is used exclusively in wizards or other forms where
users enter data. An example of Post-Submit Validation would be stopping a user from moving to the Presenting Information in the UI
next step in a wizard because the data they’ve entered in the current step is invalid. Post-Submit Validation applies only to wizard steps
and wizard submissions.
Scope/Applicability/Assumptions
• Pre-Validation - Suppose you want to hide an action in the UI from users who are not members of the current container’s team.
• Post-Select Validation - After a user selects an action, you want to determine whether or not the target object is in a certain lifecycle
state before allowing the action to proceed.
• Post-Submit Validation - After a user enters data in the first step of a wizard and tries to navigate to the next step, you want to
determine whether or not the information entered on the first step is valid before allowing them to proceed.
Intended Outcome
• Pre-Validation - Before the page is rendered, you are able to determine whether or not the user is a member of the current container’s
team. If not, the action is not displayed on the page.
• Post-Select Validation - After the user invokes the action, you are able to check the target object’s lifecycle state. If the state is not the
state you require, you can display a message to the user, and the action is not performed.
• Post-Submit Validation - When the user clicks “next” on the wizard, you get the data entered in the current step and are able to
determine whether or not it is adequate to allow the user to proceed to the next step. If the data is inadequate or invalid, you can display
a message to the user and not allow them to proceed to the next step.
Solutions
• Pre-Validation - Determine whether the business logic is specific to a single action or attribute, or if the same logic could apply to
multiple actions or attributes. If the logic is specific to a single UI Component (action or attribute), add the logic to a Validator. If the
logic could apply to multiple UI
○ Pre-Validation in a Filter - Implement the preValidateAction method in a Filter to contain the desired business logic.
• Post-Select Validation - Implement the validateSelectedAction() and validateSelectedMultiSelectAction() methods in a Validator to
define the desired business logic, and associate the Validator with the action.
• Post-Submit Validation - Implement the validateFormSubmission() method in a Validator to define the desired business logic, and
associate the Validator with the wizard step or the entire wizard.
Prerequisite Knowledge
● The actions framework in the Windchill client architecture. The Application Context or service.properties mechanism for
registering delegates.
● A basic familiarity with the NmCommandBean and its methods will be helpful.
● The validation service is very generic in nature. It does not provide the APIs you’ll need in order to
determine whether or not something is valid. The service will provide your validators or filters with the context data and form data
you need to determine whether or not a UI Component is valid. But you will need to know what to do with that data in order to
apply your validation business logic.
Preference Framework
The Preferences Framework is based on the principle that a unique preference consists of the following attributes:
● Parent Node (or root node if at the top of the hierarchy)
Together these attributes form a unique key structure of parent/node/key. This unique key structure will be referred to as the fully qualified
preference key. To separate individual user and group preferences for the same fully qualified preference key, a context is applied to the
preference.
The context consists of the following elements:
● Macro — a constant defining the type of context (see below) (optionally) Descriptor — text defining the name of the context.
● These elements are placed together with a ’:’ to form the Preference Context.
The fully qualified preference key when placed together with a context will form a unique row in the database table, allowing users, and other
divisions to have individual preferences.
Preference Macros
The wt.prefs.WTPreferences class defines the following types of Preference Context Macros:
● USER_CONTEXT - the context for individual users
Generic UI Customizations
Setting the Hierarchy
The delegates.properties value wt.prefs.delegates.DelegateOrder controls the hierarchy in which
delegates are called. For each level in the hierarchy there should be an entry in this property. The customized entries should appear as
DIVISION_CONTEXT. For example, in the out-of-the-box hierarchy, there is a division scope called Windchill Enterprise, and the out-of-the-box
wt.prefs.delegates.DelegateOrder property value is:
$DEFAULT,$CONTAINER,$DIVISION:WindchillEnterprise,$USER
In this value, there is no DIVISION_POLICY_CONTEXT defined since
DIVISION_POLICY_CONTEXT and DIVISION_CONTEXT are related and are at the same level in the preference hierarchy. Similarly, the
CONTAINER_POLICY_CONTEXT need not be included. Entries are designated differently only when storing and retrieving preferences internally.
For more details on correctly naming delegates, see the delegates.properties file. If wt.prefs.delegates.DelegateOrder has been removed from
the delegates.properties file, Windchill uses the following:
$DEFAULT,$CONTAINER,$USERSetting Preferences Edit the file Windchill/loadFiles/preferences.txt. This file is used to put the system values
into the database. Note that you don’t put quotes around the strings unless you actually want quotes persisted as part of the preference.
Syntax:
PrefEntry~keyName~default value~fullyQualifiedNodePath
Example:
PrefEntry~fileOperationType~ASK~/wt/content
Getting Preferences
You can get a preference by first navigating the preferences tree to the proper node, then setting the context for that particular user, then
getting the value for that key.
Example:
// returns an instance of the top node in the Windchill preference "tree"
Preferences root = WTPreferences.root();
// returns the preference node at that path
Preferences myPrefs = root.node( "/wt/content" );
((WTPreferences)myPrefs).setContextMask
(PreferenceHelper.createContextMask() );
// get( ), gets the value for that
// preference key
String prefValue = myPrefs.get( "fileOperationType", "SAVE" );
Clearing a Preference
There is a big difference between "clear" and "remove". Assuming there are no division-level defaults or policies, if you "clear" a user preference
by setting the value to be the empty string "", then the value returned will be ""; but if you "remove" the user-level preference, then the value
returned would be system default value. In most cases you will want to remove the user-level preference and not clear it, giving the user the
upper hierarchical preference as their default.
Example:
Preferences root = WTPreferences.root();
Preferences myPrefs = root.node( "/wt/content" );
((WTPreferences)myPrefs).setEditContext
(PreferenceHelper.createEditMask());
((WTPreferences)myPrefs).setContextMask
(PreferenceHelper.createContextMask());
String prevValue = myPrefs.remove("fileOperationType");
Preference Registry
The preference registry is a way to take a cryptic name like a preference and
provide a localized series of data about it. This registry is in the form of rbInfo
files. Anyone adding preferences to the system will have the option of adding this
localized information to the Preference Registry. Adding a preference to the preference registry
To add a Preference to the Preference Registry, you need to edit the file <Windchill>src/wt/prefs/registry/prefRegistry.rbInfo and for the
Preference you want to add, add at least:
DISPLAY_NAME
DESCRIPTION
DEFAULT
The format is as follows:
/node-name/key-name% [ ]tag.value=
Where /node-name is the name of the node (for example /wt/workflow), /key-name is the name of the key under the node (SortOrder) and % [ ]tag
is one of the tags mentioned above (% [ ]DISPLAY_NAME).
Generic UI Customizations
Creating a Preference
The creation of a preference is done through an XML load file. When creating a preference the following pieces of information need to be
determined:
● Unique preference name
● Visibility: if the preference will appear in the preference manager UI and visible at what contexts: SITE, ORGANIZATION, CONTAINER or
USER .
● Preference category: The category the new preference will appear under in the Preference Management utility.
● Display name: This is the Name column in the Preference Management utility – string in the form <RBINFO>:< RBINFO key>
● Description: This is the Description column in the Preference Management utility – string in the form: <RBINFO>:<RBINFO key>
● Long Description: This description is displayed in the Edit Preference UI, gives a more detailed description including the expected values.
Default value
Value header
In the example below, we will create a new preference named /com/mycompany/MyNewPreference along with an associated preference
category to appear in the Preference Manager UI.
1. Create a resource bundle for labels used for your new preference. Labels are needed for display name and description of preference
category which the new preference will be visible under in Preference Management UI. Labels are also needed for the display name,
description and long description of your preference. Create the file mycompanyPreferenceResource.rbInfo
in package com.mycompany.pref. In this example, this file would be added to the <Windchill>/src/mycompany/pref directory.
ResourceInfo.class=wt.tools.resource.StringResourceInfo
ResourceInfo.customizable=true
ResourceInfo.deprecated=false
# Preference Category labels
MyNewPreferenceCategory.displayName.value=My Preference Category
MyNewPreferenceCategory.description.value=Preference Category for my custom preferences.
# Preference Definition labels
MyNewPreference.displayName.value=Display name of preference /com/mycompany/MyNewPreference
MyNewPreference.description.value=Description of preference /com/mycompany/
MyNewPreference.
MyNewPreference.longDescription.value=Long description of preference
/com/mycompany/MyNewPreference.
2. Build the resource bundle by executing the following command from a
windchill shell: ResourceBuild com.mycompany.pref.mycompanyPreferenceResource
3. Restart the servlet engine and the MethodServer.
4. Create Preference load file: createMyNewPreference.xml. It will contain a definition for the new preference category and new preference
definition.
<?xml version="1.0"?><!DOCTYPE NmLoader SYSTEM "standardX10.dtd">
<NmLoader>
<csvPreferenceCategory handler="wt.preference.LoadPreference.createPreferenceCategory">
<csvname>CUSTOM_PREFERENCE_CATEGORY</csvname>
<csvparentName></csvparentName>
<csvdisplayName>com.mycompany.pref.mycompanyPreferenceResource: MyNewPreferenceCategory.displayName</csvdisplayName>
<csvdescription>com.mycompany.pref.mycompanyPreferenceResource:MyNewPreferenceCategory.description</csvdescription>
</csvPreferenceCategory><csvPreferenceDefinitionhandler="wt.preference.LoadPreference.createPreferenceDefinition"><csvname>/com/mycom
pany/MyNewPreference</csvname>
<csvvisibility>USER</csvvisibility>
<csvcategoryName>CUSTOM_PREFERENCE_CATEGORY</csvcategoryName>
<csvdisplayName>com.mycompany.pref.mycompanyPreferenceResource:MyNew
Preference.displayName</csvdisplayName>
<csvdescription>com.mycompany.pref.mycompanyPreferenceResource:MyNew
Preference.description</csvdescription>
<csvlongDescription>com.mycompany.pref.mycompanyPreferenceResource:MyNewPreference.longDescription</csvlongDescription>
<csvdefaultValue>Default Value</csvdefaultValue>
<csvhandler>com.ptc.windchill.enterprise.preference.handler.StringPreferenceValueHandler:4000</csvhandler></csvPreferenceDefinition>
<csvLinkPreferenceClientDefinition handler="wt.preference.LoadPreference.set
ClientDefinitionLink">
<csvname>/com/mycompany/MyNewPreference</csvname>
<csvclientName>WINDCHILL</csvclientName>
</csvLinkPreferenceClientDefinition>
</NmLoader>
5. Load the preference category and preference definition using the following
command:
windchill wt.load.LoadFromFile -d <fullpath>/createMyNewPreference.xml
Deleting a Preference
The deletion of a preference is also done through the use of an XML load file. we will delete the preference
/com/mycompany/MyNewPreference. The deletion of the preference will also remove any preference instances which may have been set for this
preference in the UI.
1. Create an XML file, deleteMyNewPreference.xml, containing the following definition to specify the deletion of the preference.
<?xml version="1.0"?><!DOCTYPE NmLoader SYSTEM "standardX10.dtd">
<NmLoader>
<csvDeletePreferenceDefinitionhandler="wt.preference.LoadPreference.deletePreferenceDefinition">
<csvname>/com/mycompany/MyNewPreference</csvname>
</csvDeletePreferenceDefinition>
</NmLoader>
2. Delete the preference definition using the following command:
windchill wt.load.LoadFromFile -d <fullpath>/deleteMyNewPreference.xml
Customizing Column Lengths
A column length for a modeled attribute can be customized. These column lengths are obtained through the wt.introspection package. The
value of this property can be overridden by placing entries in the customizations property file for modeled packages. To change the column
length for a modeled attribute, perform the following steps: