Make ListableObjectComponentLoaderComponent extend AbstractComponentLoaderComponent

This commit is contained in:
Alexandre Vryghem
2023-06-10 15:45:59 +02:00
parent 2327513dd0
commit fb7afaddd0
13 changed files with 59 additions and 162 deletions

View File

@@ -1,4 +1,4 @@
<ng-template dsListableObject> <ng-template dsDynamicComponentLoader>
</ng-template> </ng-template>
<div #badges> <div #badges>
<ng-content></ng-content> <ng-content></ng-content>

View File

@@ -11,7 +11,7 @@ import { SearchResultGridElementComponent } from '../../../../../shared/object-g
import { TruncatableService } from '../../../../../shared/truncatable/truncatable.service'; import { TruncatableService } from '../../../../../shared/truncatable/truncatable.service';
import { BitstreamDataService } from '../../../../../core/data/bitstream-data.service'; import { BitstreamDataService } from '../../../../../core/data/bitstream-data.service';
import { GenericConstructor } from '../../../../../core/shared/generic-constructor'; import { GenericConstructor } from '../../../../../core/shared/generic-constructor';
import { ListableObjectDirective } from '../../../../../shared/object-collection/shared/listable-object/listable-object.directive'; import { DynamicComponentLoaderDirective } from '../../../../../shared/abstract-component-loader/dynamic-component-loader.directive';
import { ThemeService } from '../../../../../shared/theme-support/theme.service'; import { ThemeService } from '../../../../../shared/theme-support/theme.service';
import { DSONameService } from '../../../../../core/breadcrumbs/dso-name.service'; import { DSONameService } from '../../../../../core/breadcrumbs/dso-name.service';
@@ -25,7 +25,7 @@ import { DSONameService } from '../../../../../core/breadcrumbs/dso-name.service
* The component for displaying a list element for an item search result on the admin search page * The component for displaying a list element for an item search result on the admin search page
*/ */
export class ItemAdminSearchResultGridElementComponent extends SearchResultGridElementComponent<ItemSearchResult, Item> implements OnInit { export class ItemAdminSearchResultGridElementComponent extends SearchResultGridElementComponent<ItemSearchResult, Item> implements OnInit {
@ViewChild(ListableObjectDirective, { static: true }) listableObjectDirective: ListableObjectDirective; @ViewChild(DynamicComponentLoaderDirective, { static: true }) dynamicComponentLoaderDirective: DynamicComponentLoaderDirective;
@ViewChild('badges', { static: true }) badges: ElementRef; @ViewChild('badges', { static: true }) badges: ElementRef;
@ViewChild('buttons', { static: true }) buttons: ElementRef; @ViewChild('buttons', { static: true }) buttons: ElementRef;
@@ -46,7 +46,7 @@ export class ItemAdminSearchResultGridElementComponent extends SearchResultGridE
super.ngOnInit(); super.ngOnInit();
const componentFactory = this.componentFactoryResolver.resolveComponentFactory(this.getComponent()); const componentFactory = this.componentFactoryResolver.resolveComponentFactory(this.getComponent());
const viewContainerRef = this.listableObjectDirective.viewContainerRef; const viewContainerRef = this.dynamicComponentLoaderDirective.viewContainerRef;
viewContainerRef.clear(); viewContainerRef.clear();
const componentRef = viewContainerRef.createComponent( const componentRef = viewContainerRef.createComponent(

View File

@@ -1,4 +1,4 @@
<ng-template dsListableObject> <ng-template dsDynamicComponentLoader>
</ng-template> </ng-template>
<div #badges class="position-absolute ml-1"> <div #badges class="position-absolute ml-1">
<div class="workflow-badge"> <div class="workflow-badge">

View File

@@ -18,8 +18,8 @@ import {
ItemGridElementComponent ItemGridElementComponent
} from '../../../../../shared/object-grid/item-grid-element/item-types/item/item-grid-element.component'; } from '../../../../../shared/object-grid/item-grid-element/item-types/item/item-grid-element.component';
import { import {
ListableObjectDirective DynamicComponentLoaderDirective
} from '../../../../../shared/object-collection/shared/listable-object/listable-object.directive'; } from '../../../../../shared/abstract-component-loader/dynamic-component-loader.directive';
import { import {
WorkflowItemSearchResult WorkflowItemSearchResult
} from '../../../../../shared/object-collection/shared/workflow-item-search-result.model'; } from '../../../../../shared/object-collection/shared/workflow-item-search-result.model';
@@ -38,7 +38,7 @@ describe('WorkflowItemSearchResultAdminWorkflowGridElementComponent', () => {
let itemRD$; let itemRD$;
let linkService; let linkService;
let object; let object;
let themeService; let themeService: ThemeService;
function init() { function init() {
itemRD$ = createSuccessfulRemoteDataObject$(new Item()); itemRD$ = createSuccessfulRemoteDataObject$(new Item());
@@ -55,7 +55,11 @@ describe('WorkflowItemSearchResultAdminWorkflowGridElementComponent', () => {
init(); init();
TestBed.configureTestingModule( TestBed.configureTestingModule(
{ {
declarations: [WorkflowItemSearchResultAdminWorkflowGridElementComponent, ItemGridElementComponent, ListableObjectDirective], declarations: [
WorkflowItemSearchResultAdminWorkflowGridElementComponent,
ItemGridElementComponent,
DynamicComponentLoaderDirective,
],
imports: [ imports: [
NoopAnimationsModule, NoopAnimationsModule,
TranslateModule.forRoot(), TranslateModule.forRoot(),

View File

@@ -10,7 +10,7 @@ import { SearchResultGridElementComponent } from '../../../../../shared/object-g
import { TruncatableService } from '../../../../../shared/truncatable/truncatable.service'; import { TruncatableService } from '../../../../../shared/truncatable/truncatable.service';
import { BitstreamDataService } from '../../../../../core/data/bitstream-data.service'; import { BitstreamDataService } from '../../../../../core/data/bitstream-data.service';
import { GenericConstructor } from '../../../../../core/shared/generic-constructor'; import { GenericConstructor } from '../../../../../core/shared/generic-constructor';
import { ListableObjectDirective } from '../../../../../shared/object-collection/shared/listable-object/listable-object.directive'; import { DynamicComponentLoaderDirective } from '../../../../../shared/abstract-component-loader/dynamic-component-loader.directive';
import { WorkflowItem } from '../../../../../core/submission/models/workflowitem.model'; import { WorkflowItem } from '../../../../../core/submission/models/workflowitem.model';
import { Observable } from 'rxjs'; import { Observable } from 'rxjs';
import { LinkService } from '../../../../../core/cache/builders/link.service'; import { LinkService } from '../../../../../core/cache/builders/link.service';
@@ -38,7 +38,7 @@ export class WorkflowItemSearchResultAdminWorkflowGridElementComponent extends S
/** /**
* Directive used to render the dynamic component in * Directive used to render the dynamic component in
*/ */
@ViewChild(ListableObjectDirective, { static: true }) listableObjectDirective: ListableObjectDirective; @ViewChild(DynamicComponentLoaderDirective, { static: true }) dynamicComponentLoaderDirective: DynamicComponentLoaderDirective;
/** /**
* The html child that contains the badges html * The html child that contains the badges html
@@ -77,7 +77,7 @@ export class WorkflowItemSearchResultAdminWorkflowGridElementComponent extends S
this.item$.pipe(take(1)).subscribe((item: Item) => { this.item$.pipe(take(1)).subscribe((item: Item) => {
const componentFactory = this.componentFactoryResolver.resolveComponentFactory(this.getComponent(item)); const componentFactory = this.componentFactoryResolver.resolveComponentFactory(this.getComponent(item));
const viewContainerRef = this.listableObjectDirective.viewContainerRef; const viewContainerRef = this.dynamicComponentLoaderDirective.viewContainerRef;
viewContainerRef.clear(); viewContainerRef.clear();
const componentRef = viewContainerRef.createComponent( const componentRef = viewContainerRef.createComponent(

View File

@@ -1,4 +1,4 @@
<ng-template dsListableObject> <ng-template dsDynamicComponentLoader>
</ng-template> </ng-template>
<div #badges class="position-absolute ml-1"> <div #badges class="position-absolute ml-1">
<div class="workflow-badge"> <div class="workflow-badge">

View File

@@ -20,8 +20,8 @@ import {
ItemGridElementComponent ItemGridElementComponent
} from '../../../../../shared/object-grid/item-grid-element/item-types/item/item-grid-element.component'; } from '../../../../../shared/object-grid/item-grid-element/item-types/item/item-grid-element.component';
import { import {
ListableObjectDirective DynamicComponentLoaderDirective
} from '../../../../../shared/object-collection/shared/listable-object/listable-object.directive'; } from '../../../../../shared/abstract-component-loader/dynamic-component-loader.directive';
import { import {
WorkflowItemSearchResult WorkflowItemSearchResult
} from '../../../../../shared/object-collection/shared/workflow-item-search-result.model'; } from '../../../../../shared/object-collection/shared/workflow-item-search-result.model';
@@ -45,7 +45,7 @@ describe('WorkspaceItemSearchResultAdminWorkflowGridElementComponent', () => {
let itemRD$; let itemRD$;
let linkService; let linkService;
let object; let object;
let themeService; let themeService: ThemeService;
let supervisionOrderDataService; let supervisionOrderDataService;
function init() { function init() {
@@ -67,7 +67,11 @@ describe('WorkspaceItemSearchResultAdminWorkflowGridElementComponent', () => {
init(); init();
TestBed.configureTestingModule( TestBed.configureTestingModule(
{ {
declarations: [WorkspaceItemSearchResultAdminWorkflowGridElementComponent, ItemGridElementComponent, ListableObjectDirective], declarations: [
WorkspaceItemSearchResultAdminWorkflowGridElementComponent,
ItemGridElementComponent,
DynamicComponentLoaderDirective,
],
imports: [ imports: [
NoopAnimationsModule, NoopAnimationsModule,
TranslateModule.forRoot(), TranslateModule.forRoot(),

View File

@@ -16,9 +16,7 @@ import {
import { TruncatableService } from '../../../../../shared/truncatable/truncatable.service'; import { TruncatableService } from '../../../../../shared/truncatable/truncatable.service';
import { BitstreamDataService } from '../../../../../core/data/bitstream-data.service'; import { BitstreamDataService } from '../../../../../core/data/bitstream-data.service';
import { GenericConstructor } from '../../../../../core/shared/generic-constructor'; import { GenericConstructor } from '../../../../../core/shared/generic-constructor';
import { import { DynamicComponentLoaderDirective } from '../../../../../shared/abstract-component-loader/dynamic-component-loader.directive';
ListableObjectDirective
} from '../../../../../shared/object-collection/shared/listable-object/listable-object.directive';
import { WorkspaceItem } from '../../../../../core/submission/models/workspaceitem.model'; import { WorkspaceItem } from '../../../../../core/submission/models/workspaceitem.model';
import { LinkService } from '../../../../../core/cache/builders/link.service'; import { LinkService } from '../../../../../core/cache/builders/link.service';
import { followLink } from '../../../../../shared/utils/follow-link-config.model'; import { followLink } from '../../../../../shared/utils/follow-link-config.model';
@@ -67,7 +65,7 @@ export class WorkspaceItemSearchResultAdminWorkflowGridElementComponent extends
/** /**
* Directive used to render the dynamic component in * Directive used to render the dynamic component in
*/ */
@ViewChild(ListableObjectDirective, { static: true }) listableObjectDirective: ListableObjectDirective; @ViewChild(DynamicComponentLoaderDirective, { static: true }) dynamicComponentLoaderDirective: DynamicComponentLoaderDirective;
/** /**
* The html child that contains the badges html * The html child that contains the badges html
@@ -102,7 +100,7 @@ export class WorkspaceItemSearchResultAdminWorkflowGridElementComponent extends
this.item$.pipe(take(1)).subscribe((item: Item) => { this.item$.pipe(take(1)).subscribe((item: Item) => {
const componentFactory = this.componentFactoryResolver.resolveComponentFactory(this.getComponent(item)); const componentFactory = this.componentFactoryResolver.resolveComponentFactory(this.getComponent(item));
const viewContainerRef = this.listableObjectDirective.viewContainerRef; const viewContainerRef = this.dynamicComponentLoaderDirective.viewContainerRef;
viewContainerRef.clear(); viewContainerRef.clear();
const componentRef = viewContainerRef.createComponent( const componentRef = viewContainerRef.createComponent(

View File

@@ -1 +0,0 @@
<ng-template dsListableObject></ng-template>

View File

@@ -8,7 +8,7 @@ import { ViewMode } from '../../../../core/shared/view-mode.model';
import { import {
ItemListElementComponent ItemListElementComponent
} from '../../../object-list/item-list-element/item-types/item/item-list-element.component'; } from '../../../object-list/item-list-element/item-types/item/item-list-element.component';
import { ListableObjectDirective } from './listable-object.directive'; import { DynamicComponentLoaderDirective } from '../../../abstract-component-loader/dynamic-component-loader.directive';
import { TranslateModule } from '@ngx-translate/core'; import { TranslateModule } from '@ngx-translate/core';
import { By } from '@angular/platform-browser'; import { By } from '@angular/platform-browser';
import { provideMockStore } from '@ngrx/store/testing'; import { provideMockStore } from '@ngrx/store/testing';
@@ -36,7 +36,7 @@ describe('ListableObjectComponentLoaderComponent', () => {
}); });
TestBed.configureTestingModule({ TestBed.configureTestingModule({
imports: [TranslateModule.forRoot()], imports: [TranslateModule.forRoot()],
declarations: [ListableObjectComponentLoaderComponent, ItemListElementComponent, ListableObjectDirective], declarations: [ListableObjectComponentLoaderComponent, ItemListElementComponent, DynamicComponentLoaderDirective],
schemas: [NO_ERRORS_SCHEMA], schemas: [NO_ERRORS_SCHEMA],
providers: [ providers: [
provideMockStore({}), provideMockStore({}),
@@ -65,7 +65,7 @@ describe('ListableObjectComponentLoaderComponent', () => {
describe('When the component is rendered', () => { describe('When the component is rendered', () => {
it('should call the getListableObjectComponent function with the right types, view mode and context', () => { it('should call the getListableObjectComponent function with the right types, view mode and context', () => {
expect(comp.getComponent).toHaveBeenCalledWith([testType], testViewMode, testContext); expect(comp.getComponent).toHaveBeenCalled();
}); });
it('should connectInputsAndOutputs of loaded component', () => { it('should connectInputsAndOutputs of loaded component', () => {
@@ -78,29 +78,29 @@ describe('ListableObjectComponentLoaderComponent', () => {
let reloadedObject: any; let reloadedObject: any;
beforeEach(() => { beforeEach(() => {
spyOn((comp as any), 'instantiateComponent').and.returnValue(null); spyOn(comp, 'instantiateComponent').and.returnValue(null);
spyOn((comp as any).contentChange, 'emit').and.returnValue(null); spyOn(comp.contentChange, 'emit').and.returnValue(null);
listableComponent = fixture.debugElement.query(By.css('ds-item-list-element')).componentInstance; listableComponent = fixture.debugElement.query(By.css('ds-item-list-element')).componentInstance;
reloadedObject = 'object'; reloadedObject = 'object';
}); });
it('should re-instantiate the listable component', fakeAsync(() => { it('should re-instantiate the listable component', fakeAsync(() => {
expect((comp as any).instantiateComponent).not.toHaveBeenCalled(); expect(comp.instantiateComponent).not.toHaveBeenCalled();
(listableComponent as any).reloadedObject.emit(reloadedObject); listableComponent.reloadedObject.emit(reloadedObject);
tick(200); tick(200);
expect((comp as any).instantiateComponent).toHaveBeenCalledWith(reloadedObject, undefined); expect(comp.instantiateComponent).toHaveBeenCalledWith(undefined);
})); }));
it('should re-emit it as a contentChange', fakeAsync(() => { it('should re-emit it as a contentChange', fakeAsync(() => {
expect((comp as any).contentChange.emit).not.toHaveBeenCalled(); expect(comp.contentChange.emit).not.toHaveBeenCalled();
(listableComponent as any).reloadedObject.emit(reloadedObject); listableComponent.reloadedObject.emit(reloadedObject);
tick(200); tick(200);
expect((comp as any).contentChange.emit).toHaveBeenCalledWith(reloadedObject); expect(comp.contentChange.emit).toHaveBeenCalledWith(reloadedObject);
})); }));
}); });

View File

@@ -1,40 +1,26 @@
import { import { ChangeDetectorRef, Component, EventEmitter, Input, Output, SimpleChanges } from '@angular/core';
ChangeDetectorRef, import { combineLatest, Observable, of as observableOf } from 'rxjs';
Component,
ComponentRef,
EventEmitter,
Input,
OnChanges,
OnDestroy,
OnInit,
Output,
SimpleChanges,
ViewChild
} from '@angular/core';
import { Subscription, combineLatest, of as observableOf, Observable } from 'rxjs';
import { take } from 'rxjs/operators'; import { take } from 'rxjs/operators';
import { ListableObject } from '../listable-object.model'; import { ListableObject } from '../listable-object.model';
import { ViewMode } from '../../../../core/shared/view-mode.model'; import { ViewMode } from '../../../../core/shared/view-mode.model';
import { Context } from '../../../../core/shared/context.model'; import { Context } from 'src/app/core/shared/context.model';
import { getListableObjectComponent } from './listable-object.decorator'; import { getListableObjectComponent } from './listable-object.decorator';
import { GenericConstructor } from '../../../../core/shared/generic-constructor'; import { GenericConstructor } from '../../../../core/shared/generic-constructor';
import { ListableObjectDirective } from './listable-object.directive';
import { CollectionElementLinkType } from '../../collection-element-link.type'; import { CollectionElementLinkType } from '../../collection-element-link.type';
import { hasValue, isNotEmpty, hasNoValue } from '../../../empty.util';
import { DSpaceObject } from '../../../../core/shared/dspace-object.model'; import { DSpaceObject } from '../../../../core/shared/dspace-object.model';
import { ThemeService } from '../../../theme-support/theme.service'; import { AbstractComponentLoaderComponent } from '../../../abstract-component-loader/abstract-component-loader.component';
import { ThemeService } from 'src/app/shared/theme-support/theme.service';
@Component({ @Component({
selector: 'ds-listable-object-component-loader', selector: 'ds-listable-object-component-loader',
styleUrls: ['./listable-object-component-loader.component.scss'], styleUrls: ['./listable-object-component-loader.component.scss'],
templateUrl: './listable-object-component-loader.component.html' templateUrl: '../../../abstract-component-loader/abstract-component-loader.component.html',
}) })
/** /**
* Component for determining what component to use depending on the item's entity type (dspace.entity.type) * Component for determining what component to use depending on the item's entity type (dspace.entity.type)
*/ */
export class ListableObjectComponentLoaderComponent implements OnInit, OnChanges, OnDestroy { export class ListableObjectComponentLoaderComponent extends AbstractComponentLoaderComponent<Component> {
/** /**
* The item or metadata to determine the component for * The item or metadata to determine the component for
*/ */
@@ -80,99 +66,36 @@ export class ListableObjectComponentLoaderComponent implements OnInit, OnChanges
*/ */
@Input() value: string; @Input() value: string;
/**
* Directive hook used to place the dynamic child component
*/
@ViewChild(ListableObjectDirective, { static: true }) listableObjectDirective: ListableObjectDirective;
/** /**
* Emit when the listable object has been reloaded. * Emit when the listable object has been reloaded.
*/ */
@Output() contentChange = new EventEmitter<ListableObject>(); @Output() contentChange = new EventEmitter<ListableObject>();
/**
* Array to track all subscriptions and unsubscribe them onDestroy
* @type {Array}
*/
protected subs: Subscription[] = [];
/**
* The reference to the dynamic component
*/
protected compRef: ComponentRef<Component>;
/** /**
* The list of input and output names for the dynamic component * The list of input and output names for the dynamic component
*/ */
protected inAndOutputNames: string[] = [ protected inAndOutputNames: (keyof this)[] = [
...this.inAndOutputNames,
'object', 'object',
'index', 'index',
'linkType', 'linkType',
'listID', 'listID',
'showLabel', 'showLabel',
'showThumbnails', 'showThumbnails',
'context',
'viewMode', 'viewMode',
'value', 'value',
'hideBadges',
'contentChange', 'contentChange',
]; ];
constructor(private cdr: ChangeDetectorRef, private themeService: ThemeService) { constructor(
protected themeService: ThemeService,
protected cdr: ChangeDetectorRef,
) {
super(themeService);
} }
/** public instantiateComponent(changes?: SimpleChanges): void {
* Setup the dynamic child component super.instantiateComponent(changes);
*/
ngOnInit(): void {
this.instantiateComponent(this.object);
}
/**
* Whenever the inputs change, update the inputs of the dynamic component
*/
ngOnChanges(changes: SimpleChanges): void {
if (hasNoValue(this.compRef)) {
// sometimes the component has not been initialized yet, so it first needs to be initialized
// before being called again
this.instantiateComponent(this.object, changes);
} else {
// if an input or output has changed
if (this.inAndOutputNames.some((name: any) => hasValue(changes[name]))) {
this.connectInputsAndOutputs();
if (this.compRef?.instance && 'ngOnChanges' in this.compRef.instance) {
(this.compRef.instance as any).ngOnChanges(changes);
}
}
}
}
ngOnDestroy() {
this.subs
.filter((subscription) => hasValue(subscription))
.forEach((subscription) => subscription.unsubscribe());
}
private instantiateComponent(object: ListableObject, changes?: SimpleChanges): void {
const component = this.getComponent(object.getRenderTypes(), this.viewMode, this.context);
const viewContainerRef = this.listableObjectDirective.viewContainerRef;
viewContainerRef.clear();
this.compRef = viewContainerRef.createComponent(
component, {
index: 0,
injector: undefined
}
);
if (hasValue(changes)) {
this.ngOnChanges(changes);
} else {
this.connectInputsAndOutputs();
}
if ((this.compRef.instance as any).reloadedObject) { if ((this.compRef.instance as any).reloadedObject) {
combineLatest([ combineLatest([
observableOf(changes), observableOf(changes),
@@ -181,7 +104,7 @@ export class ListableObjectComponentLoaderComponent implements OnInit, OnChanges
if (reloadedObject) { if (reloadedObject) {
this.compRef.destroy(); this.compRef.destroy();
this.object = reloadedObject; this.object = reloadedObject;
this.instantiateComponent(reloadedObject, simpleChanges); this.instantiateComponent(simpleChanges);
this.cdr.detectChanges(); this.cdr.detectChanges();
this.contentChange.emit(reloadedObject); this.contentChange.emit(reloadedObject);
} }
@@ -189,26 +112,8 @@ export class ListableObjectComponentLoaderComponent implements OnInit, OnChanges
} }
} }
/** public getComponent(): GenericConstructor<Component> {
* Fetch the component depending on the item's entity type, view mode and context return getListableObjectComponent(this.object.getRenderTypes(), this.viewMode, this.context, this.themeService.getThemeName());
* @returns {GenericConstructor<Component>}
*/
getComponent(renderTypes: (string | GenericConstructor<ListableObject>)[],
viewMode: ViewMode,
context: Context): GenericConstructor<Component> {
return getListableObjectComponent(renderTypes, viewMode, context, this.themeService.getThemeName());
}
/**
* Connect the in and outputs of this component to the dynamic component,
* to ensure they're in sync
*/
protected connectInputsAndOutputs(): void {
if (isNotEmpty(this.inAndOutputNames) && hasValue(this.compRef) && hasValue(this.compRef.instance)) {
this.inAndOutputNames.filter((name: any) => this[name] !== undefined).forEach((name: any) => {
this.compRef.instance[name] = this[name];
});
}
} }
} }

View File

@@ -1,11 +0,0 @@
import { Directive, ViewContainerRef } from '@angular/core';
@Directive({
selector: '[dsListableObject]',
})
/**
* Directive used as a hook to know where to inject the dynamic listable object component
*/
export class ListableObjectDirective {
constructor(public viewContainerRef: ViewContainerRef) { }
}

View File

@@ -177,7 +177,6 @@ import {
import { import {
ItemSearchResultListElementComponent ItemSearchResultListElementComponent
} from './object-list/search-result-list-element/item-search-result/item-types/item/item-search-result-list-element.component'; } from './object-list/search-result-list-element/item-search-result/item-types/item/item-search-result-list-element.component';
import { ListableObjectDirective } from './object-collection/shared/listable-object/listable-object.directive';
import { import {
ItemMetadataRepresentationListElementComponent ItemMetadataRepresentationListElementComponent
} from './object-list/metadata-representation-list-element/item/item-metadata-representation-list-element.component'; } from './object-list/metadata-representation-list-element/item/item-metadata-representation-list-element.component';
@@ -484,7 +483,6 @@ const DIRECTIVES = [
AutoFocusDirective, AutoFocusDirective,
RoleDirective, RoleDirective,
MetadataRepresentationDirective, MetadataRepresentationDirective,
ListableObjectDirective,
ClaimedTaskActionsDirective, ClaimedTaskActionsDirective,
FileValueAccessorDirective, FileValueAccessorDirective,
FileValidator, FileValidator,