Searching Feature In Blazor

In this article, we are going to add a searching feature to the table. We will Implement server Side Search As we type. In my previous article, I walked through CRUD Using Blazor, Entity Framework Core And Dapper, Sorting Table In Blazor and Pagination In Blazor.  If you haven’t read yet please read both articles first to understand this article. You can download the CRUD Blazor code from here. If you are new to Blazor, I recommend reading Getting Started With Blazor.

Prerequisite

  1. CRUD Using Blazor, Entity Framework Core And Dapper
  2. Sorting Table In Blazor
  3. Pagination In Blazor

In the previous post, we have done with pagination logic. so we are continuing from there.

Let’s start!

First, we need to modify our “Articlemanager.cs” class to implement a search feature.

To do this modify the “Count” and “ListAll” method as below.

public Task<int> Count(string search)
{
    var totArticle = Task.FromResult(_dapperManager.Get<int>($"select COUNT(*) from [Article] WHERE Title like '%{search}%'", null,
            commandType: CommandType.Text));
    return totArticle;
}

public Task<List<Article>> ListAll(int skip, int take, string orderBy, string direction = "DESC", string search = "")
{
    var articles = Task.FromResult(_dapperManager.GetAll<Article>
        ($"SELECT * FROM [Article] WHERE Title like '%{search}%' ORDER BY {orderBy} {direction} OFFSET {skip} ROWS FETCH NEXT {take} ROWS ONLY; ", null, commandType: CommandType.Text));
    return articles;
}

In above method, we add a search parameter in both methods and update SQL query to filter records.

As we update the manager class we need to update interface too, Open “IArticleManager” interface and update methods as below.

Task<int> Count(string search);
Task<List<Article>> ListAll(int skip, int take, string orderBy, string direction, string search);

We have done with backend logic now we need to modify the razor component.

Open the “FetchArticle.razor” component and modify the following things.

Add the following HTML content above the table.

<div class="row col-md-3 pull-right">
    <input type="text" id="txtSearch" placeholder="Search Titles..." class="form-control" @bind="SearchTerm" @bind:event="oninput" />
</div>

Update the table’s tbody.

   <tbody>
    @if (articleModel == null || articleModel.Count == 0)
    {
        <tr>
            <td colspan="3">
                No Records to display
            </td>
        </tr>
    }
    else
    {
        foreach (var article in articleModel)
        {
            <tr>
                <td>@article.ID</td>
                <td>@article.Title</td>
                <td>
                    <a class="btn btn-primary" href='/editArticle/@article.ID'>Edit</a>  |
                    <a class="btn btn-danger" @onclick="() => DeleteArticle(article.ID)">Delete</a>
                </td>
            </tr>
        }
    }

</tbody>

Add the following things inside @code { }  blog.

  • Add properties.
private string searchTerm;
private string SearchTerm
{
    get { return searchTerm; }
    set { searchTerm = value; FilterRecords(); }
}
  • Update the following methods method.
protected override async Task OnInitializedAsync()
{
    //display total page buttons
    pagerSize = 3;
    pageSize = 2;
    curPage = 1;
    articleModel = await articleManager.ListAll((curPage - 1) * pageSize, pageSize, sortColumnName, sortDir, searchTerm);
    totalRecords = await articleManager.Count(searchTerm);
    totalPages = (int)Math.Ceiling(totalRecords / (decimal)pageSize);
    SetPagerSize("forward");
}
protected async Task DeleteArticle(int id)
{
    await articleManager.Delete(id);
    articleModel = await articleManager.ListAll((curPage - 1) * pageSize, pageSize, sortColumnName, sortDir, searchTerm);
}
private async Task<List<Article>> SortRecords(string columnName, string dir)
{
    return await articleManager.ListAll((curPage - 1) * pageSize, pageSize, columnName, dir, searchTerm);
}
public async Task refreshRecords(int currentPage)
{
    articleModel = await articleManager.ListAll((currentPage - 1) * pageSize, pageSize, sortColumnName, sortDir, searchTerm);
    curPage = currentPage;
    this.StateHasChanged();
}
public void SetPagerSize(string direction)
    {
        if (direction == "forward" && endPage < totalPages)
        {
            startPage = endPage + 1;
            if (endPage + pagerSize < totalPages)
            {
                endPage = startPage + pagerSize - 1;
            }
            else
            {
                endPage = totalPages;
            }
            this.StateHasChanged();
        }
        else if (direction == "back" && startPage > 1)
        {
            endPage = startPage - 1;
            startPage = startPage - pagerSize;
        }
        else
        {
            startPage = 1;
            endPage = totalPages;
        }
    }
  • Add the “FilterRecords” method.
public void FilterRecords()
{
    endPage = 0;
    this.OnInitializedAsync().Wait();
}

Your “FetchArticle.razor” will look like this.

@page "/articlelist"

@using BlazorCRUD.Entities
@using BlazorCRUD.Contracts
@inject IArticleManager articleManager

<link href="https://stackpath.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css" rel="stylesheet">

<style>
    .sort-th {
        cursor: pointer;
    }

    .fa {
        float: right;
    }

    .btn-custom {
        color: black;
        float: left;
        padding: 8px 16px;
        text-decoration: none;
        transition: background-color .3s;
        border: 2px solid #000;
        margin: 0px 5px 0px 5px;
    }
</style>

<div>
    <a class="btn btn-primary" href='/addArticle'>Add</a>
</div>
<br />

@if (articleModel == null)
{
    <p><em>Loading...</em></p>
}
else
{
    <div class="row col-md-3 pull-right">
        <input type="text" id="txtSearch" placeholder="Search Titles..." class="form-control" @bind="SearchTerm" @bind:event="oninput" />
    </div>
    <table class="table table-bordered table-hover">
        <thead>
            <tr>
                <th class="sort-th" @onclick="@(() => SortTable("ID"))">
                    ID
                    <span class="fa @(SetSortIcon("ID"))"></span>
                </th>
                <th class="sort-th" @onclick="@(() => SortTable("Title"))">
                    Title
                    <span class="fa @(SetSortIcon("Title"))"></span>
                </th>
                <th>Action</th>
            </tr>
        </thead>
        <tbody>
            @if (articleModel == null || articleModel.Count == 0)
            {
                <tr>
                    <td colspan="3">
                        No Records to display
                    </td>
                </tr>
            }
            else
            {
                foreach (var article in articleModel)
                {
                    <tr>
                        <td>@article.ID</td>
                        <td>@article.Title</td>
                        <td>
                            <a class="btn btn-primary" href='/editArticle/@article.ID'>Edit</a>  |
                            <a class="btn btn-danger" @onclick="() => DeleteArticle(article.ID)">Delete</a>
                        </td>
                    </tr>
                }
            }

        </tbody>
    </table>
    <div class="pagination">
        <button class="btn btn-custom" @onclick=@(async ()=>await NavigateToPage("previous"))>Prev</button>

        @for (int i = startPage; i <= endPage; i++)
        {
            var currentPage = i;
            <button class="btn btn-custom pagebutton @(currentPage==curPage?"btn-danger":"")" @onclick=@(async () =>await refreshRecords(currentPage))>
                @currentPage
            </button>
        }

        <button class="btn btn-custom" @onclick=@(async ()=>await NavigateToPage("next"))>Next</button>

    </div>
}


@code {
    private string searchTerm;
    private string SearchTerm
    {
        get { return searchTerm; }
        set { searchTerm = value; FilterRecords(); }
    }

    List<Article> articleModel;
    Article articleEntity = new Article();


    #region Pagination

    int totalPages;
    int totalRecords;
    int curPage;
    int pagerSize;
    int pageSize;
    int startPage;
    int endPage;
    string sortColumnName = "ID";
    string sortDir = "DESC";

    #endregion

    protected override async Task OnInitializedAsync()
    {
        //display total page buttons
        pagerSize = 3;
        pageSize = 2;
        curPage = 1;
        articleModel = await articleManager.ListAll((curPage - 1) * pageSize, pageSize, sortColumnName, sortDir, searchTerm);
        totalRecords = await articleManager.Count(searchTerm);
        totalPages = (int)Math.Ceiling(totalRecords / (decimal)pageSize);
        SetPagerSize("forward");
    }


    protected async Task DeleteArticle(int id)
    {
        await articleManager.Delete(id);
        articleModel = await articleManager.ListAll((curPage - 1) * pageSize, pageSize, sortColumnName, sortDir, searchTerm);
    }

    private bool isSortedAscending;
    private string activeSortColumn;

    private async Task<List<Article>> SortRecords(string columnName, string dir)
    {
        return await articleManager.ListAll((curPage - 1) * pageSize, pageSize, columnName, dir, searchTerm);
    }

    private async Task SortTable(string columnName)
    {
        if (columnName != activeSortColumn)
        {
            articleModel = await SortRecords(columnName, "ASC");
            isSortedAscending = true;
            activeSortColumn = columnName;
        }
        else
        {
            if (isSortedAscending)
            {
                articleModel = await SortRecords(columnName, "DESC");
            }
            else
            {
                articleModel = await SortRecords(columnName, "ASC");
            }

            isSortedAscending = !isSortedAscending;
        }
        sortColumnName = columnName;
        sortDir = isSortedAscending ? "ASC" : "DESC";
    }

    private string SetSortIcon(string columnName)
    {
        if (activeSortColumn != columnName)
        {
            return string.Empty;
        }
        if (isSortedAscending)
        {
            return "fa-sort-up";
        }
        else
        {
            return "fa-sort-down";
        }
    }

    public async Task refreshRecords(int currentPage)
    {
        articleModel = await articleManager.ListAll((currentPage - 1) * pageSize, pageSize, sortColumnName, sortDir, searchTerm);
        curPage = currentPage;
        this.StateHasChanged();
    }

    public void SetPagerSize(string direction)
    {
        if (direction == "forward" && endPage < totalPages)
        {
            startPage = endPage + 1;
            if (endPage + pagerSize < totalPages)
            {
                endPage = startPage + pagerSize - 1;
            }
            else
            {
                endPage = totalPages;
            }
            this.StateHasChanged();
        }
        else if (direction == "back" && startPage > 1)
        {
            endPage = startPage - 1;
            startPage = startPage - pagerSize;
        }
        else
        {
            startPage = 1;
            endPage = totalPages;
        }
    }

    public async Task NavigateToPage(string direction)
    {
        if (direction == "next")
        {
            if (curPage < totalPages)
            {
                if (curPage == endPage)
                {
                    SetPagerSize("forward");
                }
                curPage += 1;
            }
        }
        else if (direction == "previous")
        {
            if (curPage > 1)
            {
                if (curPage == startPage)
                {
                    SetPagerSize("back");
                }
                curPage -= 1;
            }
        }
        await refreshRecords(curPage);
    }

    public void FilterRecords()
    {
        endPage = 0;
        this.OnInitializedAsync().Wait();
    }

}

Output:

You can download source code from here.

If you want to learn File Upload in Blazor please visit here.

I hope you guys found something useful. Please give your valuable feedback/comments/questions about this article. Please let me know how you like and understand this article and how I could improve it.

9 Comments

  1. RAZA MUHAMMAD

    Very nice

    0
    0
    Reply
  2. Tarik

    Your articles are highly appreciated,.
    Thank you very much for all excellents articles you did, and all upcoming articles, please do not forget the “guidance to export the data in the view to Microsoft Excel” with many thanks.

    0
    0
    Reply
  3. Maurice

    Thanx Faisal Pathan for your rapid and effiecient responses. The coding is so clear and directive.

    ✔Kindly build up on this same project to export the records in the view to Microsoft Excel.

    Many Thanx in Advance as you work on this.

    Maurice

    0
    0
    Reply
    1. Faisal Pathan

      Sure will do, Thanks

      0
      0
      Reply
      1. Maurice Aboagye

        Thank You in advance for giving attention to the request for guidance to export the data in the view to Microsoft Excel. We are still expectation of a reply from you.

        0
        0
        Reply
        1. Faisal Pathan

          sure, will do.

          0
          0
          Reply
          1. Maurice Aboagye

            Hello Faisal.

            Your approach and teaching has been very much appreciated.

            After implementing the searching feature, we hope to hear from you concerning my question on exporting the data in the View to Microsoft Excel.

            Thank You in advance

            0
            0
  4. oprol evorter

    I respect your work, thanks for all the interesting content.

    0
    0
    Reply
    1. Faisal Pathan

      Thanks for read, and good words

      0
      0
      Reply

Submit a Comment

Your email address will not be published. Required fields are marked *

Subscribe

Select Categories