webbhuset / elm-actor-model / Webbhuset.Component


type alias PID =
Webbhuset.Internal.PID.PID

A PID is an identifier for a Process.

Components

A component is a independent part in the system and has a single responsibility. They can not import other components.

Components are like normal elm programs. They have their own model, they can do commands and subscriptions. In addition to that they also have out messages.

This means you will have two Msg types: MsgIn and MsgOut. MsgIn is conceptually the same as your normal Msg type would be.

MsgOut is a way to "tell" the rest of the system that something happened in your component.

For Example, the msg types for a login form component could look like this:

type MsgIn
    = EmailFieldChanged String
    | PasswordFieldChanged String
    | SubmitbuttonClicked

type MsgOut
    = FormWasSubmitted
        { email : String
        , password : String
        }

From the system's perspective, this is all you need to care about. This is the public API for the component.

The normal output tuple from the init and update functions are replaced with a 3-Tuple:

( Model, List MsgOut, Cmd MsgIn )

UI Component

A UI-component is very similar in concept to a Browser.element program.


type alias UI model msgIn msgOut =
{ init : PID -> ( model
, List msgOut
, Platform.Cmd.Cmd msgIn )
, update : msgIn -> model -> ( model
, List msgOut
, Platform.Cmd.Cmd msgIn )
, view : model -> Html msgIn
, onSystem : SystemEvent -> SystemEvent.Handling msgIn
, subs : model -> Platform.Sub.Sub msgIn 
}

UI Component Type

Service Component

The service component does not have any view function. Remember Platform.worker?


type alias Service model msgIn msgOut =
{ init : PID -> ( model
, List msgOut
, Platform.Cmd.Cmd msgIn )
, update : msgIn -> model -> ( model
, List msgOut
, Platform.Cmd.Cmd msgIn )
, onSystem : SystemEvent -> SystemEvent.Handling msgIn
, subs : model -> Platform.Sub.Sub msgIn 
}

Service Component Type

Layout Component

A layout component can render other components using their PID as a reference. The difference comparing to a UI component is the view function.


type alias Layout model msgIn msgOut msg =
{ init : PID -> ( model
, List msgOut
, Platform.Cmd.Cmd msgIn )
, update : msgIn -> model -> ( model
, List msgOut
, Platform.Cmd.Cmd msgIn )
, view : (msgIn -> msg) -> model -> (PID -> Html msg) -> Html msg
, onSystem : SystemEvent -> SystemEvent.Handling msgIn
, subs : model -> Platform.Sub.Sub msgIn 
}

Layout Component Type

The view function of a layout component:

view : (MsgIn -> msg) -> Model -> (PID -> Html msg) -> Html msg
view toSelf model renderPID =
    div
        []
        [ renderPID model.child
        , button [ onClick (toSelf ButtonWasClicked) ] [ text "Button!" ]
        ]

The view function has three arguments:

As you can see, the output type of the view function is Html msg. This is necessary to allow components to be composed. What would the return type be on renderPID if they were not mapped to the same type?

Helpers for the output 3-Tuple

There is no native elm module for a Tuple with three arguments.

mapFirst : (input -> out) -> ( input, x, y ) -> ( out, x, y )

Map the first argument (Model).

mapSecond : (input -> out) -> ( x, input, y ) -> ( x, out, y )

Map the second argument (List MsgOut).

mapThird : (input -> out) -> ( x, y, input ) -> ( x, y, out )

Map the third argument (Cmd).

andThen : (model -> ( model, List msgOut, Platform.Cmd.Cmd msgIn )) -> ( model, List msgOut, Platform.Cmd.Cmd msgIn ) -> ( model, List msgOut, Platform.Cmd.Cmd msgIn )

Run a series of updates on the model

The msgOut's and Cmd's will be composed using System.batch and Cmd.batch.

( model, [], Cmd.none )
    |> Component.andThen doSomethingWithModel

addOutMsg : msg -> ( x, List msg, y ) -> ( x, List msg, y )

Add an out message to the output 3-Tuple.

( model, [], Cmd.none )
    |> Component.addOutMsg SomeOutMsg

addCmd : Platform.Cmd.Cmd msg -> ( x, y, Platform.Cmd.Cmd msg ) -> ( x, y, Platform.Cmd.Cmd msg )

Add a Cmd to the output 3-Tuple.

( model, [], Cmd.none )
    |> Component.addCmd cmd

toCmd : msg -> Platform.Cmd.Cmd msg

Convert a msg to Cmd.

toCmdWithDelay : Basics.Float -> msg -> Platform.Cmd.Cmd msg

Convert a msg to Cmd with a timeout in milliseconds.

Helper for Queue

Sometimes you'd want to put messages in a queue. Maybe your component is in a state where it can't process them at this point, eg. an InitState.


type Queue msgIn

Store messages in a queue.

emptyQueue : Queue msgIn

Create an Empty Queue

{ model
    | queue = Component.emptyQueue
}

addToQueue : msgIn -> Queue msgIn -> Queue msgIn

Add a msg to the queue

{ model
    | queue = Component.addToQueue msgIn model.queue
}

runQueue : Queue msgIn -> (msgIn -> model -> ( model, List msgOut, Platform.Cmd.Cmd msgIn )) -> ( model, List msgOut, Platform.Cmd.Cmd msgIn ) -> ( model, List msgOut, Platform.Cmd.Cmd msgIn )

Run the update function on all messages in the queue and compose all output.

( model, [], Cmd.none )
    |> Component.runQueue queue update