How do I highlight a grid row on hover and make it selected on click in Bootstrap?

Viewed 1402

I am loading data to a grid with 3 columns in Blazor. For the grid structure I have used Bootstrap rows and columns. If I need to highlight rows on hover and keep the row selected using onClick event what is the best way to achieve it?

Below is how I populate data.

<div class="container-fluid">
    <div class="row">
        <div class="col">User Name</div>
        <div class="col">First Name</div>
        <div class="col">Last Name</div>
    </div>

    @foreach (var user in users)
    {
        <div class="row">
            <div class="col">@user.UserName</div>
            <div class="col">@user.FirstName</div>
            <div class="col">@user.LastName</div>
        </div>
    }
</div>

Thanks.

4 Answers

You need to maintain the rows selected state. Here is a generic view model to wrap any row type:

public class RowState<TItem>
{
    public RowState(TItem item) => Item = item;
    public TItem Item { get; set; }
    public bool IsSelected { get; set; }
}

Then using style classes for both selected and hover:

.hover-row:hover {
    background-color: #0000ff33;
}

.selected-row {
    background-color: #0000ff77;
}

I seperated the row into its own component UserRow.razor with a [Parameter] that takes the RowState<User> viewmodel.

This simplifies the logic to handle the click event and logic for the css class to highlight the row.

UserRow.razor

<div class="row hover-row @CssClass" @onclick="RowClicked" >
    <div class="col">@User.UserName</div>
    <div class="col">@User.FirstName</div>
    <div class="col">@User.LastName</div>
</div>

@code {
    [Parameter]
    public RowState<User> UserRowState { get; set; }   

    private void RowClicked() => UserRowState.IsSelected = !UserRowState.IsSelected;

    private User User => UserRowState.Item;
    private bool IsSelected => UserRowState.IsSelected;
    private string CssClass => IsSelected ? "selected-row" : "";
}

Refactoring your original code:

<div class="container-fluid">
    <div class="row">
        <div class="col">User Name</div>
        <div class="col">First Name</div>
        <div class="col">Last Name</div>
    </div>

    @foreach (var user in UserRows)
    {
        <UserRow UserRowState=user />     
    }
</div>

@code {
    IEnumerable<RowState<User>> UserRows = User.DemoData
        .Select(user => new RowState<User>(user))
        .ToList();
}

Here is a working REPL

<table class="table table-hover">
<tr>
    <td >User Name</td>
    <td >First Name</td>
    <td >Last Name</td>
</tr>

@foreach (var user in users)
{
    string bg = (user.Id == selectedUserId)? "bg-info" : "";
    <tr class=@bg @onclick=@SelectUser(user.Id) @key=user>
        <td >@user.UserName</td>
        <td >@user.FirstName</td>
        <td >@user.LastName</td>
    </tr>
}
@code{

  private int selectedUserId {get;set;} = 0;
  private void SelectUser(int userId){
   selectedUserId = userId;
  }

}

None of the JavaScript answers is appropriate for Blazor. Ignore them.

If you just want to select a single row, you can use @Ali Borjian's answer

In order to track selection of multiple rows, you can use one of two methods:

  1. Use a class that tracks state information per row. This is Brian's answer.
  2. Create a secondary list that tracks which rows are selected. This follows.

.

<div class="container-fluid">
    <div class="row">
        <div class="col">User ID</div>
        <div class="col">User Name</div>
    </div>

    @foreach (var user in Users)
    {
        string CSS = SelectedUsers.Contains(user) ? " Selected" : "";
        <div class="row @CSS" style="" @onclick="()=>ToggleSelection(user)">
            <div class="col">@user.ID</div>
            <div class="col">@user.Name</div>
        </div>
    }
</div>

@code {
    class User { public int ID; public string Name;  }

    List<User> Users = new List<User> { new User { ID = 1, Name = "Benjamin" }, new User { ID = 2, Name = "John" }, new User { ID = 3, Name = "David" } };
    List<User> SelectedUsers = new();

    void ToggleSelection(User user)
    {
        if (SelectedUsers.Any(u => u == user)) SelectedUsers.Remove(user);
        else SelectedUsers.Add(user);
    }

}
<style>
    .row {cursor:pointer}
    .row:hover { background-color:#c2d8ff }
    .Selected {background-color: #4781e9}
    .Selected:hover {background-color: #83aefb}
</style>

You can utilize Radio toggle buttons (for single selection) or Checkbox toggle buttons (for multiple) as Block buttons with just a little bit of custom styling. Put the whole user row inside the label and use the username or other unique field for the input id.

Here is an example using Radio toggle buttons:

label.btn {
  border: none;
  border-radius: 0;
}
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.1/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-F3w7mX95PdgyTmZZMECAngseQB83DfGTowi0iMjiWaeVhAn4FJkqJByhZMI3AhiU" crossorigin="anonymous">

<div class="container-fluid">
  <div class="row">
    <div class="col-4">User Name</div>
    <div class="col-4">First Name</div>
    <div class="col-4">Last Name</div>
  </div>

  <div class="row">
    <!-- @foreach (var user in users)
    { -->
    <div class="col-12 d-grid p-0">
      <input type="radio" class="btn-check" name="options" id="@user.UserName" autocomplete="off">
      <label class="btn btn-outline-dark text-start" for="@user.UserName">
        <div class="row">
          <div class="col-4">@user.UserName</div>
          <div class="col-4">@user.FirstName</div>
          <div class="col-4">@user.LastName</div>
        </div>
      </label>
    </div>
    <!-- } -->

    <div class="col-12 d-grid p-0">
      <input type="radio" class="btn-check" name="options" id="@user.UserName2" autocomplete="off">
      <label class="btn btn-outline-dark text-start" for="@user.UserName2">
        <div class="row">
          <div class="col-4">@user.UserName2</div>
          <div class="col-4">@user.FirstName</div>
          <div class="col-4">@user.LastName</div>
        </div>
      </label>
    </div>
    <div class="col-12 d-grid p-0">
      <input type="radio" class="btn-check" name="options" id="@user.UserName3" autocomplete="off">
      <label class="btn btn-outline-dark text-start" for="@user.UserName3">
        <div class="row">
          <div class="col-4">@user.UserName3</div>
          <div class="col-4">@user.FirstName</div>
          <div class="col-4">@user.LastName</div>
        </div>
      </label>
    </div>

  </div>
</div>

Related