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:

images/download/attachments/46270892/sample_item_view_.png

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:

images/download/attachments/46270892/sample_item_attribute_view_.png

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".

images/download/attachments/46270892/ViewContribMod.png

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$
}