Tour of Exosphere Codebase
This document orients new contributors to the general structure of Exosphere's codebase. It is a continuous work in progress, and is not intended to be exhaustive -- the code itself is the source of truth.
Exosphere's Elm source code lives in the
src/ directory. The main file is
Exosphere.elm. When you run the Elm compiler, you're pointing it there.
Go look at
Exosphere.elm, you'll see it has just one
main function. This function calls
Browser.application to start the app. It passes several arguments. The most important ones are:
initfunction builds the initial state of the app, i.e., it initializes the Model that is persisted in memory while the app is running. If there is an existing saved user session (in the browser's local storage),
initwill decode that stored state and 'hydrate' the Model from it.
initalso fires initial
viewfunction decides what to show to the user on the screen. It takes as input the application state (i.e. Model, specifically here
OuterModel). It outputs HTML and CSS (along with a page title) that is rendered by the user's web browser, along with a page title. You can see the top-level
src/View/View.elm, though it imports many other modules that we'll visit later in the tour.
updatefunction is responsible for advancing the state of the app forward, in response to external events (like button clicks and results of API calls). It receives a
Msg(pronounced "message") from the Elm runtime, and the current state (Model) of the app. Then, it computes the new state of the app. It returns a new Model (representing the new state) and a new
This uses the same basic architecture described in the Elm guide, but with more modules and helper code.
Model: OuterModel, SharedModel, and Page-Specific models
main function in
Exosphere.elm handles an
OuterModel defined in
src/Types/OuterModel.elm -- this model contains the entire state of the app for as long as it's running.
OuterModel splits up data between the
SharedModel (defined in
src/Types/SharedModel.elm) and a
ViewState (defined in
src/Types/View.elm), which often contains a page-specific model.
SharedModel models data that is shared between pages in the UI, i.e., data that aren't specific to whatever page is currently displayed. Examples of these are the current time, and whichever OpenStack projects the user is logged into.
ViewState is a custom type that stores which page the UI is currently displaying. Many of the variants of that custom type contain a page-specific model. The page-specific model stores data specific to that current page. Examples include the contents of any text boxes on the page, and whatever checkboxes are currently checked in a list.
The separation between
SharedModel and page-specific models allows each page to have its own nested architecture (
view). This is a common pattern in larger Elm applications. It simplifies the app's outer-most data model, and reduces the need to think about plumbing for the entire app if you're only working on one page of the UI.
View, Pages, and Style Code
src/View/View.elm contains the top-level
view function that's passed to the main function.
view handles the possibility of an invalid application state, but if it's valid, it calls a stack of functions that:
- Provide a page title (
- Set up a base elm-ui layout (
- Draw a page header and a main content container (
- Render a non-project-specific (
nonProjectViews) or project-specific (
projectContentView) view, depending on the current view state.
From here, we call out from the top-level view code to individual pages. You'll see that
View.elm imports many modules starting with
Page., and calls the view function for each of these pages.
src/Page usually correspond to a specific page in the application. Most pages have their own Elm architecture -- each page has its own
Msg types, and
view functions. Each page's architecture is nested inside of the overall app's architecture.
Further down the call stack, we have
src/Style code. This includes
src/Style/Widgets, self-contained UI widgets that can each be re-used on many pages. These widgets are generally stateless -- they don't have their own Elm architecture, just a view function that takes parameters and renders the widget. Widgets are also generally independent from the Exosphere codebase. Someday, they could live in a separate Elm package.
There are also helper functions in both
src/Style. The distinction is that
View helpers are specific to Exosphere's codebase, while
Style helpers could be independent. You may find exceptions, but that is the intention going forward.