Web UIを作る時に何かと需要があるdate picker
最近ElmでDatePicker触れる機会があったので、PurometheusのAlertmanagerにもdatepicker入った方が良くない?
と思って入れてみた。
ElmのDatePicker
DatePickerについては
Elmで日付を扱うならとりあえずこれ、なパッケージたち。
この記事が分かりやすくまとまってたので、参考にさせてもらった。
話の前段として、Elmで自由にカスタマイズできるDatePickerは少ない。というよりほぼ無い。
cssがelm-cssで埋め込まれていたり、入力にいinput fieldしか使えなかったり、、
既存のシステムに組み込むにはかなり怠いこと間違いなし。
CurrySoftware/elm-datepicker
https://package.elm-lang.org/packages/CurrySoftware/elm-datepicker/latest
色々調べて一番良さそうだったのがこれ。
見た目が何よりもシンプル。そしてカスタマイズがしやすい。
↓な感じで、AttributesやFormatを自分で定義できるからほぼ思い通りのDatePickerが表示できるのが魅力。
type alias Settings =
{ placeholder : String
, classNamespace : String
, containerClassList : List ( String, Bool )
, inputClassList : List ( String, Bool )
, inputName : Maybe String
, inputId : Maybe String
, inputAttributes : List (Attribute Msg)
, isDisabled : Date -> Bool
, parser : String -> Result String Date
, dateFormatter : Date -> String
, dayFormatter : Weekday -> String
, monthFormatter : Month -> String
, yearFormatter : Int -> String
, cellFormatter : String -> Html Msg
, firstDayOfWeek : Weekday
, changeYear : YearRange
}
割と重要なのが、Posixで全部管理されていること。
Elmで日付を扱う0.19編
の記事にあるように最近は標準のdateパッケージが無い。
ので、できるだけ出所不明のパッケージに依存しないという意味で最高。
基本的にはelm/timeとtime-extraで事足りるけど、いろんなdateパッケージが出てて何使って良いのやらって感じ。
Elmerの人たちはDateパッケージの導入嫌うよね。独自のDate型に依存するくらいなら、時間は全部Posixで管理しようということなのだろう(と思う。この辺は正直分からない。)
というわけで 「CurrySoftware/elm-datepicker」をalertmanagerに入れてPR投げてみる。
https://github.com/prometheus/alertmanager/pull/2262
が、速攻でTimePickerもクレとのレスがついた
I guess it would be good to support time too but probably it isn't that easy (at least the component supports only dates)?
Time Pickerってそんなに必要じゃなく無い?
個人的には時間まできたら手入力で良いと思ってる。
が、あった方が便利なのは間違いないので、今度はDateTimePickerを探す
ElmのDateTimePicker
DateTimePickerとなるとグッと選択肢が狭まる。
これが最高!というものは無いが、良さそうなのはいくつか。
mercurymedia/elm-datetime-picker
最初にw0rmにお勧めされたのがこれ
https://package.elm-lang.org/packages/mercurymedia/elm-datetime-picker/latest/
DateTimePickerとしては申し分ない作り。最高だね。これ使おう。
と思ったが、
Dateを選択するとtimeが0クリアされる仕様
人によってどう思うか違うところだが、せっかく設定した時間が日付変更で0クリアされるのは個人的には頂けない。
他のも探してみる。
PanagiotisGeorgiadis/elm-datepicker
次点で良さそうなのが
PanagiotisGeorgiadis/elm-datepicker
見た目もカッコいいし、duration pickerがついてるからAlertmanagerにはあってるかな。
「せっかくなのでこれ使って、duration pickerにしよう」ということになったが、1点致命的な欠陥が。。。
Duration Pickerでは、開始日/終了日に同じ日付が選べない仕様
これは仕様なのか?と疑いたくなる様な挙動。Alertmanager的にはこれはNG。
色々あって、最終的には自分で作った方が早くね?となったのでした。
DateTimePickerを作る
コードはここ
https://github.com/prometheus/alertmanager/pull/2262/files
で以上なのだが、
意外と簡単にできたのでメモ。
DatePicker
肝になるのが下のアップデート文。DatePickerに関してはほぼこれが全て
-- UPDATE
....
MouseOverDay time ->
{ dateTimePicker | mouseOverDay = Just time }
ClearMouseOverDay ->
{ dateTimePicker | mouseOverDay = Nothing }
OnClickDay ->
let
addDateTime_ : Posix -> Maybe Posix -> Posix
addDateTime_ date maybeTime =
case maybeTime of
Just time ->
floorDate date
|> Time.posixToMillis
|> (\d ->
trimTime time
|> Time.posixToMillis
|> (\t -> d + t)
)
|> Time.millisToPosix
Nothing ->
floorDate date
updateTime_ : Maybe Posix -> Maybe Posix -> Maybe Posix
updateTime_ maybeDate maybeTime =
case maybeDate of
Just date ->
Just <| addDateTime_ date maybeTime
Nothing ->
maybeTime
( startDate, endDate ) =
case dateTimePicker.mouseOverDay of
Just m ->
case ( dateTimePicker.startDate, dateTimePicker.endDate ) of
( Nothing, Nothing ) ->
( Just m
, Nothing
)
( Just start, Nothing ) ->
case
compare (floorDate m |> Time.posixToMillis)
(floorDate start |> Time.posixToMillis)
of
LT ->
( Just m
, Just start
)
_ ->
( Just start
, Just m
)
( Nothing, Just end ) ->
( Just m
, Just end
)
( Just start, Just end ) ->
( Just m
, Nothing
)
_ ->
( dateTimePicker.startDate
, dateTimePicker.endDate
)
in
{ dateTimePicker
| startDate = startDate
, endDate = endDate
, startTime = updateTime_ startDate dateTimePicker.startTime
, endTime = updateTime_ endDate dateTimePicker.endTime
}
-- VIEW
-- カレンダー1マス分のviewを抜粋
[ div
[ class ("date front" ++ mouseoverClass ++ startClass ++ endClass ++ thisMonthClass)
, onMouseOver <| MouseOverDay day
, onClick OnClickDay
]
[ text (Time.toDay utc day |> String.fromInt) ]
]
MouseOverDay time
でマウスが置かれているマス(日付)をモデルmouseOverDay
に記録する。
OnClickDay
でクリックされた日付(mouseOverDayに設定された日付)をpick済みの日付としてセットする(startDate, endDate)。
あとは、Duration Pickerなので、クリックした2つの日付比べて、startDateにするかendDateにするかを長々と判定している。
TimePicker
TimePicker側はDateと明確に分けるためにstartTime,endTimeの入れ物を定義している。
-- UPDATE
....
SetInputTime startOrEnd inputHourOrMinute num ->
let
limit_ : Int -> Int -> Int
limit_ limit n =
if n < 0 then
0
else
modBy limit n
updateHourOrMinute_ : InputHourOrMinute -> Posix -> Posix
updateHourOrMinute_ ihom s =
case ihom of
InputHour ->
updateHour (limit_ 24 num) s
InputMinute ->
updateMinute (limit_ 60 num) s
( startTime, endTime ) =
setTime_ startOrEnd inputHourOrMinute updateHourOrMinute_
in
{ dateTimePicker | startTime = startTime, endTime = endTime }
IncrementTime startOrEnd inputHourOrMinute num ->
let
updateHourOrMinute_ : InputHourOrMinute -> Posix -> Posix
updateHourOrMinute_ ihom s =
let
compare_ : Posix -> Posix
compare_ a =
if
(floorDate s |> Time.posixToMillis)
== (floorDate a |> Time.posixToMillis)
then
a
else
s
in
case ihom of
InputHour ->
addHour num s
|> compare_
InputMinute ->
addMinute num s
|> compare_
( startTime, endTime ) =
setTime_ startOrEnd inputHourOrMinute updateHourOrMinute_
in
{ dateTimePicker | startTime = startTime, endTime = endTime }
-- VIEW
, div [ class "minute" ]
[ button
[ class "up-button d-flex-center"
, onClick <| IncrementTime startOrEnd InputMinute 1
]
[ i
[ class "fa fa-angle-up" ]
[]
]
, input
[ on "blur" <| Decode.map (SetInputTime startOrEnd InputMinute) targetValueIntParse
, value
(case startOrEnd of
Start ->
case dateTimePicker.startTime of
Just t ->
Time.toMinute utc t |> String.fromInt
Nothing ->
"0"
End ->
case dateTimePicker.endTime of
Just t ->
Time.toMinute utc t |> String.fromInt
Nothing ->
"0"
)
, maxlength 2
, class "view"
]
[]
ボタンクリックで時間をインクリメントしたい場合はIncrementTime
を、テキスト入力で時間を指定したい場合はSetInputTime
を、
という形で若干こだわったので長い。
全部Posixで管理するためには、Time.floorとかTime.posixToMillisとか頻繁に使ってDateとTimeの足算をしてあげないといけない。
この辺りは無駄にコードが長くなるので、全て別ファイルUtils.elm
にまとめた。
所感
ElmのDateTimePickerは外部ライブラリが多くあり、それなりに使えそう。自由にカスタマイズできるものは少ない。
どれも若干クセがあるので、選定の前に要件をはっきりさせておかないと路頭に迷う。
(↑はElmに限った話では無いが、どのライブラリもサクッと導入できる物ではないので、トライ&エラーで選ぶと労力が半端ない)
ElmでDatePickerを作るのはかなり簡単。気に入ったライブラリがなければ、自分で作るのは選択肢の1つだと思う。
DatePickerSDKとかDatePickerHelperとかが欲しい気分