External Box Advanced
In this chapter we create a more complex external component, to illustrate the user interactions and the communication with the Configurator Service and Configurator Engine Layers.
The goal of this component (in the orange box) is to provide the client with another way to change the current form loaded in the Configuration Box. It is another kind of “selector” (comparable to the horizontal selector bar)
At the beginning the root form/cp is selected. Two navigation buttons – left and right arrows – allows to load the previous and next forms/cp. Left arrow button is disabled when the first form/cp is reached, right arrow button is disabled when the last form/cp is disabled.
Moreover, the selector displays some additional information – such as the total number of forms/cps and the description of the current element.
The component will be registered in order to be refreshed when the visibility property of some forms/cps of the configuration model changes: for example, if a form becomes visible (or becomes hidden) due to some model constraints then the selector needs to reload since the total forms count becomes invalid.
Topics:
- Exception handling
- Tile refresh
- Action implementationWarning: The component is designed to be integrated into the configuration page. It is not supposed to be inserted into any other page of the application. Doing so will produce an error.
Setting the development environment up
The following libraries are needed:
- Internal--supplied by PROS:
- externalComponentsFramework.jar
- ConfiguratorUI service layer
- ConfiguratorEngine.jar
- Configurator Engine layer
- business.jar
- configuration engine public business objects
- framework.jar
- utility library
- externalComponentsFramework.jar
- External--third party’s libraries:
- struts.jar
- Struts web GUI framework
- javax.servlet.jar
- servlet API, used by Struts
- log4j.jar
- logging facility
- commons-beanutils.jar
- Apache commons bean utilities
- jboss-aop-jdk50-client.jar
- AOP facilities
- AOP facilities
- struts.jar
Creating the Controller
In this selector example, the controller interacts with the ConfiguratorUI Service Layer – to extract configuration data from the XML Configuration file, and with the Configurator Engine Layer to extract the data from the forms and configurable products.
Data needed for the View:
- Cameleon Process Expression (CPE) of the currently selected form/CP
- Description of the current form/CP
- Total number of forms and CPs in the configuration tree
- CPEs of the previous and next form/CP – so we can redirect to the next/previous configuration element when pressing the buttons
- Translation of the strings “Total forms count” & “Navigation” from the XML Layout file
- Resources of previous & next buttons
In order to retrieve the current CP, we’ll need an instance of the Configurator UI Service Layer, which has a service returning the current configurable product’s CPE.
Like in preceding example, we could store each data in a separate attribute in the tile context. However, it would lead to have too much imports in the tile’s JPS (Multiple call to “tile:attribute” and “bean:write”).
The data transfer object will be a DynaBean instance of type LazyDynaBean [in the Apache’s commons beanutils library]
DynaBean selectorBean = new LazyDynaBean();
// ...
selectorBean.set("imagePrevious", imagePrevious);
selectorBean.set("imageNext", imageNext);
// Set all other properties of the external selector
// ...
// Set the prepared bean into the tile context so it can be used by the JSP
tileContext.putAttribute("selectorData", selectorBean);
The idea is to prepare all data for the JSP and save it in this bean as plain strings, or list of strings for example. So in the JSP no other function calls than getting of properties will be executed.
Since all data is processed as strings in the controller, the controller can manage exceptions – if one occurs, JSP error is shown and normal JSP view is skipped.
However, if an exception is raised in the JSP, then the server just stops sending HTML code to the client and the page stops loading at this point, which produces partial and erroneous page.
The implementation of the controller starts by subclassing ExternalControllerAbs and overriding the process method:
package com.cameleon.demo.tiles;
import javax.servlet.ServletContext;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.struts.tiles.ComponentContext;
import com.cameleon.configuratorui.external.ExternalControllerAbs;
publicclass ExternalSelectorDemoController extends ExternalControllerAbs {
@Override
protectedvoid process(ComponentContext tileContext,
HttpServletRequest request, HttpServletResponse response,
ServletContext servletContext) {
// TODO Auto-generated method stub
}
}
In the next sections each step in the process of implementation of the controller will be explained. At the end of the chapter, the process() method will be filled with all the code supplying data to the JSP.
HOW TO RETRIEVE THE CONFIGURATOR UI SERVICE LAYER?
In order to get the currently selected object, or get a translation, or get a resource, the external component’s controller needs an instance of the Configurator UI Service Layer:
// Gets the Configurator UI service layer
ConfiguratorUIService configUIService =
(ConfiguratorUIService)request.getSession().
getAttribute(ConfiguratorUIService.*CONFIGURATOR_UI_SERVICE*);
HOW TO RETRIEVE THE MODEL FORMS AND CPS?
To provide the final UI rendering, the JSP page needs:
- the current CP description, which can easily be given by retrieving from the engine the CP of the current object.
- the previous and next CPs (if such are present).
There is no direct service in the Configurator Engine to retrieve the next form/CP, so we build a list of all CPs in the configuration tree (in-depth iteration over the tree).
We find the currently selected object and get the items at the previous and next position in the list. The iteration can be optimized and stopped when we find the current node (and the following one). But we still need all nodes as described in the next paragraph.
This linear representation of the configuration tree is also needed to implement another feature of the external selector – it displays the total count of nodes in the tree. During the configuration process the client may trigger an event resulting in changing the visibility of node in the tree – for example, if the client selects some option matching a configurable product, a new Form or CP becomes visible. That is why the external selector needs to be refreshed after a change in the visibility state of each node of the configuration tree.
We start by getting the instance of the Configuration Engine and by using it to retrieve the root node in the configuration tree. Then a helper function is called to construct the list:
ConfiguratorClient confEngine = configUIService.getEngine();
String rootObjId = configUIService.getRootObjectID();
List<ConfigurableNode> nodesList = new ArrayList<ConfigurableNode>();
List<ConfigurableNode> visibleNodesList = new ArrayList<ConfigurableNode>();
// Retrieve a list of all configuration nodes
constructNodeLists(confEngine, rootObjId, nodesList, visibleNodesList);
The lists nodesList and visibleNodesList will later be used to both build the bean with previous, current and next nodes data (for the JSP) and to register the tile for refresh. The first list contains all nodes modeled in the configuration tree, while the second one only contains the nodes, which are visible at the moment you call the function.
HOW TO CREATE THE BEAN FOR THE JSP?
Once the visible nodes list is ready, we can get the current node ID from the Configurator UI Service Layer and then take the description of the current node. We can also construct the links to the previous and next nodes. These links will be associated to the previous/next images in the JSP:
String selectedId = configUIService.getSelectedObjId(request);
// Gets the XML model file properties for the external component
Properties configProps = configUIService.getComponentProperties(tileContext);
// Prepare the bean with display data for the JSP
DynaBean beanSelector = prepareSelector(configProps, selectedId, confEngine,
configUIService, visibleNodesList);
// Set the moduleContext ID parameter of the component in the bean
beanSelector.set("moduleContextParam", catalogService.
buildModuleContextParameter(catalogService.getModuleContextId()));
// Set the prepared bean into the tile context so it can be used by the JSP
tileContext.putAttribute("selectorData", beanSelector);
The bean selectorData in the tileContext holds all the data needed by the JSP to build the HTML code of the page.
/**
*Createsalistofjavabeanscontainingtabdataforselectorbartabs
*display.
*
*@paramconfPropsconfigurationproperties
*@paramselectedObjIdCPEoftheselectedform/cp
*@paramconfigEngineConfiguratorengine
*@paramconfigUIServiceConfiguratorUIservicelayer
*@paramauthorizedNodelistofallvisiblenodesintheconfigurationtree
*@returnResultlistof<code>DynaBean</code>containingtabdatafor
*externalselectortabsdisplay
*/
protected DynaBean prepareSelector(Properties confProps, String selectedObjId,
ConfiguratorClientSvc configEngine, configuratorUIService configUIService,
List<ConfigurableNode> authorizedNode) {
DynaBean selectorBean = new LazyDynaBean();
// Set the total number of currently visible forms/CPs
selectorBean.set("totalNum", Integer.valueOf(authorizedNode.size()));
// Find the position of the current node in the list of visible nodes
short selectedPos = getSelectedPos(authorizedNode, selectedObjId);
// Get the current node description
if(selectedPos != -1) {
try {
// Set the external selector's description
selectorBean.set("description",
getDescription(authorizedNode.get(selectedPos)));
} catch (NotLoadedException e) {
// No description is given
selectorBean.set("description", "");
}
} else {
selectorBean.set("description", "");
}
// Set the parameters to previous button
String prevObjId = "";
if(selectedPos > 0 ){
prevObjId = CPEBuilder.cpeToString(authorizedNode.get(selectedPos-1)
.getCPE());
}
selectorBean.set("prevObjId", prevObjId);
// Set the parameters to next button
String nextObjId = "";
if(selectedPos != authorizedNode.size()-1){
nextObjId = CPEBuilder.cpeToString(authorizedNode.get(selectedPos+1)
.getCPE());
}
selectorBean.set("nextObjId", nextObjId);
//TODO: Fill all other properties of the bean needed in the JSP
return selectorBean;
}
Next we fill the bean with all data expected by the JSP:
- the total number of visible forms and CPs is already computed – that’s the number of elements in the list of all visible elements
- the description of the current form/CP (its position in the list of all CPEs of forms and CPs is found with the helper function getSelectedPos())is retrieved from the Configurator Engine. Default description is an empty string.
- the previous and next CPEs (if present) from the visible items list are stored in the bean.
HOW TO GET STRING LITERALS TRANSLATIONS?
The new component owns string literals, which need internationalization.
The XML Layout file contains a dedicated section for custom translations. The section consists of a
multiple translations keys each one specifying the translation in a given language. The *Configurator UI service layer *has a dedicated service retrieving a translation (of an element corresponding to a given key) for a given user’s language:
// Set the external selector's title
String title = configUIService.getTranslation("vtExtlSel.title");
The vtExtlSel.title key is required as a key in the custom translations section of the Layout XML file.
Instead of looking for a translation directly in the dedicated section of the Layout file, the best solution to associate a translatable string to an external component is to use a special parameter in the tile’s XML definition. This parameter specifies the key of the string that has to be retrieved from the translation section. The name of the parameter is chosen by the external component developer. Then the way to retrieve a translation is:
- The translation key is retrieved from the tile’s parameters using the
getProperty(String key)method of the Configurator UI Service Layer. - The translation for the given key is retrieved by using the method
getTranslation(titleKey); - Then the translation is stored into the bean
// Set the external selector's title String titleKey = confProps.getProperty("titleTranslationName"); String title = configUIService.getTranslation(titleKey); selectorBean.set("title", title);
Tip: We advise you that each external component XML tile definition contains a parameter for each needed translation key.
We use the same approach for the second translation and set it in the selector data bean:
// Set the external selector's title
String titleKey = confProps.getProperty("titleTranslationName");
String title = configUIService.getTranslation(titleKey);
selectorBean.set("title", title);
HOW TO GET RESOURCE URLS?
There are four resources in this external component – next and previous buttons, each having an image for ‘on’ and ‘off’ state. The management of resources is identical to the management of translations. Again the developer chooses a parameter name in the tile’s definition under which the key of the resource is given. Then this key is used to retrieve the resource with the help of a dedicated service in the Configurator UI Service Layer:
// Previous image
String imgPrevOn = confProps.getProperty("imgPreviousOn");
String imagePrevious = configUIService.getGraphicResource(imgPrevOn);
if((selectedPos == 0) || (selectedPos == -1)){
String imgPrevOff = confProps.getProperty("imgPreviousOff");
imagePrevious = configUIService.getGraphicResource(imgPrevOff);
}
selectorBean.set("imagePrevious", imagePrevious);
// Next image
String imgNextOff = confProps.getProperty("imgNextOff");
String imageNext = configUIService.getGraphicResource(imgNextOff);
if(selectedPos != authorizedNode.size()-1){
String imgNextOn = confProps.getProperty("imgNextOn");
imageNext = configUIService.getGraphicResource(imgNextOn);
}
selectorBean.set("imageNext", imageNext);
The code checks if the current element is the first element in the list of visible nodes. It is the first element, then no previous element is present so the ‘off’ resource for previous node is used. If not – the ‘on’ resource is used. Another check is done to decide whether the ‘off’ or ‘on’ next resource is to be used.
HOW TO IMPLEMENT THE REFRESH MECHANISM?
The Configurator UI supports seamless browser-server interaction – the idea is to make a full page
refresh only under exceptional circumstances (like switching from Home to Configuration page). To achieve this goal, Ajax requests are sent to an action on the server (triggered by client events such as click on a link). The action code is executed. This may involve update of the data model, which data model is displayed by multiple tiles on the current page.
For example, if the client submits a wrong value to a form property, then the form property must change its status to ‘error’. But also the status must be updated in both vertical selector and horizontal menu bar.
To achieve this refresh mechanism, each tile must register as interested in the state of model objects it expresses in some way.
After each update in the data model, the Configurator UI retrieves a list of the tiles which were registered as interested in the state of the changed objects. All tiles, registered for refresh on the next Ajax request are also added to this list.
Then the resulting XML is generated. It contains CDATA sections with the new HTML code for each affected tile. Each CDATA section is associated to the tile’s unique HTML ID. Generation of the new code for a tile means that the tile’s controller is called and then the code (generated by its JSP), will be included in the resulting XML.
Client-side JavaScript is triggered when the Ajax request for the action execution is completed. It receives the resulting XML and parses it. Each affected tile on the page is then replaced by the new HTML code from the resulting XML.
An example of such a resulting XML:
<?xml version="1.0" encoding="utf-8" ?>
<siteUpdate>
<updates>
<update>
<id>tile27dt1195203574078</id>
<html><![CDATA[
<!-- HTML code for the tile -->
]]>
</html>
The Configurator UI automatically handles this mechanism.
The developer of the external component must:
- Either register its tile as interested to a set of objects using a service of the ConfiguratorUI Service Layer.
- Or register it to be refreshed on next page refresh – with a dedicated service.
- Or register it to be refreshed if the currently selected object has changed – again with a dedicated service.
The association of a tile with its set of ‘interested’ objects (called watching set) allows a partial refresh of each page.
The code to register the tile is the following one:
// Register the tile for refreshing when some of the objects
// displayed in it change
WatchingSet set = createWatchingSet(configUIService, nodesList);
configUIService.registerObjects(set, tileContext);
configUIService.registerRefreshOnCurrentChanged(tileContext);
Here is the code for the helper function createWatchingSet():
/**
*Createawatchingsetandaddalldataobjectsinwhichthecomponentis
* interestedintheset.Whenanobjectinthissetchanged,thetilewill
* berefreshedbytheConfiguratorUI
*
*@paramconfigUIServiceConfiguratorservicelayer
*@paramnodesListlistofallnodesintheconfigurationtree
*@returnwatchingset
*/
private WatchingSet createWatchingSet(ConfiguratorUIService configUIService,
List<ConfigurableNode> nodesList) {
String id = configUIService.getWatchingSetId();
WatchingSet set = new WatchingSet(id);
for(ConfigurableNode node : nodesList) {
CPE = node.getCPE();
CPEProvider = null;
PKType type = cpe.getLastPK().getType();
if (PKType.FORM.equals(type)) {
cpeProvider = (new FormCPEProvider(cpe)).addStateVisible().addStateExist();
} elseif(PKType.CP.equals(type)) {
cpeProvider = (new ConfigurableProductCPEProvider(cpe)).addStateVisible().addStateExist();
}
set.add(cpeProvider);
}
return set;
}
In this example we are interested in the visibility and existence state of each node. Each CPEProvider is then added to the resulting set.
HOW TO EMBED A CUSTOM TILE IN THE EXTERNAL COMPONENT?
For complex externalBoxes, it is also possible to include custom tiles inside these component.
This feature may be useful when the component needs to be partially refreshed outside of the standard WatchingSet mechanism (e.g. If the component calls an external Web service returning the stock status of a standard item and it is necessary to refresh only this part of the component)
To embed tiles in CPQ External Components, it is necessary to use Cameleon Service Layer method.
/**
* Method used to create a component definition for a child tile of this
* tile
*
* @param request
*The http servlet request
* @param confService
*The Configuratoruiservice object
* @param valueSelectorBean
*A dynabean object
* @param tileContext
*The tile context
* @param servletContext
*The servlet context
*/
private void createMySubTile(HttpServletRequest request, ConfiguratorUIService
confService,
DynaBean valueSelectorBean, ComponentContext tileContext, ServletContext servletContext)
{
// Get a new Tile Id for this child tile.
String childTileId = getNextTileId(request);
// Create the component definition of the subTile with this method.
ComponentDefinition = confService.getComponentDefinition
(".subTile", childTileId, tileContext, request, servletContext);
// Set the component definition in the bean
valueSelectorBean.set("subTileDefinition", componentDefinition);
// Set child tile Id too
valueSelectorBean.set("childTileId", childTileId);
}
return set;
}
Once created in the controller, the subTile is inserted in the JSP as follows:
<%-- Display any content only if there is no exception raised from Controller class --%>
<logic:empty name="exception">
<tiles:useAttribute name="valueSelectorBean"/>
<%-- Write current time of the externalComponent tile generation --%>
Current time of the main external tile generation is :
<bean:write name="valueSelectorBean" property="randomNumber" />
<%-- Create link to execute action that will permit child tile refresh --%>
<a onclick="executeAction('refreshSubTileAction.do', '<bean:write name="valueSelectorBean"
property="moduleContextIdParam" />&childTileId=<bean:write name="valueSelectorBean"
property="childTileId" />');">
Click here to refresh the child sub tile!
</a>
<%-- Insert child tileId --%>
<tiles:insert beanName="valueSelectorBean" beanProperty="subTileDefinition" flush="false"/>
</logic:empty>
The refresh action itself:
public class RefreshSubTileAction extends AjaxUpdateAction
{
/**
* {@inheritDoc}
*/
@Override
protected void doModelUpdate(HttpServletRequest request, ActionForm arg1)
{
// Get tileId to refresh from request
String tileToRefresh = (String) request.getParameter("childTileId");
System.out.println("Tile to refresh is " + tileToRefresh);
// Add the tileId of the tile to refresh.
addTileForUpdateControllerRefresh(request, getModuleContextIdentifier(request),
tileToRefresh);
System.out.println("Did you see that 'Current time of the main external tile
generation' changed ?");
}
}
HOW TO HANDLE EXCEPTIONS INSIDE CONTROLLERS?
Each exception (subclass of java.lang.Exception), thrown inside external components, is caught
in ExternalControllerAbs superclass. The exception’s stack trace is printed in the server’s console. Then an attribute under the name “exception” is put in the external component’s tile context. The attribute’s value is a generated ID, which ID holds in the session the exception itself.
It can use the exception id to extract the exception from the session. Since the tile’s content is inside tag – it’s not rendered when exception is present.
HOW TO INTERACT WITH DEFAULT ACTIONS (OPEN_FLYER, ACTIVATE ...)
Using the service layers, it is possible to interact with some Configurator or catalogUI standard actions. You are now able to launch from your external component some actions like the OPEN_FLYER, ACTIVATE and SEND_CUSTOM_EVENT action.
List of available actions:
- OPEN_FLYER: Allows to open a flyer (or externalFlyer) pointing on an object, being a configuration element (ConfigurableProduct, Form or FormProperty) or a product (StandardItem, SalesProduct).
- ACTIVATE: Allows to redirect the end-user to a specific configuration element (ConfigurableProduct, Form or FormProperty) or to a catalog page (StandardItem, ConfigurableProduct, SalesProduct or Collection).
- SEND_CUSTOM_EVENT: Allows to launch an event parameterized on model side.
The service layer will provide functions that will return the JavaScript code (or an URL) in charge of executing the action.
In the following example, we open a flyer named “config.productFlyer” displaying data related to SSI_Whirlpool_ET1 product.
CPE et1SI = CPEBuilder.stringToCPE("CPE.AC/SI/SSI_Whirlpool_ET1");
valueSelectorBean.set("openFlyerAction",ConfiguratorService.createOpenFlyerAction(
request, "config.productFlyer", et1SI));
In your JSP, you are now able to use the JavaScript code :
<logic:present name="valueSelectorBean" property="openFlyerAction">
<a onclick="<bean:write name="valueSelectorBean" property="openFlyerAction"
/>">
openFlyerActionLink
</a>
</logic:present>
A click on the “openFlyerActionLink" link will open the flyer named
"config.productFlyer" representing the SSI_Whirlpool_ET1 product.
Creating the View
HOW TO DESIGN THE JSP?
We start by copying the extComponentSample.jsp:
- It adds encoding settings
- It manages the exceptions which may occur during the controller execution.
This time the code inside the <logic:empty> tag is more complex:
<tiles:useAttribute name="selectorData" />
<div class="selector">
<div class="totals"><bean:write name="selectorData" property="totalNumDescr"/>
<bean:write name="selectorData" property="totalNum"/></div>
<div class="status"><bean:write name="selectorData" property="description"/></div>
<div class="buttonNav">
<div class="previous">
<logic:equal value="" name="selectorData" property="prevObjId">
<img src='<bean:writename="selectorData"property="imagePrevious"/>'/>
</logic:equal>
<logic:notEqual value="" name="selectorData" property="prevObjId">
<a onclick="executeAction('extSelLoad.do', '<bean:write
name="selectorData"
property="moduleContextParam" /> &selectedObjId=<bean:write
name="selectorData"property="prevObjId"/>');"><img
src='<bean:writename="selectorData"property="imagePrevious"/>'/></a>
</logic:notEqual>
</div>
<div class="title"><bean:write name="selectorData" property="title"/></div>
<div class="next">
<logic:equal value="" name="selectorData" property="nextObjId">
<img src='<bean:writename="selectorData"property="imageNext"/>'/>
</logic:equal>
<logic:notEqual value="" name="selectorData" property="nextObjId">
<a onclick="executeAction('extSelLoad.do', '<bean:write
name="selectorData"
property="moduleContextParam" /> &selectedObjId
<bean:writename="selectorData"property="nextObjId"/>');"><img
src='<bean:writename="selectorData"property="imageNext"/>'/></a>
</logic:notEqual>
</div>
</div>
</div>
First the tile context attribute under which the major bean containing all properties, filled in the controller, is imported into the current context with the use of <tiles:useAttribute>tag. The component is organized in three separate <div> elements – one for total items count, one for the current item description, and another one for navigation buttons (previous and next buttons).
The first two divs are simple <bean:write> tags – just printing translations and data.
The navigation <div> contains a translation title and two buttons. Since previous item may not be present, first a check is done if the prevObjId property is empty. If it is empty, only an image HTML element is put which URL set to the inactive previous resource. If previous item ID was supplied, then the active previous image resource is placed in an anchor element. Clicking this anchor will result in sending a request to the server to an action, which will change the currently selected form. We name this new action extSelLoad.do and will implement it in one of the next steps. It needs as parameter the ID of the form that needs to be set as current. We name this request
parameter selectedObjId and set it to the ID of the previous form. In order to trigger the action, the executeAction() JavaScript function, supplied by the ConfiguratorUI, is called. We also add
a moduleContextParameter provided by the ConfiguratorUI or catalogUI service. This parameter is mandatory and allows the UI to identify if the action is a Configurator or a Catalog action.
** Note:** The executeAction function has two parameters.
- The first parameter is the context-relative URL to which the Ajax request will be made.
- The second parameter is a string representing all the parameter name-value pairs, concatenated with “&”, which will be send with the request.Warning: Since R8 version, the second parameter must contain the moduleContext parameter. Pair parameterName/Value is built by the CatalogUI/ConfiguratorUI service in the controller
The same is done for the next button – if nextObjId property of the selector bean is empty, the resource for inactive next image is printed. If the property is not empty, a link is generated sending Ajax request to the same action, this time giving it the id of the next form as value of the
parameter selectedObjId.
No CDATA section is permitted inside any of the tile's HTML code because of the format in which the tiles are send back to the client for partial update – in a CDATA section of the XML response;
When JavaScript is present in the newly rendered tile, when the new tile HTML code is returned to the browser, all scripts sections are extracted and then executed one at a time with eval().
This way if you have some JavaScript variable, declared with “var”, this variable is not visible once the eval() function completes. I.e. This variable stays local to the single JavaScript section in which it is defined.
HOW TO ASSEMBLE THE TILE?
Once the controller and JSP are present, they can be assembled into a tile by creating a tile definition file. The file must be named tiles-defs.xml and must comply with the following structure:
<?xml version="1.0" encoding="ISO-8859-1" ?>
<tiles-definitions>
<!-- Tile for the external component -->
<definition name=".extSelector"
path="/externalComponents/externalSelector/jsp/externalSelector.jsp"
controllerClass="com.cameleon.demo.tiles.ExternalSelectorDemoController">
</definition>
</tiles-definitions>
Managing the actions
In our example, the goal of the external component is to set a next/previous element as the ‘current’ element of the configuration. The executeAction performing this manipulation is launched when the user clicks on the arrows of the selector.
When the executeAction() JavaScript function sends a request to the server, this request usually contains some parameters. In our example, the parameter of the executeAction is the ID of the object that has to bet set as ‘Current’.
Since R8 version, the second parameter must contain the moduleContext.
HOW TO CREATE A FORM BEAN?
Each action class receives a parameter of type HttpServletRequest that can be used to retrieve directly parameters from the HTTP request:
String selId = request.getParameter("selectedObjId");
Another solution is to use the Struts' form mapping mechanism. The form bean is a class, which complies with the JavaBeans specification – serializable class that has a public default constructor and getter/setter methods (so called properties). In order for a JavaBean to be used as a Struts form, it must subclass ActionForm class.
In the current example the form looks as follows:
package com.cameleon.demo.forms;
import org.apache.struts.action.ActionForm;
publicclass ExternalSelectorForm extends ActionForm {
privatestaticfinallongserialVersionUID = -2466458099277235794L;
/**
*IDoftheselectedobject
*/
private String m_selObjId;
/**
*Constructor
*/
public ExternalSelectorForm() {
m_selObjId = "";
}
/**
*Setthecurrentobjectid
*
*@paramselObjIdcurrentobjectid
*/
publicvoid setSelectedObjId(String selObjId) {
m_selObjId = selObjId;
}
/**
*Getthecurrentobjectid
*
*@returncurrentobjectid
*/
public String getSelectedObjId() {
returnm_selObjId;
}
}
The form bean encapsulates all the request parameters that the action expects. This gives a clearer view over the input of the action for which the form is created.
When a request is made to a given URL, Struts framework tries to find the Action, which is mapped to this URL. Then if a form bean is specified in the XML configuration file for this action, Struts creates an instance of the form bean and fills all request parameters (which have corresponding
properties) into the form with the data from the request. In the action, the developer can cast the form to the concrete form type, which has been configured, and then retrieve the properties, as follows:
ExternalSelectorForm extForm = (ExternalSelectorForm) form;
String selObjId = extForm.getSelectedObjId();
HOW TO CREATE AN ACTION?
Since the external component needs to change the selection when the client clicks on previous or next button, an action is required to process the request on server-side. The way to do this is by subclassing the AjaxUpdateAction and supplying implementation of the method:
protectedabstractvoid doModelUpdate(HttpServletRequest request,
ActionForm form);
The method receives the original HTTP request, as well as the form, which has been populated by the Struts framework – if such was configured for the action. The action contains the following code:
ExternalSelectorForm extForm = (ExternalSelectorForm) form;
String selObjId = extForm.getSelectedObjId();
LOGGER.debug("Change currently selected object with : \"" + selObjId + "\"");
ConfiguratorUIService configUIService = (ConfiguratorUIService) request.
getSession().getAttribute(ConfiguratorUIService.CONFIGURATOR_UI_SERVICE);
configUIService.setSelectedObjId(request, selObjId);
First the form is cast to the appropriate subtype, which is configured for the action. Then the request parameter is retrieved from the form by using the corresponding property of the form bean. Configurator UI Service Layer is retrieved in the same way in which it is done in the tile’s controller. The service setSelectedObjId () is called in order for the action to change the currently selected object.
Adding message into the Configurator UI’s Information box
Configurator UI has a special tile, called Information box, which is used for presenting different kinds of messages to the end client. Configurator UI Service Layer has a specific service, which the external component developer can call in order to use this mechanism.
In this external component, an information message will be added in action – on successful selection change. The following code is added in the controller, where configUIService is an instance of
the Configurator UI Service Layer:
configUIService.addFrontEndMessage(
request,
FrontEndMessage.Type.DOMAIN_VALUE,
FrontEndMessage.Severity.INFO,
"vtExtlSel.info.selChangedOk",
"Selection changed.",
null);
There are four different message types:
FrontEndMessage.Type.DOMAIN_VALUEFrontEndMessage.Type.DOMAIN_CONTROLFrontEndMessage.Type.CONSTRAINTFrontEndMessage.Type.EXECUTION
The external component developer may choose between different severity levels for the messages added in the information box. The severity level is connected to a different presentation of the message. Each severity level has different CSS style – for example errors’ text may be bold and in red, while information messages may be in the normal font weight and blue text color. Possible severity levels are:
FrontEndMessage.Severity.INFOFrontEndMessage.Severity.WARNINGFrontEndMessage.Severity.ERROR
The fourth parameter of the function is the translation key. This translation key must be presented in the custom translations section of the Layout XML file. In case this key is not found, the default translation will be used. This default text is the fifth parameter of the function.
The last parameter is an array, containing all values, which string representation to be substituted inside the message.
Example: if the translation is “You must specify a number between {0} and {1}.”, then the array may be : new Object[] { Integer.valueOf(1), Integer.valueOf(10)}.
For more details, look at the Java API Specification for the class java.text.MessageFormat.
Exception handling inside Ajax actions
Each exception, thrown inside Ajax action, is caught inside a base class of the AjaxUpdateAction. The normal processing is used – affected tiles, if such are present, are inserted into the response. Information flyer is always refreshed. If information should be visible about the error, then it should be put into information box as a message. If this is done, when exception is thrown in the external action, on the client side the information box will be displayed and will contain the error information. If no information is added to the box, then no information flyer will be displayed.
HOW TO CONFIGURE AN ACTION?
If actions are present in the external component then the struts-config.xml file must be supplied.
<?xml version="1.0" encoding="UTF-8" ?>
<struts-config>
<!-- ========== Form Bean Definitions ======================== -->
<form-beans>
<form-bean
name="externalSelectorForm"
type="com.cameleon.demo.forms.ExternalSelectorForm"
/>
</form-beans>
<!-- ========== Action Mapping Definitions =================== -->
<action-mappings>
<action
path="/extSelLoad"
type="com.cameleon.demo.actions.ExternalSelectorAction"
name="externalSelectorForm"
scope="request"
validate="false"
/>
</action-mappings>
</struts-config>
The first section of the XML specifies the package qualified names of the form used in the action. The second section parameterizes the action.
Attributes are as follows:
- path – the URL which will be used to call the action; in the given example, the URL will be
„/extSelLoad.do“ since the “do” postfix is the default one for Struts. This URL is the one that was used in the JSP of the selector – to generate the Ajax call to the action.
- type – the package qualified name of the action class
- name – the name of the form in the form-beans section of the XML (which is given to the action's doModelUpdate method). In this form-beans section, a mapping between the form logical name and its class is done;
- scope – where Struts should put the form on its creation and population with data;Tip: Keep all forms in the ‘request’ since this data is usually not needed on a larger scope.
- validate – set to true if the validate method of the action associated with this mapping should be called;Tip: This struts-config.xml will be merged with the Configurator UI struts-config.xml when deploying the application.
Packaging and deploying the component
The only difference with the basic component is the name of the pages folder under which the public resources of the component are located – The directory is cameleonUI.war/externalComponents/externalSelectorDemo
Running the new component
The newly deployed external component can now be plugged into the Layout XML file.
For example, if the tile is supposed to be part of the left part of the page, it should be specified as:
<externalBox mode="on" tile=".extSelct" cssName="vtExtSel" widthPercent="100"
heightPercent="15" autosize="off">
<param name="titleTranslationName" value="vtExtlSel.title" />
<param name="titleTranslationTotalNum" value="vtExtlSel.totalNumDescr" />
<param name="imgPreviousOn" value="vtExtlSel.previousOn" />
<param name="imgPreviousOff" value="vtExtlSel.previousOff" />
<param name="imgNextOn" value="vtExtlSel.nextOn" />
<param name="imgNextOff" value="vtExtlSel.nextOff" />
</externalBox>
The value of the “tile” attribute should match the “name” attribute in the definition tag of the tiles-defs.xml, which is supplied with the external component.As for the simple external selector, the “cssName” is used to specify the CSS file to be associated to the tile:
<layout>
<localizedLayout lang="EN,FR">
<!-- ... -->
<componentStyleSheet>
<!-- ... -->
<css name="vtExtSel">externalComponents/externalSelectorDemo/css/
style.css</css>
</componentStyleSheet>
</localizedLayout>
</layout>
The value of each translation parameter must correspond to a key in the custom translations section of the XML, to which key a translation is mapped:
<translations>
<localizedTranslations lang="EN,US,IT,ES,GE">
<genTranslations>
<!-- translations used on multiple places -->
</genTranslations>
<customTranslations>
<translation name="vtExtlSel.title">Navigation</translation>
<translation name="vtExtlSel.totalNumDescr">Total forms count:
</translation>
<translation name="vtExtlSel.info.selChangedOk">Selection changed
</translation>
</customTranslations>
</localizedTranslations>
</translations>
The last of the translations is the one used in the tile’s action.
Each resource parameter value must correspond to a key in the localized resources section in the XML:
<resources>
<localizedResources lang="FR,EN,US,IT,ES,GE">
<resource name=
"vtExtlSel.nextOn">externalComponents/externalSelectorDemo/images/next_on.gif
</resource>
<resource name=
"vtExtlSel.nextOff">externalComponents/externalSelectorDemo/images/next_off.gif
</resource>
<resource name=
"vtExtlSel.previousOn">externalComponents/externalSelectorDemo/images/
previous_on.gif</resource>
<resource name=
"vtExtlSel.previousOff">externalComponents/externalSelectorDemo/images/
previous_off.gif</resource>
</localizedResources>
</resources>
We show hereafter what HTML code is generated for the advanced external component tile.
It contains the code, which adds the CSS resource, associated to the tile, into the document’s head and another <div>. Unique tile ID is generated and put as HTML ID attribute of this internal <div>, which in turn contains all the code generated by the tile’s JSP.
Moreover the “class” attribute of this <div> is set to the same "****cssName***" attribute in the external component’s definition.
