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