Skip to content

Instantly share code, notes, and snippets.

@z5h
Last active April 15, 2016 03:48
Show Gist options
  • Star 8 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save z5h/41ca436679591b6c3e51 to your computer and use it in GitHub Desktop.
Save z5h/41ca436679591b6c3e51 to your computer and use it in GitHub Desktop.
module TimeApp ( start, Config, App ) where
{-| This module helps you start your application in a typical Elm workflow.
It assumes you are following [the Elm Architecture][arch] and using
[elm-effects][]. From there it will wire everything up for you!
**Be sure to [read the Elm Architecture tutorial][arch] to learn how this all
works!**
[arch]: https://github.com/evancz/elm-architecture-tutorial
[elm-effects]: http://package.elm-lang.org/packages/evancz/elm-effects/latest
# Start your Application
@docs start, Config, App
-}
import Html exposing (Html)
import Task
import Effects exposing (Effects, Never)
import Time exposing (Time)
{-| The configuration of an app follows the basic model / update / view pattern
that you see in every Elm program.
The `init` transaction will give you an initial model and create any tasks that
are needed on start up.
The `update` and `view` fields describe how to step the model and view the
model.
The `inputs` field is for any external signals you might need. If you need to
get values from JavaScript, they will come in through a port as a signal which
you can pipe into your app as one of the `inputs`.
-}
type alias Config model action =
{ init : (model, Effects action)
, update : action -> Time -> model -> (model, Effects action)
, view : Signal.Address action -> model -> Html
, inputs : List (Signal.Signal action)
}
{-| An `App` is made up of a couple signals:
* `html` — a signal of `Html` representing the current visual
representation of your app. This should be fed into `main`.
* `model` — a signal representing the current model. Generally you
will not need this one, but it is there just in case. You will know if you
need this.
* `tasks` — a signal of tasks that need to get run. Your app is going
to be producing tasks in response to all sorts of events, so this needs to
be hooked up to a `port` to ensure they get run.
-}
type alias App model =
{ html : Signal Html
, model : Signal model
, tasks : Signal (Task.Task Never ())
}
{-| Start an application. It requires a bit of wiring once you have created an
`App`. It should pretty much always look like this:
app =
start { init = init, view = view, update = update, inputs = [] }
main =
app.html
port tasks : Signal (Task.Task Never ())
port tasks =
app.tasks
So once we start the `App` we feed the HTML into `main` and feed the resulting
tasks into a `port` that will run them all.
-}
start : Config model action -> App model
start config =
let
singleton action = [ action ]
-- messages : Signal.Mailbox (List action)
messages =
Signal.mailbox []
-- address : Signal.Address action
address =
Signal.forwardTo messages.address singleton
-- updateStep : (action, Time) (model, Effects action) -> (model, Effects action)
updateStep (action, time) (oldModel, accumulatedEffects) =
let
(newModel, additionalEffects) = config.update action time oldModel
in
(newModel, Effects.batch [accumulatedEffects, additionalEffects])
-- update : (Time, (List action)) -> (model, Effects action) -> (model, Effects action)
update (time, actions) (model, _) =
List.foldl updateStep (model, Effects.none) (List.map (\a -> (a, time)) actions)
-- inputs : Signal (List action)
inputs =
Signal.mergeMany (messages.signal :: List.map (Signal.map singleton) config.inputs)
-- inputsWithTime : Signal (Time, (List action))
inputsWithTime =
Time.timestamp inputs
-- effectsAndModel : Signal (model, Effects action)
effectsAndModel =
Signal.foldp update config.init inputsWithTime
model =
Signal.map fst effectsAndModel
in
{ html = Signal.map (config.view address) model
, model = model
, tasks = Signal.map (Effects.toTask messages.address << snd) effectsAndModel
}
@z5h
Copy link
Author

z5h commented Aug 28, 2015

Note that your update function is now:

update : action -> Time -> model -> (model, Effects action)

which means it gets a timestamp for every action. Handy if it's important to keep track of when actions occurred.

@raineorshine
Copy link

Thanks for this!

Would you be willing to publish this as an Elm package?

@z5h
Copy link
Author

z5h commented Feb 1, 2016

@metaraine I had opened an issue (some time ago) in the hopes of adding this to the official start-app. I've received no official response.
https://github.com/evancz/start-app/issues/24

I'll try to add some documentation and publish it before the weekend.

@raineorshine
Copy link

Awesome, thanks!

@raineorshine
Copy link

Yeah, the signature of TimeApp.start is different enough from StartApp.start that I don't know what to do. I haven't used Effects.

My main:

main : Signal Html
main = StartApp.start { model = model, view = view, update = update }

Suggested main:

app = TimeApp.start { init = ?, inputs = ? view = view, update = update }
app = main.html

@z5h
Copy link
Author

z5h commented Feb 16, 2016

@z5h
Copy link
Author

z5h commented Feb 16, 2016

@ethagnawl @prt2121 @jorgebg @doppioslash @simonewebdesign thanks for the stars. I've published this GIST as an elm package here http://package.elm-lang.org/packages/z5h/time-app/1.0.1/

@gyzerok
Copy link

gyzerok commented Mar 12, 2016

In case Action and Time are coupled why not use (Action, Time) -> Model -> (Model, Effects Action) signature?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment