diff --git a/.ai-team/agents/beast/history.md b/.ai-team/agents/beast/history.md index b0ade346b..f414b06e8 100644 --- a/.ai-team/agents/beast/history.md +++ b/.ai-team/agents/beast/history.md @@ -94,3 +94,18 @@ - Structured for non-technical readers: bottom-line callout, business value lead, tables for data, minimal jargon - Sections: What is BWFC, Migration Scope, Component Coverage, Three-Layer Pipeline, Time & Cost Impact, Layer 1 Results, Page Readiness, Risk Reduction, What's Next +- **Issue #356 — ListView CRUD Events documentation:** + - Updated `docs/DataControls/ListView.md` with comprehensive CRUD events section covering all 16 lifecycle events. + - Events organized into 7 sub-sections: Insert, Update, Delete, Edit/Cancel, Sorting, Paging, Selection, and Lifecycle. + - Each event sub-section includes: EventArgs property reference table, Web Forms → Blazor tabbed comparison (using `pymdownx.tabbed`), and practical code examples. + - EventArgs types documented from source: `ListViewInsertEventArgs`, `ListViewInsertedEventArgs`, `ListViewUpdateEventArgs`, `ListViewUpdatedEventArgs`, `ListViewDeleteEventArgs`, `ListViewDeletedEventArgs`, `ListViewEditEventArgs`, `ListViewCancelEventArgs`, `ListViewSortEventArgs`, `ListViewPagePropertiesChangingEventArgs`, `ListViewSelectEventArgs`, `ListViewItemEventArgs`. + - Added `ListViewCancelMode` enum documentation (`CancelingEdit`, `CancelingInsert`). + - Updated Features Supported section with full event listing grouped by category. + - Updated Blazor Syntax block to show all 16 event parameters. + - Added complete CRUD example demonstrating insert/update/delete workflow with all events wired up. + - Added See Also section with cross-references to FormView, DetailsView, GridView, DataPager, and Microsoft Docs. + - Used admonitions (`!!! tip`, `!!! note`) for command routing, automatic EditIndex management, sort direction toggle, and paging via SetPageProperties. + - **Key file:** `docs/DataControls/ListView.md` (grew from ~285 lines to ~840 lines). + - **Pattern used:** Tabbed Web Forms → Blazor comparison (pymdownx.tabbed), property reference tables, grouped event sub-sections. Follows FormView CRUD events documentation pattern. + - **Branch:** `squad/356-listview-crud-events` + diff --git a/docs/DataControls/ListView.md b/docs/DataControls/ListView.md index deed7966d..21124c72b 100644 --- a/docs/DataControls/ListView.md +++ b/docs/DataControls/ListView.md @@ -2,7 +2,7 @@ The ListView component is meant to emulate the asp:ListView control in markup and is defined in the [System.Web.UI.WebControls.ListView class](https://docs.microsoft.com/en-us/dotnet/api/system.web.ui.webcontrols.listview?view=netframework-4.8) -[Usage Notes](#usage-notes) | [Web Forms Syntax](#web-forms-declarative-syntax) | [Blazor Syntax](#blazor-syntax) +[Usage Notes](#usage-notes) | [Web Forms Syntax](#web-forms-declarative-syntax) | [Blazor Syntax](#blazor-syntax) | [CRUD Events](#crud-events) ## Features supported in Blazor - Alternating Item Templates @@ -22,6 +22,15 @@ The ListView component is meant to emulate the asp:ListView control in markup an - InsertItemTemplate - EditIndex - InsertItemPosition + - **CRUD Events** — 16 lifecycle events for data operations: + - **Insert:** ItemInserting / ItemInserted + - **Update:** ItemUpdating / ItemUpdated + - **Delete:** ItemDeleting / ItemDeleted + - **Edit / Cancel:** ItemEditing / ItemCanceling + - **Sort:** Sorting / Sorted + - **Paging:** PagePropertiesChanging / PagePropertiesChanged + - **Selection:** SelectedIndexChanging / SelectedIndexChanged + - **Lifecycle:** OnLayoutCreated / ItemCreated / OnItemDataBound (DataBound override) ##### [Back to top](#listview) @@ -147,15 +156,28 @@ void ItemDataBound(ListViewItemEventArgs e) ## Blazor Syntax ```razor - + ItemDeleted="OnItemDeleted" + ItemInserting="OnItemInserting" + ItemInserted="OnItemInserted" + Sorting="OnSorting" + Sorted="OnSorted" + PagePropertiesChanging="OnPagePropertiesChanging" + PagePropertiesChanged="OnPagePropertiesChanged" + OnLayoutCreated="OnLayoutCreated" + SelectedIndexChanging="OnSelectedIndexChanging" + SelectedIndexChanged="OnSelectedIndexChanged" + ItemCreated="OnItemCreated" + OnItemDataBound="OnItemDataBound"> @Item.Name @@ -273,4 +295,702 @@ The `InsertItemTemplate` renders an insert row when `InsertItemPosition` is set ``` -##### [Back to top](#listview) \ No newline at end of file +##### [Back to top](#listview) + +## CRUD Events + +The ListView fires 16 lifecycle events that mirror the original Web Forms control's event model. These events use `EventCallback` parameters and follow the familiar *-ing / *-ed pattern: the *-ing event fires before the operation (and can cancel it), while the *-ed event fires after the operation completes. + +!!! tip "Command Routing" + All CRUD events are triggered through the `HandleCommand` method on the ListView reference. Call `listView.HandleCommand("edit", null, index)` to trigger the edit flow, `"delete"` for delete, `"update"` for update, `"insert"` for insert, `"cancel"` for cancel, `"sort"` for sort, and `"select"` for selection. + +### Event Reference + +#### Insert Events + +| Event | EventArgs | Description | +|-------|-----------|-------------| +| `ItemInserting` | `ListViewInsertEventArgs` | Fires before an item is inserted. Set `Cancel = true` to prevent insertion. | +| `ItemInserted` | `ListViewInsertedEventArgs` | Fires after the insert operation completes. | + +**`ListViewInsertEventArgs` properties:** + +| Property | Type | Description | +|----------|------|-------------| +| `Cancel` | `bool` | Set to `true` to cancel the insert operation. | +| `Item` | `object` | Gets or sets the item to be inserted. | + +**`ListViewInsertedEventArgs` properties:** + +| Property | Type | Description | +|----------|------|-------------| +| `AffectedRows` | `int` | The number of rows affected by the insert. | +| `Exception` | `Exception` | The exception raised during insert, if any. | +| `ExceptionHandled` | `bool` | Set to `true` to indicate the exception has been handled. | +| `KeepInsertedValues` | `bool` | Set to `true` to retain the values in the insert form after insertion. | + +**Web Forms → Blazor comparison:** + +=== "Web Forms" + + ```html + + ``` + ```csharp + protected void lv_ItemInserting(object sender, ListViewInsertEventArgs e) + { + // Validate before insert + if (string.IsNullOrEmpty(txtName.Text)) + { + e.Cancel = true; + } + } + + protected void lv_ItemInserted(object sender, ListViewInsertedEventArgs e) + { + if (e.Exception != null) + { + e.ExceptionHandled = true; + lblError.Text = "Insert failed."; + } + } + ``` + +=== "Blazor" + + ```razor + + ``` + ```csharp + @code { + void OnItemInserting(ListViewInsertEventArgs e) + { + if (string.IsNullOrEmpty(newName)) + { + e.Cancel = true; + } + } + + void OnItemInserted(ListViewInsertedEventArgs e) + { + if (e.Exception != null) + { + e.ExceptionHandled = true; + errorMessage = "Insert failed."; + } + } + } + ``` + +##### [Back to top](#listview) + +#### Update Events + +| Event | EventArgs | Description | +|-------|-----------|-------------| +| `ItemUpdating` | `ListViewUpdateEventArgs` | Fires before an item is updated. Set `Cancel = true` to prevent the update. | +| `ItemUpdated` | `ListViewUpdatedEventArgs` | Fires after the update operation completes. | + +**`ListViewUpdateEventArgs` properties:** + +| Property | Type | Description | +|----------|------|-------------| +| `ItemIndex` | `int` | The index of the item being updated. | +| `Cancel` | `bool` | Set to `true` to cancel the update operation. | + +**`ListViewUpdatedEventArgs` properties:** + +| Property | Type | Description | +|----------|------|-------------| +| `AffectedRows` | `int` | The number of rows affected by the update. | +| `Exception` | `Exception` | The exception raised during update, if any. | +| `ExceptionHandled` | `bool` | Set to `true` to indicate the exception has been handled. | +| `KeepInEditMode` | `bool` | Set to `true` to keep the row in edit mode after the update. | + +**Web Forms → Blazor comparison:** + +=== "Web Forms" + + ```html + + ``` + ```csharp + protected void lv_ItemUpdating(object sender, ListViewUpdateEventArgs e) + { + // e.ItemIndex identifies which item is being updated + } + + protected void lv_ItemUpdated(object sender, ListViewUpdatedEventArgs e) + { + if (e.Exception != null) + { + e.ExceptionHandled = true; + } + } + ``` + +=== "Blazor" + + ```razor + + ``` + ```csharp + @code { + void OnItemUpdating(ListViewUpdateEventArgs e) + { + var product = products[e.ItemIndex]; + product.Name = editName; + product.Price = editPrice; + } + + void OnItemUpdated(ListViewUpdatedEventArgs e) + { + if (e.Exception != null) + { + e.ExceptionHandled = true; + errorMessage = "Update failed."; + } + // Row exits edit mode automatically unless KeepInEditMode = true + } + } + ``` + +##### [Back to top](#listview) + +#### Delete Events + +| Event | EventArgs | Description | +|-------|-----------|-------------| +| `ItemDeleting` | `ListViewDeleteEventArgs` | Fires before an item is deleted. Set `Cancel = true` to prevent deletion. | +| `ItemDeleted` | `ListViewDeletedEventArgs` | Fires after the delete operation completes. | + +**`ListViewDeleteEventArgs` properties:** + +| Property | Type | Description | +|----------|------|-------------| +| `ItemIndex` | `int` | The index of the item being deleted. | +| `Cancel` | `bool` | Set to `true` to cancel the delete operation. | + +**`ListViewDeletedEventArgs` properties:** + +| Property | Type | Description | +|----------|------|-------------| +| `AffectedRows` | `int` | The number of rows affected by the delete. | +| `Exception` | `Exception` | The exception raised during delete, if any. | +| `ExceptionHandled` | `bool` | Set to `true` to indicate the exception has been handled. | + +**Web Forms → Blazor comparison:** + +=== "Web Forms" + + ```html + + ``` + ```csharp + protected void lv_ItemDeleting(object sender, ListViewDeleteEventArgs e) + { + // Confirm deletion; set e.Cancel = true to abort + } + + protected void lv_ItemDeleted(object sender, ListViewDeletedEventArgs e) + { + if (e.AffectedRows == 1) + { + // Refresh data + } + } + ``` + +=== "Blazor" + + ```razor + + ``` + ```csharp + @code { + void OnItemDeleting(ListViewDeleteEventArgs e) + { + products.RemoveAt(e.ItemIndex); + } + + void OnItemDeleted(ListViewDeletedEventArgs e) + { + statusMessage = $"Deleted. {e.AffectedRows} row(s) affected."; + } + } + ``` + +##### [Back to top](#listview) + +#### Edit and Cancel Events + +| Event | EventArgs | Description | +|-------|-----------|-------------| +| `ItemEditing` | `ListViewEditEventArgs` | Fires when an Edit command is requested. Set `NewEditIndex` to control which item enters edit mode. | +| `ItemCanceling` | `ListViewCancelEventArgs` | Fires when a Cancel command is requested. `CancelMode` indicates whether canceling an edit or an insert. | + +**`ListViewEditEventArgs` properties:** + +| Property | Type | Description | +|----------|------|-------------| +| `NewEditIndex` | `int` | The index of the item entering edit mode. | +| `Cancel` | `bool` | Set to `true` to cancel the edit operation. | + +**`ListViewCancelEventArgs` properties:** + +| Property | Type | Description | +|----------|------|-------------| +| `ItemIndex` | `int` | The index of the item containing the Cancel button. | +| `CancelMode` | `ListViewCancelMode` | Indicates whether canceling an edit (`CancelingEdit`) or an insert (`CancelingInsert`). | +| `Cancel` | `bool` | Set to `true` to prevent the cancel operation. | + +**Web Forms → Blazor comparison:** + +=== "Web Forms" + + ```html + + ``` + ```csharp + protected void lv_ItemEditing(object sender, ListViewEditEventArgs e) + { + lv.EditIndex = e.NewEditIndex; + BindData(); + } + + protected void lv_ItemCanceling(object sender, ListViewCancelEventArgs e) + { + lv.EditIndex = -1; + BindData(); + } + ``` + +=== "Blazor" + + ```razor + + ``` + ```csharp + @code { + private int editIndex = -1; + + void OnItemEditing(ListViewEditEventArgs e) + { + editIndex = e.NewEditIndex; + } + + void OnItemCanceling(ListViewCancelEventArgs e) + { + editIndex = -1; + // e.CancelMode tells you if canceling an edit or insert + } + } + ``` + +!!! note "Automatic EditIndex Management" + The ListView automatically sets `EditIndex` to the `NewEditIndex` from `ListViewEditEventArgs` after `ItemEditing` fires (unless cancelled), and resets `EditIndex` to `-1` after `ItemCanceling` fires. You can still set `EditIndex` manually in your handler for custom behavior. + +##### [Back to top](#listview) + +#### Sorting Events + +| Event | EventArgs | Description | +|-------|-----------|-------------| +| `Sorting` | `ListViewSortEventArgs` | Fires before a sort operation. Set `Cancel = true` to prevent sorting. | +| `Sorted` | `ListViewSortEventArgs` | Fires after the sort operation completes. | + +**`ListViewSortEventArgs` properties:** + +| Property | Type | Description | +|----------|------|-------------| +| `SortExpression` | `string` | The expression (typically a property name) to sort by. | +| `SortDirection` | `SortDirection` | The sort direction: `Ascending` or `Descending`. Automatically toggles. | +| `Cancel` | `bool` | Set to `true` to cancel the sort operation. | + +**Web Forms → Blazor comparison:** + +=== "Web Forms" + + ```html + + ``` + ```csharp + protected void lv_Sorting(object sender, ListViewSortEventArgs e) + { + // e.SortExpression, e.SortDirection available + } + ``` + +=== "Blazor" + + ```razor + + ``` + ```csharp + @code { + void OnSorting(ListViewSortEventArgs e) + { + // Sort direction toggles automatically between Ascending/Descending + // Sort your data source using e.SortExpression and e.SortDirection + } + + void OnSorted(ListViewSortEventArgs e) + { + statusMessage = $"Sorted by {e.SortExpression} ({e.SortDirection})"; + } + } + ``` + +!!! tip "Sort Direction Toggle" + The ListView automatically toggles `SortDirection` between `Ascending` and `Descending` when the same `SortExpression` is used consecutively, matching Web Forms behavior. + +##### [Back to top](#listview) + +#### Paging Events + +| Event | EventArgs | Description | +|-------|-----------|-------------| +| `PagePropertiesChanging` | `ListViewPagePropertiesChangingEventArgs` | Fires when page properties are about to change. | +| `PagePropertiesChanged` | `EventArgs` | Fires after the page properties have changed. | + +**`ListViewPagePropertiesChangingEventArgs` properties:** + +| Property | Type | Description | +|----------|------|-------------| +| `StartRowIndex` | `int` | The index of the first item on the new page. | +| `MaximumRows` | `int` | The maximum number of items to display per page. | + +!!! note "Paging via SetPageProperties" + Unlike the CRUD events which are triggered by `HandleCommand`, paging events fire when you call `listView.SetPageProperties(startRowIndex, maximumRows)`. This mirrors Web Forms' `DataPager` interaction pattern. + +**Web Forms → Blazor comparison:** + +=== "Web Forms" + + ```html + + ``` + ```csharp + protected void lv_PagePropertiesChanging(object sender, + PagePropertiesChangingEventArgs e) + { + // e.StartRowIndex, e.MaximumRows available + } + ``` + +=== "Blazor" + + ```razor + + ``` + ```csharp + @code { + void OnPagePropertiesChanging(ListViewPagePropertiesChangingEventArgs e) + { + // e.StartRowIndex and e.MaximumRows reflect the new page + } + + void OnPagePropertiesChanged() + { + // Page properties have been applied — refresh UI if needed + } + } + ``` + +##### [Back to top](#listview) + +#### Selection Events + +| Event | EventArgs | Description | +|-------|-----------|-------------| +| `SelectedIndexChanging` | `ListViewSelectEventArgs` | Fires before the selected index changes. Set `Cancel = true` to prevent the change. | +| `SelectedIndexChanged` | `EventArgs` | Fires after the selected index has changed. | + +**`ListViewSelectEventArgs` properties:** + +| Property | Type | Description | +|----------|------|-------------| +| `NewSelectedIndex` | `int` | The index of the newly selected item. | +| `Cancel` | `bool` | Set to `true` to cancel the selection change. | + +**Web Forms → Blazor comparison:** + +=== "Web Forms" + + ```html + + ``` + ```csharp + protected void lv_SelectedIndexChanging(object sender, ListViewSelectEventArgs e) + { + // e.NewSelectedIndex identifies the item being selected + } + ``` + +=== "Blazor" + + ```razor + + ``` + ```csharp + @code { + private int selectedIndex; + + void OnSelectedIndexChanging(ListViewSelectEventArgs e) + { + // Validate selection; set e.Cancel = true to prevent + } + + void OnSelectedIndexChanged() + { + statusMessage = $"Selected item index: {selectedIndex}"; + } + } + ``` + +##### [Back to top](#listview) + +#### Lifecycle Events + +| Event | EventArgs | Description | +|-------|-----------|-------------| +| `OnLayoutCreated` | `EventArgs` | Fires after the layout template has been created. | +| `ItemCreated` | (none) | Fires when an item template is instantiated (after first render). | +| `OnItemDataBound` | `ListViewItemEventArgs` | Fires when an item is data-bound (existing event, listed here for completeness). | + +These lifecycle events fire during the component's rendering lifecycle rather than in response to user commands. + +**Web Forms → Blazor comparison:** + +=== "Web Forms" + + ```html + + ``` + +=== "Blazor" + + ```razor + + ``` + ```csharp + @code { + void HandleLayoutCreated() + { + // Layout template has been rendered + } + + void HandleItemCreated() + { + // Item template instantiated — runs once after first render + } + + void HandleItemDataBound(ListViewItemEventArgs e) + { + if (e.Item.ItemType == ListViewItemType.DataItem) + { + var dataItem = (ListViewDataItem)e.Item; + // Access dataItem.DataItem, dataItem.DisplayIndex, etc. + } + } + } + ``` + +##### [Back to top](#listview) + +### Complete CRUD Example + +This example demonstrates a full insert/update/delete workflow with all relevant events wired up: + +```razor + + + + + + + + + + + @context +
NamePriceActions
+
+ + + @Item.Name + @Item.Price.ToString("C") + + + + + + + + + + + + + + + + + + + + + + + + + + +

No products found. Use the insert row to add one.

+
+
+ +@if (!string.IsNullOrEmpty(statusMessage)) +{ +
@statusMessage
+} + +@code { + private ListView listView; + private List products = new(); + private int editIndex = -1; + private string editName, newName, statusMessage; + private decimal editPrice, newPrice; + + void OnItemEditing(ListViewEditEventArgs e) + { + var product = products.First(p => p.Id == e.NewEditIndex); + editName = product.Name; + editPrice = product.Price; + editIndex = e.NewEditIndex; + } + + void OnItemUpdating(ListViewUpdateEventArgs e) + { + var product = products.First(p => p.Id == e.ItemIndex); + product.Name = editName; + product.Price = editPrice; + } + + void OnItemUpdated(ListViewUpdatedEventArgs e) + { + statusMessage = "Product updated successfully."; + } + + void OnItemCanceling(ListViewCancelEventArgs e) + { + editIndex = -1; + } + + void OnItemDeleting(ListViewDeleteEventArgs e) + { + products.RemoveAll(p => p.Id == e.ItemIndex); + } + + void OnItemDeleted(ListViewDeletedEventArgs e) + { + statusMessage = "Product deleted."; + } + + void OnItemInserting(ListViewInsertEventArgs e) + { + if (string.IsNullOrWhiteSpace(newName)) + { + e.Cancel = true; + statusMessage = "Name is required."; + return; + } + products.Add(new Product + { + Id = products.Any() ? products.Max(p => p.Id) + 1 : 1, + Name = newName, + Price = newPrice + }); + newName = ""; + newPrice = 0; + } + + void OnItemInserted(ListViewInsertedEventArgs e) + { + statusMessage = "Product inserted."; + } + + public class Product + { + public int Id { get; set; } + public string Name { get; set; } = ""; + public decimal Price { get; set; } + } +} +``` + +## See Also + +- [FormView](FormView.md) — Similar CRUD event model for single-record views +- [DetailsView](DetailsView.md) — Single-record display with edit support +- [GridView](GridView.md) — Tabular data display +- [DataPager](DataPager.md) — Paging companion for ListView +- [Microsoft Docs: ListView Class](https://docs.microsoft.com/en-us/dotnet/api/system.web.ui.webcontrols.listview?view=netframework-4.8) \ No newline at end of file diff --git a/src/BlazorWebFormsComponents.Test/ListView/ListViewCrudEventTests.razor b/src/BlazorWebFormsComponents.Test/ListView/ListViewCrudEventTests.razor new file mode 100644 index 000000000..1a1b2b335 --- /dev/null +++ b/src/BlazorWebFormsComponents.Test/ListView/ListViewCrudEventTests.razor @@ -0,0 +1,953 @@ +@using BlazorWebFormsComponents.Enums +@using SortDirection = BlazorWebFormsComponents.Enums.SortDirection + +@* Comprehensive tests for all 16 ListView CRUD events (#356). + Covers: event firing, EventArgs data, Cancel property, event ordering, + HandleCommand routing, and edge cases. *@ + +@code { + + ListView theListView; + + // ── Event tracking ── + List _eventOrder = new(); + ListViewEditEventArgs _editingArgs; + ListViewCancelEventArgs _cancelingArgs; + ListViewDeleteEventArgs _deletingArgs; + ListViewDeletedEventArgs _deletedArgs; + ListViewInsertEventArgs _insertingArgs; + ListViewInsertedEventArgs _insertedArgs; + ListViewUpdateEventArgs _updatingArgs; + ListViewUpdatedEventArgs _updatedArgs; + ListViewCommandEventArgs _commandArgs; + ListViewSortEventArgs _sortingArgs; + ListViewSortEventArgs _sortedArgs; + ListViewSelectEventArgs _selectingArgs; + ListViewPagePropertiesChangingEventArgs _pageChangingArgs; + bool _pageChangedFired; + bool _selectedChangedFired; + bool _layoutCreatedFired; + bool _itemCreatedFired; + int _dataBoundCount; + int _itemDataBoundCount; + + // ═════════════════════════════════════════════════════════════════ + // INSERT: ItemInserting / ItemInserted + // ═════════════════════════════════════════════════════════════════ + + [Fact] + public void HandleCommand_Insert_FiresItemInsertingBeforeItemInserted() + { + var cut = Render(@ + @Item.Name + ); + + cut.InvokeAsync(() => theListView.HandleCommand("Insert", null, 0)); + + _eventOrder.Count.ShouldBe(2); + _eventOrder[0].ShouldBe("ItemInserting"); + _eventOrder[1].ShouldBe("ItemInserted"); + } + + [Fact] + public void HandleCommand_Insert_Cancellation_PreventsItemInserted() + { + var cut = Render(@ + @Item.Name + ); + + cut.InvokeAsync(() => theListView.HandleCommand("Insert", null, 0)); + + _insertingArgs.ShouldNotBeNull(); + _insertingArgs.Cancel.ShouldBeTrue(); + _insertedArgs.ShouldBeNull(); + } + + [Fact] + public void HandleCommand_Insert_InsertedArgs_HasAffectedRows() + { + var cut = Render(@ + @Item.Name + ); + + cut.InvokeAsync(() => theListView.HandleCommand("Insert", null, 0)); + + _insertedArgs.ShouldNotBeNull(); + _insertedArgs.AffectedRows.ShouldBe(1); + _insertedArgs.Exception.ShouldBeNull(); + _insertedArgs.ExceptionHandled.ShouldBeFalse(); + _insertedArgs.KeepInsertedValues.ShouldBeFalse(); + } + + // ═════════════════════════════════════════════════════════════════ + // UPDATE: ItemUpdating / ItemUpdated + // ═════════════════════════════════════════════════════════════════ + + [Fact] + public void HandleCommand_Update_FiresItemUpdatingBeforeItemUpdated() + { + var cut = Render(@ + @Item.Name + ); + + cut.InvokeAsync(() => theListView.HandleCommand("Update", null, 1)); + + _eventOrder.Count.ShouldBe(2); + _eventOrder[0].ShouldBe("ItemUpdating"); + _eventOrder[1].ShouldBe("ItemUpdated"); + } + + [Fact] + public void HandleCommand_Update_Cancellation_PreventsItemUpdated() + { + var cut = Render(@ + @Item.Name + ); + + cut.InvokeAsync(() => theListView.HandleCommand("Update", null, 1)); + + _updatingArgs.ShouldNotBeNull(); + _updatingArgs.Cancel.ShouldBeTrue(); + _updatedArgs.ShouldBeNull(); + } + + [Fact] + public void HandleCommand_Update_UpdatedArgs_HasAffectedRows() + { + var cut = Render(@ + @Item.Name + ); + + cut.InvokeAsync(() => theListView.HandleCommand("Update", null, 0)); + + _updatedArgs.ShouldNotBeNull(); + _updatedArgs.AffectedRows.ShouldBe(1); + _updatedArgs.Exception.ShouldBeNull(); + _updatedArgs.ExceptionHandled.ShouldBeFalse(); + } + + [Fact] + public void HandleCommand_Update_KeepInEditMode_PreservesEditIndex() + { + int editIdx = 2; + var cut = Render(@ + @Item.Name + + ); + + cut.InvokeAsync(() => theListView.HandleCommand("Update", null, 2)); + + _updatedArgs.ShouldNotBeNull(); + _updatedArgs.KeepInEditMode.ShouldBeTrue(); + // EditIndex should remain at 2 since KeepInEditMode was set + theListView.EditIndex.ShouldNotBe(-1); + } + + [Fact] + public void HandleCommand_Update_DefaultKeepInEditMode_ClearsEditIndex() + { + int editIdx = 1; + var cut = Render(@ + @Item.Name + + ); + + cut.InvokeAsync(() => theListView.HandleCommand("Update", null, 1)); + + theListView.EditIndex.ShouldBe(-1); + } + + [Fact] + public void HandleCommand_Update_EventArgsContainItemIndex() + { + var cut = Render(@ + @Item.Name + ); + + cut.InvokeAsync(() => theListView.HandleCommand("Update", null, 3)); + + _updatingArgs.ShouldNotBeNull(); + _updatingArgs.ItemIndex.ShouldBe(3); + } + + // ═════════════════════════════════════════════════════════════════ + // DELETE: ItemDeleting / ItemDeleted + // ═════════════════════════════════════════════════════════════════ + + [Fact] + public void HandleCommand_Delete_FiresItemDeletingBeforeItemDeleted() + { + var cut = Render(@ + @Item.Name + ); + + cut.InvokeAsync(() => theListView.HandleCommand("Delete", null, 0)); + + _eventOrder.Count.ShouldBe(2); + _eventOrder[0].ShouldBe("ItemDeleting"); + _eventOrder[1].ShouldBe("ItemDeleted"); + } + + [Fact] + public void HandleCommand_Delete_Cancellation_PreventsItemDeleted() + { + var cut = Render(@ + @Item.Name + ); + + cut.InvokeAsync(() => theListView.HandleCommand("Delete", null, 0)); + + _deletingArgs.ShouldNotBeNull(); + _deletingArgs.Cancel.ShouldBeTrue(); + _deletedArgs.ShouldBeNull(); + } + + [Fact] + public void HandleCommand_Delete_EventArgsContainItemIndex() + { + var cut = Render(@ + @Item.Name + ); + + cut.InvokeAsync(() => theListView.HandleCommand("Delete", null, 4)); + + _deletingArgs.ShouldNotBeNull(); + _deletingArgs.ItemIndex.ShouldBe(4); + } + + [Fact] + public void HandleCommand_Delete_DeletedArgs_HasAffectedRowsAndNoException() + { + var cut = Render(@ + @Item.Name + ); + + cut.InvokeAsync(() => theListView.HandleCommand("Delete", null, 0)); + + _deletedArgs.ShouldNotBeNull(); + _deletedArgs.AffectedRows.ShouldBe(1); + _deletedArgs.Exception.ShouldBeNull(); + _deletedArgs.ExceptionHandled.ShouldBeFalse(); + } + + // ═════════════════════════════════════════════════════════════════ + // EDIT: ItemEditing + // ═════════════════════════════════════════════════════════════════ + + [Fact] + public void HandleCommand_Edit_EventArgsContainNewEditIndex() + { + var cut = Render(@ + @Item.Name + ); + + cut.InvokeAsync(() => theListView.HandleCommand("Edit", null, 3)); + + _editingArgs.ShouldNotBeNull(); + _editingArgs.NewEditIndex.ShouldBe(3); + _editingArgs.Cancel.ShouldBeFalse(); + theListView.EditIndex.ShouldBe(3); + } + + [Fact] + public void HandleCommand_Edit_ModifiedNewEditIndex_UsesModifiedValue() + { + var cut = Render(@ + @Item.Name + + ); + + cut.InvokeAsync(() => theListView.HandleCommand("Edit", null, 1)); + + // Handler modified NewEditIndex to 4 + theListView.EditIndex.ShouldBe(4); + } + + // ═════════════════════════════════════════════════════════════════ + // CANCEL: ItemCanceling + // ═════════════════════════════════════════════════════════════════ + + [Fact] + public void HandleCommand_Cancel_InEditMode_SetsCancelModeToEdit() + { + int editIdx = 1; + var cut = Render(@ + @Item.Name + + ); + + cut.InvokeAsync(() => theListView.HandleCommand("Cancel", null, 1)); + + _cancelingArgs.ShouldNotBeNull(); + _cancelingArgs.CancelMode.ShouldBe(ListViewCancelMode.CancelingEdit); + _cancelingArgs.ItemIndex.ShouldBe(1); + theListView.EditIndex.ShouldBe(-1); + } + + [Fact] + public void HandleCommand_Cancel_InInsertMode_SetsCancelModeToInsert() + { + InsertItemPosition pos = InsertItemPosition.FirstItem; + var cut = Render(@ + @Item.Name +
Insert
+
); + + cut.InvokeAsync(() => theListView.HandleCommand("Cancel", null, 0)); + + _cancelingArgs.ShouldNotBeNull(); + _cancelingArgs.CancelMode.ShouldBe(ListViewCancelMode.CancelingInsert); + } + + [Fact] + public void HandleCommand_Cancel_Cancellation_PreventsEditIndexReset() + { + int editIdx = 2; + var cut = Render(@ + @Item.Name + + ); + + cut.InvokeAsync(() => theListView.HandleCommand("Cancel", null, 2)); + + _cancelingArgs.ShouldNotBeNull(); + _cancelingArgs.Cancel.ShouldBeTrue(); + // EditIndex should remain since the cancel was vetoed + theListView.EditIndex.ShouldNotBe(-1); + } + + // ═════════════════════════════════════════════════════════════════ + // SORTING: Sorting / Sorted + // ═════════════════════════════════════════════════════════════════ + + [Fact] + public void HandleCommand_Sort_FiresSortingBeforeSorted() + { + var cut = Render(@ + @Item.Name + ); + + cut.InvokeAsync(() => theListView.HandleCommand("Sort", "Name", 0)); + + _eventOrder.Count.ShouldBe(2); + _eventOrder[0].ShouldBe("Sorting"); + _eventOrder[1].ShouldBe("Sorted"); + } + + [Fact] + public void HandleCommand_Sort_Cancellation_PreventsSortedAndPropertyUpdate() + { + var cut = Render(@ + @Item.Name + ); + + cut.InvokeAsync(() => theListView.HandleCommand("Sort", "Name", 0)); + + _sortingArgs.ShouldNotBeNull(); + _sortingArgs.Cancel.ShouldBeTrue(); + _sortedArgs.ShouldBeNull(); + theListView.SortExpression.ShouldBeNull(); + } + + [Fact] + public void HandleCommand_Sort_NewExpression_DefaultsToAscending() + { + SortDirection dir = SortDirection.Ascending; + var cut = Render(@ + @Item.Name + ); + + // Sort by a DIFFERENT expression + cut.InvokeAsync(() => theListView.HandleCommand("Sort", "Price", 0)); + + _sortingArgs.ShouldNotBeNull(); + _sortingArgs.SortExpression.ShouldBe("Price"); + _sortingArgs.SortDirection.ShouldBe(SortDirection.Ascending); + theListView.SortExpression.ShouldBe("Price"); + } + + [Fact] + public void HandleCommand_Sort_SameExpression_TogglesDirection() + { + SortDirection dir = SortDirection.Ascending; + var cut = Render(@ + @Item.Name + ); + + cut.InvokeAsync(() => theListView.HandleCommand("Sort", "Name", 0)); + + _sortingArgs.SortDirection.ShouldBe(SortDirection.Descending); + theListView.SortDirection.ShouldBe(SortDirection.Descending); + } + + [Fact] + public void HandleCommand_Sort_SortedArgs_MatchSortingArgs() + { + var cut = Render(@ + @Item.Name + ); + + cut.InvokeAsync(() => theListView.HandleCommand("Sort", "Name", 0)); + + // Both events receive the same args instance + _sortingArgs.ShouldNotBeNull(); + _sortedArgs.ShouldNotBeNull(); + _sortedArgs.SortExpression.ShouldBe(_sortingArgs.SortExpression); + _sortedArgs.SortDirection.ShouldBe(_sortingArgs.SortDirection); + } + + // ═════════════════════════════════════════════════════════════════ + // PAGING: PagePropertiesChanging / PagePropertiesChanged + // ═════════════════════════════════════════════════════════════════ + + [Fact] + public void SetPageProperties_FiresChangingBeforeChanged() + { + var cut = Render(@ + @Item.Name + ); + + cut.InvokeAsync(() => theListView.SetPageProperties(10, 5)); + + _eventOrder.Count.ShouldBe(2); + _eventOrder[0].ShouldBe("PagePropertiesChanging"); + _eventOrder[1].ShouldBe("PagePropertiesChanged"); + } + + [Fact] + public void SetPageProperties_EventArgsContainCorrectValues() + { + var cut = Render(@ + @Item.Name + ); + + cut.InvokeAsync(() => theListView.SetPageProperties(20, 10)); + + _pageChangingArgs.ShouldNotBeNull(); + _pageChangingArgs.StartRowIndex.ShouldBe(20); + _pageChangingArgs.MaximumRows.ShouldBe(10); + theListView.StartRowIndex.ShouldBe(20); + theListView.MaximumRows.ShouldBe(10); + } + + [Fact] + public void SetPageProperties_ZeroValues_Accepted() + { + var cut = Render(@ + @Item.Name + ); + + cut.InvokeAsync(() => theListView.SetPageProperties(0, 0)); + + _pageChangingArgs.ShouldNotBeNull(); + _pageChangingArgs.StartRowIndex.ShouldBe(0); + _pageChangingArgs.MaximumRows.ShouldBe(0); + _pageChangedFired.ShouldBeTrue(); + } + + // ═════════════════════════════════════════════════════════════════ + // SELECTION: SelectedIndexChanging / SelectedIndexChanged + // ═════════════════════════════════════════════════════════════════ + + [Fact] + public void HandleCommand_Select_FiresChangingBeforeChanged() + { + var cut = Render(@ + @Item.Name + ); + + cut.InvokeAsync(() => theListView.HandleCommand("Select", null, 1)); + + _eventOrder.Count.ShouldBe(2); + _eventOrder[0].ShouldBe("SelectedIndexChanging"); + _eventOrder[1].ShouldBe("SelectedIndexChanged"); + } + + [Fact] + public void HandleCommand_Select_Cancellation_PreventsSelectedIndexChanged() + { + var cut = Render(@ + @Item.Name + ); + + cut.InvokeAsync(() => theListView.HandleCommand("Select", null, 2)); + + _selectingArgs.ShouldNotBeNull(); + _selectingArgs.Cancel.ShouldBeTrue(); + _selectedChangedFired.ShouldBeFalse(); + theListView.SelectedIndex.ShouldBe(0); + } + + [Fact] + public void HandleCommand_Select_ModifiedIndex_UsesModifiedNewSelectedIndex() + { + var cut = Render(@ + @Item.Name + ); + + cut.InvokeAsync(() => theListView.HandleCommand("Select", null, 1)); + + // Handler set NewSelectedIndex to 99 + theListView.SelectedIndex.ShouldBe(99); + _selectedChangedFired.ShouldBeTrue(); + } + + [Fact] + public void HandleCommand_Select_EventArgsContainRequestedIndex() + { + var cut = Render(@ + @Item.Name + ); + + cut.InvokeAsync(() => theListView.HandleCommand("Select", null, 3)); + + _selectingArgs.ShouldNotBeNull(); + _selectingArgs.NewSelectedIndex.ShouldBe(3); + } + + // ═════════════════════════════════════════════════════════════════ + // LAYOUT CREATED + // ═════════════════════════════════════════════════════════════════ + + [Fact] + public void LayoutCreated_FiresWhenItemsExist() + { + var cut = Render(@ + @Item.Name + ); + + _layoutCreatedFired.ShouldBeTrue(); + } + + [Fact] + public void LayoutCreated_DoesNotFireWhenItemsEmpty() + { + Widget[] emptyItems = Array.Empty(); + var cut = Render(@ + @Item.Name +
No data
+
); + + _layoutCreatedFired.ShouldBeFalse(); + } + + [Fact] + public void LayoutCreated_FiresWithCustomLayoutTemplate() + { + var cut = Render(@ + @Item.Name + +
@itemPlaceholder
+
+
); + + _layoutCreatedFired.ShouldBeTrue(); + cut.FindAll("div.custom-layout").Count.ShouldBe(1); + } + + // ═════════════════════════════════════════════════════════════════ + // ITEM CREATED + // ═════════════════════════════════════════════════════════════════ + + [Fact] + public async Task ItemCreated_FiresOnFirstRender() + { + var cut = Render(@ + @Item.Name + ); + + // ItemCreated fires in OnAfterRenderAsync(firstRender: true) + // Trigger a render cycle to ensure the event fires + await cut.InvokeAsync(() => { }); + + _itemCreatedFired.ShouldBeTrue(); + } + + // ═════════════════════════════════════════════════════════════════ + // DATA BOUND (override) + // ═════════════════════════════════════════════════════════════════ + + [Fact] + public void DataBound_FiresAfterAllItemsRendered() + { + var cut = Render(@ + @Item.Name + ); + + _dataBoundCount.ShouldBeGreaterThan(0); + _itemDataBoundCount.ShouldBeGreaterThanOrEqualTo(Widget.SimpleWidgetList.Length); + } + + [Fact] + public void DataBound_ItemDataBound_FiresPerItem() + { + var cut = Render(@ + @Item.Name + ); + + // bUnit may trigger multiple render cycles; ensure at least one fire per item + _itemDataBoundCount.ShouldBeGreaterThanOrEqualTo(Widget.SimpleWidgetList.Length); + } + + // ═════════════════════════════════════════════════════════════════ + // COMMAND ROUTING: HandleCommand dispatches to correct event + // ═════════════════════════════════════════════════════════════════ + + [Fact] + public void HandleCommand_UnknownCommand_FiresItemCommandWithArgs() + { + var cut = Render(@ + @Item.Name + ); + + cut.InvokeAsync(() => theListView.HandleCommand("Approve", "request-42", 0)); + + _commandArgs.ShouldNotBeNull(); + _commandArgs.CommandName.ShouldBe("Approve"); + _commandArgs.CommandArgument.ShouldBe("request-42"); + } + + [Fact] + public void HandleCommand_CaseInsensitive_RoutesCorrectly() + { + var cut = Render(@ + @Item.Name + ); + + cut.InvokeAsync(() => theListView.HandleCommand("EDIT", null, 0)); + + _editingArgs.ShouldNotBeNull(); + _editingArgs.NewEditIndex.ShouldBe(0); + } + + [Fact] + public void HandleCommand_Sort_PassesCommandArgumentAsSortExpression() + { + var cut = Render(@ + @Item.Name + ); + + cut.InvokeAsync(() => theListView.HandleCommand("Sort", "Price", 0)); + + _sortingArgs.ShouldNotBeNull(); + _sortingArgs.SortExpression.ShouldBe("Price"); + } + + [Fact] + public void HandleCommand_Sort_NullArgument_UsesEmptyString() + { + var cut = Render(@ + @Item.Name + ); + + cut.InvokeAsync(() => theListView.HandleCommand("Sort", null, 0)); + + _sortingArgs.ShouldNotBeNull(); + _sortingArgs.SortExpression.ShouldBe(string.Empty); + } + + // ═════════════════════════════════════════════════════════════════ + // EDGE CASES + // ═════════════════════════════════════════════════════════════════ + + [Fact] + public void HandleCommand_Delete_ThenInsert_BothEventPairsFire() + { + var cut = Render(@ + @Item.Name + ); + + cut.InvokeAsync(() => theListView.HandleCommand("Delete", null, 0)); + _deletingArgs.ShouldNotBeNull(); + _deletedArgs.ShouldNotBeNull(); + + cut.InvokeAsync(() => theListView.HandleCommand("Insert", null, 0)); + _insertingArgs.ShouldNotBeNull(); + _insertedArgs.ShouldNotBeNull(); + } + + [Fact] + public void HandleCommand_Edit_ThenUpdate_FullCrudCycle() + { + var cut = Render(@ + @Item.Name + + ); + + // Edit item 1 + cut.InvokeAsync(() => theListView.HandleCommand("Edit", null, 1)); + theListView.EditIndex.ShouldBe(1); + + // Update item 1 + cut.InvokeAsync(() => theListView.HandleCommand("Update", null, 1)); + theListView.EditIndex.ShouldBe(-1); + _updatedArgs.ShouldNotBeNull(); + } + + [Fact] + public void HandleCommand_Edit_ThenCancel_FullCrudCycle() + { + var cut = Render(@ + @Item.Name + + ); + + // Edit item 2 + cut.InvokeAsync(() => theListView.HandleCommand("Edit", null, 2)); + theListView.EditIndex.ShouldBe(2); + + // Cancel + cut.InvokeAsync(() => theListView.HandleCommand("Cancel", null, 2)); + theListView.EditIndex.ShouldBe(-1); + _cancelingArgs.CancelMode.ShouldBe(ListViewCancelMode.CancelingEdit); + } + + // ═════════════════════════════════════════════════════════════════ + // Event Handlers + // ═════════════════════════════════════════════════════════════════ + + // Order-tracking handlers + void OnInsertingTrackOrder(ListViewInsertEventArgs args) { _insertingArgs = args; _eventOrder.Add("ItemInserting"); } + void OnInsertedTrackOrder(ListViewInsertedEventArgs args) { _insertedArgs = args; _eventOrder.Add("ItemInserted"); } + void OnUpdatingTrackOrder(ListViewUpdateEventArgs args) { _updatingArgs = args; _eventOrder.Add("ItemUpdating"); } + void OnUpdatedTrackOrder(ListViewUpdatedEventArgs args) { _updatedArgs = args; _eventOrder.Add("ItemUpdated"); } + void OnDeletingTrackOrder(ListViewDeleteEventArgs args) { _deletingArgs = args; _eventOrder.Add("ItemDeleting"); } + void OnDeletedTrackOrder(ListViewDeletedEventArgs args) { _deletedArgs = args; _eventOrder.Add("ItemDeleted"); } + void OnSortingTrackOrder(ListViewSortEventArgs args) { _sortingArgs = args; _eventOrder.Add("Sorting"); } + void OnSortedTrackOrder(ListViewSortEventArgs args) { _sortedArgs = args; _eventOrder.Add("Sorted"); } + void OnSelectingTrackOrder(ListViewSelectEventArgs args) { _selectingArgs = args; _eventOrder.Add("SelectedIndexChanging"); } + void OnSelectedTrackOrder(EventArgs args) { _selectedChangedFired = true; _eventOrder.Add("SelectedIndexChanged"); } + void OnPageChangingTrackOrder(ListViewPagePropertiesChangingEventArgs args) { _pageChangingArgs = args; _eventOrder.Add("PagePropertiesChanging"); } + void OnPageChangedTrackOrder(EventArgs args) { _pageChangedFired = true; _eventOrder.Add("PagePropertiesChanged"); } + + // Cancellation handlers + void OnInsertingCancel(ListViewInsertEventArgs args) { _insertingArgs = args; args.Cancel = true; } + void OnUpdatingCancel(ListViewUpdateEventArgs args) { _updatingArgs = args; args.Cancel = true; } + void OnDeletingCancel(ListViewDeleteEventArgs args) { _deletingArgs = args; args.Cancel = true; } + void OnCancelingCancel(ListViewCancelEventArgs args) { _cancelingArgs = args; args.Cancel = true; } + void OnSortingCancel(ListViewSortEventArgs args) { _sortingArgs = args; args.Cancel = true; } + void OnSelectingCancel(ListViewSelectEventArgs args) { _selectingArgs = args; args.Cancel = true; } + + // Simple capture handlers + void OnEditing(ListViewEditEventArgs args) => _editingArgs = args; + void OnCanceling(ListViewCancelEventArgs args) => _cancelingArgs = args; + void OnDeleting(ListViewDeleteEventArgs args) => _deletingArgs = args; + void OnDeleted(ListViewDeletedEventArgs args) => _deletedArgs = args; + void OnInserting(ListViewInsertEventArgs args) => _insertingArgs = args; + void OnInserted(ListViewInsertedEventArgs args) => _insertedArgs = args; + void OnUpdating(ListViewUpdateEventArgs args) => _updatingArgs = args; + void OnUpdated(ListViewUpdatedEventArgs args) => _updatedArgs = args; + void OnCommand(ListViewCommandEventArgs args) => _commandArgs = args; + void OnSorting(ListViewSortEventArgs args) => _sortingArgs = args; + void OnSorted(ListViewSortEventArgs args) => _sortedArgs = args; + void OnSelecting(ListViewSelectEventArgs args) => _selectingArgs = args; + void OnSelectedChanged(EventArgs args) => _selectedChangedFired = true; + void OnPageChanging(ListViewPagePropertiesChangingEventArgs args) => _pageChangingArgs = args; + void OnPageChanged(EventArgs args) => _pageChangedFired = true; + void OnLayoutCreated(EventArgs args) => _layoutCreatedFired = true; + void OnItemCreated() => _itemCreatedFired = true; + void OnDataBound(EventArgs args) => _dataBoundCount++; + void OnItemDataBound(ListViewItemEventArgs args) => _itemDataBoundCount++; + + // Modifier handlers + void OnEditingModifyIndex(ListViewEditEventArgs args) { _editingArgs = args; args.NewEditIndex = 4; } + void OnUpdatedKeepEditMode(ListViewUpdatedEventArgs args) { _updatedArgs = args; args.KeepInEditMode = true; } + void OnSelectingModifyIndex(ListViewSelectEventArgs args) { _selectingArgs = args; args.NewSelectedIndex = 99; } +} diff --git a/src/BlazorWebFormsComponents/ListViewDeleteEventArgs.cs b/src/BlazorWebFormsComponents/ListViewDeleteEventArgs.cs index c2ecee066..5488aa36a 100644 --- a/src/BlazorWebFormsComponents/ListViewDeleteEventArgs.cs +++ b/src/BlazorWebFormsComponents/ListViewDeleteEventArgs.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Specialized; namespace BlazorWebFormsComponents { @@ -10,6 +11,8 @@ public class ListViewDeleteEventArgs : EventArgs public ListViewDeleteEventArgs(int itemIndex) { ItemIndex = itemIndex; + Keys = new OrderedDictionary(); + Values = new OrderedDictionary(); } /// @@ -21,5 +24,15 @@ public ListViewDeleteEventArgs(int itemIndex) /// Gets or sets a value indicating whether the event should be cancelled. /// public bool Cancel { get; set; } + + /// + /// Gets a dictionary of field name/value pairs that represent the key of the item to delete. + /// + public IOrderedDictionary Keys { get; } + + /// + /// Gets a dictionary of the non-key field name/value pairs in the item to delete. + /// + public IOrderedDictionary Values { get; } } } diff --git a/src/BlazorWebFormsComponents/ListViewInsertEventArgs.cs b/src/BlazorWebFormsComponents/ListViewInsertEventArgs.cs index 6255c3009..99ee49479 100644 --- a/src/BlazorWebFormsComponents/ListViewInsertEventArgs.cs +++ b/src/BlazorWebFormsComponents/ListViewInsertEventArgs.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Specialized; namespace BlazorWebFormsComponents { @@ -9,6 +10,7 @@ public class ListViewInsertEventArgs : EventArgs { public ListViewInsertEventArgs() { + Values = new OrderedDictionary(); } /// @@ -20,5 +22,10 @@ public ListViewInsertEventArgs() /// Gets the item to be inserted. /// public object Item { get; set; } + + /// + /// Gets the values for the record to insert. + /// + public IOrderedDictionary Values { get; } } } diff --git a/src/BlazorWebFormsComponents/ListViewPagePropertiesChangingEventArgs.cs b/src/BlazorWebFormsComponents/ListViewPagePropertiesChangingEventArgs.cs index 7ff269598..58069f908 100644 --- a/src/BlazorWebFormsComponents/ListViewPagePropertiesChangingEventArgs.cs +++ b/src/BlazorWebFormsComponents/ListViewPagePropertiesChangingEventArgs.cs @@ -22,5 +22,10 @@ public ListViewPagePropertiesChangingEventArgs(int startRowIndex, int maximumRow /// Gets the maximum number of items to display on each page. /// public int MaximumRows { get; } + + /// + /// Gets or sets the total number of rows in the underlying data source. + /// + public int TotalRowCount { get; set; } } } diff --git a/src/BlazorWebFormsComponents/ListViewUpdateEventArgs.cs b/src/BlazorWebFormsComponents/ListViewUpdateEventArgs.cs index 29aecbd49..b766e1841 100644 --- a/src/BlazorWebFormsComponents/ListViewUpdateEventArgs.cs +++ b/src/BlazorWebFormsComponents/ListViewUpdateEventArgs.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Specialized; namespace BlazorWebFormsComponents { @@ -10,6 +11,9 @@ public class ListViewUpdateEventArgs : EventArgs public ListViewUpdateEventArgs(int itemIndex) { ItemIndex = itemIndex; + Keys = new OrderedDictionary(); + OldValues = new OrderedDictionary(); + NewValues = new OrderedDictionary(); } /// @@ -21,5 +25,20 @@ public ListViewUpdateEventArgs(int itemIndex) /// Gets or sets a value indicating whether the event should be cancelled. /// public bool Cancel { get; set; } + + /// + /// Gets a dictionary of field name/value pairs that represent the key of the item to update. + /// + public IOrderedDictionary Keys { get; } + + /// + /// Gets a dictionary that contains the original values of the item to update. + /// + public IOrderedDictionary OldValues { get; } + + /// + /// Gets a dictionary that contains the revised values of the item to update. + /// + public IOrderedDictionary NewValues { get; } } }