[Chinese]关于在[Elm] Alertmanager中引入DatePicker的故事

最近在制作网页界面时,日期选择器是非常需要的一个功能。
最近因为接触到Elm的DatePicker,所以想着在Purometheus的Alertmanager中加入一个datepicker不是挺好的吗?
于是我试着加了进去。

Elm的日期选择器 (Elm de qì)

关于DatePicker,在Elm中处理日期的话,首先推荐使用这些包。这篇文章总结得很清楚,我参考了它。

在Elm中,几乎没有自由定制的DatePicker可用,或者可以说几乎没有。它可能是由Elm-CSS嵌入的,或者只能使用输入字段。将其集成到现有系统中肯定非常麻烦。

CurrySoftware/elm-datepicker – 咖喱软件/榆木日期选择器

这个链接是我调查后发现最好的选项。
外观非常简洁,而且易于自定义。
你可以自己定义属性和日期格式,因此几乎可以按照自己的想法来显示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
    }

最近,如同在Elm 0.19处理日期的文章所提到的,一个相当重要的事情是所有的日期都是由Posix标准管理的。因此,在尽可能避免依赖来源不明的软件包的意义上,这是最佳选择。

基本上,elm/time和time-extra就足夠使用了,但是有很多不同的日期包可供選擇,搞得人有點不知道該用哪一個。

Elmer的人好像討厭引入Date包。他們似乎更傾向於使用自己獨有的日期類型,並全部使用Posix來管理時間(我想的。至於這方面,說實話不太清楚)。

就这样,我将 “CurrySoftware/elm-datepicker” 添加到 alertmanager,并提交了一个 PR。
https://github.com/prometheus/alertmanager/pull/2262

82245921-c946a280-997e-11ea-89a6-d2ad73c65059.gif

然而,很快就有人回复说需要一个TimePicker。

我猜也许支持时间也会很好,但可能并不那么容易(至少该组件只支持日期)?

时间选择器真的不是那么必要吗?
我个人认为,当到达时间时,手动输入就足够了。

但是,毫无疑问,有一个DateTimePicker会更方便,所以现在我们要寻找一个DateTimePicker。

Elm 的日期时间选择器

当谈及DateTimePicker时,选择变得更加有限。
虽然没有“最佳”选择,但有一些看起来不错的选项。

水星媒体/榆树日期时间选择器

最初我是被w0rm推荐了这个网址的:
https://package.elm-lang.org/packages/mercurymedia/elm-datetime-picker/latest/
作为一个DateTimePicker,它的设计无可挑剔。非常棒。我想我会使用它。
但是,选择日期时会清零时间的规定,
每个人对此可能有不同的看法,但在我个人看来,安排好的时间在更改日期时被清零是无法接受的。

我会继续寻找其他的。

潘纳吉奥蒂斯乔治亚斯/榆树日期选择器

看起来次点的选择是PanagiotisGeorgiadis/elm-datepicker。
外观也很酷,而且拥有持续时间选择器,所以可能适合Alertmanager。
我们决定使用它,并将其用作持续时间选择器,但存在一个致命的缺陷…
在持续时间选择器中,不能选择相同的开始日期/结束日期。
这是规定吗?这种行为令人怀疑。对于Alertmanager来说,这是不可接受的。

因为有很多事情发生,最终我决定自己动手做会更快,是这样的。

制作一个DateTimePicker

代码在这里
https://github.com/prometheus/alertmanager/pull/2262/files
以上就是全部内容了,
因为意外地容易,所以做了个备忘录。

日期选择器

以下是一种汉语本地化的表达方式:
下面是更新内容之一,与日期选择器相关的事项几乎都包含在内。


-- 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时间的单元格(日期)上时,将其记录在mouseOverDay模型中。
当点击日期时(设置为MouseOverDay日期),将其设置为已选取的日期(起始日期,结束日期)。

然后,由于持续时间选择器,我们需要判断所点击的两个日期,是将其设为开始日期还是结束日期,这个过程会有很多决定。

时间选择器

为了明确区分日期,TimePicker组件定义了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等函数来进行日期和时间的加法操作。
由于这些操作会使代码变得冗长,所以我将它们全部整理到了另一个名为Utils.elm的文件中。

87542666-5176c900-c6de-11ea-9a7b-64b11e2acf48.gif
image.png

个人感受

Elm的DateTimePicker有很多外部库可供选择,看起来还算好用。虽然有一些可以自由定制的选项,但数量相对较少。
由于它们都有一些特殊之处,所以在选择之前必须明确需求,否则会迷失方向。
(上述情况并不仅限于Elm,因为任何库都不是简单导入即可使用,因此选择是一个繁琐的试错过程,需要耗费大量精力)

使用Elm创建DatePicker非常简单。如果没有喜欢的库,我认为自己创建是一个选择。我想要一个叫做DatePickerSDK或者DatePickerHelper的东西。