2

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 class="form-group row">
    <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 class="form-control form-control-sm" 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");
    }

}
Rod
  • 14,529
  • 31
  • 118
  • 230
  • I think this answers your question: https://stackoverflow.com/questions/57533970/blazor-textfield-oninput-user-typing-delay/66091134#66091134 – Good Night Nerd Pride Oct 13 '22 at 14:59
  • The `onchange` on a text input isn't called until you exit the input, so if this is specific to a text box then I fail to see a point in doing it. On other types the change "event" is dependent on the entry method. Here's an article that should help you - https://blog.jeremylikness.com/blog/an-easier-blazor-debounce/. – MrC aka Shaun Curtis Oct 13 '22 at 15:19

2 Answers2

4

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();
    }
}
Konrad Psiuk
  • 181
  • 1
  • 11
1

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 class="input-group @CssOuter">
    <input id="@ElementId" class="@CssClass" @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
    }
}
Coop
  • 74
  • 6