Home > Back-end >  C# Blazor - How do I throttle an onChange event for my textbox
C# Blazor - How do I throttle an onChange event for my textbox

Time:10-14

I'm very new to Blazor and razor pages. I'm trying to handle an onChange event for my text box, but I want to throttle it so it doesn't trigger until a few seconds after input has stopped to save on network traffic.

Can/should you used JavaScript to do something like this? Like a setTimeOut or something?

Here's the code I'm working with and below that is what I've found and tried from here

@page "/todo"

<pagetitle>Todo</pagetitle>

<h1>Todo (@todos.Count(todo => !todo.IsDone))</h1>

<table>
    @foreach (var todo in todos)
    {
    <tr>
        <td>
            <input type="checkbox" @bind="todo.IsDone" />
        </td>
        <td>
            <input type="text" style="border:none;" @bind="todo.Title" />
        </td>
    </tr>
    }
</table>

<input placeholder="Something todo" @bind="newTodo" />
<button @onclick="AddTodo">Add todo</button>

@code {
    private List<TodoItem> todos = new();
    private string? newTodo;

    private void AddTodo()
    {
        if (!string.IsNullOrWhiteSpace(newTodo))
        {
            todos.Add(new TodoItem { Title = newTodo });
            newTodo = string.Empty;
        }
    }
}

I've tried a couple things, one worked the other didn't (the one that didn't, the syntax seems more intuitive)...

This didn't work because it didn't know what <InputText> element was

<div >
    <label for="name">Contact: </label>
    <InputText id="name" Value="@Contact.Name" ValueChanged="NameChanged" ValueExpression="() => Contact.Name" ></InputText>
</div>

@code {
    private void NameChanged(string value)
    {
        Contact.Name = value;
    }
}

This did work but don't know how to throttle it?

<input  type="number" step="any" @bind-value:event="onchange"
    @oninput="CalculateStandardDimensions" @bind-value="Cyclone.CycloneSize" />

@code
{
    public class Cyclon
    {
        public Int32 CycloneSize { get; set; } = 10;
    }

    public Cyclon Cyclone = new Cyclon();

    private void CalculateStandardDimensions(ChangeEventArgs args)
    {
        // Do Stuff Here
        System.Console.WriteLine("test123");
    }

}

CodePudding user response:

You can set up the Task as below. Every time the "onchange" is triggered, it will cancel the task if it's already running. If the task to be cancelled is async, You can cancel it directly.

@using System.ComponentModel
@implements IDisposable

<input type="text" value="@someText" @onchange="OnChangeTask"/>
<label>@counter</label>

@code
{
    private string someText;
    private CancellationTokenSource src = new();
    int counter;

    private async Task OnChangeTask(ChangeEventArgs args)
    {
        someText = args.Value.ToString();
        src.Cancel();
        src = new();
        await Task.Delay(5000, src.Token).ContinueWith(DoSomeWork,src.Token);

    }

    private void DoSomeWork(Task obj)
    {
        counter  ;
    }


    public void Dispose()
    {
        src.Dispose();
    }
}

CodePudding user response:

Good opportunity to create a custom component that handles this. In your project's shared folder create a new razor component.

@using System.Timers
@implements IDisposable

<div >
    <input id="@ElementId"  @bind="@SearchText" @onkeydown="SearchTextChanged" placeholder="@PlaceholderText"
           @bind:event="oninput" disabled="@IsDisabled"/>
</div>

So, we will wrap input element and allow to modify its CSS with a parameter @CssOuter.

The Input element itself has parameters which we can use to access the input's id, class, placeholder and whether its disabled. Note the default values bellow (meaning we don't have to specify it when using the element)

@code 
{
    [Parameter] public string ElementId { get; set; }
    [Parameter] public bool IsDisabled { get; set; } = false;
    [Parameter] public string SearchText { get; set; }
    [Parameter] public EventCallback<string> OnSearchFinished { get; set; }
    [Parameter] public string PlaceholderText { get; set; }
    [Parameter] public string CssClass { get; set; } = "form-control";
    [Parameter] public string CssOuter { get; set; } = "mb-4";

    private Timer searchTimer;
    private int debounceTime = 700;

    protected override void OnInitialized()
    {
        searchTimer = new Timer(debounceTime);
        searchTimer.Elapsed  = OnTimerElapsed;
        searchTimer.AutoReset = false;       
    }

    private void SearchTextChanged()
    {
        searchTimer.Stop();
        searchTimer.Start();
    }

    private void OnTimerElapsed(object source, ElapsedEventArgs e)
    {
        OnSearchFinished.InvokeAsync(SearchText);
    }

    public void Dispose()
    {
        searchTimer.Elapsed -= OnTimerElapsed;
    }
}

Change the debounceTime to the value you wish, in this case it is 0.7s. The EventCallback method is what will get raised when the debounce timer elapses after last keyboard stroke.

And this is how you would use it in your razor page

<InputWithDebounce CssClass="form-control form-control-sm"
                   ElementId="myIdStyle" PlaceholderText="Search anything..."
                   OnSearchFinished="MethodToRaise"
                   SearchText="@SearchText">
</InputWithDebounce>

@code {
    public string SearchText {get;set;} = string.Empty;

    public async Task MethodToRaise(string searchText)
    {
        // do something
    }
}
  • Related