# Mode: Routes - [Mode: Routes](#mode-routes) - [Overview](#overview) - [Route](#route) - [Route: path](#route-path) - [Route: init](#route-init) - [Default init](#default-init) - [Writing a custom init](#writing-a-custom-init) - [Route: layoutTemplate](#route-layouttemplate) - [FAQ](#faq) ## Overview Modes are tied to a specific route in the viewer, and multiple modes/routes can be present within a single application. This makes `routes` config, THE most important part of the mode configuration. ## Route `@ohif/viewer` **compose** extensions to build applications on different routes for the platform. Below, you can see a simplified version of the `longitudinal` mode and the `routes` section which has defined one `route`. Each route has three different configuration: - **route path**: defines the route path to access the built application for that route - **route init**: hook that runs when application enters the defined route path, if not defined the default init function will run for the mode. - **route layout**: defines the layout of the application for the specified route (panels, viewports) ```js export default function mode() { return { id: 'viewer', displayName: '', routes: [ { path: 'longitudinal', /*init: ({ servicesManager, extensionManager }) => { //defaultViewerRouteInit },*/ layoutTemplate: ({ location, servicesManager }) => { return { id: ohif.layout, props: { leftPanels: [ 'org.ohif.measurement-tracking.panelModule.seriesList', ], rightPanels: [ 'org.ohif.measurement-tracking.panelModule.trackedMeasurements', ], viewports: [ { namespace: 'org.ohif.measurement-tracking.viewportModule.cornerstone-tracked', displaySetsToDisplay: [ 'org.ohif.default.sopClassHandlerModule.stack', ], }, { namespace: 'org.ohif.dicom-sr.viewportModule.dicom-sr', displaySetsToDisplay: [ 'org.ohif.dicom-sr.sopClassHandlerModule.dicom-sr', ], }, ], }, } }, }, ], /* ... */ } } ``` ### Route: path Upon initialization the viewer will consume extensions and modes and build up the route desired, these can then be accessed via the study list, or directly via url parameters. > Note: Currently, only one route is built for each mode, but we will enhance route > creation to create separate routes based on the `path` config for each `route` object. There are two types of `routes` that are created by the mode. - Routes with dataSourceName `/${mode.id}/${dataSourceName}` - Routes without dataSourceName `/${mode.id}` Therefore navigating to `http://localhost:3000/viewer/?StudyInstanceUIDs=1.3.6.1.4.1.25403.345050719074.3824.20170125113417.1` will run the app with the layout and functionalities of the `viewer` mode using the `defaultDataSourceName` which is defined in the [App Config](../configuring/index.md) You can use the same exact mode using a different registered data source (e.g., `dicomjson`) by navigating to `http://localhost:3000/viewer/dicomjson/?StudyInstanceUIDs=1.3.6.1.4.1.25403.345050719074.3824.20170125113417.1` ### Route: init The mode also has an init hook, which initializes the mode. If you don't define an `init` function the `default init` function will get run (logic is located inside `Mode.jsx`). However, you can define you own init function following certain steps which we will discuss next. #### Default init Default init function will: - `retriveSeriesMetaData` for the `studyInstanceUIDs` that are defined in the URL. - Subscribe to `instanceAdded` event, to make display sets after a series have finished retrieving its instances metadata. - Subscribe to `seriesAdded` event, to run the `HangingProtocolService` on the retrieves series from the study. A *simplified* "pseudocode" for the `defaultRouteInit` is: ```js async function defaultRouteInit({ servicesManager, studyInstanceUIDs, dataSource, }) { const { DisplaySetService, HangingProtocolService } = servicesManager.services // subscribe to run the function after the event happens DicomMetadataStore.subscribe( 'instancesAdded', ({ StudyInstanceUID, SeriesInstanceUID }) => { const seriesMetadata = DicomMetadataStore.getSeries( StudyInstanceUID, SeriesInstanceUID ) DisplaySetService.makeDisplaySets(seriesMetadata.instances) } ) studyInstanceUIDs.forEach((StudyInstanceUID) => { dataSource.retrieveSeriesMetadata({ StudyInstanceUID }) }) DicomMetadataStore.subscribe('seriesAdded', ({ StudyInstanceUID }) => { const studyMetadata = DicomMetadataStore.getStudy(StudyInstanceUID) HangingProtocolService.run(studyMetadata) }) return unsubscriptions } ``` #### Writing a custom init You can add your custom init function to enhance the default initialization for: - Fetching annotations from a server for the current study - Changing the initial image index of the series to be displayed at first - Caching the next study in the work list - Adding a custom sort for the series to be displayed on the study browser panel and lots of other modifications. You just need to make sure, the mode `retrieveSeriesMetadata`, `makeDisplaySets` and `run` the HangingProtocols at some point. There are various `events` that you can subscribe to and add your custom logic. point to events For instance for jumping to the slice where a measurement is located at the initial render, you need to follow a pattern similar to the following: ```js init: async ({ servicesManager, extensionManager, hotkeysManager, dataSource, studyInstanceUIDs, }) => { const { DisplaySetService } = servicesManager.services /** ... **/ const onDisplaySetsAdded = ({ displaySetsAdded, options }) => { const displaySet = displaySetsAdded[0] const { SeriesInstanceUID } = displaySet const toolData = myServer.fetchMeasurements(SeriesInstanceUID) if (!toolData.length) { return } toolData.forEach((tool) => { const instance = displaySet.images.find( (image) => image.SOPInstanceUID === tool.SOPInstanceUID ) const { SOPInstanceUID, url } = instance displaySet.initialImageIdIndex = displaySet.images.indexOf(instance) }) MeasurementService.addMeasurement(/**...**/) } // subscription to the DISPLAY_SETS_ADDED const { unsubscribe } = DisplaySetService.subscribe( DisplaySetService.EVENTS.DISPLAY_SETS_ADDED, onDisplaySetsAdded ) /** ... **/ return unsubscriptions } ``` ### Route: layoutTemplate `layoutTemplate` is the last configuration for a certain route in a `mode`. `layoutTemplate` is a function that returns an object that configures the overall layout of the application. The returned object has two properties: - `id`: the id of the `layoutTemplate` being used (it should have been registered via an extension) - `props`: the required properties to be passed to the `layoutTemplate`. For instance `default extension` provides a layoutTemplate that builds the app using left/right panels and viewports. Therefore, the `props` include `leftPanels`, `rightPanels` and `viewports` sections. Note that the `layoutTemplate` defines the properties it is expecting. So, if you write a `layoutTemplate-2` that accepts a footer section, its logic should be written in the extension, and any mode that is interested in using `layoutTemplate-2` **should** provide the `id` for the footer component. What module should the footer be registered? ```js /* ... */ layoutTemplate: ({ location, servicesManager }) => { return { id: 'org.ohif.default.layoutTemplateModule.viewerLayout', props: { leftPanels: [ 'myExtension.panelModule.leftPanel1', 'myExtension.panelModule.leftPanel2', ], rightPanels: ['myExtension.panelModule.rightPanel'], viewports: [ { namespace: 'myExtension.viewportModule.viewport1', displaySetsToDisplay: ['myExtension.sopClassHandlerModule.sop1'], }, { namespace: 'myExtension.viewportModule.viewport2', displaySetsToDisplay: ['myExtension.sopClassHandlerModule.sop2'], }, ], }, } } /* ... */ ``` ## FAQ > What is the difference between `onModeEnter` and `route.init` `onModeEnter` gets run first than `route.init`; however, each route can have their own `init`, but they share the `onModeEnter`. > How can I change the `workList` appearance or add a new login page? This is where `OHIF-v3` shines! Since the default `layoutTemplate` is written for the viewer part, you can simply add a new `layoutTemplate` and use the component you have written for that route. `Mode` handle showing the correct component for the specified route. ```js export default function mode() { return { id: 'viewer', displayName: '', routes: [ { path: 'worklist', init, layoutTemplate: ({ location, servicesManager }) => { return { id: 'worklistLayout', props: { component: 'myNewWorkList' }, } }, }, ], /* ... */ } } ```