How to implement modal dialogs in an Elm SPA?

Hi,

In an application structured like elm-spa-example, how would you implement modal dialogs with the following properties?

  1. at most one dialog should be displayed at any time
  2. a dialog can be called from any page of the application and the result should be returned to this page
  3. focus (with the mouse or keyboard using TAB) should be possible only among the dialog fields and buttons if one is displayed

The dialog would be displayed on top of the interface and could be configured with several fields and buttons, however I’m not interested in the dialog itself, but in the way it would be activated, stored in a model, return results and how could the focus issue be handled. Therefore for the sake of the discussion, we can only consider a simple OK/Cancel dialog.

For the focus issue, the modal status could be passed to all elements of the application that can receive focus in order to disable them when a modal dialog is active (menus, navigation links, …). This is a little cumbersome but not more than passing a Language all over the app to translate strings.

However this would imply that the top level view, and therefore model, can access the modal state:

  • If the modal state is stored in each page that can use them, this would require returning the state as an external message all the way to the top or using a solution like toSession in the elm-spa-example 0.19 rewrite. Not having the modal state stored in the Main model also makes difficult to guaranty that only one exists at any time (making impossible states impossible).
  • If the modal state is stored at the top level, this makes more difficult for views or pages to activate the dialog (external message?) and retrieve the result.

Note that I have a working solution but as it may not be ideal, I would prefer to get some fresh views on the subject.

Any idea or experience to share?

Thank you

2 Likes

Idea (haven’t tried yet):

-- Main.elm
type alias Model =
  { ...
  , dialog : Maybe Dialog
  }

type Dialog
  = LoginDialog
  | RegistrationDialog
  | PageOneDialog PageOne.Dialog
  | PageTwoDialog PageTwo.Dialog

view : Model -> Html Msg
view model =
  -- Use PageOne/PageTwo.viewBody and PageOne/PageTwo.viewDialog here.
-- PageOne.elm
type Model
   = ...

type Msg
  = ...

type OutMsg
  = OpenDialog Dialog
  | CloseDialog
  | ...

type Dialog
  = Dialog1 Dialog1State
  | Dialog2 Dialog2State


update : Msg -> Model -> ( Model, Cmd Msg, OutMsg )
update msg model =
  ...

viewBody : Model -> Html Msg
viewBody model =
  ...

viewDialog : Dialog -> Html Msg
viewDialog dialog =
 ...

Sorry if it doesn’t satisfy all of your requirements, just an idea I wanted to share :slight_smile:

1 Like

Thank you, two questions:

  • How would you communicate the dialog result to the page?
  • It seems to me that CloseDialog does not need to be an OutMsg, but only a Msg right?

How would you communicate the dialog result to the page?

viewDialog produces Html Msg where Msg is the (nested) message type of the page, and you then use Html.map in the main module in the same way as with viewBody. So messages can be sent directly to the page’s update function from it’s viewDialog.

It seems to me that CloseDialog does not need to be an OutMsg , but only a Msg right?

No, the idea is that the main module is responsible for closing the dialog. When it receives CloseDialog (from any page) it should do { model | dialog = Nothing }.

PageOne.viewDialog only returns the contents of the dialog, and the main module can then decide how to render it (e.g. inside of a container with shadow in the center of the screen which is the same for all dialogs in the app).

Got it. So the impact if it were elm-spa-example would be to add an external message to all pages that use a dialog (the Html.map are already there).

This is most likely the closest solution to the way elm-spa-example is structured, even more the 0.18 version but @rtfeldman said he would add back external messages if needed.

Thank you for sharing.

This topic was automatically closed 10 days after the last reply. New replies are no longer allowed.