Reputation: 6708
In elm I have a model that includes a currentDate
, it's a string representing the date. I update it with a signal:
Signal.map (SetCurrentDate << timeToDateString) (Time.every Time.second)
SetCurrentDate
is an action that updates the model (SetCurrentDate date -> { model | currentDate <- date}
), and timeToDateString
converts from time to a string like "yyyy-mm-dd".
However, there are two problems with this:
model.currentDate
is only correctly set after one second. So there's a second at the start of the app where the currentDate is not properly set.model.currentDate
is set every second, even though it only changes every day.Is there a way to fix this? So the currentDate is set at the start of the app (without the one second delay), and that it's only updated one a day?
Extra findings:
If I change the signal to Signal.dropRepeats <| Signal.map (SetCurrentDate << timeToDateString) (Time.every Time.second)
(so I drop the repeats), the signal is only fired when the day changes, and not also once on the start of the app.
Edit: If there is a better way of knowing the currentDate in an Elm application, I would love to hear it. My Googling gave nothing.
Thanks!
Upvotes: 3
Views: 1753
Reputation: 33637
Apanatshka's answer (and others) made sense, but as a beginner to Elm I had trouble applying the explanation to actual code. After some fuddling around, here is a minimal amount of code needed to render an auto-updating date (using Elm v0.16.0):
import Signal
import Html exposing (Html, text)
import Time exposing (Time, every)
import Date exposing (Date, Month, fromTime, year, month, day, hour, minute, second)
-- not used by foldp (https://stackoverflow.com/a/34095298/480608)
startTime = 0
type Action = Update Date
type alias Model = Date
showDate : Date -> String
showDate date = toString (month date) ++ " " ++
toString (day date) ++ ", " ++
toString (year date) ++ " " ++
toString (hour date) ++ ":" ++
toString (minute date) ++ ":" ++
toString (second date)
update : Action -> Model -> Model
update (Update date) _ = date
model : Signal Model
model =
Signal.foldp update (fromTime startTime) clock
timeToAction : Time -> Action
timeToAction time = Update <| fromTime time
clock : Signal Action
clock =
Signal.map timeToAction <| every Time.second
main : Signal Html
main =
Signal.map view model
view : Model -> Html
view model =
text <| showDate model
Here's a slightly more complex example that merges the clock into other signals. No idea how well this conforms to elm best practices, but it works, and it may serve as a helpful learning tool as it did for me.
import Signal
import Html exposing (..)
import Html.Events exposing (..)
import Keyboard
import Char
import Time exposing (Time)
import Date exposing (Date, Month, fromTime, year, month, day, hour, minute, second)
startTime = 0
type Action =
NoOp
| Increment
| Decrement
| Update Time
type alias Model = {
count: Int,
time: Time
}
showDate : Date -> String
showDate date = toString (month date) ++ " " ++
toString (day date) ++ ", " ++
toString (year date) ++ " " ++
toString (hour date) ++ ":" ++
toString (minute date) ++ ":" ++
toString (second date)
actions : Signal.Mailbox Action
actions =
Signal.mailbox NoOp
update : Action -> Model -> Model
update action model =
case action of
NoOp -> model
Increment -> { model | count = model.count + 1 }
Decrement -> { model | count = model.count - 1 }
Update time -> { model | time = time }
model : Signal Model
model =
Signal.foldp update { count = 0, time = startTime } (Signal.mergeMany [
actions.signal,
keyPressesToAction,
clock
])
keyPressesToAction : Signal Action
keyPressesToAction =
let
keyCodeToAction keyCode =
case Char.fromCode keyCode of
'=' -> Increment
'-' -> Decrement
_ -> NoOp
in
Signal.map keyCodeToAction Keyboard.presses
timeToAction : Time -> Action
timeToAction time = Update time
clock : Signal Action
clock = Signal.map timeToAction (Time.every Time.second)
main : Signal Html
main =
Signal.map (view actions.address) model
view : Signal.Address Action -> Model -> Html
view address model =
div [] [
text <| showDate <| fromTime model.time,
div []
[ button [ onClick address Decrement ] [ text "-" ]
, text <| toString model.count
, button [ onClick address Increment ] [ text "+" ]
]
]
Upvotes: 1
Reputation: 5958
Are you using StartApp
or do you have your own foldp
? If you are using StartApp
it's best to look up the code for the start
function and inline it so you have access to the foldp
.
Signal.foldp
doesn't do anything with the initial value of a signal. So if you use the Time.every second
signal, you only get your update after a second, and only after a day if you use the date conversion and Signal.dropRepeats
. You can use Signal.Extra.foldp'
from the 3rd party signal-extra library* to fix that. It takes a function to create the initial state of the foldp
from the initial value of the input.
*Full disclosure: I'm the author of the library
There is a very useful task in the task-tutorial library called getCurrentTime
. I think that can serve your needs without needing a signal that updates every second. Along with Task.sleep
, you can probably get something that only checks the time once or twice a day.
Upvotes: 4