User Interface

Overview

The Windchill Extension Framework provides the developer with ways of adding UIs to Windchill or adjusting the existing ones. Menu entries are the primary and most simple example of it, but most of the other aspects of the user interface can be edited through a Windchill Extension. All added UI elements can be localized.

There are two types of UI that can be implemented through a WEX:

It is important to read the Security section of this manual when implementing user interfaces.

Windchill UI

A Windchill UI is one that is developed using standard Windchill techniques such as the JCA framework.

Implementation Techniques

Adding a UI through Touch Points

A touch point is where Windchill looks in order to add a new element to its UI features; Menus, Tabs, Validators and Quick Links entries are examples of UI elements generated by this procedure. The injection of touch points into the system is managed via the definition.xml file and the WEX's localized resources. Some of the elements that can be added with this technique are the following:

The action model of Windchill allows the addition of new menus. There are two steps to this procedure:

  1. Add the action entry defining the XML action file location.
    1. Add the resource bundle entries.
Adding the Action Entry

Adding an action menu is done by inserting a line into the Windchill XML files that control the menu system. This is achieved by defining the action in the definition.xml file of the WEX, which will used to automatically make the menu changes when the extension is installed. The root tags are:

wex-definition -> install -> menus

Within this, the xml tag is added:

<extension menu="[xml tag]" file="[xml file]" action="[jsp location]" position="[position tag]"/>

An example would look like this:

<!-- Add a menu to the quick link -->
<extension menu="header actions" file="navigation-actionModels" action="wexAction"/>

<!-- Add a menu to the various objects -->
<extension menu="more parts actions" file="PartClient-actionmodels" action="wexAction"/>
<extension menu="epmdocuments details page actions" file="UwgmCadx-actionmodels" action="wexAction"/>
<extension menu="docs row actions toolbar" file="DocumentManagement-actionmodels" action="wexAction"/>
<extension menu="more change notice row actions" file="ChangeManagement-actionModels" action="wexAction"/>
<extension menu="promotion request row actions" file="PromotionRequest-actionModels" action="wexAction"/>
<extension menu="search_table_actions_toolbar" file="search-actionmodels" action="wexAction"/>
<extension menu="folderbrowser_toolbar_actions" file="FolderManagement-actionModels" action="wexRepEwexActionxport"/>
Action Models

The action model (e.g. menus) can be added to tables using the standard techniques in Java and specified in the definition.xml.

Example:

table.setActionModel("wex matrixcomp toolbar");
<menu name="wex matrixcomp toolbar">
    <action name="wexModeUsage"/>
    <action name="wexModeOcc"/>    
    <action name="wexMatrixSettings"/>
    <action name="wexChangeAlign"/>
    <action name="wexShowUnchanged"/>
    <action name="wexHideUnchanged"/>           
    <action name="separator" type="separator"/>
    <action name="exportTableXLSX"  type="object"/>  
    <action name="exportTableCSV"   type="object"/>   
    <action name="exportTableHTML"  type="object"/>   
    <action name="exportTablePDF"   type="object"/>    
</menu>      
Command Actions

Command actions provide the ability to call a Java Class directly from a menu:

<install>
    <menus>
        <extension menu="more parts actions" file="PartClient-actionmodels" action="action-methods.example-command" position="SETSTATE"/>
    </menus>
    <actions>
        <command class="com.wincomplm.wex.kernel.impl.actions.WexActionMethods" method="execute" name="action-methods.example-command"  shortcut="false"/>
    </actions>
</install>

Note the action is a declaration of a method that must be defined as a Feature as it follows:

@WexComponent(uid = "action-methods", description = "Example Actions")
public class ExampleActionMethods {

@WexActionMethod("example-command")
public static FormResult exampleCommand(NmCommandBean ncb) throws Exception {}
Adding the Resource Bundle Entries

The resource bundle must be defined as follows:

    @RBEntry("Hello World")
    public static final String C0 = "com.acme.wex.example.wexAction.description";
    @RBEntry("Hello World")
    public static final String C1 = "com.acme.wex.example.wexAction.description";
    @RBEntry("Hello World")
    public static final String C2 = "com.acme.wex.example.wexAction.description";
Tabs

The action model can also add tabs using this meta tag:

<extension menu="general" file="infoPage-actionmodels" action="wexActionName"  position="SETSTATE"/>

and this action for a new tab:

<page name="wexActionName" url="wexActionName"/>
Validators

All menu and tab actions can have validators that control visibility. It uses the @WexValidator annotation that takes an array of actions. Given this action in the meta:

<extension menu="xxxx" file="xxx" action="wexActionName"/>

The code for the validator would go as follows:

@WexValidator({"wexActionName"})
@WexComponent(uid = "wex-example.validator", description = "Example Validator")
public class WexActionValidator extends DefaultUIComponentValidator {

    @Override
    public UIValidationStatus preValidateAction(UIValidationKey uivk, UIValidationCriteria uivc) {
        UIValidationStatus displayStatus = super.preValidateAction(uivk, uivc);
        if (hide==true) {
            return UIValidationStatus.HIDDEN;   
        }
        return displayStatus;   
    }
}

Note: there is a class to extend from that will implement an action only for admin users:

com.wincomplm.wex.ui.commons.validators.WexAdminUIValidator

The wt-ui-commons module provides the necessary imports for all of this to work.

<dependency>
    <groupId>com.wincomplm</groupId>
    <artifactId>wt-ui-commons</artifactId>
</dependency>

Adding a UI through JSP

Wizards

Wizards are pop-ups prompted up by Windchill that are often comprised of multiple steps:

image-20230419172636530

These are a combination of definition.xml file changes and JSP files which include beans and can also include WexInvoker.invoke commands. This guide includes an example on Wizards that sheds some light on the fundamentals of their code.

Tables

A table uses all standard JCA table techniques, but is created as a component. There are two elements to it: the JSP file, and the implementing Java code.

The JSP is as follows:

<%@ include file="/netmarkets/jsp/util/begin.jspf"%>
<%@ taglib uri="http://www.ptc.com/windchill/taglib/mvc" prefix="mvc"%>
<%@ taglib uri="http://www.ptc.com/windchill/taglib/fmt" prefix="fmt"%>
<%@ taglib uri="http://www.ptc.com/windchill/taglib/wrappers" prefix="w" %>     
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>

<jsp:include page="${mvc:getComponentURL('com.wincomplm.example.reporttable')}"/>
<%@ include file="/netmarkets/jsp/util/end.jspf"%

and the implementing code goes like this:

@WexBean("com.wincomplm.report.designer.reporttable")
@WexComponent(uid = "com.wincomplm.report.designer.reporttable", description = "Report Table")
@ComponentBuilder("com.wincomplm.report.designer.reporttable")
public class ReportTableBuilder extends AbstractConfigurableTableBuilder {
...
}

Within the table, a POJO is used as the data source for the rows.

These methods, that are accessed by the definition, use reflection to obtain the getter:

addColumn(factory, "data"... => public String getData() {

The methods can return Strings but also complex objects such as IconComponent and other elements that extend the com.ptc.core.components.rendering.guicomponents.HTMLGuiComponent class. These fields can also be collected together using the GUIComponentArray object.

Trees

Windchill supports Trees in a similar way that it does with Tables:

1623876434794

The data provider must implement AbstractConfigurableTableBuilder, which in turn uses a TreeHandlerAdapter to return the data from buildComponentData.

Data Utilities

The Data Utility gives a custom table access to data that is not defined in OOTB Windchill. We suggest developers are familiar with the Carambola examples that come with Windchill.

Implementation

@WexDataUtility("wex-example-tree-data")
@WexComponent(uid = "wex-example-tree-data", description = "Data")
public class ExampleDataUtil extends AbstractDataUtility {
    @Override
    public Object getDataValue(String string, Object o, ModelContext mc) throws WTException {
        if (!(o instanceof WexTreeRow)) {
            return null;
        }
        return ((WexTreeRow) o).getData();
    }
}

Performance

The Data Utility must be implemented with minimal impact on UI performance. As a utility may be called 1000s of times in the display of 1 page, we must have a highly performant algorithm. Consider that even a 10ms delay, such as the one added by a fast query, would become a 2s one if said query is run 2000 times in the display of the page. Therefore, we must either avoid queries or cache the result in a persistent cache to ensure multiple duplicate queries are not run.

It is encouraged to use extension performance throttles in the case the utility may be unduly affected by poor performance of an underlying asset, such as a database.

Localization

Windchill supports 12 languages, so we can build our extension to support one or more of these languages. In order to do this, we need to use localized Resource Bundles.

Beans

The bean must be defined

<wex:bean id="[beanid]" packageId="[groupid].[artifactid]" featureId="[featureid]">

Code

@WexComponent(uid = "[featureid]", description = "[description]")

See Example UI

Menus localization is done by adding a localized version of the Resource Bundle. An example would look like the one below:

@WexComponent(uid = "com.wincomplm.wex.example.ui.resources.WexResources", description = "Resource bundle")
@RBUUID("com.wincomplm.wex.example.ui.resources.WexResources")
@WexLocale(WexLanguage.FRENCH)
@RBNameException
public class WexResources_fr extends WTListResourceBundle {

    @RBEntry("French Example Menu")
    public static final String P1 = "com.wincomplm.wex.example.ui.edkHelloWorld.description";
    @RBEntry("French Example Menu")
    public static final String P2 = "com.wincomplm.wex.example.ui.edkHelloWorld.tooltip";
    @RBEntry("French Example Menu")
    public static final String P3 = "com.wincomplm.wex.example.ui.edkHelloWorld.activetooltip";

UI Text

We can get the Resource Bundle as follows:

ResourceBundle.getBundle("com.wincomplm.wex.kernel.resources.WexResources", WTContext.getContext().getLocale());

The wexid is in the key:

resourceBundle.getString(wexid + "." + key);

To do this, a helper class can be coded to access the RBs:

package com.wincomplm.wex.manager.resources;

import java.util.ResourceBundle;
import wt.util.WTContext;

public class RBHelper {

    private static ResourceBundle resourceBundle;
    private static String id = "com.wincomplm.wex.manager";


    public RBHelper() {
         resourceBundle = ResourceBundle.getBundle("com.wincomplm.wex.kernel.resources.WexResources", WTContext.getContext().getLocale());
    }
    public String getRbString(String key) { 
        return resourceBundle.getString(id + "." + key);
    }

}

The helper would be used like this:

rbHelper=new RBHelper();
table.setLabel(rbHelper.getRbString("table.header.installedexts"));

Others

Localization does not only refer to languages: we need to manage times, dates, number, currencies (even though rarely used in PLM), etc.

The Windchill API provides support for determining the locale. Some date localization can be managed by using a format that is year/month/day, which is viable for the European and US locales and has the added advantage of being more easily sorted.

Debugging the Windchill UI

The jcaDebug parameter can be added to the URL to get a better look at the source of the elements present in the UI. It is helpful to know if an element is already there but being hidden by a Validator or to find out which class is generating a certain menu entry, among many other purposes.

1619108525429

Resizing

Windchill does offer some window size management but another technique is including this code within the page:

<script>
    window.resizeTo(550,340);
</script>

This is useful to ensure that a page with elements is always scaled to an exact size.

Custom User UI

This is a stand alone JSP that is coded independently of Windchill but call Windchill APIs.

Using CSS and JavaScript

We can include CSS and JavaScript resources in our page, but we cannot place them in the jsp area as this is not valid and on some secure servers these resources will not load.

The correct locations for them are:

codebase\netmarkets\javascript\com\wincomplm\wex\documentation
codebase\netmarkets\css\com\wincomplm\wex\documentation

To ensure the extension installer places your resources in the correct location, you must add these folders to your WEX's file structure:

1622809776381

This example will help you to access those resources from a JSP file:

<%@ page import="javax.servlet.http.HttpUtils"%>
<%
    String urlpath = request.getRequestURI(); // Get the full URL
    urlpath=urlpath.substring(0,request.getRequestURI().lastIndexOf("/")); // Shorten
    String jspath =  urlpath.replace("/jsp/","/javascript/"); // Adjust to new paths
    String csspath = urlpath.replace("/jsp/","/css/");
%>
<script src="<%=jspath%>/bootstrap-4.5.2.min.js"></script>    
<script type="text/javascript" src="<%=jspath%>/jquery-3.5.1.min.js"></script>

Please ensure you use the latest versions of the resources and verify them against the NIST database for security reports.

Console Errors and jQuery

jQuery is a universally used library and it is already included in Windchill, but developers may wish to include a later version; in this case, console errors might occur. The following call can be used to mitigate these:

jQuery.noConflict();

Build Techniques

Building user interfaces requires a knowledge of the JSP engine that is being used in Windchill to render the UI, and also a good understanding of how browser caching works.

Preventing JSP Caching

All modern browsers cache requests that are identical as ones that have been previously made. When loading fast changing JavaScript and CSS, this can mean the deployment of a new extension with new resource will not function correctly if a previous version was installed and accessed with the same browser: this is because the resource may have changed but the browser is not aware of that, and will use its cached version as opposed to the newer resource now available, which will cause unpredictable behavior for the user.

In order to prevent this, we need an index that is unique to this build to be added to the URL so that the browser will reload resources on a new build deployment.

Implementation

This video illustrates the technique we use to prevent the browser from inappropriately caching newer versions of a resource.

We can use the following to replace a token in the JSP:

<plugin>
 <groupId>com.google.code.maven-replacer-plugin</groupId>
 <artifactId>replacer</artifactId>
 <version>1.5.3</version>
 <executions>
     <execution>
         <phase>prepare-package</phase>
         <goals>
             <goal>replace</goal>
         </goals>
     </execution>
 </executions>
 <configuration>
     <file>src\main\wex\jsp\jspf\pindex.jspf</file>
     <replacements>
         <replacement>                            
             <token>\?[1-9][0-9]*</token>
             <value>?${​​​​​​maven.build.timestamp}​​​​​​</value>
         </replacement>
     </replacements>
 </configuration>
</plugin>

The code is as follows:

<%  final String PINDEX = "?20210314074128"; // <- number will be replaced by pom plugin %>

And used as follows:

<script src="js/notifications.js<%=PINDEX%>"></script>

In order to check the effectivity of this technique, we can use the Developer Tools (F12) in the browser and check the network to ensure the URLs have the new index.

1616964109358

Unauthenticated Pages in Windchill

This is the method for a developer to allow an unauthenticated JSP to be added to Windchill.

In order to access Windchill through a WEX without authentication, the developer needs to: