Search code examples
elm

Sending messages between modules


Can't quite figure out how to go about sending messages between modules when working with reusable components.

I have an expanding text area that I'd like to use on a number of different sections on a site. The Text Area accepts a portion of HTML that makes up the user actions. Typically handling submit, cancel, upload icons, etc..

Tried to write up a quick example of what I'm talking about without throwing a ton of code on here. So essentially, I'd like to just plug and play peices of HTML that are already attached.

I'm assuming CancelNote is getting fired as a TextArea msg, so it never sees a Cancel Note msg. Not sure how I would use Html.map here (or even if I would).....feel like plug and play method is probably a bad approach, but not sure how else I could achieve decent reusability .

SEPERATE MODULE

update model msg =
    case msg of
        CancelText ->
            ( { model | note = (Just "") }
            , Cmd.none
            )


view: stuff
view stuff = 
......
    TextArea.view 
         (button [ Html.Events.onClick CancelText] [])






TEXT AREA MODULE 


view : Html.Html msg -> Html msg
view actionHtml =
    div [ class "extended_text_area_container" ] [
        textarea [] [
        ]
        , actionHtml
    ]

Solution

  • Messages are just values like any other. You can pass them around directly:

    -- SEPERATE MODULE
    
    
    view: stuff
    view stuff = 
    ......
        TextArea.view CancelText
    
    
    
    -- TEXT AREA MODULE 
    
    
    view : msg -> Html msg
    view msg =
        div [ class "extended_text_area_container" ]
            [ textarea [] []
            , button [ onClick msg ] []
            ]
    

    Edit: If you need to also maintain internal state, just use another message to tell the parent to update the state:

    -- Main module
    
    
    type msg =
        ...
        SetTextAreaState TextArea.state
    
    
    update model msg =
        case msg of
            ...
            SetTextAreaState state ->
                { model | textAreaState = state }
    
    
    view : Model -> Html msg
        TextArea.view SetTextAreaState model.textAreaState
    
    
    
    -- TextArea module
    
    type State =
        ...
    
    type Msg =
        Clicked
    
    
    update : State -> Msg -> State
    update state msg =
        case msg of
            Clicked ->
                { state | clicked = True }
    
    
    view : (State -> msg) -> State -> Html msg
    view toMsg state =
        let
            updateAndWrap msg =
                toMsg (update state msg)
        in
        div [ class "extended_text_area_container" ]
            [ textarea [] []
            , button [ onClick (updateAndWrap Clicked) ] []
            ]
    

    Here, instead of passing a msg to onClick directly in TextArea.view, call a function that updates the state and then wraps it in the msg constructor passed in from the parent, which will produce a message of a type that we don't know anything about.

    Also, while I use an internal Msg type and update function similarly to the overall Elm architecture, that is in no way mandatory. It's just a nice way of doing it since it's familiar and scales well.