Creating custom views
View types
There are two general types of table views: list model views and detail model views. Read more about list and detail models: Data Models
List model views
A list model view shows the entries of a root entity and uses a list model to load this entries. The basis class of all list model table views is ListModelTableView. A list model view works only with a repository root entity.
The most popular example of a list model view is the item view:
A list model view can load entries for a selection (e.g. items of a selected structure group) or all available entries of the specified root entity (e.g. structure systems).
Detail model views
A detail model view shows the details of an element of the superordinate entity. A detail model view uses an entity detail model to load the entries. The basis class of all detail model table views is EntityDetailModelTableView.
An example of a detail model view is the item attribute view:
A detail model view loads entries for a selected element of the superordinate entity (e.g. attributes of a selected item).
View contribution
To contribute your custom view use the extension point "org.eclipse.ui.views".
In the "Extension Element Details" the properties of your view can be set.
key |
value |
id |
Unique id for the view. |
name |
Translatable name for the view. When starting the name with the % sign it is indicating that a variable in a properties file in the plugin directory is used. |
class |
The class value is build like this: <used factory class>:<used tableview>:<entity> <used factory class> should be "com.heiler.ppm.std.core.contribution.ContributionClassFactory" in every case. <used tableview> depending if the entity is a root entity or a sub entity a different TableView can be used. <entity> has to be the used entity. |
category (optional) |
Category in which the view can be found in the UI. |
icon (optional) |
Relative path to the icon which will be shown beside the name of the view. |
If you also want to replace an existing standard view with your own custom view - use the extension point "com.heiler.ppm.std.core.contributionClassProviders" (see more details here: Possibility to replace the execution class of a view contribution)
Be attentive defining the "class" attribute of your view contribution. Since PIM version 7.1.08 (resp. 8.0.03.02.00) there is extended "frontend visibility" functionality: Frontend visibility. The generic implementation of the HiddenElementProvider hides all views which are contributed for deactivated entities. Unfortunately it cannot be differentiated whether an entity for a specifc identifier is deactivated or doesn't exist at all. So if your contribution "class" contains any string in place of the entity identifier - the view will not be shown (see the last row in the example below). The entity identifier is expected directly after the view class name following by a colon (":"), e.g.:
com.heiler.ppm.std.core.contribution.ContributionClassFactory:com.heiler.ppm.article.ui.internal.view.ArticleTableView:Article
// <- will be hidden if the "Article" entity is deactivated
com.heiler.ppm.std.core.contribution.ContributionClassFactory:com.heiler.ppm.task.ui.view.TaskTreeView:Task;task_treeview.html
// <- will be hidden if the "Task" entity is deactivated
com.heiler.ppm.std.core.contribution.ContributionClassFactory:com.heiler.ppm.std.ui.table.EntityDetailModelTableView:GDSNTargetMarketExtension
// <- will be hidden if the "GDSNTargetMarketExtension" entity is deactivated
com.heiler.ppm.std.core.contribution.ContributionClassFactory:com.heiler.ppm.contextselection.ui.views.ContextSelectionTreeView
// <-- no entity defined = never hidden
com.heiler.ppm.fulltextsearch.ui.index.views.FulltextSearchIndexViewImpl
// <-- no entity defined = never hidden
com.heiler.ppm.std.core.contribution.ContributionClassFactory:com.heiler.ppm.structure.ui.internal.views.mapping.StructureGroupMappingTableView:SourceTree
// <-- there is no repository entity 'SourceTree' - so this view will be hidden
PermissionProvider
The visibility of the views is depending on the corresponding action rights (user permissions). To make sure, your custom view can be viewed by the user, add a corresponding entry in the PermissionProvider. The permission provider is a class which extends the com.heiler.ppm.security.core.permission.PermissionProviderBaseImpl. The permission provider "links" a view or a perspective with a specific action right. Typically the visibility of views and perspectives is depending on the "read" permission of the corresponding entity:
/**
* Adds Permissions for custom views and perspectives
* @author WMichel
* @since 7.1
*/
public
class
CustomUiPermissionProvider
extends
PermissionProviderBaseImpl
{
private
static
final
String PERMISSION_BUNDLE_NAME =
"com.heiler.ppm.customizing.ui.permission.permissions"
;
//$NON-NLS-1$
public
CustomUiPermissionProvider()
{
RepositoryService repositoryService = RepositoryComponent.getRepositoryService();
Entity channelEntity = repositoryService.getEntityByIdentifier( ChannelConst.E_CHANNEL );
String channelReadPermission = channelEntity.getPermissions()
.getRead();
addViewPermission(
"hlr.customizing.ui.view.customListModelTableView"
, channelReadPermission );
//$NON-NLS-1$
Entity articleEntity = repositoryService.getEntityByIdentifier( ArticleCoreConst.ENTITY_ARTICLE );
String articleReadPermission = articleEntity.getPermissions()
.getRead();
addViewPermission(
"hlr.customizing.ui.view.customEntityDetailModelTableView"
, articleReadPermission );
//$NON-NLS-1$
}
@Override
protected
ResourceBundle getBundle( Locale locale )
{
return
ResourceBundle.getBundle( PERMISSION_BUNDLE_NAME, locale );
}
}
The permission provider should be also contributed in the corresponding custom plugin:
<
extension
point
=
"com.heiler.ppm.security.core.permissionProviders"
>
<
permissionProvider
class
=
"com.heiler.ppm.customizing.ui.permission.CustomUiPermissionProvider"
name
=
"Custom UI Permission Provider"
>
</
permissionProvider
>
</
extension
>
Overwriting view methods
The most functionalities of list model and detail model views are generic and are implemented in the basis view class. Nevertheless it is necessary to override some methods of the view class.
initEntityMapping()
Since all field based views work entity-based, it is necessary to define for which repository entity the custom view is created. To do this you can override the method initEntityMapping() and set the corresponding entity identifier:
@Override
protected
void
initEntityMapping()
{
setEntityIdentifier( ChannelConst.E_CHANNEL );
}
Note: for all detail model views you can alternatively define the entity identifier within the view contribution. Just add the entity identifier to the class name after a semicolon (e.g. com.heiler.ppm.article.ui.internal.view.attribute.ArticleAttributeTableView:ArticleAttribute)
getEntityForPermissions()
The default permissions (edit/create/delete) for the views are initialized in a generic way on the basis of the "permission" entity. To determine which entity should be used for the permission initialization the method getEntityForPermissions() is called. You can override this method to provide the entity for which the default permissions should be determined:
@Override
public
Entity getEntityForPermissions()
{
Entity entity =
super
.getEntityForPermissions();
if
( entity ==
null
)
{
entity = RepositoryComponent.getRepositoryService()
.getEntityByIdentifier( ChannelConst.E_CHANNEL );
}
return
entity;
}
Note: for all detail model views it is not necessary to override this method since the entity which is defined in the view contribution will be used for the permission initialization.
initPermissions()
To initialize additional permissions for the view you can override the method initPermissions() where the default permissions (edit/create/delete) are initialized:
@Override
protected
void
initPermissions()
{
super
.initPermissions();
// <- call 'super' to init default permissions
LoginToken login = LoginManager.getInstance()
.getLoginToken();
this
.permissionAdd = login.hasPermission( DictionaryCorePermissionProvider.PERMISSION_WORD_ADD );
this
.permissionSuggest = login.hasPermission( DictionaryCorePermissionProvider.PERMISSION_WORD_SUGGEST );
}
createContentProvider()
Especially for list model views you should override the method createContentProvider() to ensure that the new created entries (e.g. created by other clients) appears in your custom view. Therefore you should create a ListModelContentProvider and override the queryAddNewItem() method:
@Override
protected
IStructuredContentProvider createContentProvider()
{
EntityManager entityManager = EntityManagementComponent.getManagerRegistry()
.getEntityManager( ChannelConst.ET_CHANNEL );
return
new
ListModelContentProvider( entityManager,
false
,
true
)
{
@Override
protected
boolean
queryAddNewItem( EntityOperationOriginator originator, EntityProxy entityProxy )
throws
Exception
{
return
true
;
}
};
}
createDatasetCreationMechanism()
If you want to customize the data set new creation process you can override the method createDatasetCreationMechanism(). Here you can provide a custom implementation of the ICreateMechanism. For the list model views there is a default implementation - ListModelTableViewCreateMechanism and for detail model views - EntityDetailModelTableViewCreateMechanism. If you want, for example, to manipulate the entity properties for the creation of a new entry, you can override the method getEntityProperties() in the custom implementation of the ICreateMechanism:
@Override
protected
ICreateMechanism createDatasetCreationMechanism()
{
return
new
ListModelTableViewCreateMechanism(
this
)
{
@Override
protected
EntityProperty[] getEntityProperties()
throws
CoreException
{
Entity entity = getEntity();
EntityProperty[] initValues =
new
EntityProperty[
1
];
initValues[
0
] = EntityPropertyFactory.create( entity, ChannelConst.FT_IDENTIFIER,
String.valueOf( System.currentTimeMillis() ) );
return
initValues;
}
};
}
initTableConfigGroup()
If there are several views which work with the same entity they will share the table layout configuration, because the layout configuration is stored by default under the name of the entity. You can override the method initTableConfigGroup() in your custom view to define another table layout config identifier:
@Override
protected
void
initTableConfigGroup( String tableConfigGroupIdentifier )
{
// ignore the tableConfigGroupIdentifier - we need our own!
tableConfigGroupIdentifier = CONFIG_IDENTIFIER;
super
.initTableConfigGroup( tableConfigGroupIdentifier );
}
Customization opportunities
Selection handling
If the custom view should react to selection events from the other views, there are several opportunities to manipulate the selection handling in the view.
Selection predicate
The selection predicate is used to determine whether a view should react to a specific selection. When a view has been opened by the user it tries to load it's content on the basis of the currently selection in other visible views. Therefor the view uses it's selection predicate and asks the selection service to determine which selection is relevant for the view. Read more here about selection predicates in custom views.
selectionChanged()
All detail model views handle by default all selection events in the selectionChanged() method. The default implementation uses the selection predicate to determine if the specific selection is relevant for the view. If true, the detail model of the selected entry will be loaded in the view. For the list model views there is no default implementaion of the selectionChanged() method in the basis class. So if your custom view should handle any selections, you have to implement this method. Here is an example from the word view which reacts to the dictionary selection and loads all words for the selected dictionary:
@Override
public
void
selectionChanged( IWorkbenchPart selectionPart, ISelection selection )
{
if
( selectionPart ==
this
|| DataSetUIAdapter.isDirty( getDataSetUIAdapter() ) )
{
return
;
}
// use selection predicate to evaluate the given selection
Predicate< ISelection > selectionPredicate = getSelectionPredicate();
if
( selectionPredicate.evaluate( selection ) )
{
DictionaryProxy dictionaryProxy = StdUiUtils.getSingleSelectedElement( selection, DictionaryProxy.
class
);
if
( dictionaryProxy !=
null
)
{
// Avoid unnecessary requests
if
( ObjectUtils.equals( dictionaryProxy,
this
.lastHandledSelection ) )
{
return
;
}
this
.lastHandledSelection = dictionaryProxy;
this
.lastReceivedSelectionPart = selectionPart;
// enable edit mode
setEditMode(
this
.permissionEdit ? EDIT_ENABLED : EDIT_DISABLED );
reloadWordsForPrefix();
}
}
else
if
( selectionPart.equals(
this
.lastReceivedSelectionPart ) )
{
handleEmptySelection( selection );
setEditMode( EDIT_DISABLED );
}
}
Data maintenance
Data loading
The most views load the content on the basis of a received selection. But some views have no selection provider. In this case all entries of the corresponding entity should be shown initial in the view. To do that you can override the postCreate() method and load the data:
@Override
protected
void
postCreate()
{
super
.postCreate();
// fill table with all channels
SearchParameters searchParams =
new
SearchParameters( DataSource.MAIN.getIdentifier(), ChannelConst.ET_CHANNEL,
null
);
// 'null' expression means: 'all elements'
SearchQueryItemList query =
new
SearchQueryItemList( getEntity(), searchParams );
queryViewerInput( query );
}
Note: if you extend the GenericDataTableView this step is already implemented in the basis class.
Data editing
By default the editing behavior of the fields based on the repository settings. The TableCellAPI has control about this. It determines whether a specific field is editable, which value is shown in the field and what happens when the value has been changed. For the most views suffices the default implementation in the view basis classes. But if you want to customize the editing behavior in you custom view, you can manipulate the TableCellAPI:
@Override
protected
TableCellAPI createTableCellAPI()
{
ListModelTableCellAPI cellAPI =
new
ListModelTableCellAPI(
this
);
TableCellValueDecorator valueProvider =
new
TableCellValueDecorator( cellAPI.getValueProvider() )
{
@Override
public
boolean
canModify( Object rowElement, String colProperty )
{
return
false
;
}
};
cellAPI.setValueProvider( valueProvider );
return
cellAPI;
}
In this example all fields of the view will be read-only (independent on the corresponding repository settings).
Example ListModelTableView
In the example below we have a custom list model table view for the Channel entity. There are following customization in the view:
we override the method postCreate() to load all available channels after the view has been created
then we override the content provider to ensure that all new created channels (maybe created by other clients) apper in the view automatically
the getEntityForPermissions() method is overriden to provide the entity for the permissions initialization
in the initEntityMapping() method we define the entity for which the view has been created
and lastly we customize the ICreateMechanism to ensure that all new created channels get a default unique identifier (just the current time in millis)
This sample class is also contained in the SDK examples (since 7.1)
/**
* A sample custom implementation of the {@link ListModelTableView} which works with the {@link Channel} entity.
* @author WMichel
* @since 7.1
*/
public
class
CustomListModelTableView
extends
ListModelTableView
{
public
CustomListModelTableView()
{
super
();
}
@Override
protected
void
postCreate()
{
super
.postCreate();
// fill table with all channels
SearchParameters searchParams =
new
SearchParameters( DataSource.MAIN.getIdentifier(), ChannelConst.ET_CHANNEL,
null
);
// 'null' expression means: 'all elements'
SearchQueryItemList query =
new
SearchQueryItemList( getEntity(), searchParams );
queryViewerInput( query );
}
@Override
protected
IStructuredContentProvider createContentProvider()
{
EntityManager entityManager = EntityManagementComponent.getManagerRegistry()
.getEntityManager( ChannelConst.ET_CHANNEL );
return
new
ListModelContentProvider( entityManager,
false
,
true
)
{
@Override
protected
boolean
queryAddNewItem( EntityOperationOriginator originator, EntityProxy entityProxy )
throws
Exception
{
return
true
;
}
};
}
@Override
public
Entity getEntityForPermissions()
{
Entity entity =
super
.getEntityForPermissions();
if
( entity ==
null
)
{
entity = RepositoryComponent.getRepositoryService()
.getEntityByIdentifier( ChannelConst.E_CHANNEL );
}
return
entity;
}
@Override
protected
void
initEntityMapping()
{
setEntityIdentifier( ChannelConst.E_CHANNEL );
}
@Override
protected
ICreateMechanism createDatasetCreationMechanism()
{
return
new
ListModelTableViewCreateMechanism(
this
)
{
@Override
protected
EntityProperty[] getEntityProperties()
throws
CoreException
{
Entity entity = getEntity();
EntityProperty[] initValues =
new
EntityProperty[
1
];
initValues[
0
] = EntityPropertyFactory.create( entity, ChannelConst.FT_IDENTIFIER,
String.valueOf( System.currentTimeMillis() ) );
return
initValues;
}
};
}
}
Example EntityDetailModelTableView
In the example below we have a custom detail model table view for the ArticleSales sub-entity. There are following customization in the view:
we customize the ICreateMechanism to ensure that all new created ArticleSales entries get the "Sales" classifier and the "Ean" field applies the value of the parent item "Ean" by default
the createSelectionPredicate() is overriden to let the view only react to item selections (including multi-selections)
we overriden the handleEmptySelection() method to set a custom message to the view content description area if no item is selected
This sample class is also contained in the SDK examples (since 7.1)
/**
* A sample custom implementation of the {@link EntityDetailModelTableView} which works with the 'ArticleSales'
* sub-entity.
* @author WMichel
* @since 7.1
*/
public
class
CustomEntityDetailModelTableView
extends
EntityDetailModelTableView
{
@Override
protected
ICreateMechanism createDatasetCreationMechanism()
{
return
new
EntityDetailModelTableViewCreateMechanism(
this
)
{
@Override
protected
EntityProperty[] getEntityProperties()
{
Entity entity = getEntity();
LogicalKey logicalKey = entity.getLogicalKey(
"ArticleTradingType.LK.Classifier"
);
//$NON-NLS-1$
EntityProperty[] initValues =
new
EntityProperty[
2
];
initValues[
0
] = EntityPropertyFactory.create( logicalKey,
"Sales"
);
//$NON-NLS-1$
try
{
EDataObject articleObject = getParentObject();
Object articleEan = articleObject.get(
"ean"
);
//$NON-NLS-1$
initValues[
1
] = EntityPropertyFactory.create( entity,
"ArticleTradingType.Ean"
, articleEan );
//$NON-NLS-1$
}
catch
( CoreException e )
{
ErrorDialog.openError( getSite().getShell(), getPartName(), e.getLocalizedMessage(), e.getStatus() );
}
return
initValues;
}
};
}
@Override
protected
Predicate< ISelection > createSelectionPredicate()
{
return
new
SelectionPredicateFactory.EntityPredicate(
"Article"
,
true
);
//$NON-NLS-1$
}
@Override
protected
void
handleEmptySelection()
{
super
.handleEmptySelection();
StdViewDescriptionArea descriptionArea = ( StdViewDescriptionArea ) getTableContainer().getDescriptionArea();
descriptionArea.setContentDescription(
"No item selected"
);
//$NON-NLS-1$
}