You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
Connected.Components/Components/Input/DebouncedInput.cs

122 lines
3.5 KiB

using System.Timers;
using Connected.Annotations;
using Microsoft.AspNetCore.Components;
namespace Connected.Components;
public abstract class DebouncedInput<T> : InputBase<T>
{
private System.Timers.Timer _timer;
private double _debounceInterval;
/// <summary>
/// Interval to be awaited in milliseconds before changing the Text value
/// </summary>
[Parameter]
[Category(CategoryTypes.FormComponent.Behavior)]
public double DebounceInterval
{
get => _debounceInterval;
set
{
if (NumericConverter<double>.AreEqual(_debounceInterval, value))
return;
_debounceInterval = value;
if (_debounceInterval == 0)
{
// not debounced, dispose timer if any
ClearTimer(suppressTick: false);
return;
}
SetTimer();
}
}
/// <summary>
/// callback to be called when the debounce interval has elapsed
/// receives the Text as a parameter
/// </summary>
[Parameter] public EventCallback<string> OnDebounceIntervalElapsed { get; set; }
protected Task OnChange()
{
if (DebounceInterval > 0 && _timer != null)
{
_timer.Stop();
return base.UpdateValuePropertyAsync(false);
}
return Task.CompletedTask;
}
protected override Task UpdateValuePropertyAsync(bool updateText)
{
// This method is called when Value property needs to be refreshed from the current Text property, so typically because Text property has changed.
// We want to debounce only text-input, not a value being set, so the debouncing is only done when updateText==false (because that indicates the
// change came from a Text setter)
if (updateText)
{
// we have a change coming not from the Text setter, no debouncing is needed
return base.UpdateValuePropertyAsync(updateText);
}
// if debounce interval is 0 we update immediately
if (DebounceInterval <= 0 || _timer == null)
return base.UpdateValuePropertyAsync(updateText);
// If a debounce interval is defined, we want to delay the update of Value property.
_timer.Stop();
// restart the timer while user is typing
_timer.Start();
return Task.CompletedTask;
}
protected override void OnParametersSet()
{
base.OnParametersSet();
// if input is to be debounced, makes sense to bind the change of the text to oninput
// so we set Immediate to true
if (DebounceInterval > 0)
Immediate = true;
}
private void SetTimer()
{
if (_timer == null)
{
_timer = new System.Timers.Timer();
_timer.Elapsed += OnTimerTick;
_timer.AutoReset = false;
}
_timer.Interval = DebounceInterval;
}
private void OnTimerTick(object sender, ElapsedEventArgs e)
{
InvokeAsync(OnTimerTickGuiThread).AndForget();
}
private async Task OnTimerTickGuiThread()
{
await base.UpdateValuePropertyAsync(false);
await OnDebounceIntervalElapsed.InvokeAsync(Text);
}
private void ClearTimer(bool suppressTick = false)
{
if (_timer == null)
return;
var wasEnabled = _timer.Enabled;
_timer.Stop();
_timer.Elapsed -= OnTimerTick;
_timer.Dispose();
_timer = null;
if (wasEnabled && !suppressTick)
OnTimerTickGuiThread().AndForget();
}
protected override void Dispose(bool disposing)
{
base.Dispose(disposing);
ClearTimer(suppressTick: true);
}
}