Ths module allows you to "smooth out" messages over time, so that they don't get applied immediately -- instead, they are applied at a future moment. Depending on the configuration you provide, you can use this to implemented debouncing, throttling, or other ways of managing messages.
This module is simpler than Debouncer.Basic, because it is specialized for
the common case where the "input" is your Msg type, and what you ultimately
want do do is have the messages applied. You can use Debouncer.Basic instead
if you want to do something more complex.
A quick note on terminology: the debouncer is said to be "unsettled" while it
is collecting inputs and considering when to emit output. It becomes "settled"
again once a specified period has passed without any inputs (see
settleWhenQuietFor).
To use this module, you will need to integrate it into your Model and Msg
type, and handle it in your update function. Here's one example -- it's the
same example as given in Debouncer.Basic, but you can see that it's simpler
here.
import Browser
import Debouncer.Messages as Debouncer exposing (Debouncer, fromSeconds, provideInput, settleWhenQuietFor, toDebouncer)
import Html exposing (..)
import Html.Attributes exposing (..)
import Html.Events exposing (..)
type alias Model =
{ quietForOneSecond : Debouncer Msg
, messages : List String
}
init : ( Model, Cmd Msg )
init =
( { quietForOneSecond =
Debouncer.manual
|> settleWhenQuietFor (Just <| fromSeconds 1)
|> toDebouncer
, messages = []
}
, Cmd.none
)
type Msg
= MsgQuietForOneSecond (Debouncer.Msg Msg)
| DoSomething
updateDebouncer : Debouncer.UpdateConfig Msg Model
updateDebouncer =
{ mapMsg = MsgQuietForOneSecond
, getDebouncer = .quietForOneSecond
, setDebouncer = \debouncer model -> { model | quietForOneSecond = debouncer }
}
update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
case msg of
MsgQuietForOneSecond subMsg ->
Debouncer.update update updateDebouncer subMsg model
DoSomething ->
( { model | messages = model.messages ++ [ "I did something" ] }
, Cmd.none
)
view : Model -> Html Msg
view model =
div [ style "margin" "1em" ]
[ button
[ DoSomething
|> provideInput
|> MsgQuietForOneSecond
|> onClick
]
[ text "Click here repeatedly." ]
, p [] [ text " I'll add a message below once you stop clicking for one second." ]
, model.messages
|> List.map (\message -> p [] [ text message ])
|> div []
]
main : Program () Model Msg
main =
Browser.element
{ init = always init
, view = view
, update = update
, subscriptions = always Sub.none
}An opaque type representing the configuration needed for a debouncer.
To create a debouncer, start with config, modify it as needed, and then
use toDebouncer. For instance:
config
|> settleWhenQuietFor (fromSeconds 2.0)
|> emitWhileUnsettled (fromSeconds 0.5)
|> toDebouncerInitialize a Debouncer using the supplied Config.
For now, a debouncer's configuration cannot be changed once the debouncer is created. (This is a feature that could be provided in future, if desirable).
A starting point for configuring a debouncer that only emits when you tell
it to, via emitNow.
By default, it:
So, without more, you would need to tell this debouncer when to emit something
(via emitNow) -- it would never happen automatically.
To change any of those parameters, use the various functions that alter a
Config (i.e. settleWhenQuietFor, emitWhenUnsettled, emitWhileUnsettled).
By default, the output type is the same as the input type. However, you can
change that by using the accumulateWith function to provide a different
accumulator.
A starting point for a configuring a debouncer which debounces-- that is, which will emit once quiet for the time you specify.
So, debounce (fromSeconds 2) is equivalent to
manual
|> settleWhenQuietFor (Just (fromSeconds 2))If you also want to emit using the first input, then you can use
emitWhenSettled. For instance, the following configuration would emit the
first input immediately when becoming unsettled, and then emit any
subsequent input once the debouncer was quiet for 2 seconds.
debounce (fromSeconds 2)
|> emitWhenUnsettled (Just 0)A starting point for configuring a debouncer which throttles -- that is, which will emit the first input immediately, and then accmulate and emit no more often than the specified interval.
So, throttle (fromSeconds 2) is equivalent to
manual
|> emitWhileUnsettled (Just (fromSeconds 2))
|> emitWhenUnsettled (Just 0)This is a handy type alias to note that we work in millisecond intervals.
A convenience when you'd rather think in seconds than milliseconds.
fromSeconds 0.5 --> 500How long should the debouncer wait without input before becoming "settled" again?
If you are "debouncing" (i.e. emitWhileUnsettled is Nothing), then this is
the key parameter controlling when you will receive output -- you'll receive
the output after no inputs have been provided for the specified time.
If you are "throttling" (i.e. emitWhileUnsettled is notNothing), then this
parameter won't make much difference, unless you are also specifying
emitWhenUnsettled in order to do something with the initial input.
What should the debouncer do with the first input when it becomes unsettled?
If Nothing (the default), the debouncer emits only on the "trailing edge."
That is, it won't do anything special with the first input -- it will just
keep it and do something with it later.
If Just 0, the debouncer will immediately emit the first input when becoming
unsettled. It will then remain unsettled, accumulating further input and
eventually emitting it.
If Just interval, the debouncer will use the provided interval to emit after
becoming unsettled. It will then remain unsettled, collecting further input and
eventually emitting it.
Modify a Config by controlling whether to emit the first
input when becoming unsettled.
emitFirstInput True config is equivalent to
emitWhenUnsettled (Just 0) configemitFirstInput False config is equivalent to
emitWhenUnsettled Nothing configFor more complex cases, where you want to emit the first input on
a different interval than others, but not immediately, you can
use emitWhenSettled directly.
Should the debouncer emit while it is unsettled?
If Nothing (the default), the debouncer will wait until it becomes settled
again before emitting. This is what you might refer to as "debouncing".
If Just interval, the debouncer will emit at the provided interval while it
is unsettled. This is what you might refer to as "throttling".
How should the debouncer combine new input with the output collected so far?
You can use one of the pre-built accumulators:
- `lastInput` (the default)
- `firstInput`Or, if you need to do something more complex, you can provide your own function of the form
msg -> Maybe msg -> Maybe msgAn accumulator which just keeps the last provided input. This is probably the one you'll want most often (and is the default), but you do have other choices.
An accumulator which just keeps the first provided input. Thus, the remaining inputs are only used for timing purposes -- their values aren't actually remembered.
Messages which the debouncer handles.
You will need to integrate this into your own Msg type, and then handle it
in your update function (see code example above).
The type parameter represents the type of your own Msg type. Thus, it should
match the msg in Debouncer msg.
The only message you will typically need to send to the debouncer explicitly
is the message that provides input. You can construct such a message with the
provideInput function. Other messages are used internally by the debouncer.
Construct a message that provides input to a debouncer.
The type parameter represents your own Msg type.
Construct a message which will emit any accumulated output. This doesn't
affect whether the debouncer is "settled" or not. If you'd like to emit output
and force the debouncer to be settled, then use settleNow instead.
Construct a message which settles the debouncer now, even if it wouldn't otherwise settle at this time.
Any accumulated output will be emitted. If you want to settle without emitting
any output, use cancel or cancelNow instead.
Cancel any input collected so far (and not yet emitted). This throws away whatever input has been provided in the past, and forces the debouncer back to a "settled" state (without emitting anything).
Like cancel, but operates via a message instead of acting directly on
the debouncer. This is a convenience for cases where you'd like to cancel
via a message.
Configuration that simplifies how your update function calls our update
function.
This module handles the case where the input to the debouncer is your Msg
type, and what you want to happen when output is emitted is that the Msg gets
applied. So, the debouncer is basically delaying some messages, according to
your configuration.
Our update function is simplified (compared to Debouncer.Basic.update), so
that you can call it (from your update function) like this:
update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
case msg of
MsgQuietForOneSecond subMsg ->
Debouncer.Messages.update update updateDebouncer subMsg modelThis way, you don't have to "massage" what our update function returns. You
provide the debouncer and your model, and we return the standard tuple that
your update function also needs to return.
To make this work, we need some configuration:
- `mapMsg` is the tag, in your `Msg` type, that wraps our `Msg` type
- `getDebouncer` is a function we can use to get the debouncer from your `model` type
- `setDebouncer` is a function we can use to update your model with an updated debouncerIn practice, it is pretty straight-forward to specify these things -- see the
updateDebouncer example in the module docs for one example:
updateDebouncer : Debouncer.UpdateConfig Msg Model
updateDebouncer =
{ mapMsg = MsgQuietForOneSecond
, getDebouncer = .quietForOneSecond
, setDebouncer = \debouncer model -> { model | quietForOneSecond = debouncer }
}Handle a message for the debouncer.
You will need to integrate this into your update function, so that the debouncer
can act on its messages. (There is a code example at the top of the module docs).
Note that we actually return your own (model, Cmd msg) type. This simplifies
your update function, since you don't need to massage what we return in order
to make things work -- we return exactly what your update function will need
to return.
To make that work, you need to provide a couple of additional parameters.
- The first parameter is essentially your own `update` function. If your
`update` function takes additional parameters, you will need to partially
apply them. This allows us to immediately execute an emitted message at
the appropriate time, without you needing to handle it specially.
- The second parameter allows us to update your model with the new debouncer
state, without you needing to handle it specially.
An opaque type which holds the state of a debouncer. You will need to integrate this into your
Modeltype to keep the state.The type parameter represents your
Msgtype, which will be used both for the inputs and the outputs. If that doesn't suit you,Debouncer.Basicallows you to use any input or output type you wish.To create a
Debouncer, start with aconfig, modify the config as needed, and then usetoDebouncer. For instance: