# Searchable Grouped List

# SearchableGroupedList

# Description

SearchableGroupedList is a versatile collection component designed to render a list of items that can be grouped and searched. It provides built-in search functionality to filter items based on input terms and organizes them by their respective groups. Use this component when you need to display a categorized list that supports live searching over a finite set of items. It is part of the Collections group in the UI toolkit.

# Usage

Create a SearchableGroupedList by providing an array or an observable list of items that implement the ISearchableGroupedItem interface, along with a function to generate a header component for each group. The component includes a search box that filters items in real time, and you can further customize its behavior, such as adding custom content before or after the search box or setting a custom “no results” message.

Below is an example of how to instantiate the component:

using System;
using Tesserae;
using static Tesserae.UI;
using static H5.Core.dom;

public class MySearchableGroupedListSample : IComponent
{
    public HTMLElement Render()
    {
        // Sample items must implement ISearchableGroupedItem.
        var items = new SearchableGroupedListItem[]
        {
            new SearchableGroupedListItem("Apple", "Fruits"),
            new SearchableGroupedListItem("Broccoli", "Vegetables"),
            new SearchableGroupedListItem("Banana", "Fruits"),
            new SearchableGroupedListItem("Carrot", "Vegetables")
        };

        // Create the list with a header generator for groups.
        var groupedList = SearchableGroupedList(items, groupName => 
            HorizontalSeparator(TextBlock(groupName).Primary().SemiBold()).Left())
         // Optional: add a custom message if no results match the search.
         .WithNoResultsMessage(() =>
            BackgroundArea(Card(TextBlock("No Results Found").Padding(16.px()))).WidthStretch().HeightStretch().MinHeight(100.px()));
        
        return groupedList.Render();
    }
}

public class SearchableGroupedListItem : ISearchableGroupedItem
{
    private readonly string _value;
    private readonly IComponent _component;

    public SearchableGroupedListItem(string value, string group)
    {
        _value = value;
        _component = Card(TextBlock(value).NonSelectable());
        Group = group;
    }

    // Searches both on the item's value and the group name.
    public bool IsMatch(string searchTerm) =>
        _value.ToLower().Contains(searchTerm.ToLower()) || Group.ToLower().Contains(searchTerm.ToLower());

    public string Group { get; }

    public IComponent Render() => _component;
}

# Methods

  • WithNoResultsMessage(Func emptyListMessageGenerator)
    Allows you to specify a custom component to display when no items match the search criteria.
    Parameter:
    • emptyListMessageGenerator: A delegate that returns an IComponent for the no-results display.

  • WithGroupOrdering(IComparer groupComparer)
    Sets a custom comparer to order the groups.
    Parameter:
    • groupComparer: An IComparer to control the order of groups.

  • SearchBox(Action sb)
    Provides access to the internal SearchBox to perform further customization, such as changing its placeholder text or styles.
    Parameter:
    • sb: An action to configure the SearchBox.

  • CaptureSearchBox(out SearchBox sb)
    Retrieves the reference to the internal SearchBox for additional modifications.
    Parameter:
    • sb: An output parameter that captures the SearchBox instance.

  • BeforeSearchBox(params IComponent[] beforeComponents)
    Adds additional UI components before the search box, such as buttons or labels.
    Parameter:
    • beforeComponents: An array of IComponent to display before the search box.

  • AfterSearchBox(params IComponent[] afterComponents)
    Adds additional UI components after the search box for enhanced controls or information.
    Parameter:
    • afterComponents: An array of IComponent to display after the search box.

# Properties

  • ObservableList Items
    A list that holds the current set of rendered components (both headers and item components) as they are filtered and grouped.

  • HTMLElement StylingContainer
    Returns the root HTML element used for styling purposes. This container holds the inner elements of the component.

  • bool PropagateToStackItemParent
    Indicates whether styling should propagate to the parent stack container of the component. Always returns true.

# Samples

# Basic Grouped Searchable List

The following sample demonstrates how to create a searchable grouped list with a custom no-results message. In this example, items are grouped by category, and the search functionality filters items based on the provided search term.

using System;
using Tesserae;
using static Tesserae.UI;
using static H5.Core.dom;

public class BasicSearchableGroupedListSample : IComponent
{
    public HTMLElement Render()
    {
        var items = new SearchableGroupedListItem[]
        {
            new SearchableGroupedListItem("Alpha", "Group A"),
            new SearchableGroupedListItem("Beta", "Group B"),
            new SearchableGroupedListItem("Gamma", "Group A"),
            new SearchableGroupedListItem("Delta", "Group C"),
        };

        var searchableGroupedList = SearchableGroupedList(items,
            groupName => HorizontalSeparator(TextBlock(groupName).Primary().SemiBold()).Left())
            .WithNoResultsMessage(() =>
                BackgroundArea(Card(TextBlock("No Results").Padding(16.px()))).WidthStretch().HeightStretch().MinHeight(100.px()));

        return searchableGroupedList.Render();
    }
}

public class SearchableGroupedListItem : ISearchableGroupedItem
{
    private readonly string _value;
    private readonly IComponent _component;

    public SearchableGroupedListItem(string value, string group)
    {
        _value = value;
        _component = Card(TextBlock(value).NonSelectable());
        Group = group;
    }

    public bool IsMatch(string searchTerm) =>
        _value.ToLower().Contains(searchTerm.ToLower()) || Group.ToLower().Contains(searchTerm.ToLower());

    public string Group { get; }

    public IComponent Render() => _component;
}

# Advanced Customization with Additional Buttons

This sample shows how to add extra commands before and after the search box to extend the searchable grouped list functionality:

using System;
using Tesserae;
using static Tesserae.UI;
using static H5.Core.dom;

public class AdvancedSearchableGroupedListSample : IComponent
{
    public HTMLElement Render()
    {
        var items = new SearchableGroupedListItem[]
        {
            new SearchableGroupedListItem("Item One", "Category 1"),
            new SearchableGroupedListItem("Item Two", "Category 1"),
            new SearchableGroupedListItem("Item Three", "Category 2"),
            new SearchableGroupedListItem("Item Four", "Category 2")
        };

        var searchableGroupedList = SearchableGroupedList(items,
            groupName => HorizontalSeparator(TextBlock(groupName).Primary().SemiBold()).Left())
            .BeforeSearchBox(Button("Before Button").Link())
            .AfterSearchBox(Button("After Button").Primary())
            .WithNoResultsMessage(() =>
                BackgroundArea(Card(TextBlock("Nothing found").Padding(16.px()))).WidthStretch().HeightStretch().MinHeight(100.px()));

        return searchableGroupedList.Render();
    }
}

public class SearchableGroupedListItem : ISearchableGroupedItem
{
    private readonly string _value;
    private readonly IComponent _component;

    public SearchableGroupedListItem(string value, string group)
    {
        _value = value;
        _component = Card(TextBlock(value).NonSelectable());
        Group = group;
    }

    public bool IsMatch(string searchTerm) =>
        _value.ToLower().Contains(searchTerm.ToLower()) || Group.ToLower().Contains(searchTerm.ToLower());

    public string Group { get; }

    public IComponent Render() => _component;
}

# See also