Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.

Import kam-pickers

Kam-pickers is implemented as a WebComponent and can be imported by the use of WebPack and Module federation.

Example of import

There is several ways to import kam-pickers. The first approach it to add a remote entry in the ModuleFederationPlugin in webpack.config.js. For demonstration purposes the kam-pickers project is running on http://localhost:4001.

Code Block
languagejs
const ModuleFederationPlugin = require('webpack/lib/container/ModuleFederationPlugin');
 
...
 
module.exports = {
  ...,
  plugins: [
    new ModuleFederationPlugin({
      name: 'kamUi',
      remotes: {
        kamPickers: 'kamPickers@http://localhost:4001/remoteEntry.js',
      },
      shared: share({
        '@angular/core': { singleton: true, strictVersion: true, eager: true, requiredVersion: 'auto' },
        '@angular/common': { singleton: true, strictVersion: true, eager: true, requiredVersion: 'auto' },
        '@angular/common/http': { singleton: true, strictVersion: true, eager: true, requiredVersion: 'auto' },
        '@angular/router': { singleton: true, strictVersion: true, eager: true, requiredVersion: 'auto' },
      }),
    }),
  ],
};

If you are using TypeScript in your host application you have to make sure you have the following compiler options specified in tsconfig.json. To enable dynamic module loading.

Code Block
{
...,
"compilerOptions": {
  ...,
  "moduleResolution": "node",
  "target": "es2017",
  "module": "es2020"
  },
  ...
}

Additionally you have to declare a module to "trick" TypeScript into thinking the module exists. You do this by adding a kamPickers.d.ts file in the root of the project with the following content.

Code Block
languagetypescript
declare module 'kamPickers/WebComponent';

Now you can import a remote module as a JavaScript Promise and use WebComponents like the following example in Angular:

Code Block
languagetypescript
import { Component, OnInit } from '@angular/core';
 
@Component({
  selector: 'kam-information',
  templateUrl: './information.component.html',
  styleUrls: ['./information.component.scss'],
})
export class InformationComponent {
  webcomponent$ = from(import('kamPickers/WebComponent'));
  isLoaded$ = this.webcomponent$.pipe(
    startWith(false),
    map((res) => !!res)
  );
 
  constructor() {}
 
 
  openOrganizationPicker() {
    window.dispatchEvent(
      new CustomEvent('openOrganizationPicker', {
        detail: {
          callerId: '1',
          selected: [ '139636' ],
        },
      })
    );
  }
}

information.component.html

Code Block
languagehtml
<button [disabled]="(isLoaded$ | async) === false" mat-raised-button color="accent" (click)="openOrganizationPicker()">
  Open organization picker
</button>
 
<ng-container *ngIf="webcomponent$ | async">
  <kam-pickers></kam-pickers>
</ng-container>

The above code snippets also showcases how inputs which interacts with the web component are disabled while the web component loads. The tag <kam-pickers> is custom tag for the WebComponent

When using Angular as frontend framework a library called @angular-architects/module-federation and @angular-architects/module-federation-tools can be used instead of doing the ModuleFederation work yourself. Here you can call loadRemoteModule() instead as shown below.

information.component.ts

Code Block
...
  webcomponent$ = from(
    loadRemoteModule({
      remoteEntry: 'http://localhost:4001/remoteEntry.js',
      exposedModule: './WebComponent',
      remoteName: 'kamPickers',
    })
  );
 ...
}

ValueSet picker

The valueSet picker can be used to select the following valueSets:

  • ObservationCodes

  • Conditions

To open the valueSet picker, sent a custom event with name “openValueSetPicker”.

Example:

...

languagetypescript

...

consist of a number of generic pickers which makes it possible for other solutions to reuse the already implemented pickers. See Description Of Pickers

Indholdsfortegnelse

Table of Contents

Using kam-pickers in other applications than Common Workplace

The pickers is implemented as Web Components which makes it possible to use the generic pickers in other web applications using the Angular framework.

It has been tested under the following conditions:

  • Angular version 12.2.16

  • Angular Material 12.2.13

    • With the css class mat-typography added on the body tag in index.html

  • angular-architects/module-federation 12.5.3

  • angular-architects/module-federation-tools 12.5.3

Theming

If the theming for the pickers should be the same as for the Common Workplace. The below is the theme from Common Workplace:

Code Block
languagecss
@use '~@angular/material' as mat;

$kam-primary-palette: (
  50: #e4e8eb,
  100: #bbc5cd,
  200: #8e9eab,
  300: #617789,
  400: #285175,
  500: #1d3c57,
  600: #1a364f,
  700: #162c40,
  800: #11273c,
  900: #132638,
  A100: #ffffff,
  A200: #ffffff,
  A400: #ffffff,
  A700: #e8eaff,
  contrast: (
    50: #1a1a1a,
    100: #1a1a1a,
    200: #1a1a1a,
    300: #ffffff,
    400: #ffffff,
    500: #ffffff,
    600: #ffffff,
    700: #ffffff,
    800: #ffffff,
    900: #ffffff,
    A100: #1a1a1a,
    A200: #ffffff,
    A400: #ffffff,
    A700: #ffffff,
  ),
);

$kam-accent-palette: (
  50: #f2fef3,
  100: #e0fee0,
  200: #cbfdcb,
  300: #b6fcb6,
  400: #a6fba7,
  500: #96fa97,
  600: #8ef98f,
  700: #83f984,
  800: #79f87a,
  900: #68f669,
  A100: #ffffff,
  A200: #ffffff,
  A400: #ffffff,
  A700: #e8ffe8,
  contrast: (
    50: #1a1a1a,
    100: #1a1a1a,
    200: #1a1a1a,
    300: #1a1a1a,
    400: #1a1a1a,
    500: #1a1a1a,
    600: #1a1a1a,
    700: #1a1a1a,
    800: #1a1a1a,
    900: #1a1a1a,
    A100: #1a1a1a,
    A200: #1a1a1a,
    A400: #1a1a1a,
    A700: #1a1a1a,
  ),
);

$kam-typography: mat.define-typography-config(
  $title: mat.define-typography-level(20px, 20px, 400),
  $headline: mat.define-typography-level(24px, 24px, 400),
  $display-4: mat.define-typography-level(34px, 34px, 400),
  $display-3: mat.define-typography-level(48px, 48px, 400),
  $display-2: mat.define-typography-level(60px, 60px, 200),
  $display-1: mat.define-typography-level(96px, 96px, 200),
);

$kam-primary: mat.define-palette($kam-primary-palette);
$kam-accent: mat.define-palette($kam-accent-palette);

$kam-theme: mat.define-light-theme(
  (
    color: (
      primary: $kam-primary,
      accent: $kam-accent,
    ),
  )
);

@include mat.core($kam-typography);

@include mat.core-theme($kam-theme);

@include mat.all-component-themes($kam-theme);

Import kam-pickers

Kam-pickers is implemented as a WebComponent and can be imported by the use of WebPack and Module federation.

Example of import

There is several ways to import kam-pickers. The first approach it to add a remote entry in the ModuleFederationPlugin in webpack.config.js. For demonstration purposes the kam-pickers project is running on http://localhost:4001.

Code Block
languagejs
const ModuleFederationPlugin = require('webpack/lib/container/ModuleFederationPlugin');
 
...
 
module.exports = {
  ...,
  plugins: [
    new ModuleFederationPlugin({
      name: 'kamUi',
      remotes: {
        kamPickers: 'kamPickers@http://localhost:4001/remoteEntry.js',
      },
      shared: share({
        '@angular/core': { singleton: true, strictVersion: true, eager: true, requiredVersion: 'auto' },
        '@angular/common': { singleton: true,  readonlyMode: false,
            multiSelect: true,
            language: 'da',
 strictVersion: true, eager: true, requiredVersion: 'auto' },
        '@angular/common/http': { singleton: true, strictVersion: true, eager: true, requiredVersion: 'auto' },
        '@angular/router': { singleton: true, strictVersion: true, eager: true, requiredVersion: 'auto' },
      }),
    }),
  ],
};

If you are using TypeScript in your host application you have to make sure you have the following compiler options specified in tsconfig.json. To enable dynamic module loading.

Code Block
{
...,
"compilerOptions": {
  ...,
  "moduleResolution": "node",
  "target": "es2017",
  "module": "es2020"
  },
  ...
}

Additionally you have to declare a module to "trick" TypeScript into thinking the module exists. You do this by adding a kamPickers.d.ts file in the root of the project with the following content.

Code Block
languagetypescript
declare module 'kamPickers/WebComponent';

Now you can import a remote module as a JavaScript Promise and use WebComponents like the following example in Angular:

Code Block
languagetypescript
import { Component, OnInit } from '@angular/core';
 
@Component({
  selector: 'kam-information',
  templateUrl: './information.component.html',
  styleUrls: ['./information.component.scss'],
})
export class InformationComponent {
  webcomponent$ = from(import('kamPickers/WebComponent'));
  isLoaded$ = this.webcomponent$.pipe(
    startWith(false),
    map((res) => !!res)
  );
 
  constructor() {}
 
 
  openOrganizationPicker() {
    window.dispatchEvent(
      new CustomEvent('openOrganizationPicker', {
        detail: {
          callerId: '1',
          selected: [ '139636' ],
        },
      })
    );
  }
}

information.component.html

Code Block
languagehtml
<button [disabled]="(isLoaded$ | async) === false" mat-raised-button color="accent" (click)="openOrganizationPicker()">
  Open organization picker
</button>
 
<ng-container *ngIf="webcomponent$ | async">
  <kam-pickers></kam-pickers>
</ng-container>

The above code snippets also showcases how inputs which interacts with the web component are disabled while the web component loads. The tag <kam-pickers> is custom tag for the WebComponent and needs to be added before it is possible to trigger the pickers by custom events.

Dynamic import

When using Angular as frontend framework a library called @angular-architects/module-federation and @angular-architects/module-federation-tools can be used instead of doing the ModuleFederation setup yourself. By using angular-architects solution it is possible to load the module in code without specifying the remote in webpack.config.js. As shown below it is possible to call loadRemoteModule() instead.

information.component.ts

Code Block
...
  webcomponent$ = from(
    loadRemoteModule({
      remoteEntry: 'http://localhost:4001/remoteEntry.js',
      exposedModule: './WebComponent',
      remoteName: 'kamPickers',
    })
  );
 ...
}

Anchor
DescriptionOfPickers
DescriptionOfPickers
Description of Kam-pickers

Below is a technical guide for each of the pickers implemented by Kam-pickers.

ValueSet picker

The valueSet picker can be used to select the following valueSets:

  • ObservationCodes

  • Conditions

To open the valueSet picker, sent a custom event with name “openValueSetPicker”.

Example:

Code Block
languagetypescript
window.dispatchEvent(
    new CustomEvent('openValueSetPicker', {
        detail: {
            callerId: 'useContextCondition',
            readonlyMode: false,
            multiSelect: true,
            language: 'da',
            designationUseCode: 'consumer',
            valueSetType: 'Conditions',
            codeableConcepts: this.form.value.useContext,
        },
    })
);

Input data:

Code Block
languagetypescript
interface ValueSetPickerData {
  callerId: string;
  readonlyMode: boolean;
  multiSelect: boolean;
  language: string;
  designationUseCode: string;
  valueSetType: ValueSetType;
  selectedValues: CodeableConcept[];
}
 
enum ValueSetType {
  ObservationCodes = 'ObservationCodes',
  Conditions = 'Conditions',
}

Explanation:

  • callerId: an unique id that will be returned with the output result. CallerId makes it possible for the caller to distingues which form field the pickers was opened as the same picker can be used multiple times

  • readonlyMode: true for readonly mode

  • multiSelect: true if it should be possible to select more than one

  • language: language code. eg. "da" for danish

  • designationUseCode: display text targeting specific audience

    • values:

      • "consumer"

        • description: Designation for use in display to non-clinicians and those not in healthcare professions as a more friendly term for coommunication.

  • valueSetType:

    • values:

      • "ObservationCodes"

      • "Conditions"

  • selectedValues: Which CodeSystem should be preselected when the valueSet picker is opened.

    • Supported CodeableConcept:

      • Only one coding per CodeableConcept

Output data:

Code Block
languagetypescript
interface ValueSetPickerDialogResult {
  callerId: string;
  codeableConcepts: CodeableConcept[];
}

Explanation:

  • callerId: An unique id that will be returned back with the output result. CallerId makes it possible for the caller to distingues which form field the pickers was opened from as the same picker can be used multiple times

  • codeableConcepts: List of all the selected codes.

    • Always only one code per CodeableConcept

Organization picker

The organization picker can be used to select organizations from STS-ORG or SOR.

The organization picker also handles user-favourite organizations, which are stored in a Key-Value Store. That makes them available to users, even when using other devices or browsers than usual.

To open the organization picker, sent a custom event with name “openOrganizationPicker”.

Example:

Code Block
languagetypescript
window.dispatchEvent(
          designationUseCode: 'consumernew CustomEvent('openOrganizationPicker', {
           valueSetTypedetail: 'Conditions',{
            codeableConceptscallerId: this.form.value.useContext'ownerField',
        },  onlyAllowRootSelection: false,
 })
);

Input data:

Code Block
languagetypescript
interface ValueSetPickerData {   callerId: string;   readonlyMode: boolean;selectedIds: ['139636'],
   multiSelect: boolean;   language: string;},
  designationUseCode: string;   valueSetType:})
ValueSetType;   selectedValues: CodeableConcept[];
}
 
enum ValueSetType {
  ObservationCodes = 'ObservationCodes',
  Conditions = 'Conditions',);

Input data:

Code Block
interface OrganizationPickerData {
  callerId: string;
  onlyAllowRootSelection: boolean;
  selectedIds: string[];
}

Explanation:

  • callerId: an unique id that will be returned with the output result. CallerId makes it possible for the caller to distingues which form field the pickers was opened from as the same picker can be used multiple times

  • readonlyModeonlyAllowRootSelection: If true for readonly mode

  • multiSelect: true if it should be possible to select more than one

  • language: language code. eg. "da" for danish

  • designationUseCode: display text targeting specific audience

    • values:

      • "consumer"

        • description: Designation for use in display to non-clinicians and those not in healthcare professions as a more friendly term for coommunication.

  • valueSetType:

    • values:

      • "ObservationCodes"

      • "Conditions"

  • selectedValues: Which CodeSystem should be preselected when the valueSet picker is opened.

    • Supported CodeableConcept:

      • Only one coding per CodeableConcept

Output data:

...

languagetypescript

...

  • only organization on top level i selectable. At the moment top level is: Regions and municipalities. Be aware that the top level for a municipality is the first organization from the top of type “OrganisationsEnhed” (system: http://ehealth.sundhed.dk/cs/oio-organization-type, code: OrganisationEnhed

  • selectedIds: Which organization should be preselected when the organization picker is opened.

Output data:

Code Block
interface OrganizationPickerDialogResult {
  callerId: string;
  selected: Organization[];
}
 
interface Organization {
  id: string;
  name: string;
  source: Source | null;
  alias: string[];
  parentReferenceId: string | null;
  cvrNumber: string | null;
  referenceUrl: string;
}

Explanation:

  • OrganizationPickerDialogResult

    • callerId:

    An
    • an unique id that will be returned back with the output result. CallerId makes it possible for the caller to distingues which form field the pickers was opened from as the same picker can be used multiple times

    codeableConcepts
    • selected: List of all the selected

    codes.
    • Always only one code per CodeableConceptorganizations

  • Organization

...

The organization picker can be used to select organizations from STS-ORG or SOR.

To open the organization picker, sent a custom event with name “openOrganizationPicker”.

Example:

...

languagetypescript

...

    • id: organization id

    • name: organization name

    • source: Possible values: 'SOR', 'STS-ORG', 'manual'

    • alias: A hierarchy from the root organization down to the specific organization

    • parentReferenceId: id of the parent organization

    • cvrNumber: cvr number

    • referenceUrl: URL to the specific organization

Questionnaire picker

The questionnaire picker can be used to select questionnaire. To open the questionnaire picker, sent a custom event with name “openQuestionnairePicker”.

Example:

Code Block
languagetypescript
window.dispatchEvent(
          selectedIds: ['139636'],
new CustomEvent('openQuestionnairePicker', {
       },
      })
    );

Input data:

Code Block
interface OrganizationPickerData {
  callerId: string;
  onlyAllowRootSelection: boolean;
  selectedIds: string[];
}

Explanation:

  • callerId: an unique id that will be returned with the output result. CallerId makes it possible for the caller to distingues which form field the pickers was opened from as the same picker can be used multiple times

  • onlyAllowRootSelection: If true only organization on top level i selectable. At the moment top level is: Regions and municipalities

  • selectedIds: Which organization should be preselected when the organization picker is opened.

Output data:

Code Block
interface OrganizationPickerDialogResult {
  callerId: string;
  selected: Organization[];
}
 
interface Organization {
  id: string;
  name: string;
  source: Source | null;
  alias: string[];
  parentReferenceId: string | null;
  cvrNumber: string | null;
  referenceUrl: string;
}

Explanation:

...

 detail: {
          callerId: 'questionnaireField',
          selectedIds: ['23'],
          multiSelect: true,
        },
      })
    );

Input data: 

Code Block
languagetypescript
interface QuestionnairePickerData {
    callerId: string;  
    selectedIds: string[];
    multiSelect: boolean;
}

Explanation:

  • callerId: an unique id that will be returned

    back

    with the output result. CallerId makes it possible for the caller to distingues which form field the pickers was opened

    from

    as the same picker can be used multiple times

  • selected: List of all the selected organizations

  • Organization

    • id: organization id

    • name: organization name

    • source: Possible values: 'SOR', 'STS-ORG', 'manual'

    • alias: A hierarchy from the root organization down to the specific organization

    • parentReferenceId: id of the parent organization

    • cvrNumber: cvr number

    • referenceUrl: URL to the specific organization

Questionnaire picker

...

  • selectedIds: Which organization should be preselected when the organization picker is opened.

  • multiSelect: true if it must be possible to select more than one questionnaire

Output data:

Code Block
interface QuestionnairePickerDialogResult {
  callerId: string;
  selected: Questionnaire[];
}

Explanation:

  • callerId: an unique id that will be returned back with the output result. CallerId makes it possible for the caller to distingues which form field the pickers was opened from as the same picker can be used multiple times

  • selected: List of all the selected questionnaire

Timing picker

The timing picker can be used to setup timing expressions. To open the timing picker, sent a custom event with name “openQuestionnairePicker”“openTimingPicker”.

Example:

Code Block
languagetypescript
window.dispatchEvent(
      new CustomEvent('openQuestionnairePickeropenTimingPicker', {
        detail: {
          timingPickerData: {
            callerId: 'questionnaireFieldtimingField',
            selectedIdstiming: ['23']timingObject,
           multiSelect includeAsExtra: true,
          },
        },
      })
    );

Input data: 

typescript
Code Block
language
interface QuestionnairePickerDataTimingPickerData {
 
  callerId: string;
      selectedIdstiming: string[]Timing;
    multiSelectincludeAsExtra: boolean;
}

Explanation:

  • callerId: an unique id that will be returned with the output result. CallerId makes it possible for the caller to distingues which form field the pickers was opened as the same picker can be used multiple times

  • selectedIds: Which organization should be preselected when the organization picker is opened.

  • multiSelect: true if it must be possible to select more than one questionnairetiming: fhir timing object.

  • includeAsExtra: a boolean value determining whether the activity (or group of activities) should be offered as additional activities that can be performed outside the specified timing

Output data:

Code Block
interface QuestionnairePickerDialogResultTimingPickerData {
  callerId: string;
  timing: Timing;
 selected includeAsExtra: Questionnaire[]boolean;
}

Explanation:

  • callerId: an unique id that will be returned back with the output result. CallerId makes it possible for the caller to distingues which form field the pickers was opened from as the same picker can be used multiple times

  • selected: List of all the selected questionnaire

Timing picker

...

  • the pickers was opened as the same picker can be used multiple times

  • timing: fhir timing object

  • includeAsExtra: a boolean value determining whether the activity (or group of activities) should be offered as additional activities that can be performed outside the specified timing

Reference range picker

The reference range picker can be used to setup timing expressionsthresholds for measurement. To open the timing reference range picker, sent a custom event with name “openTimingPicker”“openReferenceRangePicker”.

Example:

Code Block
languagetypescript
window.dispatchEvent(
 
    new CustomEvent('openTimingPickeropenReferenceRangePicker', {
 
      detail: {
          timingPickerDatacallerId: {
'ReferenceRangeField',
           callerIdreadonlyMode: 'timingField'false,
            timingmeasurementType: timingObjectthis.form.code.value,
          },
 referenceRangeData: this.ReferenceRangeExtensions
      },
      })
   
);

Input data: 

Code Block
interface TimingPickerDataReferenceRangePickerData {
  callerId: string;
  readonlyMode: boolean;
   timing: TimingmeasurementType: CodeableConcept;
  referenceRangeData: Extension[] | undefined;
}

Explanation:

  • callerId: an unique id that will be returned with the output result. CallerId makes it possible for the caller to distingues which form field the pickers was opened with as the same picker can be used multiple times.

  • timing: fhir timing object. readonlyMode: true for readonly mode.

  • measurementType: ValueSet: observation codes

    • Supported CodeableConcept:

      • Only one coding per CodeableConcept

  • referenceRangeData: Extensions of type reference range

Output data:

Code Block
export interface TimingPickerDataReferenceRangePickerDialogResult {
  callerId: string;
  timingreferenceRangeData: TimingExtension[];
}

Explanation:

  • callerId: an unique id that will be returned with the output result. CallerId makes it possible for the caller to distingues which form field the pickers was opened as the same picker can be used multiple timestiming: fhir timing object updated/created in the timing

  • pickerreferenceRangeData: Extensions of type reference range