Skip to Content
DocsWorkspacesCreating workspaces

Creating Workspaces

To create a workspace, implement a React component and register it with the workspace v2 system in routes.json.

Workspace Component

Workspace v2 components receive Workspace2DefinitionProps from @openmrs/esm-framework. Custom launch props are available on workspaceProps; shared window and group context is available on windowProps and groupProps; and the component can close itself with closeWorkspace.

The same props object also includes launchChildWorkspace, workspaceName, windowName, isRootWorkspace, and showActionMenu. Use launchChildWorkspace only from inside an open workspace when you want to push another workspace onto the same window’s stack. Use launchWorkspace2 for top-level launches from buttons, pages, or other app code.

Wrap the workspace content in <Workspace2>. The wrapper owns the workspace header, title, maximize and close controls, and unsaved changes state.

// gastrodynamics.workspace.tsx import React, { useState } from 'react'; import { useTranslation } from 'react-i18next'; import { Button, ButtonSet, Form, TextInput } from '@carbon/react'; import { Workspace2, type Workspace2DefinitionProps } from '@openmrs/esm-framework'; import { postGastrodynamicsForm } from './gastrodynamics.resource'; import styles from './gastrodynamics-form.module.scss'; interface GastrodynamicsWorkspaceProps { patientUuid: string; } export default function GastrodynamicsWorkspace({ workspaceProps, closeWorkspace, }: Workspace2DefinitionProps<GastrodynamicsWorkspaceProps>) { const { t } = useTranslation(); const [textValue, setTextValue] = useState(''); const patientUuid = workspaceProps?.patientUuid; if (!patientUuid) { return null; } const handleSubmit = async (event: React.FormEvent) => { event.preventDefault(); await postGastrodynamicsForm(patientUuid, textValue, new AbortController()); await closeWorkspace({ discardUnsavedChanges: true }); }; return ( <Workspace2 title={t('gastrodynamics', 'Gastrodynamics')} hasUnsavedChanges={textValue.length > 0}> <Form className={styles.form} onSubmit={handleSubmit}> <TextInput id="howsThePlumbing" labelText={t('howsThePlumbing', "How's the plumbing?")} onChange={(event) => setTextValue(event.target.value)} value={textValue} /> <ButtonSet> <Button className={styles.button} kind="secondary" onClick={() => void closeWorkspace({ discardUnsavedChanges: true })} > {t('discard', 'Discard')} </Button> <Button className={styles.button} kind="primary" type="submit"> {t('submit', 'Submit')} </Button> </ButtonSet> </Form> </Workspace2> ); }

Registration

Register the workspace in routes.json using workspaces2. Each workspace must belong to a workspace window, and each window must belong to a group.

{ "workspaces2": [ { "name": "gastrodynamics-form-workspace", "component": "gastrodynamicsWorkspace", "window": "gastrodynamics" } ], "workspaceWindows2": [ { "name": "gastrodynamics", "group": "patient-chart", "width": "narrow", "canMaximize": true } ] }

If your app owns the group, declare it in workspaceGroups2:

{ "workspaceGroups2": [ { "name": "gastrodynamics", "scopePattern": "^/home/gastrodynamics", "overlay": true, "persistence": "closable" } ] }

Window width can be narrow, wider, or extra-wide. Adding icon to a window turns on the workspace group’s action menu and registers the icon component as an extension in the slot named after the group.

Group persistence controls how long the group stays open and how the action menu behaves. The default behavior is app-wide: the group can keep multiple windows open and closes on an app change or scope change. Set persistence to closable for focused workflows that should show a group close button; when a different window in a closable group is launched, O3 checks for unsaved changes and then closes the other windows in that group.

scopePattern is a regular expression matched against the old and new URL pathnames when navigation happens within the same app. If it is omitted, the group closes only when navigating to a different app. If it is defined, O3 closes the group when either URL no longer matches the pattern; if the pattern has capture groups, O3 also closes the group when the captured values change. Anchor the pattern with ^ when the pattern should only match a route prefix.

Then export the workspace component from index.ts:

import { getAsyncLifecycle } from '@openmrs/esm-framework'; const moduleName = '@myorg/esm-gastrodynamics-app'; const options = { featureName: 'gastrodynamics', moduleName, }; export const gastrodynamicsWorkspace = getAsyncLifecycle( () => import('./gastrodynamics.workspace'), options, );

After registration, launch the workspace with launchWorkspace2('gastrodynamics-form-workspace', props).

Last updated on