I’m working on the new O3 workspace system and I ran into an issue I have accounted for. I believe some sort of inheritance in routes.json
could be the best way to solve the issue.
Background:
Each workspace now belongs to a “window”. A window may correspond to an icon in the action menu. Related workspaces (like child workspaces) can belong to the same window. For example, the “clinical forms” window in the patient chart contains 3 workspaces: the forms dashboard workspace that lists all forms, the form entry workspace that opens a RFE form , and the HTML form entry workspace that opens a HFE form.
A workspace window can belong to a “group”. A “group” is just a list of related windows. For example, in the patient-chart group, we have the following windows: clinical forms, visit notes, order basket, patient list.
With our new design, the clinical forms workspaces and window are declared in esm-patient-forms-app’s routes.json as follows:
{
"workspaces2": [
{
"name": "clinical-forms-workspace", // the dashboard
"component": "clinicalFormsWorkspace",
"window": "clinical-forms",
},
{
"name": "patient-form-entry-workspace", // opens RFE forms
"component": "patientFormEntryWorkspace",
"window": "clinical-forms",
},
{
"name": "patient-html-form-entry-workspace", // opens HFE forms
"component": "patientHtmlFormEntryWorkspace",
"window": "clinical-forms",
}
],
"workspaceWindows2": [
{
"name": "clinical-forms-window",
"icon": "clinicalFormActionButton",
"width": "extra-wide",
"group": "patient-chart"
}
]
}
The Problem:
We want the Clinical Forms window to appear in both the patient chart and the ward app (each app has its own workspace group). In the current system, this is done in a hacky manner:
- Those 3 workspaces are each declared twice in
patient-froms-app
’sroutes.json
file, once for the patient chart and once for the ward app. (Having anything related to the ward app outside the ward app is an anti-pattern) - Wherever we launch a clinical form workspace, we have to launch the “right” one (“patient-form-entry-workspace” vs “ward-patient-form-entry-workspace”). This includes places within some workspaces where we launch child workspaces. So those workspaces are laced with extra props for the names of the “right” child workspaces to open.
It’d be really nice to be able to say “I want this window to belong to the patient-chart group AND the ward-app group” easily, without having to re-declare each workspace within the window and parameterize the child workspace names as props in those workspaces. This will be an even bigger problem for windows with many (child) workspaces, like the order basket.
Design issue 1:
If we want a workspace to belong to multiple windows, we can kind of work around it by re-declaring the workspace, so we have it twice like this:
{
workspaces: [
{
name: "patient-chart-clinical-forms-workspace",
component: "clinical-forms-dashboard",
window: "patient-chart-forms"
},
// we can refer to components from another ESM, so it's possible for this entry to be declared in the ward app's routes.json instead of
// in the forms app
{
name: "ward-app-clinical-forms-workspace",
component: "@openmrs/esm-patient-forms-app#clinical-forms-dashboard",
window: "ward-app-forms"
},
]
}
However, if we want a window to belong to multiple groups, we can’t simply re-declare the window. We have to also re-declare every one of its workspace and have it belong to the new window.
Design issue 2:
When we launch a workspace, we only specify the workspace name; the corresponding window and group are opened as well, but implicitly. The implicit opening works by the assumption that a workspace only belongs to one window, and a window only belongs to one group. So if we want to get away with not needing to declare the “clinical-forms-workspace” twice, we have to somehow be able to specify the right window / group from which to open the workspace in when we do launchWorkspace("clinical-forms-workspace")
.
Proposal:
We allow some sort of inheritance when declaring a window, so if we want a clinical form window in the ward app group, we can do this in the ward app’s routes.json:
{
workspaceWindows: [
{
// specifies name of the "base" window to inherit from.
// This window inherits all workspaces that are declared to belong to the base window.
"_extends": "clinical-forms-window",
"name": "ward-app-clinical-forms-window",
"icon": "some-icon-component", // optional, overrides value from base
"width": "extra-wide", // optional, overrides value from base
"group": "ward-app-group", // optional, overrides value from base (although it'd be silly to not specify)
}
]
}
Note that the inheritance can be nested (window A extends window B extends window C). The complete set of workspaces that belong a window will not be known until routes.json
files from all apps are parsed.
When we launch a workspace, we can either specify just the workspace name:
// when a workspace is declared, we must specify the window it belongs to. So no ambiguity as to which window to open.
launchWorkspace('clinical-forms-dashboard-workspace')
Or, we can explicitly specify the window (required if we use a window that extends another window):
launchWorkspace('ward-app-forms#clinical-forms-dashboard-workspace')
Note that when we launch a child workspace, we use a specialized launchChildWorkspace(workspaceName: string)
function that is only available within a workspace, and the parent workspace MUST have already been opened within a window already. So there is no ambiguity as to which window (and group) to open the child workspace in, and we don’t need to support the #
syntax.