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.

Reply via email to