# BasicList
## Description
BitBasicList renders items into a scrolling region, one RowTemplate per item, and stays agnostic of the row's layout and of selection. Items come from a collection or from an ItemsProvider called for a region at a time; Virtualize keeps only the visible rows in the DOM, while LoadMore pages through the set by a button or, with AutoLoad, by scrolling.
## Parameters
| Name | Type | Default Value | Description |
| :--- | :--- | :------------ | :---------- |
| AutoLoad | `bool` | false | Loads the next page as soon as the end of the loaded items scrolls into view, turning the LoadMore button into an infinite scrolling list. Only effective while LoadMore is enabled. |
| AutoLoadThreshold | `int` | 0 | How many pixels before the end of the loaded items the next page starts loading in AutoLoad mode, measured to the visible end of whichever box scrolls the list: the list itself, a pane around it or the page. |
| Classes | `BitBasicListClassStyles?` | null | Custom CSS classes for different parts of the list. |
| EmptyContent | `RenderFragment?` | null | The content rendered in place of the rows while the list holds no items. |
| FitHeight | `bool` | false | Sets the height of the list to fit its content. |
| FitSize | `bool` | false | Sets the width and height of the list to fit its content. |
| FitWidth | `bool` | false | Sets the width of the list to fit its content. |
| FooterTemplate | `RenderFragment?` | null | The content rendered below the items of the list, inside its scrolling region. |
| FullHeight | `bool` | false | Sets the height of the list to 100%. |
| FullSize | `bool` | false | Sets the width and height of the list to 100%. |
| FullWidth | `bool` | false | Sets the width of the list to 100%. |
| HeaderTemplate | `RenderFragment?` | null | The content rendered above the items of the list, inside its scrolling region. |
| Horizontal | `bool` | false | Lays the items of the list out in a row and scrolls it sideways instead of down. Ignored while Virtualize is enabled, since virtualization is vertical only. |
| Items | `ICollection<TItem>?` | null | The list of items to render. |
| ItemSize | `float` | 50 | Size of each item in pixels, used by the Virtualize mode to calculate the scroll range and the number of rows to render. |
| ItemsProvider | `BitBasicListItemsProvider<TItem>?` | null | The function providing items to the list. It always takes priority over Items, and is called for a region at a time in Virtualize mode, for a page at a time in LoadMore mode, and once for the whole set otherwise. |
| ItemsProviderDelay | `int` | 100 | The number of milliseconds the list waits before calling the ItemsProvider in Virtualize mode, which debounces the requests a scroll issues. A value of 0 turns the debouncing off. Never applies to the pages of the LoadMore mode. |
| Loading | `bool` | false | Shows the loading content in place of the rows. The list also raises this state on its own while it fetches the first page or a plain ItemsProvider set. |
| LoadingLabel | `string?` | Loading... | The text next to the spinner of the default loading content and of the default LoadMore button while a page loads; announced to screen readers as the list starts loading (a LoadingTemplate is announced with its own text). |
| LoadingTemplate | `RenderFragment?` | null | The template rendered while the list is loading its items. |
| LoadMore | `bool` | false | Enables the LoadMore mode for the list. |
| LoadMoreSize | `int` | 20 | The number of items to be loaded and rendered after the LoadMore button is clicked. |
| LoadMoreTemplate | `RenderFragment<bool>?` | null | Replaces the default LoadMore button. Its context is whether a page is being loaded at that moment. |
| LoadMoreText | `string?` | Load more | The custom text of the default LoadMore button. |
| OnLoadingChange | `EventCallback<bool>` | | The callback that is invoked when the list starts and stops loading its items. |
| OnLoadMore | `EventCallback<int>` | | The callback that is invoked after each page of the LoadMore mode has been appended, with the number of items the list holds at that point. |
| OverscanCount | `int` | 3 | A value that determines how many additional items will be rendered before and after the visible region in Virtualize mode. |
| Role | `string?` | list | The role of the element holding the rows (which also carries the AriaLabel). Without a RowTemplate each row is rendered as a listitem while the role is list. Left off while the EmptyContent shows; set it to null to leave it off altogether. Wherever the element has no role to name, the AriaLabel names the root, as a group, instead. |
| RowTemplate | `RenderFragment<TItem>?` | null | The template to render each row. Without it each item is rendered as its own text. |
| Styles | `BitBasicListClassStyles?` | null | Custom CSS styles for different parts of the list. |
| StickyFooter | `bool` | false | Keeps the footer in view at the end of the list while its rows scroll past it (at the inline end of a horizontal list). It pins the wrapper the FooterTemplate is rendered into, which has no background of its own: paint one in the template or through Styles.Footer. |
| StickyHeader | `bool` | false | Keeps the header in view at the start of the list while its rows scroll past it (at the inline start of a horizontal list). It pins the wrapper the HeaderTemplate is rendered into, which has no background of its own: paint one in the template or through Styles.Header. |
| Virtualize | `bool` | false | Renders only the rows around the visible region. Vertical only. |
| VirtualizePlaceholder | `RenderFragment<PlaceholderContext>?` | null | The template of the rows whose items the ItemsProvider has not delivered yet in Virtualize mode. |
| AriaLabel | `string?` | null | Gets or sets the accessible label for the component, used by assistive technologies. |
| Class | `string?` | null | Gets or sets the CSS class name(s) to apply to the rendered element. |
| Dir | `BitDir?` | null | Gets or sets the text directionality for the component's content. |
| Disabled | `bool` | false | Gets or sets a value indicating whether the component is disabled and cannot respond to user interaction. |
| ForceAnimation | `bool` | false | Gets or sets a value indicating whether the component's animations play at their full duration even when reduced motion is requested. |
| HtmlAttributes | `Dictionary<string, object>` | new Dictionary<string, object>() | Captures additional HTML attributes to be applied to the rendered element, in addition to the component's parameters. An attribute written on the component wins over an entry of the same name. |
| Id | `string?` | null | Gets or sets the unique identifier for the component's root element. |
| Style | `string?` | null | Gets or sets the CSS style string to apply to the rendered element. |
| TabIndex | `string?` | null | Gets or sets the tab order index for the component when navigating with the keyboard. |
| Visibility | `BitVisibility` | BitVisibility.Visible | Gets or sets the visibility state (visible, hidden, or collapsed) of the component. |
## Public Members
| Name | Type | Default Value | Description |
| :--- | :--- | :------------ | :---------- |
| LoadMoreAsync | `Task` | | Loads the next page of the LoadMore mode, the same way clicking the LoadMore button does. |
| RefreshDataAsync | `Task` | | Reloads the items of the list: a LoadMore list starts over from its first page, a virtualized provider list re-requests the region it shows, a plain provider list fetches the whole set again, and a plain list picks up the current contents of its Items collection. |
| ScrollToEndAsync | `Task` | | Scrolls the list to its end. Pass true to animate the scrolling. |
| ScrollToIndexAsync | `Task` | | Scrolls the list so that the item at the given index sits at its start edge. Pass true as the second argument to animate the scrolling. |
| ScrollToOffsetAsync | `Task` | | Scrolls the list to an absolute offset in pixels on its scrolling axis. Pass true as the second argument to animate the scrolling. |
| ScrollToStartAsync | `Task` | | Scrolls the list to its start. Pass true to animate the scrolling. |
| UniqueId | `Guid` | Guid.NewGuid() | Gets the readonly unique identifier for the component's root element, assigned when the component instance is constructed. |
| RootElement | `ElementReference` | | Gets the reference to the root HTML element associated with this component. |
## Enums
### BitVisibility Enum
| Name | Value | Description |
| :--- | :--- | :---------- |
| Visible | 0 | The content of the component is visible. |
| Hidden | 1 | The content of the component is hidden, but the space it takes on the page remains (visibility:hidden). |
| Collapsed | 2 | The component is hidden (display:none). |
### BitDir Enum
| Name | Value | Description |
| :--- | :--- | :---------- |
| Ltr | 0 | Ltr (left to right) is to be used for languages that are written from the left to the right (like English). |
| Rtl | 1 | Rtl (right to left) is to be used for languages that are written from the right to the left (like Arabic). |
| Auto | 2 | Auto lets the user agent decide. It uses a basic algorithm as it parses the characters inside the element until it finds a character with a strong directionality, then applies that directionality to the whole element. |
## Sub Classes
### BitBasicListClassStyles Properties
| Name | Type | Default Value | Description |
| :--- | :--- | :------------ | :---------- |
| Root | `string?` | null | Custom CSS classes/styles for the root element of the list. |
| Header | `string?` | null | Custom CSS classes/styles for the header container of the list. |
| Items | `string?` | null | Custom CSS classes/styles for the element holding the rows, where a grid or a gap between them is laid out. |
| Footer | `string?` | null | Custom CSS classes/styles for the footer container of the list. |
| LoadingContent | `string?` | null | Custom CSS classes/styles for the loading container of the list. |
| LoadMoreButton | `string?` | null | Custom CSS classes/styles for the LoadMore button of the list. |
| LoadMoreText | `string?` | null | Custom CSS classes/styles for the LoadMore text of the list. |
## CSS Variables
Public CSS custom properties read off the component's root. Set one on `:root` to restyle every instance, on any ancestor to restyle the ones inside it, or on the `Style` of one instance to restyle it and every instance nested inside it, which inherits the value too - a parameter changes one alone, as does the real CSS property on its `Style` / `Styles` when that property does not inherit (`opacity`); an inherited one (`color`, `font-family`) reaches the nested instances that do not set it themselves.
| Name | Default Value | Description |
| :--- | :------------ | :---------- |
| `--bit-BasicList-height` | 31.25rem (spacing(62.5)) | Height of a vertical list. A Horizontal list is as tall as its rows. |
| `--bit-BasicList-focus-color` | --bit-clr-pri-focus | Focus ring of the list (when it is focusable) and of its LoadMore element. While it is unset, the focus ring is the library's own --bit-shd-focus-ring, so replacing that token re-shapes it too. |
| `--bit-BasicList-disabled-color` | --bit-clr-fg-dis | Text of the default LoadMore button when Disabled is true. |
| `--bit-BasicList-loading-padding` | spacing(2) | Padding of the loading content. |
| `--bit-BasicList-loading-color` | --bit-clr-fg-sec | Text of the default loading content. |
| `--bit-BasicList-spinner-color` | --bit-clr-pri | Moving arc of the default spinners (loading content and busy LoadMore button). |
| `--bit-BasicList-spinner-size` | --bit-siz-icon-md | Width and height of the default spinners. |
| `--bit-BasicList-load-more-padding` | spacing(2) | Padding of the default LoadMore button. |
| `--bit-BasicList-load-more-color` | --bit-clr-pri | Text of the default LoadMore button at rest. |
| `--bit-BasicList-load-more-hover-color` | --bit-clr-pri-hover | Text of the default LoadMore button on hover (pointer devices only). |
| `--bit-BasicList-load-more-background` | transparent | Background of the default LoadMore button at rest, while busy and when disabled. |
| `--bit-BasicList-load-more-hover-background` | --bit-clr-bg-pri-hover | Background of the default LoadMore button on hover (pointer devices only). |
| `--bit-BasicList-load-more-active-background` | The hover background, else --bit-clr-bg-pri-active | Background of the default LoadMore button while pressed. |
| `--bit-BasicList-load-more-font-size` | inherit | Text size of the default LoadMore button. |
| `--bit-BasicList-load-more-font-weight` | --bit-tpg-fw-semibold | Text weight of the default LoadMore button. |
## Examples
\n**Basic**:
```razor
```
```csharp
private readonly List fewPeople = [.. Enumerable.Range(0, 100).Select(i => new Person
{
Id = i + 1,
FirstName = $"Person {i + 1}",
LastName = $"Person Family {i + 1}",
Job = $"Programmer {i + 1}"
})];
private readonly List fruits = ["Apple", "Apricot", "Banana", "Cherry", "Fig",
"Grape", "Lemon", "Mango", "Orange", "Peach"];
public class Person
{
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string Job { get; set; }
}
```
\n**Empty content**:
```razor
Nobody to show here yet.
```
```csharp
private readonly List emptyPeople = [];
public class Person
{
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string Job { get; set; }
}
```
\n**Header & Footer**:
```razor
People
@fewPeople.Count
- end of the list -
```
```csharp
private readonly List fewPeople = [.. Enumerable.Range(0, 100).Select(i => new Person
{
Id = i + 1,
FirstName = $"Person {i + 1}",
LastName = $"Person Family {i + 1}",
Job = $"Programmer {i + 1}"
})];
public class Person
{
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string Job { get; set; }
}
```
\n**Loading**:
```razor
Custom loading template
```
```csharp
private bool isLoading;
private readonly List fewPeople = [.. Enumerable.Range(0, 100).Select(i => new Person
{
Id = i + 1,
FirstName = $"Person {i + 1}",
LastName = $"Person Family {i + 1}",
Job = $"Programmer {i + 1}"
})];
public class Person
{
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string Job { get; set; }
}
```
\n**Virtualization**:
```razor
```
```csharp
private readonly List lotsOfPeople = [.. Enumerable.Range(0, 8000).Select(i => new Person
{
Id = i + 1,
FirstName = $"Person {i + 1}",
LastName = $"Person Family {i + 1}",
Job = $"Programmer {i + 1}"
})];
public class Person
{
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string Job { get; set; }
}
```
\n**ItemsProvider**:
```razor
```
```csharp
[Inject] private HttpClient HttpClient { get; set; } = default!;
[Inject] private NavigationManager NavManager { get; set; } = default!;
private BitBasicListItemsProvider productsProvider;
protected override void OnInitialized()
{
productsProvider = async req =>
{
try
{
var query = new Dictionary()
{
{ "$top", req.Count},
{ "$skip", req.StartIndex }
};
var url = NavManager.GetUriWithQueryParameters("api/Products/GetProducts", query);
var data = await HttpClient.GetFromJsonAsync(url, AppJsonContext.Default.PagedResultProductDto);
return BitBasicListItemsProviderResult.From(data!.Items, data!.TotalCount);
}
catch
{
return BitBasicListItemsProviderResult.Empty();
}
};
base.OnInitialized();
}
public class ProductDto
{
public int Id { get; set; }
public string? Name { get; set; }
public decimal Price { get; set; }
public int CategoryId { get; set; }
}
[JsonSerializable(typeof(PagedResult))]
public partial class AppJsonContext : JsonSerializerContext { }
```
\n**Grouped ItemsProvider**:
```razor
@* Every row and the placeholder are 32px tall on purpose: the virtualization sizes its scroll
region by a single item size, so rows of differing heights keep it correcting that size on
every render and never let the list settle. *@
@if (catOrProd.IsProduct)
{
}
else
{
}
```
```csharp
[Inject] private HttpClient HttpClient { get; set; } = default!;
[Inject] private NavigationManager NavManager { get; set; } = default!;
private BitBasicListItemsProvider categoriesAndProductsProvider;
protected override void OnInitialized()
{
categoriesAndProductsProvider = async req =>
{
try
{
var query = new Dictionary()
{
{ "$top", req.Count},
{ "$skip", req.StartIndex }
};
var url = NavManager.GetUriWithQueryParameters("api/Products/GetCategoriesAndProducts", query);
var data = await HttpClient.GetFromJsonAsync(url, AppJsonContext.Default.PagedResultCategoryOrProductDto);
return BitBasicListItemsProviderResult.From(data!.Items, data!.TotalCount);
}
catch
{
return BitBasicListItemsProviderResult.Empty();
}
};
base.OnInitialized();
}
public class CategoryOrProductDto
{
public int? ProductId { get; set; }
public int? CategoryId { get; set; }
public bool IsProduct => ProductId is not null;
public string? Name { get; set; }
public decimal? Price { get; set; }
}
[JsonSerializable(typeof(PagedResult))]
public partial class AppJsonContext : JsonSerializerContext { }
```
\n**LoadMore**:
```razor
@if (isLoadingMore)
{
Loading...
}
else
{
Load more people
}
fewPeople = [.. Enumerable.Range(0, 100).Select(i => new Person
{
Id = i + 1,
FirstName = $"Person {i + 1}",
LastName = $"Person Family {i + 1}",
Job = $"Programmer {i + 1}"
})];
private readonly List lotsOfPeople = [.. Enumerable.Range(0, 8000).Select(i => new Person
{
Id = i + 1,
FirstName = $"Person {i + 1}",
LastName = $"Person Family {i + 1}",
Job = $"Programmer {i + 1}"
})];
private BitBasicListItemsProvider loadMoreProvider = default!;
private BitBasicListItemsProvider loadMoreVirtualizeProvider = default!;
protected override void OnInitialized()
{
loadMoreProvider = async req =>
{
await Task.Delay(1000);
return BitBasicListItemsProviderResult.From([.. fewPeople.Skip(req.StartIndex).Take(req.Count)], fewPeople.Count);
};
loadMoreVirtualizeProvider = async req =>
{
await Task.Delay(500);
return BitBasicListItemsProviderResult.From([.. lotsOfPeople.Skip(req.StartIndex).Take(req.Count)], lotsOfPeople.Count);
};
base.OnInitialized();
}
public class Person
{
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string Job { get; set; }
}
```
\n**AutoLoad**:
```razor
Loading more people...
```
```csharp
private readonly List lotsOfPeople = [.. Enumerable.Range(0, 8000).Select(i => new Person
{
Id = i + 1,
FirstName = $"Person {i + 1}",
LastName = $"Person Family {i + 1}",
Job = $"Programmer {i + 1}"
})];
private BitBasicListItemsProvider autoLoadProvider = default!;
protected override void OnInitialized()
{
autoLoadProvider = async req =>
{
await Task.Delay(700);
return BitBasicListItemsProviderResult.From([.. lotsOfPeople.Skip(req.StartIndex).Take(req.Count)], lotsOfPeople.Count);
};
base.OnInitialized();
}
public class Person
{
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string Job { get; set; }
}
```
\n**Layout**:
```razor
```
```csharp
private readonly List fewPeople = [.. Enumerable.Range(0, 100).Select(i => new Person
{
Id = i + 1,
FirstName = $"Person {i + 1}",
LastName = $"Person Family {i + 1}",
Job = $"Programmer {i + 1}"
})];
public class Person
{
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string Job { get; set; }
}
```
\n**Scrolling & refreshing**:
```razor
Scroll to start
Scroll to end
Scroll to #51
Add a person
```
```csharp
private bool scrollToEndPending;
private BitBasicList? listRef;
private readonly List mutablePeople = [.. Enumerable.Range(0, 100).Select(i => new Person
{
Id = i + 1,
FirstName = $"Person {i + 1}",
LastName = $"Person Family {i + 1}",
Job = $"Programmer {i + 1}"
})];
private async Task AddPerson()
{
var id = mutablePeople.Count + 1;
mutablePeople.Add(new Person
{
Id = id,
FirstName = $"Person {id}",
LastName = $"Person Family {id}",
Job = $"Programmer {id}"
});
// The collection instance itself did not change, so the list is told to pick up its new contents.
if (listRef is not null)
{
await listRef.RefreshDataAsync();
// The new row is only scrollable to once it has been rendered, so the scrolling waits for that render.
scrollToEndPending = true;
}
}
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (scrollToEndPending && listRef is not null)
{
scrollToEndPending = false;
await listRef.ScrollToEndAsync(true);
}
await base.OnAfterRenderAsync(firstRender);
}
public class Person
{
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string Job { get; set; }
}
```
\n**Accessibility**:
```razor
```
```csharp
private readonly List teamMembers = [.. Enumerable.Range(0, 25).Select(i => new Person
{
Id = i + 1,
FirstName = $"Member {i + 1}",
LastName = $"Member Family {i + 1}",
Job = $"Engineer {i + 1}"
})];
private BitBasicListItemsProvider accessibleProvider = default!;
protected override void OnInitialized()
{
accessibleProvider = async req =>
{
await Task.Delay(800);
return BitBasicListItemsProviderResult.From([.. teamMembers.Skip(req.StartIndex).Take(req.Count)], teamMembers.Count);
};
base.OnInitialized();
}
public class Person
{
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string Job { get; set; }
}
```
\n**Cascading parameters**:
```razor
```
```csharp
private readonly BitBasicListParams[] basicListParams =
[
new()
{
LoadMore = true,
LoadMoreSize = 5,
LoadMoreText = "Show more",
Style = "height: 200px; border: 1px solid var(--bit-clr-brd-sec); border-radius: 4px;",
}
];
private readonly List fewPeople = [.. Enumerable.Range(0, 100).Select(i => new Person
{
Id = i + 1,
FirstName = $"Person {i + 1}",
LastName = $"Person Family {i + 1}",
Job = $"Programmer {i + 1}"
})];
private readonly List fruits = ["Apple", "Apricot", "Banana", "Cherry", "Fig",
"Grape", "Lemon", "Mango", "Orange", "Peach"];
public class Person
{
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string Job { get; set; }
}
```
\n**Style & Class**:
```razor
People
```
```csharp
private readonly List lotsOfPeople = [.. Enumerable.Range(0, 8000).Select(i => new Person
{
Id = i + 1,
FirstName = $"Person {i + 1}",
LastName = $"Person Family {i + 1}",
Job = $"Programmer {i + 1}"
})];
private readonly List fewPeople = [.. Enumerable.Range(0, 100).Select(i => new Person
{
Id = i + 1,
FirstName = $"Person {i + 1}",
LastName = $"Person Family {i + 1}",
Job = $"Programmer {i + 1}"
})];
public class Person
{
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string Job { get; set; }
}
```
`BitBasicListDemo.razor.scss`:
```scss
.custom-class {
.list-item {
gap: 0.5rem;
color: white;
display: flex;
padding: 1rem;
margin: 0.5rem;
flex-wrap: wrap;
border-radius: 0.25rem;
background-color: tomato;
}
}
```
\n**RTL**:
```razor
```
```csharp
private readonly List fewPeopleRtl = [.. Enumerable.Range(0, 100).Select(i => new Person
{
Id = i + 1,
FirstName = $"شخص {i + 1}",
LastName = $"نام خانواگی شخص {i + 1}",
Job = $"برنامه نویس {i + 1}"
})];
public class Person
{
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string Job { get; set; }
}
```
Name: @person.FirstName
Name: @person.FirstName
Name: @person.FirstName
Name: @person.FirstName
Name: @person.FirstName
Id: @person.Id
Full Name: @person.FirstName @person.LastName
Job: @person.Job
Id: @product.Id
Name: @product.Name
Price: @product.Price
Id: Loading...
Name: Loading...
Price: Loading...
Loading...
Name: @person.FirstName
Full Name: @person.FirstName @person.LastName
Full Name: @person.FirstName @person.LastName
Loaded so far (second list): @loadedCount
```
```csharp
private int loadedCount;
private readonly List
Full Name: @person.FirstName @person.LastName
@person.FirstName
@person.Job
@person.FirstName
@person.Job
@person.Id - @person.FirstName
@person.FirstName, @person.Job
@person.FirstName
@person.Job
Class:
Id: @person.Id
Full Name: @person.FirstName
Job: @person.Job
Styles:
Name: @person.FirstName
CSS variables:
Name: @person.FirstName
شناسه: @person.Id
نام کامل: @person.FirstName @person.LastName
شغل: @person.Job