abymosa/elm-debounce/2.0.0

Debounce

The Debounce type keeps track of messages it's received in a particular burst. Every message added to the list schedules a check some time in the future; if the list hasn't changed in that time we emit the newest message in the list and discard the rest.

The Basics

type Debounce msg
= Debounce
{ cooldown : Int
, queue : List msg
, tagger : DebounceMsg msg -> msg
}

A type for the Debouncer that keeps track of messages it's received in a particular burst. When that burst settles after the provided cooldown period, the newest meesage gets emitted.

-- define a debouncer in your component
type alias Model =
    { inputText : String
    , debouncer : Debounce Msg
    }
type DebounceMsg msg

A type for messages internal to the Debounce.

-- define a `Debounce.DebounceMsg Msg` type in your component
type Msg
    = DebounceMsg (Debounce.DebounceMsg Msg)
    | ...
init : (DebounceMsg msg -> msg) -> Int -> Debounce msg

You will need to provide a tagger function that will wrap the DebounceMsg and a cooldown value that will determine how long the Debouncer will wait before checking if the burst has settled.

init : () -> ( Model, Cmd Msg )
init _ =
    ( { inputText = ""
      , debouncer = Debounce.init DebounceMsg 500
      }
    , Cmd.none
    )
update : DebounceMsg msg -> Debounce msg -> ( Debounce msg, Cmd msg )

The update function produces Cmds like any other update function. You will need to use it for every Debouncer you have in an application in order for the Debounce to handle its internal messages.

update : Msg -> Model -> (Model, Cmd Msg)
update msg model =
    case msg of
        ...
        DebounceMsg debounceMsg ->
            Debounce.update debounceMsg model.debouncer
                |> Tuple.mapFirst (\debouncer -> { model | debouncer = debouncer })

Usage

To use the Debounce module. First, you will need to define a Debounce in your model, initialize it with init function, and update it in your update function.

Then the actual debounce functionality is provided either in your update function using Debounce.push or in your HTML using one of the Debounce events' functions.

The difference between the two is that the push function is used when you DO want to capture some data from the event source as they occur, while events functions are used when you DO NOT want to capture data from the event source as they occur.

Example: Suppose you want search an API in realtime as the user types, or developing an autocomplete feature that gets results from an API as the user types. You need the text input to be updated as the user types. However, Http request needs to be sent to the API when the user stops typing.

Debounce.push is perfect fit as it will allow you to capture and update the text input as the user types, and then send the Http request when the user stops typing.

If you use events in this case, you will not be able to capture the text input as the user types.

Example : a pagination component that sends out a HTTP request on page change, we want to prevent a sudden burst of requests being sent if the user clicks the "next page" button multiple times.

In this case, one of the Debounce events such as Debounce.event would be a perfict fit as opposed to Debounce.push as

  • It doesn't need any data comeing from the event source.

  • It applies the debouncing functionality right in HTML without cluttering up the update function unnecessarily.

button
    [ Debounce.event onClick NextPage model.debouncer ]
    [ Html.text ">>" ]

Push

push : msg -> Debounce msg -> ( Debounce msg, Cmd msg )

Push a messages into the Debouncer. Every message added to the list schedules a check some time in the future. If the list hasn't changed in that time, we emit the newest message in the list and discard the rest.

This is useful when you DO want to capture some data from the event source as they occur.

-- define a debouncer in your component
type alias Model =
    { inputText : String
    , debouncer : Debounce Msg
    }


-- define a `Debounce.DebounceMsg Msg` type in your component
type Msg
    = UpdateInputText String
    | SendRequest String
    | DebounceMsg (Debounce.DebounceMsg Msg)




-- init
init : () -> ( Model, Cmd Msg )
init _ =
    ( { inputText = ""
      , debouncer = Debounce.init DebounceMsg 500
      }
    , Cmd.none
    )


-- update
update : Msg -> Model -> (Model, Cmd Msg)
update msg model =
    case msg of
        ...
        UpdateInputText text ->
            Debounce.push (SendRequest text) model.debouncer
                |> Tuple.mapFirst
                    (\debouncer ->
                        { model
                            | debouncer = debouncer
                            , inputText = text
                        }
                    )


        SendRequest text ->
            ( model
            , Http.get
                { ...
                }
            )


        DebounceMsg debounceMsg ->
            Debounce.update debounceMsg model.debouncer
                |> Tuple.mapFirst
                    (\debouncer ->
                        { model
                            | debouncer = debouncer
                        }
                    )






-- view
view : Model -> Html Msg
view model =
    input
        [ type_ "text"
        , value model.inputText
        , onInput UpdateInputText
        ]
        []

Events

Debounce right at the source, in the HTML.

This is useful when you want to debounce events that are coming from the user, like clicks, key presses, etc. It almost covers all the events that are available in Http.Events module.

This is useful when you DO NOT want to capture some data from the event source as they occur.

It produces an Attribute msg that can be used in the HTML.

Both event and composeEvent cover all elm events that are available in elm Html.Events module.

on, alwaysPreventDefaultOn, alwatsStopPropagationOn, and custom allow you create custom events with custom decoders. This is useful when you want to debounce custom events that are not available in the Html.Events module like mousemove, scroll, etc.

event : (msg -> Attribute msg) -> msg -> Debounce msg -> Attribute msg

event allows you to debounce any HTML event with (msg -> Attribute msg) definition such as onClick, onMouseDown, etc.

type alias Model =
    { debouncer : Debounce Msg
    , ...
    }


type Msg
    = DoSomething
    | DebounceMsg (Debounce.DebounceMsg Msg)
    | ...


init : Model
init =
    { debouncer = Debounce.init DebounceMsg 500
    , ...
    }


update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
    case msg of
        DoSomething ->
            ( model
            , Http.get
                { ...
                }
            )


        DebounceMsg DebounceMsg ->
            Debounce.update DebounceMsg model.debouncer
                |> Tuple.mapFirst (\debouncer -> { model | debouncer = debouncer })


view : Model -> Html Msg
view model =
    button
        [ Debounce.event onClick DoSomething model.debouncer ]
        [ Html.text "Click" ]
((a -> msg) -> Attribute msg)
-> (a -> msg)
-> Debounce msg
-> Attribute msg

Debounce any event with ((a -> msg) -> Attribute msg) definition

It composes event (a -> msg) -> Attribute msg with Msg (a -> msg) to give you the final Attribute msg while applying the debounce functionality.

-- Usage


Input
    [ type "text"
    , Debounce.composeEvent onInput UpdateText model.debouncer
    ]
    []
on : String -> Decoder msg -> Debounce msg -> Attribute msg

Debounce custom events with custom decoders. This is useful when you want to debounce custom events that are not available in the Html.Events module like mousemove.

-- Eg: do something when the mouse moves stops


mousemoveDecoder : (Int -> Int -> msg) -> Decode.Decoder msg
mousemoveDecoder msg =
    Decode.map2 msg
        (Decode.field "clientX" Decode.int)
        (Decode.field "clientY" Decode.int)


div
    [ Debounce.on "mousemove" (mousemoveDecoder GotPosition) model.debouncer ]
    []
alwaysPreventDefaultOn : String -> Decoder msg -> Debounce msg -> Attribute msg

Similar to on it debounces custom events with custom decoders but it always preventDefault.

alwatsStopPropagationOn : String -> Decoder msg -> Debounce msg -> Attribute msg

Similar to on it debounces custom events with custom decoders but it always stopPropagation.

String
-> Decoder
{ message : msg
, stopPropagation : Bool
, preventDefault : Bool
}
-> Debounce msg
-> Attribute msg

Similar to Html.Events.custom but it allows you to debounce custom events with custom decoders while controlling stopPropagation and preventDefault.