Wrapping Elements in different modules (Msg conflicts)

Hi,

I am creating an Element (elm ui) in my Main.elm, which I would like to wrap in another Element. The wrapping is done in another module. The issue is that my element in Main.elm is of type Element Msg and the wrapping is also of type Element Msg, whereby Msg refers to different types in the main module and the other module.
It seems like I cannot resolve this issue, since I want to generate an element that generates messages for two modules (and therefore of two types) at the same time.

Is there some sort of workaround for this problem?

Originally, I defined types for the Main.elm module in my other module and exported them. Then I created created elements of type Element OtherModule.Msg. In this way I was able to resolve the described conflicts. However, the “solution” is quite bad, because I mix the boundaries of both modules.

Why start another post?

The solution is to use Element.map as said in the other post.

What do you mean exactly? Your exposed types from the other module can be opaque if you want them to stay “private”.

Maybe your issue is with this:

It does not feel right to want to use a Main element in another module. Most likely you want it the other way: wrap the other element in your Main using Element.map.

Also do you really need another element with its own msg type?

Maybe you could use the same pattern as the one for example used in Input.text that takes a String -> msg instead of requiring to use Element.map (which is an heavy solution that should be used only when really necessary, for example for full SPA “pages”).

Look for example at https://medium.com/@mickey.vip/an-approach-to-nested-reusable-view-functions-in-elm-a1531b9abaf3 for a more detailed description.

Thank you for your extensive response!

Maybe I have to explain the actual issue in more detail.

What I want to build are tooltips. In my Main.elm I would like to call a function from my tooltip module, which returns a passed element wrapped inside another element, which handels the tooltip stuff. The signature looks like this Tooltip -> Element Msg -> Element Msg.
Inside my Main.elm I create elements of type Element Msg, which I cannot map to Tooltip.Msg since I do not export a constructor for this type (module Tooltip exposing (Msg(..),...)).
I use Element.map to transform the result from my tooltip module and display the element in my Main.elm.

I export the Msg type in my tooltip using Msg(..), since I have to listen to the toggle event in my Main.elm in order to update the state and keep track of which tooltip is hidden or displayed.

A Tooltip -> Element Msg -> Element Msg is useless, as you will only be able to pass elements from the Tooltip module itself.

I dont know what Tooltip is, but you may want instead something like Tooltip -> (Tooltip.Msg -> msg) -> Element msg -> Element msg, so that any element can be passed to it.

The Tooltip.Msg -> msg function could be also added to the Tooltip record (which is most likely some kind of configuration).

And the Tooltip.update would be called in your Main.

That’s why I said:

It does not feel right to want to use a Main element in another module. Most likely you want it the other way: wrap the other element in your Main using Element.map

and why I gave you a link explaining the -> msg function pattern.

But for a tooltip, having a dedicated state and msg seems really overkill to me.
Here is a tooltip trick with elm-ui that does not use any state nor message:

https://ellie-app.com/7bxvQpSMtLwa1

module Main exposing (main)

import Element exposing (..)
import Element.Background as Background
import Element.Border as Border
import Element.Font as Font
import Html exposing (Html)
import Html.Attributes


main : Html msg
main =
    layout [ width fill, height fill ] <|
        el
            [ centerX
            , centerY
            , tooltip "tooltip"
            ]
            (text "hello")


tooltip : String -> Attribute msg
tooltip str =
    inFront <|
        el
            [ width fill
            , height fill
            , transparent True
            , mouseOver [ transparent False ]
            , above (tooltipHelp str)
            ]
            none


tooltipHelp : String -> Element msg
tooltipHelp str =
    el
        [ Background.color (rgb 0 0 0)
        , Font.color (rgb 1 1 1)
        , padding 4
        , Border.rounded 5
        , Font.size 14
        , Border.shadow
            { offset = ( 0, 3 ), blur = 6, size = 0, color = rgba 0 0 0 0.32 }
        , htmlAttribute (Html.Attributes.style "pointerEvents" "none")
        ]
        (text str)

which could be generalized with:

tooltip : Element Never -> Attribute msg
tooltip element =
    inFront <|
        el
            [ width fill
            , height fill
            , transparent True
            , mouseOver [ transparent False ]
            , (above << Element.map never) <|
                el [ htmlAttribute (Html.Attributes.style "pointerEvents" "none") ]
                    element
            ]
            none

and using the Element.move* functions to move the tooltip farther if wanted.

PS: If you want to let the user choose the position of the tooltip, you can even have:

tooltip : (Element msg -> Attribute msg) -> Element Never -> Attribute msg
tooltip usher tooltip =
    inFront <|
        el
            [ width fill
            , height fill
            , transparent True
            , mouseOver [ transparent False ]
            , (usher << Element.map never) <|
                el [ htmlAttribute (Html.Attributes.style "pointerEvents" "none") ]
                    tooltip
            ]
            none

Then for example tooltip Element.above (Element.text "tooltip):
https://ellie-app.com/7byf7QFGMVza1.

2 Likes

I really like your solution!

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