Skip to content

Conversation

@ArgoZhang
Copy link
Member

@ArgoZhang ArgoZhang commented May 27, 2025

Link issues

fixes #6102

Summary By Copilot

Regression?

  • Yes
  • No

Risk

  • High
  • Medium
  • Low

Verification

  • Manual (required)
  • Automated

Packaging changes reviewed?

  • Yes
  • No
  • N/A

☑️ Self Check before Merge

⚠️ Please check all items below before review. ⚠️

  • Doc is updated/provided or not needed
  • Demo is updated/provided or not needed
  • Merge the latest code from the main branch

Summary by Sourcery

Add the MultiSelectGeneric component with rich multi-selection capabilities and integrate it into the existing demos.

New Features:

  • Introduce MultiSelectGeneric component supporting generic multi-select with search, virtualization, customizable templates, and toolbar actions (select all, invert, clear).

Documentation:

  • Add a demo and localization entries for MultiSelectGeneric in the MultiSelects sample page.

Chores:

  • Remove obsolete SelectGeneric.razor.scss styling file.

@bb-auto bb-auto bot added the enhancement New feature or request label May 27, 2025
@sourcery-ai
Copy link
Contributor

sourcery-ai bot commented May 27, 2025

Reviewer's Guide

Introduces a new fully-featured generic multi-select component (MultiSelectGeneric) with virtualization, toolbar and template support; integrates it into the sample page with demo data and localization; and refactors the base class metadata while removing obsolete styling.

Sequence Diagram for Item Selection in MultiSelectGeneric

sequenceDiagram
    actor User
    participant RazorUI as "MultiSelectGeneric.razor"
    participant ComponentLogic as "MultiSelectGeneric.razor.cs"

    User->>RazorUI: Clicks on an item (e.g., 'Item A')
    RazorUI->>ComponentLogic: ToggleRow(itemA_Instance)
    activate ComponentLogic
    ComponentLogic->>ComponentLogic: Update SelectedItems list (add/remove itemA_Instance)
    ComponentLogic->>ComponentLogic: SetValue() calls CurrentValue = ..., OnSelectedItemsChanged(), StateHasChanged()
    ComponentLogic-->>RazorUI: Updated state (selection changed)
    deactivate ComponentLogic
    RazorUI-->>User: UI reflects new selection (Item A is selected/deselected)
Loading

Class Diagram for the new MultiSelectGeneric Component

classDiagram
    direction LR
    class SelectBase_List_TValue_ {
        <<BlazorComponent, generic>>
        #List~TValue~ CurrentValue
        +EventCallback~List~TValue~~ ValueChanged
        #OnClearValue() Task
        #OnValidate(bool? valid) void
    }
    class MultiSelectGeneric_TValue_ {
        <<BlazorComponent, generic>>
        +IEnumerable~SelectedItem~TValue~~ Items
        +RenderFragment~List~SelectedItem~TValue~~~ DisplayTemplate
        +RenderFragment~SelectedItem~TValue~~ ItemTemplate
        +bool ShowToolbar
        +bool ShowSearch
        +bool ShowCloseButton
        +int Max
        +int Min
        +string SelectAllText
        +string ReverseSelectText
        +string ClearText
        +Func~VirtualizeQueryOption, Task~QueryData~SelectedItem~TValue~~~~ OnQueryAsync
        +Func~IEnumerable~SelectedItem~TValue~~, Task~ OnSelectedItemsChanged
        -List~SelectedItem~TValue~~ SelectedItems
        +ToggleRow(SelectedItem~TValue~ item) Task
        +SelectAll() Task
        +InvertSelect() Task
        +Clear() Task
        +TriggerOnSearch(string searchText) Task
    }
    class SelectedItem_TValue_ {
        <<Data, generic>>
        +TValue Value
        +string Text
        +bool IsDisabled
    }
    MultiSelectGeneric_TValue_ --|> SelectBase_List_TValue_
    MultiSelectGeneric_TValue_ "1" o-- "0..*" SelectedItem_TValue_ : selectedItems
    MultiSelectGeneric_TValue_ ..> SelectedItem_TValue_ : uses_items_parameter

    class VirtualizeQueryOption {
      +int StartIndex
      +int Count
      +string SearchText
    }
    class QueryData_T_ {
        <<generic>>
        +IEnumerable~T~ Items
        +int TotalCount
    }
    MultiSelectGeneric_TValue_ ..> VirtualizeQueryOption : uses_for_OnQueryAsync
    MultiSelectGeneric_TValue_ ..> QueryData_T_ : uses_for_OnQueryAsync_return
Loading

Class Diagram: MultiSelects Page using MultiSelectGeneric

classDiagram
    class MultiSelectsPage {
        <<RazorComponent>>
        -FooItems: List~SelectedItem~Foo~~
        -_genericValue: List~Foo~
        #OnInitialized() void
    }
    class MultiSelectGeneric_Foo_ {
        <<BlazorComponent>>
        +IEnumerable~SelectedItem~Foo~~ Items
        +List~Foo~ Value
        +EventCallback~List~Foo~~ ValueChanged
    }
    MultiSelectsPage ..> MultiSelectGeneric_Foo_ : uses_and_configures
    note for MultiSelectsPage "_genericValue is bound to MultiSelectGeneric_Foo_.Value"

    class SelectedItem_Foo_ {
        <<Data>>
        +Foo Value
        +string Text
    }
    MultiSelectsPage ..> SelectedItem_Foo_ : initializes (FooItems)
    MultiSelectGeneric_Foo_ ..> SelectedItem_Foo_ : processes (Items)

    class Foo {
        +string Name
    }
    SelectedItem_Foo_ *-- Foo : wraps
Loading

File-Level Changes

Change Details Files
Add MultiSelectGeneric component implementation
  • Create Razor template for the dropdown, items, toolbar and search UI
  • Implement code-behind with selection logic, JS interop, virtualization and validation
  • Expose parameters for display templates, events and localization
src/BootstrapBlazor/Components/SelectGeneric/MultiSelectGeneric.razor
src/BootstrapBlazor/Components/SelectGeneric/MultiSelectGeneric.razor.cs
Integrate MultiSelectGeneric into samples
  • Insert a DemoBlock in the sample page to host the generic component
  • Define FooItems and _genericValue properties and initialize them in OnInitialized
  • Bind the new component to sample data with search and popover enabled
src/BootstrapBlazor.Server/Components/Samples/MultiSelects.razor
src/BootstrapBlazor.Server/Components/Samples/MultiSelects.razor.cs
Add localization entries for generic component
  • Extend English resource file with MultiSelectGeneric title and intro keys
  • Extend Chinese resource file with corresponding translations
src/BootstrapBlazor.Server/Locales/en-US.json
src/BootstrapBlazor.Server/Locales/zh-CN.json
Refactor base class attributes and remove legacy styles
  • Add [CascadingTypeParameter] attribute and update component summary in SelectGeneric.razor.cs
  • Delete obsolete SelectGeneric.razor.scss file
src/BootstrapBlazor/Components/SelectGeneric/SelectGeneric.razor.cs
src/BootstrapBlazor/Components/SelectGeneric/SelectGeneric.razor.scss

Assessment against linked issues

Issue Objective Addressed Explanation
#6102 Implement a new MultiSelectGeneric component in the BootstrapBlazor library.

Possibly linked issues


Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@bb-auto bb-auto bot added this to the v9.6.0 milestone May 27, 2025
@ArgoZhang ArgoZhang merged commit a7d575b into main May 27, 2025
2 of 3 checks passed
@ArgoZhang ArgoZhang deleted the feat-multi-select branch May 27, 2025 08:27
Copy link
Contributor

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey @ArgoZhang - I've reviewed your changes and they look great!

Here's what I looked at during the review
  • 🟡 General issues: 4 issues found
  • 🟢 Security: all looks good
  • 🟢 Testing: all looks good
  • 🟢 Complexity: all looks good
  • 🟢 Documentation: all looks good

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

[ExcludeFromCodeCoverage]
public partial class MultiSelectGeneric<TValue>
{
private List<SelectedItem<TValue>> SelectedItems { get; } = [];
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (bug_risk): SelectedItems is always initialized empty, which may not reflect the initial Value.

If the component uses @bind-Value, ensure SelectedItems is initialized from the initial Value to keep the UI and bound data consistent.


private List<SelectedItem<TValue>> GetVirtualItems() => [.. FilterBySearchText(GetRowsByItems())];

private async ValueTask<ItemsProviderResult<SelectedItem<TValue>>> LoadItems(ItemsProviderRequest request)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion: LoadItems does not handle exceptions from OnQueryAsync.

Add try/catch in LoadItems to handle exceptions from OnQueryAsync and ensure the Virtualize component remains stable.

@<DynamicElement OnClick="() => ToggleRow(item)" TriggerClick="@CheckCanTrigger(item)" class="@GetItemClassString(item)">
<div class="multi-select-item">
<div class="form-check">
<input class="form-check-input" type="checkbox" disabled="@CheckCanSelect(item)" checked="@GetCheckedString(item)" />
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (bug_risk): The checked attribute is set as a string, which may cause issues.

Blazor checkboxes require a boolean for the 'checked' attribute. Update GetCheckedState to return a boolean and bind it directly to 'checked'.

{
@DisplayTemplate(SelectedItems)
}
else
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion: Group dividers are rendered both before and after each group.

Rendering dividers both before and after each group can create duplicates. Consider rendering a divider only before each group except the first, or after each group except the last, to avoid unnecessary dividers.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat(MultiSelectGeneric): add MultiSelectGeneric component

2 participants