To anyone that is interested, I used Elm Ports and Subscriptions to solve
this problem. The result was a clean and simple implementation. Here is the
solution posted in another thread:
https://groups.google.com/forum/#!topic/elm-discuss/i99LBvYSkpY
On Thursday, July 7, 2016 at 7:29:39 PM UTC-4, Erik Lott wrote:
>
> No, that's clear Alex. Thanks for taking the time to write that out. This
> is a great pattern. It accomplishes what I mentioned above, yet in a
> cleaner way.
>
> When I see something like this, I'm surprised how versatile union types
> can be.
>
> On Thursday, July 7, 2016 at 5:04:24 PM UTC-4, Alex Lew wrote:
>>
>> Here's one way to achieve the all-http-errors-should-go-to-root
>> functionality described by OP. I'm not sure I love it, but thought I'd
>> throw it in.
>>
>> A general idea of what's going on: every component, rather than just
>> returning a `Cmd Msg`, produces a `Cmd (Wrapped Msg)`, where a `Wrapped
>> Msg` can either be a message-for-the-root-component or a
>> message-for-the-component-itself. When the parent uses `Cmd.map` on these
>> wrapped messages, it lets root messages through unchanged, but tags other
>> messages as being for-the-child.
>>
>> Here's the code. First, create a separate module called, e.g.,
>> `RootMessage`, that looks like this:
>>
>> module RootMessage exposing (..)
>>
>> type RootMessage =
>> LoginFailed Http.Error
>>
>> type Wrapped msg = Internal msg | ForRoot RootMessage
>>
>> tag : (childMsg -> parentMsg) -> Wrapped childMsg -> Wrapped parentMsg
>> tag tagger msg =
>> case msg of
>> Internal m -> Internal (tagger m)
>> ForRoot r -> ForRoot r
>>
>> Then, in each *non-root* module, you do something like the following.
>> Here, we have a Child module that includes a Grandchild component.
>>
>> module Child exposing (..)
>>
>> import RootMessage exposing (..)
>>
>> type Msg = GrandchildMsg Grandchild.Msg
>> | APIResponse String
>> | ButtonPressed
>>
>> update : Msg -> Model -> (Model, Cmd (Wrapped Msg))
>> update msg model =
>> case msg of
>> GrandchildMsg subMsg ->
>> let
>> (newModel, cmd) = Grandchild.update subMsg model.grandchild
>> in
>> { model | grandchild = newModel } ! [ Cmd.map (tag GrandchildMsg)
>> cmd ]
>>
>> ButtonPressed ->
>> model ! [ Task.perform (ForRoot << LoginFailed) (Internal <<
>> APIResponse) Http.get... ]
>>
>> The trick here is in `Cmd.map (tag GrandchildMsg) cmd`. The `tag`
>> function, defined in the RootMessage module, takes in a normal tag, like
>> GrandchildMsg, but returns a "tagger" that only applies the `GrandchildMsg`
>> tag to non-root messages. It leaves the root messages unaltered.
>>
>> Finally, in the root, you can handle these generated root messages:
>>
>> module Root exposing (..)
>>
>> import RootMessage exposing (..)
>> import Child exposing (..)
>>
>> type Msg = Something | AnotherThing | ChildMsg Child.Msg
>>
>> update : Wrapped Msg -> Model -> (Model, Cmd (Wrapped Msg))
>> update msg model =
>> case msg of
>> ForRoot LoginFailed err -> ...
>> Internal Something -> ...
>> Internal AnotherThing -> ...
>> Internal ChildMsg subMsg ->
>> let
>> (newModel, cmd) = Child.update subMsg model.child
>> in
>> { model | child = newModel } ! [ Cmd.map (tag ChildMsg) cmd ]
>>
>>
>> Sorry for the terseness -- happy to elaborate if something is unclear!
>>
>> -Alex
>>
>>
>> On Thursday, July 7, 2016 at 11:30:08 AM UTC-4, Erik Lott wrote:
>>>
>>> Mark:
>>>
>>> That's lead me to think about but not yet write a command alternative
>>>> that could also handle tasks that didn't need to be routed back to the
>>>> originator and that could be used to send messages to the top-level (or
>>>> elsewhere).
>>>
>>>
>>> I think you've nailed it. The suggested ELM architecture doesn't easily
>>> allow for application specific communication from the lower nested
>>> components to the upper components. There are times when in the scope of an
>>> overall architecture, you'd like to return some type of application wide
>>> recognized event/message back up the update stack, and have any interested
>>> ancestor component react to it... This is essentially how Commands
>>> function. Commands are a globally recognized type, that are returned up the
>>> stack, and although they may carry a local message type with them, the
>>> command itself is never returned or reused. The Command reaches the
>>> application root, and is processed by the core language.
>>>
>>> Like you said: I think what we need (or at least what I need) is our own
>>> Application specific type to return up the stack instead of Cmd. An "Event"
>>> union type possibly? And CmdEvt tag could wrap the native elm Cmd, and be
>>> unwrapped at the root.
>>>
>>> type Event
>>> = CmdEvt Cmd
>>> | MsgEvt
>>> | Unauthorized
>>> | LoggedIn User
>>> | ConfirmPopup CancelMsg OkMsg
>>>
>>>
>>>
>>> On Tuesday, July 5, 2016 at 1:30:51 PM UTC-4, Mark Hamburg wrote:
>>>>
>>>> The first option feels repugnant from an encapsulation standpoint. I've
>>>> built the second option and it works but it increases the amount of
>>>> boilerplate in hierarchical systems because we now have three results to
>>>> deal with in update functions. That's lead me to think about but not yet
>>>> write a command alternative that could also handle tasks that didn't need
>>>> to be routed back to the originator and that could be used to send
>>>> messages
>>>> to the top-level (or elsewhere). That said, once one gets into replacing
>>>> Cmd, the API request model makes a lot of sense.
>>>>
>>>> Mark
>>>>
>>>> On Jul 5, 2016, at 5:46 AM, Erik Lott <[email protected]> wrote:
>>>>
>>>> My app has several layers of nested components. Various components
>>>> throughout the tree will need to interact with our API via http requests.
>>>> If any API request returns a 401 - Not Authorized error, or a Timeout
>>>> Error, the error needs to bubble up to the root component where is can be
>>>> handled appropriately.
>>>>
>>>> What is the most idiomatic way of dealing with this?
>>>>
>>>> *1. Parent should pattern match against important child messages*:
>>>> Reference
>>>> <https://groups.google.com/d/msg/elm-discuss/QPqrJd4C78Y/_TLLg81SAQAJ>
>>>> This could work, but would be unreasonable in this case. The root
>>>> component would need to match against every failing api http request made
>>>> by every child, grandchild, great-grandchild, etc, component in the tree.
>>>> If a single pattern is missed, the app would be in an error state, so this
>>>> is prone to mistakes.
>>>>
>>>> *2. Nested Components return additional info from the "update" function*:
>>>> Reference
>>>> <http://stackoverflow.com/questions/37328203/elm-0-17-how-to-subscribe-to-sibling-nested-component-changes>
>>>> Each component returns an additional value from its update function
>>>> like this:
>>>> update : Msg -> Model -> (Model, Cmd Msg, SomeInfo)
>>>>
>>>> The parent component could inspect the returned "SomeInfo" value from
>>>> its direct children, and act on that information if necessary. In my
>>>> case,
>>>> any nested component that makes http requests to our API would be
>>>> responsible for returning a APINotAuthorized and APITimeout value to its
>>>> parent, and its parent would do the same, until the error has bubbled up
>>>> to
>>>> the root component.
>>>>
>>>>
>>>> Option 2 is simple and robust, and can be used to pass messages of any
>>>> type, for any situation... but I'm wondering if I'm missing an obvious 3rd
>>>> solution?
>>>>
>>>> --
>>>> You received this message because you are subscribed to the Google
>>>> Groups "Elm Discuss" group.
>>>> To unsubscribe from this group and stop receiving emails from it, send
>>>> an email to [email protected].
>>>> For more options, visit https://groups.google.com/d/optout.
>>>>
>>>>
--
You received this message because you are subscribed to the Google Groups "Elm
Discuss" group.
To unsubscribe from this group and stop receiving emails from it, send an email
to [email protected].
For more options, visit https://groups.google.com/d/optout.