Skip to content

Instantly share code, notes, and snippets.

@daphee
Last active March 29, 2016 08:02
Show Gist options
  • Save daphee/b2af8e7aafc83b78497b to your computer and use it in GitHub Desktop.
Save daphee/b2af8e7aafc83b78497b to your computer and use it in GitHub Desktop.
module Button.Model (Model)
-- nothing changed here
type alias Model =
{ initialsSaved : Bool
}
initialModel : Model
initialModel =
{ initialsSaved = False
}
module Button.Action (Action(..), type) where
import Button.Model exposing (Model)
-- Top/App Level update
-- I like to define my action type at the same place I have my update function
type Action =
InitialsSaved Bool
| Noop
-- Update: Something happened --> Update the model to contain the changes
update :: Action -> Model -> (Model, Effects Action)
update action model =
case action of
Noop ->
-- Nothing happened. This action isn't necessary
-- I need it mostly if I have a Task which's result I want to ignore
(model, Effects.none)
InitialsSaved initialsSaved ->
-- Save into model
({model | initialsSaved = initialsSaved}, Effects.none)
module Button.View (view) where
import Button.Action as Action exposing (Action)
import Button.Model exposing (Model)
-- Top/App Level view
-- View: give me a model and *one* address where I can send actions/events to
-- Elm Architecture example 4 used an additional signal for removal because the update function
-- This signal was handled in was in a level "above" the view.
-- The single Counter shouldn't know about the CounterList
type alias Context =
{ removeAddress : Signal.Address () }
view :: Context -> Signal.Address Action -> Model -> Model
view context address model =
div
[]
[ button
-- Okay so this address is the address we "created" using forwardTo.
-- if this onClick functions decides to emit this InitialsSaved event
-- it is boxed into a ButtonAction
-- So we end up with (ButtonAction (InitialsSaved True))
-- If the root update function is called with this it "unpacks" it and lets button/update.elm handle (IntitialsSaved True)
[onClick address (Action.InitialsSaved True)]
[text "Play!"]
, button
-- Okay so how about this?
-- [onClick address (RootAction.RemoveButton)]
-- Nah, wont work. All actions sent to this address will be "routed" to the button/update.elm function
-- RemoveButton isn't defined/handled in button/update.elm - this won't even compile
-- We want to communicate something "up" - that's why we have this problem
-- Okay, we *could* add a RemoveButton Action to button/update.elm
-- [onClick address (Action.RemoveButton)]
-- But how can the root update know and change it's "buttonModel" ?
-- We could change update.elm to add a second case:
--------
-- case action of
-- ButtonAction buttonAction ->
-- let
-- (buttonModel, buttonEffects) = Button.update buttonAction model.buttonModel
-- in
-- case buttonAction of
-- ButtonAction.RemoveButton ->
-- -- The button module decided to remove itself
-- -- Do cleanup on this level (above the button module) as well
-- ({model| buttonModel = Nothing})
-- _ ->
-- -- some otheraction in the button module; just update the model as usual
-- ({model| buttonModel = buttonModel})
-----------
-- But this is kind of redundant, right? Besides - The ButtonModule.RemoveButton is unnecessary as there is
-- no Cleanup to be done in the button module / button/update.elm
-- Sometimes this approach isn't that bad though, I followd elm-hedley where there is work to be done in two update functions
-- E.g. in my login page the whole user info is saved. But the root update function interecepts the Login action and just picks the token and saves
-- it in the root model. In the Login.update function all user info is saved.
--
-- This time it is better to include another address (in view.elm)
-- Just send () (I believe it is the only value of the () type)
-- This address is wired directly to the root update function and not the Button.Update function
-- It will get replaced by the always in update.elm
[onClick context.removeAddress ()]
[text "RemoveMe"]
module ColorPicker where
import Model exposing (initialModel)
import Action exposing (update)
import View exposing (view)
type Action =
InitialsSaved Bool
| Noop
app =
StartApp.start {
init = (initialModel, Effects.none),
view = view,
update = update,
inputs = [incomingGameObject]
}
module Model (Model)
import Button.Model as Button
type alias Model =
-- I am using a Maybe here to be able to indicate a removed button
{ buttonModel : Maybe Button.Model
}
initialModel : Model
initialModel =
{ buttonModel = Just Button.initialModel
}
-- I am not sure if Action(..) is correct.
-- I know that to import possible values for a type this works but I tend to expose everything in my app
module Action (Action(..), type) where
-- Top/App Level update
-- I like to define my action type at the same place I have my update function
type Action =
RemoveButton
-- I explain this in more detail in the button's view function
ButtonAction Button.Action
| Noop
-- Update: Something happened --> Update the model to contain the changes
update :: Action -> Model -> (Model, Effects Action)
update action model =
case action of
Noop ->
-- Nothing happened. This action isn't necessary
-- I need it mostly if I have a Task which's result I want to ignore
(model, Effects.none)
ButtonAction action
-- Some action for the button module happened
-- Let the button update handle it
let
-- we ignore effects here
(buttonModel, _) = Button.update action model.buttonModel
in
({model | buttonModel = buttonModel}, Effects.none)
RemoveButton ->
({model | buttonModel = Nothing}, Effects.none)
module View (view) where
import Action exposing (Action)
import Model exposing (Model)
import Button.View as Button
-- Top/App Level view
-- View: give me a model and *one* address where I can send actions/events to
view :: Signal.Address Action -> Model -> Model
view address model =
-- Some wrapper for the button
div
[]
[
-- ForwardTo does nothing but label actions
-- Everytime an action arrives (from the button module) the "label" ButtonAction is added to let the
-- root update function forward the action to the button module's update function
-- It's like an additional "box" around the original action
-- If you have multiple modules nested each view puts a box around the actions of its children which leads to multiple
-- nested boxes which get unpacked on after another in the update functions
Button.view
-- I discuss this context in more detail in the button/view.elm file
-- The always works because the second parameter to forwardTo is actually a function
-- In the second forwardTo the funciton is the ButtonAction type. It has a type variable which gets filled this way
-- The type variable is the content of the box
-- In the case of the button removal we don't want that the button module needs to know about the RemoveButton
-- action and send it itself. We know that every value that arrives at this address is meant to indicate
-- that the button is removed. That's why always is a perfect fit. It just discards the value sent and always returns
-- RemoveButton
{ removeAddress = Signal.forwardTo address (always Action.RemoveButton) }
(Signal.forwardTo address Action.ButtonAction)
model.button
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment