Le système d’extension
Introduit par RFC-27 , le système d’extension permet aux modules frontaux d’insérer des éléments d’interface utilisateur les uns dans les autres, et à ces interactions d’être configurées par les administrateurs du système.
Les personnes qui connaissent le système d’extension d’OpenMRS RefApp
2.x retrouveront des
concepts proches, mais plus simples. Les “extensions” jouent un rôle similaire, les anciens “points” sont maintenant
appelés “slots”, et les modules frontend O3 utilisent routes.json au lieu des anciennes définitions d’apps RefApp.
Concepts clés
Le système d’extension repose sur deux concepts: les extensions et les slots. Une extension est un composant. Un slot est un emplacement dans l’interface utilisateur.
Les extensions sont rendues dans des slots. Une extension est associée à un slot de l’une des manières suivantes:
- L’extension nomme le slot dans sa définition, en utilisant la propriété
slotouslots. - Un appel à la fonction
attach. - Un administrateur système ajoute l’extension au slot avec le tableau
adddu slot.
Les noms de slots sont globaux au runtime. Si deux modules rendent le même nom de slot, O3 le traite comme le même point d’extension; s’ils essaient d’enregistrer des propriétaires différents, le runtime affiche un avertissement et garde le premier propriétaire.
Quand utiliser les extensions et les slots
Le système d’extension doit être considéré comme un système permettant aux administrateurs de configurer un comportement. Il ne doit pas être considéré comme un moyen de réutiliser les composants entre les modules.
La question clé est la suivante: Suis-je en train de créer une collection de choses similaires, comme des boutons ou des tuiles, qu’un administrateur pourrait vouloir réorganiser ou modifier d’une autre manière?
Si c’est le cas, il peut s’agir d’un bon endroit pour utiliser les extensions.
Que se passe-t-il si je veux simplement monter quelque chose d’un framework dans quelque chose d’un autre framework?
Il suffit d’utiliser la fonction Single SPA mountParcel .
Que faire si je veux juste utiliser un composant d’un module dans un autre module, et que je peux changer les deux?
Pensez à exporter le composant et à l’utiliser de la manière habituelle.
Utilisation
Les extensions sont définies dans le fichier routes.json d’un module. C’est l’approche actuelle pour les modules frontend O3. L’ancienne approche d’enregistrement avec setupOpenMRS, utilisée dans Core v4 et antérieur, n’est plus prise en charge dans Core v5+. Chaque définition d’extension inclut un nom et une référence de composant. Elle peut aussi préciser les slots auxquels l’extension est attachée par défaut, les privilèges requis, les contraintes de connectivité, les feature flags, les expressions d’affichage et meta.
Les slots sont des composants. Il existe un composant React ExtensionSlot. Si vous travaillez dans un framework différent et que vous souhaitez créer un slot d’extension, veuillez contacter l’équipe OpenMRS Frontend 3.0 sur Slack.
Au runtime, le flux est le suivant:
- Le build de la distro inclut le
routes.jsonde chaque module dansroutes.registry.json. - L’app shell lit le registre de routes et enregistre chaque définition d’extension.
- Un module hôte rend
<ExtensionSlot name="..." />. - O3 calcule les IDs d’extensions assignés à partir des attaches
slot/slotspar défaut et des réglages administrateuradd,remove,orderetconfigure. - O3 filtre les extensions assignées selon les privilèges, les expressions d’affichage, les feature flags et les champs
online/offlinede la définition de l’extension. - Le slot rend chaque extension comme un parcel single-spa et lui transmet
_meta,_extensionContextet les props destatedu slot.
Principes
Nomenclature
Nommer les extensions
Une extension aura un nom qui l’identifiera. Ce nom doit décrire ce que fait l’extension. Il ne doit rien avoir à voir avec l’endroit où l’extension apparaîtra dans l’application. Elle n’a pas de sens inné de la place.
✅ Bons noms d’extensions:
- Table vitale
- Avatar de l’utilisateur
- Tuile biométrique
❌ Mauvais noms d’extension:
- Barre Top (“top” indique un lieu)
- Lien vers les rapports de la page d’accueil (“page d’accueil” indique un lieu)
- Steve (les noms doivent être descriptifs)
Remarque: vous verrez probablement beaucoup de noms d’extensions et de slots en minuscules avec des tirets. Ce n’est pas obligatoire; il est préférable de donner aux extensions des noms agréables à lire. De même, beaucoup de slots ont le suffixe “slot”, mais ce n’est pas nécessaire non plus.
Nommer les slots
Un slot a également un nom qui l’identifie. Ce nom doit décrire l’emplacement qu’il représente dans l’application. S’il décrit les éléments qui peuvent s’y trouver, il ne doit utiliser que les termes les plus généraux possibles, comme “bouton”, “tuile” ou “widget”.
✅ Bons noms d’emplacements:
- Menu de droite de la navigation principale
- Boîte de détails de l’en-tête du patient
- Boutons d’en-tête de formulaire
❌ Mauvais noms de créneaux:
- Adresse du patient (trop prescriptif sur le contenu)
- homepage-widgets-slot (devrait être
Homepage widgets) - Boutons supplémentaires (trop vagues)
Style
Une extension doit être aussi indifférente que possible au contexte dans lequel elle apparaît. Cela signifie qu’il faut éviter de définir la taille d’une extension. Les extensions doivent être réactives (dans la limite du raisonnable), de sorte que leur contenu s’adapte à différentes dimensions d’extension.
Les slots doivent être responsables de la quantité de style qui s’applique de manière générique à l’ensemble de leur contenu. Si toutes les extensions d’un slot doivent avoir une bordure, le slot doit appliquer la bordure. Le slot doit également être responsable de la définition des dimensions dans lesquelles les extensions seront rendues.
Un slot peut appliquer des styles à une extension avec le sélecteur CSS suivant:
.slot > * > * {
...;
}Configurabilité des extensions
L’avantage de la configurabilité du système d’extension est qu’il n’est pas nécessaire d’y penser. Les extensions et les slots ont une interface de configuration standard qui permet aux administrateurs d’ajouter, de supprimer et de réorganiser les extensions, ainsi que de configurer des paramètres spécifiques à l’extension dans un slot particulier.
Vous pouvez utiliser useConfig comme d’habitude dans une extension.
Le schéma d’une extension peut être spécifié en utilisant defineExtensionConfigSchema. Si aucun schéma n’est défini spécifiquement pour votre extension, l’extension héritera de la configuration du module qui la contient.
Contrôle d’accès basé sur les rôles (RBAC)
Les extensions prennent en charge le contrôle d’accès basé sur les rôles (RBAC) via les privilèges. Vous pouvez déclarer les privilèges requis directement dans la définition de l’extension dans routes.json, et les administrateurs peuvent remplacer ou affiner ces exigences via la configuration.
Déclarer les privilèges dans routes.json :
{
"extensions": [
{
"name": "vitals-widget",
"component": "vitals",
"slot": "patient-header-slot",
"privileges": ["View Vitals"]
}
]
}La propriété privileges peut être une chaîne unique ou un tableau de chaînes. Si un tableau est fourni, l’utilisateur doit avoir tous les privilèges spécifiés pour voir l’extension. Si une chaîne unique est fournie, l’utilisateur doit avoir ce privilège spécifique. Si l’utilisateur n’a pas les privilèges requis, l’extension ne sera pas rendue. Note : Les utilisateurs avec le rôle “System Developer” contournent les vérifications de privilèges.
Remplacer les privilèges via la configuration :
Les administrateurs peuvent remplacer les exigences de privilèges par défaut de l’extension en utilisant l’objet de configuration Display conditions (décrit dans la section suivante). Cela permet d’ajuster finement le contrôle d’accès sans modifier le code.
Conditions d’affichage
Chaque extension dispose automatiquement d’un objet de configuration Display conditions (Conditions d’affichage), qui permet aux administrateurs de contrôler quand les extensions sont affichées sans modifier le code. Les conditions d’affichage sont évaluées pendant la phase de filtrage avant que les extensions ne soient rendues. Ceci est particulièrement utile pour affiner les exigences de contrôle d’accès basé sur les rôles définies dans routes.json ou pour mettre en œuvre des politiques d’accès spécifiques à l’implémentation.
L’objet Display conditions prend en charge les propriétés suivantes :
-
privileges(tableau de chaînes) : Remplace l’exigence de privilèges par défaut de l’extension déclarée dansroutes.json. Si spécifié, l’utilisateur doit avoir tous ces privilèges pour voir l’extension. Si non spécifié, la propriétéprivilegespar défaut de la définition de l’extension est utilisée. Cela permet aux administrateurs de personnaliser le contrôle d’accès par implémentation sans modifications de code. Note : Les utilisateurs avec le rôle “System Developer” contournent les vérifications de privilèges. -
expression(chaîne) : Une expression JavaScript booléenne qui doit s’évaluer àtruepour que l’extension s’affiche. L’expression a accès à un objetsessioncontenant :session.authenticated- si l’utilisateur est authentifiésession.user- l’objet utilisateur connecté (avec des propriétés commeuuid,username,systemId,privileges,roles, etc.)session.sessionLocation- l’emplacement de session actuelsession.currentProvider- le fournisseur actuelsession.locale- la locale actuelle
Le contexte de l’expression inclut aussi les valeurs de l’objet
statedu slot; les slots liés à un patient peuvent donc exposer des valeurs commepatientUuidaux expressions d’affichage.
Les conditions d’affichage peuvent être fournies comme configuration de base de l’extension, ou comme remplacement spécifique au slot sous l’objet extensionSlots.<slotName>.configure du module propriétaire du slot. Si l’extension a son propre schéma de configuration, les valeurs de base se placent sous le nom de l’extension; sinon elles se placent sous le nom du module qui fournit l’extension. Les remplacements spécifiques au slot sont le bon choix lorsque la même extension apparaît dans plusieurs slots et nécessite des règles de visibilité différentes selon l’emplacement.
La connectivité est contrôlée par les champs online et offline de la définition de l’extension dans routes.json, pas par la configuration Display conditions.
Exemple de configuration :
La configuration d’un slot se place sous le module qui possède ce slot. Dans cet exemple, @openmrs/esm-patient-chart-app est le module qui rend patient-header-slot.
{
"@openmrs/esm-patient-chart-app": {
"extensionSlots": {
"patient-header-slot": {
"configure": {
"vitals-widget": {
"Display conditions": {
"privileges": ["View Vitals", "Manage Vitals"],
"expression": "session.user.systemId !== 'admin' && session.sessionLocation?.uuid === 'some-location-uuid'"
}
}
}
}
}
}
}Cette configuration :
- Exigerait que l’utilisateur ait à la fois les privilèges “View Vitals” et “Manage Vitals”
- Masquerait l’extension pour les utilisateurs avec systemId “admin”
- N’afficherait l’extension que lorsque l’emplacement de session correspond à un UUID spécifique
Éditeur UI
L’application Implementer Tools inclut une fonctionnalité Éditeur UI qui fournit une interface visuelle pour configurer les extensions. Lorsqu’elle est activée, elle affiche des superpositions sur les slots d’extension et les extensions dans toute l’application, permettant aux administrateurs de :
- Cliquer sur les slots pour configurer quelles extensions y apparaissent
- Cliquer sur les extensions pour configurer leurs paramètres dans un slot spécifique
- Identifier visuellement où les extensions sont rendues
- Voir les comptes d’extensions et les métadonnées en un coup d’œil
L’éditeur UI utilise les attributs data-extension-slot-name et data-extension-id qui sont automatiquement ajoutés aux éléments DOM des slots et des extensions. Le style position: relative sur les conteneurs d’extension permet à l’éditeur UI de positionner correctement ses éléments de superposition.
Pour activer l’éditeur UI, ouvrez les Implementer Tools (généralement via un raccourci clavier ou un menu) et activez l’interrupteur “UI editor” dans le panneau de configuration.
État
Parfois, les extensions ne sont pas aussi indépendantes que nous le souhaiterions, et doivent s’attendre à un certain état de la part du slot dans lequel elles sont montées. Le plus souvent, les extensions qui concernent un patient spécifique acceptent un paramètre patientUuid qui peut être utilisé pour récupérer des informations pertinentes sur le patient.
L’état est fourni en tant que paramètre aux composants ExtensionSlot ou Extension, et reçu en tant que propriété par l’extension.
Utiliser le composant Extension :
Lorsque vous fournissez des enfants personnalisés à ExtensionSlot (soit comme nœud React, soit comme fonction), vous devez utiliser le composant <Extension /> pour marquer où chaque extension doit être rendue. Le composant Extension est un helper React qui rend une seule instance d’extension dans un slot.
Si ExtensionSlot a des enfants personnalisés, transmettez l’état à <Extension state={...} />. Fournir à la fois children et state directement à ExtensionSlot déclenchera une erreur.
import { ExtensionSlot, Extension } from "@openmrs/esm-framework";
// Avec un wrapper personnalisé
<ExtensionSlot name="patient-header-slot">
<div className="custom-wrapper">
<Extension />
</div>
</ExtensionSlot>
// Avec des enfants fonction
<ExtensionSlot name="dashboard-slot">
{(extension) => (
<div className={`widget-${extension.meta?.size}`}>
<Extension state={{ patientUuid: "123" }} />
</div>
)}
</ExtensionSlot>Si vous ne fournissez pas d’enfants à ExtensionSlot, le composant Extension est automatiquement rendu pour chaque extension.
Voir les API docs de ExtensionSlot pour plus d’informations.
Meta
Parfois, les extensions peuvent vouloir transmettre des informations au slot qui les reçoit. Ceci est utilisé, par exemple, par les widgets des dossiers des patients. Les tableaux de bord restituent ces widgets sous forme de grille. Lorsqu’un tableau de bord reçoit un widget, celui-ci informe le tableau de bord (qui est un slot) du nombre de colonnes de la grille qu’il souhaite occuper. Cela se fait à l’aide de meta.
Les métadonnées sont fournies par les extensions dans leur définition dans routes.json. (Dans Core v4 et antérieur, cela se faisait dans la fonction setupOpenMRS, mais cette approche n’est plus prise en charge dans Core v5+.)
Les slots peuvent accéder à la méta via l’API du système d’extension, par exemple en utilisant useExtensionSlotMeta. meta vient de la définition de l’extension; utilisez les réglages configure du slot quand les administrateurs doivent remplacer une configuration spécifique à l’extension.
Ordre
Par défaut, les extensions sont rendues dans l’ordre où elles sont déclarées ou attachées. L’ordre final est déterminé par un système de priorité à trois niveaux :
- Ordre configuré (priorité la plus élevée) : Les extensions listées dans le tableau de configuration
orderdu slot apparaissent en premier, dans l’ordre spécifié dans ce tableau. - Ordre enregistré : Les extensions avec une propriété
orderdans leur définitionroutes.jsonapparaissent ensuite, triées par leur valeur d’ordre (décalées de 1000 pour s’assurer qu’elles viennent après les extensions configurées). - Ordre attaché (priorité la plus faible) : Les extensions sans spécification d’ordre apparaissent en dernier, dans l’ordre où elles ont été attachées (décalées de 2000 pour s’assurer qu’elles viennent après toutes les extensions ordonnées).
Les extensions ajoutées par les administrateurs via le tableau add participent à l’ordre configuré par order et à l’ordre enregistré par l’extension. Si une extension ajoutée n’est pas listée dans order et n’a pas de valeur order enregistrée, le runtime n’a pas d’index d’attachement pour elle; ne vous fiez donc pas à l’ordre du tableau add pour un placement important. Ajoutez l’ID de l’extension dans order lorsque sa position compte.
Les extensions peuvent fournir un index order dans leur définition pour influencer l’ordre dans lequel elles sont rendues. Cela fonctionne comme z-index en CSS - de la même manière, c’est un moyen de définir un ordre relatif entre des éléments qui ne sont pas officiellement connus les uns des autres.
Les administrateurs peuvent également modifier cet ordre avec le tableau order en même temps que les tableaux add et remove dans la configuration du slot. Le runtime applique automatiquement ces changements de configuration pour déterminer l’ordre final des extensions.
Drapeaux de fonctionnalité
Les extensions peuvent être rendues conditionnellement en fonction des drapeaux de fonctionnalité. Cela permet de déployer progressivement de nouvelles fonctionnalités ou d’activer des fonctionnalités expérimentales pour des implémentations spécifiques.
Déclarer les drapeaux de fonctionnalité dans routes.json :
{
"extensions": [
{
"name": "experimental-widget",
"component": "experimental",
"slot": "dashboard-slot",
"featureFlag": "experimental-feature"
}
]
}Si la propriété featureFlag est spécifiée, l’extension ne sera rendue que lorsque ce drapeau de fonctionnalité est activé. Enregistrez le flag dans le tableau featureFlags du routes.json du module, ou depuis le code avec registerFeatureFlag. Les flags enregistrés apparaissent dans les Implementer Tools avec des interrupteurs, et les implémenteurs peuvent les activer globalement avec la configuration de l’app shell.
Rendu avancé des slots
Le composant ExtensionSlot prend en charge des modèles de rendu avancés pour personnaliser l’affichage des extensions.
Filtrer les extensions avec la prop select :
Vous pouvez filtrer quelles extensions sont rendues en utilisant la prop select, qui accepte une fonction qui reçoit le tableau des extensions assignées et retourne un tableau filtré :
<ExtensionSlot
name="patient-header-slot"
select={(extensions) => extensions.filter(ext => ext.meta?.priority === 'high')}
/>Rendu personnalisé avec des enfants fonction :
Vous pouvez personnaliser le rendu de chaque extension en passant une fonction comme enfants. La fonction reçoit l’objet extension assignée. Si vos extensions ont besoin d’état, capturez cet état depuis votre composant et transmettez-le à <Extension state={...} /> :
const slotState = { patientUuid };
<ExtensionSlot name="dashboard-slot">
{(extension) => (
<div className={`widget widget-${extension.meta?.size || 'medium'}`}>
<Extension state={slotState} />
</div>
)}
</ExtensionSlot>L’objet extension (de type AssignedExtension) inclut des propriétés comme name, id, meta, moduleName, config, online, offline, et featureFlag, vous permettant de créer des mises en page sophistiquées basées sur les métadonnées de l’extension.
Accéder au contexte d’extension :
Chaque extension reçoit une prop _extensionContext contenant :
extensionId: L’identifiant unique de cette instance d’extensionextensionSlotName: Le nom du slot dans lequel cette extension est rendueextensionSlotModuleName: Le module qui possède le slotextensionModuleName: Le module qui fournit l’extension
Ce contexte est utile pour le débogage, la journalisation ou la logique conditionnelle qui dépend de l’endroit où une extension est rendue.
Gestion dynamique des slots
Bien que la plupart des extensions soient attachées aux slots de manière déclarative via routes.json ou la configuration de l’administrateur, vous pouvez également gérer les attachements de slots programmatiquement au moment de l’exécution.
Attacher des extensions dynamiquement :
import { attach } from "@openmrs/esm-framework";
// Attacher une extension à un slot au moment de l'exécution
attach("patient-header-slot", "vitals-widget");
// Attacher la même extension plusieurs fois avec des IDs différents
attach("patient-header-slot", "vitals-widget#instance1");
attach("patient-header-slot", "vitals-widget#instance2");Les IDs d’extension peuvent inclure un suffixe # optionnel pour distinguer plusieurs instances de la même extension dans un slot. Par exemple, "vitals-widget#primary" et "vitals-widget#secondary" sont deux instances distinctes de l’extension "vitals-widget". Lorsque vous configurez, retirez ou ordonnez une instance, utilisez l’ID complet avec le suffixe #.
Ceci est utile pour :
- Les slots dynamiques qui sont créés au moment de l’exécution (par exemple, les slots d’espace de travail)
- La logique spécifique à l’implémentation qui affiche conditionnellement des extensions
- Les extensions temporaires qui ne doivent apparaître que sous certaines conditions
- Rendre la même extension plusieurs fois dans le même slot avec des configurations différentes
Détacher des extensions :
Déprécié : Les fonctions detach et detachAll sont dépréciées. Les attachements d’extensions doivent être considérés comme déclaratifs. Utilisez la configuration (tableau remove) ou évitez d’attacher des extensions en premier lieu plutôt que de les détacher au moment de l’exécution.
import { detach, detachAll } from "@openmrs/esm-framework";
// Détacher une extension spécifique d'un slot (déprécié)
detach("patient-header-slot", "vitals-widget");
// Détacher toutes les extensions d'un slot (déprécié)
detachAll("patient-header-slot");Interroger les extensions assignées :
Vous pouvez obtenir programmatiquement la liste des extensions assignées à un slot :
import { getAssignedExtensions } from "@openmrs/esm-framework";
const extensions = getAssignedExtensions("patient-header-slot");
// Retourne un tableau d'objets AssignedExtension avec les propriétés :
// - id : ID unique de l'instance d'extension (peut inclure un suffixe #)
// - name : nom de l'extension
// - moduleName : le module qui fournit l'extension
// - meta : objet de métadonnées de l'extension
// - config : configuration de l'extension (null jusqu'à ce que le slot soit monté)
// - online, offline : drapeaux de connectivité
// - featureFlag : nom du drapeau de fonctionnalité optionnelCeci est utile pour créer des implémentations de slots personnalisées ou pour déboguer les assignations d’extensions.
Comment les slots et les extensions s’enregistrent
En coulisses, les slots appellent registerExtensionSlot(moduleName, slotName) lorsqu’ils sont montés (le helper React <ExtensionSlot> s’en charge pour vous). Cela indique au runtime quel module est propriétaire du slot et alimente le store interne de @openmrs/esm-extensions.
Les extensions déclarées dans routes.json sont enregistrées via registerExtension. (L’approche setupOpenMRS() de Core v4 et antérieur n’est plus prise en charge dans Core v5+.) Préciser slot/slots dans la même définition est la manière déclarative d’alimenter attachedIds ; invoquer attach(slotName, extensionId) permet de piloter cela de façon impérative (utile pour les slots dynamiques). Une fois enregistrées/attachées, les extensions se voient fusionner la configuration administrateur, les feature flags, les privilèges et les contraintes online/offline avant d’être rendues.
Meta et API de slot
Les slots peuvent lire meta via useExtensionSlotMeta() et s’en servir pour ajuster leur mise en page : les dashboards lisent par exemple les demandes de colonnes, les entêtes patients lisent les dimensions des badges, etc. meta est une métadonnée de définition, tandis que la configuration de l’extension est exposée séparément via la valeur config de l’extension assignée et via useConfig() dans l’extension.
Pour aller plus loin (par exemple quand les helpers par défaut ne suffisent pas), consultez registerExtensionSlot, renderExtension, getAssignedExtensions et useExtensionSlotMeta depuis @openmrs/esm-framework, afin de comprendre comment les stores, configurations et vérifications de connectivité interagissent.
Dépannage
Si votre extension n’apparaît pas :
- Vérifiez que le module apparaît dans
/openmrs/spa/routes.registry.jsonet que l’entréeextensionsest présente. - Vérifiez
routes.json— la propriétécomponentdoit correspondre à l’export nommé danssrc/index.ts. - Assurez-vous que le slot cible existe et est rendu (recherchez
<ExtensionSlot name="..." />dans le module hôte). - Vérifiez les champs
privileges,displayExpression,featureFlagetonline/offlinedéfinis dansroutes.json. Vérifiez aussi la configurationDisplay conditionspour les remplacements de privilèges ou d’expression. - À moins d’avoir besoin d’un contrôle à l’exécution, enregistrez le slot dans
routes.jsonplutôt qu’avecattach. - La configuration du slot peut ajouter, retirer, configurer ou réordonner les entrées avec les tableaux
add,remove,configureetorder.
Exemple guidé
-
Exposez le composant dans
src/index.ts:import { getSyncLifecycle } from "@openmrs/esm-framework"; import { createDashboardLink } from "./createDashboardLink"; import { dashboardMeta } from "./dashboard.meta"; const moduleName = "@openmrs/esm-patient-list-management-app"; const options = { featureName: "patient list", moduleName, }; export const patientListDashboardLink = getSyncLifecycle(createDashboardLink(dashboardMeta), options);Note : Le wrapper
getSyncLifecycleest requis pour convertir votre composant React en un cycle de vie compatible avec les extensions. L’objet options doit inclurefeatureNameetmoduleName. -
Ajoutez une entrée dans
routes.json:{ "extensions": [ { "name": "patient-lists-dashboard-link", "component": "patientListDashboardLink", "slot": "homepage-dashboard-slot", "meta": { "name": "patient-lists", "slot": "patient-lists-dashboard-slot", "title": "Patient lists" } } ] } -
Vérifiez qu’un module hôte rend le slot cible, par exemple
<ExtensionSlot name="homepage-dashboard-slot" />. -
Les administrateurs peuvent ensuite ajuster le tableau
orderdu slot ou utiliser les tableauxadd/removevia le système de configuration pour contrôler quelles extensions apparaissent et dans quel ordre. -
Pour réutiliser la même extension dans un autre slot, appelez
attach("autre-slot", "patient-lists-dashboard-link")à l’exécution, ou ajoutez l’extension au tableauaddde l’autre slot dans la configuration.
Ressources supplémentaires
Courtes vidéos d’introduction:
- OpenMRS Frontend 3 Extension System 1 - Basics
- OpenMRS Frontend 3 Extension System 2 - State and Meta
- Présentation d’introduction: Quick Guide to Slots (Guide rapide des créneaux)
Pour une description technique succincte du système d’extension, voir le Extensions RFC .
Atelier
Un atelier en direct a été organisé sur Zoom, fournissant une introduction complète au système d’extension, ainsi que des problèmes pratiques. Les enregistrements et le matériel sont disponibles ci-dessous.
- Partie 1: À propos de l’architecture de notre module frontal et de l’utilisation des extensions
- Partie 2: Session pratique sur l’architecture de l’EMF et l’utilisation des extensions
Comment fonctionne le système d’extension
Pour que le système d’extension fonctionne, quatre éléments sont nécessaires:
- Un modèle de composant générique avec un cycle de vie défini et un mécanisme de chargement
- Un moyen de définir où les extensions doivent être placées (ce que l’on appelle un “slot”)
- Un moyen de définir une extension en la couplant à (1)
- Une configuration pour assigner les extensions disponibles de (3) aux slots (2)
Explorons ces quatre éléments en profondeur.
Les coulisses
Pour (1), on utilise le mécanisme de colisage de single-spa.
Pour (2), vous pouvez utiliser la fonction registerExtensionSlot avec renderExtension. Pour les modules React, les helpers ExtensionSlot et Extension sont exportés par @openmrs/esm-framework.
Pour (3), vous définissez les extensions dans le fichier routes.json de votre application (recommandé pour Core v5+). Voici un exemple :
{
"extensions": [
{
"name": "foo",
"component": "fooComponent"
}
]
}Note : fooComponent est le nom de l’export défini dans src/index.ts.
Comme raccourci pour (4), vous pouvez spécifier un slot cible via la propriété slot dans la définition de l’extension. Alternativement, vous pouvez attacher les extensions par programme en utilisant attach :
// attache une extension "foo" à un slot "foo-slot"
attach("foo-slot", "foo");Généralement, l’assignation de slot est effectuée par défaut au moment de l’initialisation (via la propriété slot dans routes.json), ou explicitement via la configuration de l’administrateur. L’exception concerne les slots “dynamiques” créés à l’exécution, tels que les slots d’espace de travail dans le module de dossier patient.
Extensions et emplacements
Une extension peut se trouver dans l’un des quatre états suivants par rapport à un emplacement d’extension :
- attached : Défini par le code en utilisant
attach()(ou de manière déclarative via la propriétéslot/slotsdansroutes.json). Note :detach()est déprécié. - configured : Défini via la configuration de l’administrateur en utilisant les tableaux
addetremove - assigned : Calculé en fusionnant les extensions attachées et configurées, puis filtré en fonction des privilèges, des feature flags et de la connectivité (drapeaux
online/offline) - connected : (Terme déprécié) C’est la même chose que “assigned” après filtrage — les extensions qui sont réellement rendues
Le runtime utilise l’état “assigned” pour déterminer quelles extensions rendre. Le filtrage se fait automatiquement en fonction des privilèges de l’utilisateur, des feature flags activés et de l’état de connectivité.
Rendu
Les extensions sont rendues en suivant leurs fonctions de cycle de vie exportées. Les fonctions getAsyncLifecycle et getSyncLifecycle de @openmrs/esm-framework sont des couches de commodité qui exportent ces fonctions de cycle de vie, reliées ensemble avec single-spa-react. Utilisez getAsyncLifecycle pour les composants qui doivent être chargés de manière paresseuse, et getSyncLifecycle pour les composants déjà disponibles de manière synchrone.
En bref:
- Lorsque le composant doit être rendu, la fonction
loadest évaluée. Si elle retourne unePromise(via unimport()chargé de manière asynchrone), le système attend que le composant soit disponible. - Le composant est enveloppé avec les fonctions de cycle de vie fournies par
single-spa-react. - Les fonctions de cycle de vie (
bootstrap,mount,unmountetupdate) sont exportées et gérées par Single-SPA.
Ces fonctions de cycle de vie ne sont pas magiques - théoriquement, vous pourriez les écrire vous-même, cependant, puisque l’écosystème single-spa fournit déjà des wrappers de commodité tels que single-spa-react pour de nombreux frameworks, nous ne le recommandons pas.
Avant le rendu, deux facteurs supplémentaires sont évalués :
- Mode de connectivité : Si l’extension doit être rendue en fonction de l’état en ligne/hors ligne actuel
- Props du composant : Quelles données et services doivent être passés au composant rendu
Le mode de connectivité est déterminé en vérifiant l’état de connectivité du navigateur via isOnline() de @openmrs/esm-utils. Cette fonction vérifie d’abord si window.offlineEnabled est vrai ; sinon, elle suppose que l’application est toujours en ligne. Si le mode hors ligne est activé, elle vérifie ensuite navigator.onLine pour déterminer l’état de connectivité réel.
Les drapeaux online et offline de l’extension contrôlent si elle se rend dans chaque mode :
- Si
online: false, l’extension ne se rendra pas lorsque le navigateur est en ligne - Si
offline: true, l’extension se rendra lorsque le navigateur est hors ligne - Par défaut, les extensions se rendent en ligne (
online: true) et ne se rendent pas hors ligne (offline: false)
Note : Bien que le système de types permette à online et offline d’être des objets, cette fonctionnalité n’est pas actuellement implémentée dans la logique de rendu. Utilisez des valeurs booléennes.
Les props du composant incluent :
_meta: L’objet de métadonnées de l’extension_extensionContext: Informations sur l’extension et son slot (ID d’extension, nom du slot, noms des modules)- Toutes les props supplémentaires passées via la prop
statedeExtensionSlot, ou via<Extension state={...} />lorsque des enfants personnalisés sont utilisés