diff --git a/README.md b/README.md index 4519aee92..5ff1b8485 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,10 @@ ## Change log +### March 27, 2023 + +- Added some [code samples](sdk_samples/README.md) with explanations. + ### December 21, 2022 - The repository has been updated with fixes for the following issues. diff --git a/sdk_samples/01_dashboard_tiles/README.md b/sdk_samples/01_dashboard_tiles/README.md new file mode 100644 index 000000000..adf3bf7af --- /dev/null +++ b/sdk_samples/01_dashboard_tiles/README.md @@ -0,0 +1,119 @@ +# Adding a tile component to the dashboard + +The IMX Portal landing page consists of the IMX Dashboard component, which can be found in the QER library (projects\qer\src\lib\wport\start). + +It is composed of 3 sections with different tiles. + +![Dashboard](./images/1.png) + + +Tiles can be added dynamically to the dashboard. The following example gives an overview of the different types of tiles and demonstrates how to add a new element - the blue bordered tile - to the dashboard. + + +## Dashboard tiles + +The Tiles modules (projects\qbm\src\lib\tile) and (projects\qer\src\libtiles) offers different base components : + +- TileComponent (QBM) +- BadgeTileComponent (QER) +- IconTileComponent (QER) +- NotificationTileComponent (QER) + + +These components are variations of the same concept. In the further course we will implement a new tile based on the IconTileComponent. + + +## Implementing the "Block Identity" Tile + +What is the fictitious but realistic scenario we will implement? +There is a security breach and an administrator wants to block the account of the affected identity. The implementation of this scenario will span several examples. Here we will first create the tile that can trigger that process. + +### Creating the "Block Identity" Component + +First we need to create the "Block Identity" component. We assume that the reader has a basic knowledge of the Angular framework and knows how to create components, services, etc. + +The component consists of 3 files, but we will not pay further attention to the stylesheet. + +![Block Identity Component](./images/2.png) + +### The anatomy of the "Block Identity" component + +Basically, the component consists of 2 parts, a Typescript file and the corresponding HTML template. + + +The HTML template is based on the previously mentioned IconTileComponent component. + +> Code + +``` html + + + + + +``` + +The IconTileComponent expects some input fields like "caption", "image" or "subtitle". Which tiles components expect which inputs can be found in the Tiles Module. + +> NOTE + +> IMX components and applications are based on the One Identity Elemental UI framework, which in turn extends Angular Material. The "eui-icon" tag is such an Elemental UI component (https://elemental.dev.oneidentity.com/) + +The corresponding *.ts component is not very exciting. On the one hand it sets the "Description" property/input used in the template and implements the (dummy) "block()" method. + +> Code +``` ts +import { Component } from '@angular/core'; + +@Component({ + selector: 'imx-block-identity', + templateUrl: './block-identity.component.html', + styleUrls: ['./block-identity.component.scss'] +}) +export class BlockIdentityComponent { + public description = 'Blocks an identity and marks the identity as security risk.'; + + constructor() { } + + public block(): void { + alert('Block Tile Clicked'); + } + +} +``` + +That's all we need at this time for the "Block Identity" component. + +## Wiring it up +The next step is to include the component into the dashboard. +To do this, we must make it available to the web application, in this case the portal. +This is done in the init service of the corresponding module (don't worry, there will be more samples on the topic). + +Here is the relevant section of the service. + +> Code + +``` ts +@Injectable({ providedIn: 'root' }) +export class InitService { + public onInit(routes: Route[]): void { + this.extService.register('Dashboard-MediumTiles', { + instance: BlockIdentityComponent, + }); + } +} +``` + +The final result looks like this. + + +![block-identity.component.ts](./images/5.png) + + + + + diff --git a/sdk_samples/01_dashboard_tiles/images/1.png b/sdk_samples/01_dashboard_tiles/images/1.png new file mode 100644 index 000000000..edf1cfc10 Binary files /dev/null and b/sdk_samples/01_dashboard_tiles/images/1.png differ diff --git a/sdk_samples/01_dashboard_tiles/images/2.png b/sdk_samples/01_dashboard_tiles/images/2.png new file mode 100644 index 000000000..b13885428 Binary files /dev/null and b/sdk_samples/01_dashboard_tiles/images/2.png differ diff --git a/sdk_samples/01_dashboard_tiles/images/3.png b/sdk_samples/01_dashboard_tiles/images/3.png new file mode 100644 index 000000000..8c3e2c37c Binary files /dev/null and b/sdk_samples/01_dashboard_tiles/images/3.png differ diff --git a/sdk_samples/01_dashboard_tiles/images/4.png b/sdk_samples/01_dashboard_tiles/images/4.png new file mode 100644 index 000000000..a44f720ac Binary files /dev/null and b/sdk_samples/01_dashboard_tiles/images/4.png differ diff --git a/sdk_samples/01_dashboard_tiles/images/5.png b/sdk_samples/01_dashboard_tiles/images/5.png new file mode 100644 index 000000000..8acfc5764 Binary files /dev/null and b/sdk_samples/01_dashboard_tiles/images/5.png differ diff --git a/sdk_samples/02_adding_a_menue/README.md b/sdk_samples/02_adding_a_menue/README.md new file mode 100644 index 000000000..5ad621e14 --- /dev/null +++ b/sdk_samples/02_adding_a_menue/README.md @@ -0,0 +1,121 @@ +# Adding a menu to the Portal + +In the previous example, we added a tile component to the dashboard to block a user's account. + +In this example, we want to add a new item to the portal main menu to achieve the same functionality. + +The main menu (projects\qbm\src\lib\menu) is a central component, just like the dashboard. + +![Main menu](./images/1.png) + +New menus and menu items are dynamically added to the main menu via a plugin system. + +Before we implement the menu, we have to add a route that navigates to the (currently empty) component where you can select the identity you want to block. + +We will name the new component SelectIdentityComponent. + +![Select Identity Component](./images/2.png) + +First we add a new entry to the routing table. + +> Code + +``` ts +const routes: Routes = [ + : + { + path: 'selectidentity', + component: SelectIdentityComponent + } +]; +``` + +Now we can add the new menu with the associated route. Again, as in the previous example, this is done in the init service (init-service.ts). In the code snippet below, only the part where the menu is added is shown, the rest is hidden. + +> Code + +``` ts +: + +@Injectable({ providedIn: 'root' }) +export class InitService { + : + + public onInit(routes: Route[]): void { + this.addRoutes(routes); + : + } + + private addRoutes(routes: Route[]): void { + const config = this.router.config; + routes.forEach((route) => { + config.unshift(route); + }); + this.router.resetConfig(config); + } + + private setupMenu(): void { + this.menuService.addMenuFactories( + : + (preProps: string[], __: string[]) => { + return { + id: 'ROOT_SAMPLES', + title: '#LDS#Samples' + items: [ + { + id: 'SAMPLE_SELECT_IDENTITY', + route: 'selectidentity', + title: '#LDS#Select Identity' + }, + ], + }; + ); + } +} +``` + +You can add menues and menu items via the menu service (projects\qbm\src\lib\menu). The structure of the menu and the menu items is defined by the menu-item.interface.ts file. The most important properties are "id" and "title". If you add a menu item, the "route" property specifies the route of the component to be displayed. + +Here is an extract of the file. + +> Code + +``` ts +import { ProjectConfig } from 'imx-api-qbm'; +import { NavigationCommandsMenuItem } from './navigation-commands-menu-item.interface'; + +/** Represents a single menu item. */ +export interface MenuItem { + /** Unique identifier for the menu item. */ + readonly id?: string; + + /** Display name. */ + readonly title: string; + + /** Returns a descriptive text, intended for tooltips. */ + readonly description?: string; + + /** Property for simple navigation. */ + readonly route?: string; + + /** Property for sorting the items. */ + readonly sorting?: string; + + /** Property for complex navigation, including outlets etc. */ + navigationCommands?: NavigationCommandsMenuItem; + + /** Called when the menu item is clicked. */ + readonly trigger?: () => void; + + /** Submenu items. */ + items?: MenuItem[]; + +} + +export type MenuFactory = (preProps: string[], groups: string[], projectConfig: ProjectConfig) => MenuItem; + +``` + +The final result looks like this. + +![Final Result](./images/3.png) \ No newline at end of file diff --git a/sdk_samples/02_adding_a_menue/images/1.png b/sdk_samples/02_adding_a_menue/images/1.png new file mode 100644 index 000000000..51181e40a Binary files /dev/null and b/sdk_samples/02_adding_a_menue/images/1.png differ diff --git a/sdk_samples/02_adding_a_menue/images/2.png b/sdk_samples/02_adding_a_menue/images/2.png new file mode 100644 index 000000000..671e583f9 Binary files /dev/null and b/sdk_samples/02_adding_a_menue/images/2.png differ diff --git a/sdk_samples/02_adding_a_menue/images/3.png b/sdk_samples/02_adding_a_menue/images/3.png new file mode 100644 index 000000000..a857698c4 Binary files /dev/null and b/sdk_samples/02_adding_a_menue/images/3.png differ diff --git a/sdk_samples/03_using_data_tables/README.md b/sdk_samples/03_using_data_tables/README.md new file mode 100644 index 000000000..aa7de676e --- /dev/null +++ b/sdk_samples/03_using_data_tables/README.md @@ -0,0 +1,261 @@ +# Working with data tables + +A frequently recurring task is the representation of data in tables. The IMX QBM library offers components that facilitate the visualization of data and take the special IMX data structure into account. + +Data tables offer a lot of configuration possibilities. We will present the most important ones in this sample. + +The two most important modules in this context are "data-source-toolbar" (projects\qbm\src\lib\data-source-toolbar) and "data-table" (projects\qbm\src\lib\data-table). + +The basic structure consists of 3 elements, the data source toolbar, the actual table and a paginator. + +![overall Structure](./images/1.png) + +In addition to the ability to search, filter, etc, the Data Source Toolbar contains a Data Source component that is used by the Data Table and the Paginator to display data and move within the data set. Think of the DST (Data Source Tool Bar) as a link between the Data Table and the Paginator. + + +The "Hello World" version of the Data Table component looks like as follow. + +> Code +``` html +

{{ '#LDS#Identities' | translate }}

+ + + + + + + +``` + +> Code +``` ts + +@Component({ + selector: 'imx-select-identity', + templateUrl: './select-identity.component.html', + styleUrls: ['./select-identity.component.scss'], +}) +export class SelectIdentityComponent implements OnInit { + public dstSettings: DataSourceToolbarSettings; + public readonly schema: EntitySchema; + public readonly DisplayColumns = DisplayColumns; + public navigationState: CollectionLoadParameters = { PageSize: 20 }; + + private displayedColumns: IClientProperty[] = []; + + constructor(private readonly qerApiClient: QerApiService) { + this.schema = this.qerApiClient.typedClient.PortalPersonAll.GetSchema(); + this.displayedColumns = [ + this.schema.Columns[DisplayColumns.DISPLAY_PROPERTYNAME], + this.schema.Columns.DefaultEmailAddress + ]; + } + + public async ngOnInit(): Promise { + await this.navigate(); + } + + public async onNavigationStateChanged(newState?: CollectionLoadParameters): Promise { + if (newState) { + this.navigationState = newState; + } + await this.navigate(); + } + + private async navigate(): Promise { + const data = await this.qerApiClient.typedClient.PortalPersonAll.Get(this.navigationState); + + this.dstSettings = { + displayedColumns: this.displayedColumns, + dataSource: data, + entitySchema: this.schema, + navigationState: this.navigationState, + }; + } +} + +``` + +The minimum set of properties that must be set are "EntitySchema", "DisplayColumns" and "CollectionLoadParameters". + +Three places in the .ts Datei are worth to be highlighted. + +> Code +``` ts + this.displayedColumns = [ + this.schema.Columns[DisplayColumns.DISPLAY_PROPERTYNAME], + this.schema.Columns.DefaultEmailAddress + ]; +``` + +"displayedColumns" defines which columns the table should display. + +> Code +``` ts + public async onNavigationStateChanged(newState?: CollectionLoadParameters): Promise { + if (newState) { + this.navigationState = newState; + } + await this.navigate(); + } +``` + +This event handler is called every time the state of the data changes, e.g. when the user navigates to the next page. + +> Code +``` ts + private async navigate(): Promise { + const data = await this.qerApiClient.typedClient.PortalPersonAll.Get(this.navigationState); + + this.dstSettings = { + displayedColumns: this.displayedColumns, + dataSource: data, + entitySchema: this.schema, + navigationState: this.navigationState, + }; + } +``` +The "navigate()" method retrievs data from the API server. The actual request is made by calling the API client ("this.qerApiClient.typedClient.PortalPersonAll.Get(this.navigationState)"). The concept of API clients is described in a separate sample. + +The first version of the Component looks like this. + +![First Version](./images/2.png) + +## Designing the table + +The table above shows two fields that are rendered automatically. It is also possible to design columns manually. Whether the table is rendered automatically or manually is controlled by the "mode" input field. + +> Code +``` html + + +``` + +"mode" can take two values: "auto" and "manual". + +To display the previous table in manual mode, we need to add the two columns to the html template. + +> Code +``` html + + + +
{{ item.GetEntity().GetDisplay() }}
+
+
+ + +
{{ item.DefaultEmailAddress.Column.GetDisplayValue() }}
+
+
+
+``` +In the next step we will add a new column to the table containing a button and slightly modify the first column. + +The first column currently shows the default display of the object. We want to add a second row that indicates whether the person is a primary identity or not. + +> Code +``` html + + + +
{{ item.GetEntity().GetDisplay() }}
+
{{ item.IdentityType.Column.GetDisplayValue() }}
+
+
+ + +
{{ item.DefaultEmailAddress.Column.GetDisplayValue() }}
+
+
+
+``` + +Next we will add a third column that will contain a button. To display data of an object the table uses the "" tag. To display other types of elements, such as buttons, we use the "" tag. +Before we can display the button, we need to add the new synthetic column to the columns to be displayed. This is done in the *.ts file. + +> Code +``` ts + this.displayedColumns = [ + this.schema.Columns[DisplayColumns.DISPLAY_PROPERTYNAME], + this.schema.Columns.DefaultEmailAddress, + { + ColumnName: 'viewDetailsButton', + Type: ValType.String + } + ]; +``` + + +> Code +``` html + + + +
{{ item.GetEntity().GetDisplay() }}
+
{{ item.IdentityType.Column.GetDisplayValue() }}
+
+
+ + +
{{ item.DefaultEmailAddress.Column.GetDisplayValue() }}
+
+
+ + + + + +
+``` + +The extended component now looks like this. + +![Version with search enabled](./images/4.png) + +## Adding search + +Enabling search is pretty straightforward. +To do this, you must first enable the "search" option and secondly implement a method that processes the output of the "search" output parameter. + +The following code snippets shows these changes. + +> Code +``` html + +``` + +> Code +``` ts + public async onSearch(keywords: string): Promise { + this.navigationState.StartIndex = 0; + this.navigationState.search = keywords; + await this.navigate(); + } +``` + + +![Version with search enabled](./images/3.png) + + + + + diff --git a/sdk_samples/03_using_data_tables/images/1.png b/sdk_samples/03_using_data_tables/images/1.png new file mode 100644 index 000000000..75393aaad Binary files /dev/null and b/sdk_samples/03_using_data_tables/images/1.png differ diff --git a/sdk_samples/03_using_data_tables/images/2.png b/sdk_samples/03_using_data_tables/images/2.png new file mode 100644 index 000000000..f8daef573 Binary files /dev/null and b/sdk_samples/03_using_data_tables/images/2.png differ diff --git a/sdk_samples/03_using_data_tables/images/3.png b/sdk_samples/03_using_data_tables/images/3.png new file mode 100644 index 000000000..3759f2f26 Binary files /dev/null and b/sdk_samples/03_using_data_tables/images/3.png differ diff --git a/sdk_samples/03_using_data_tables/images/4.png b/sdk_samples/03_using_data_tables/images/4.png new file mode 100644 index 000000000..6a09aad1f Binary files /dev/null and b/sdk_samples/03_using_data_tables/images/4.png differ diff --git a/sdk_samples/README.md b/sdk_samples/README.md new file mode 100644 index 000000000..2f951d185 --- /dev/null +++ b/sdk_samples/README.md @@ -0,0 +1,5 @@ +# SDK Samples + +- [Adding a tile component to the dashboard](01_dashboard_tiles/README.md) +- [Adding a menu item](02_adding_a_menue/README.md) +- [Using data tables](03_using_data_tables/README.md)