From fd72cdfdae590e2b5635a2de6bc02eba8c427e46 Mon Sep 17 00:00:00 2001 From: exyi Date: Tue, 11 Aug 2020 14:36:24 +0000 Subject: [PATCH 1/2] GridView.SortChange has precedence over default sorting This allows users to override the default sorting logic. Also, GridView is not senzitive to the real value in DataSource, the sort command is determined by the DataSource result type. --- src/DotVVM.Framework/Controls/GridView.cs | 52 ++++++++++++----------- 1 file changed, 28 insertions(+), 24 deletions(-) diff --git a/src/DotVVM.Framework/Controls/GridView.cs b/src/DotVVM.Framework/Controls/GridView.cs index 4193e004f4..bc910faa28 100644 --- a/src/DotVVM.Framework/Controls/GridView.cs +++ b/src/DotVVM.Framework/Controls/GridView.cs @@ -166,30 +166,14 @@ private void DataBind(IDotvvmRequestContext context) var dataSourceBinding = GetDataSourceBinding(); var dataSource = DataSource; - var sortCommand = - dataSource is ISortableGridViewDataSet sortableSet && sortableSet.SortingOptions is ISortingOptions sortOptions ? - expr => { - if (sortOptions.SortExpression == expr) - { - sortOptions.SortDescending ^= true; - } - else - { - sortOptions.SortExpression = expr; - sortOptions.SortDescending = false; - } - (sortableSet as IPageableGridViewDataSet)?.GoToFirstPage(); - } - : - SortChanged; - - // WORKAROUND: DataSource is null => don't throw exception - if (sortCommand == null && dataSource == null) - { - sortCommand = s => { - throw new DotvvmControlException(this, "Cannot sort when DataSource is null."); - }; - } + var sortCommand = SortChanged; + sortCommand ??= + typeof(ISortableGridViewDataSet).IsAssignableFrom((GetBinding(DataSourceProperty) as IStaticValueBinding)?.ResultType) + ? (Action)SortChangedCommand + : null; + + sortCommand ??= s => + throw new DotvvmControlException(this, "Cannot sort when DataSource is null."); CreateHeaderRow(context, sortCommand); @@ -227,6 +211,26 @@ private void DataBind(IDotvvmRequestContext context) } } + protected virtual void SortChangedCommand(string? expr) + { + var dataSource = this.DataSource; + if (dataSource is null) + throw new DotvvmControlException(this, "Can not execute sort command, DataSource is null"); + var sortOptions = (dataSource as ISortableGridViewDataSet)?.SortingOptions; + if (sortOptions is null) + throw new DotvvmControlException(this, "Can not execute sort command, DataSource does not have sorting options"); + if (sortOptions.SortExpression == expr) + { + sortOptions.SortDescending ^= true; + } + else + { + sortOptions.SortExpression = expr; + sortOptions.SortDescending = false; + } + (dataSource as IPageableGridViewDataSet)?.GoToFirstPage(); + } + protected virtual void CreateHeaderRow(IDotvvmRequestContext context, Action? sortCommand) { head = new HtmlGenericControl("thead"); From 6d09c5001f7ed876e01af166f1ca117dd1214927 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Herceg?= Date: Sun, 23 Aug 2020 12:45:32 +0200 Subject: [PATCH 2/2] Sample and UI test added --- .../Controls/GridViewCheckBoxColumn.cs | 14 ++++ .../GridView/GridViewSortChangedViewModel.cs | 66 +++++++++++++++++++ .../GridView/GridViewSortChanged.dothtml | 43 ++++++++++++ .../Control/GridViewTests.cs | 54 +++++++++++++++ .../SamplesRouteUrls.designer.cs | 2 + 5 files changed, 179 insertions(+) create mode 100644 src/DotVVM.Samples.Common/ViewModels/ControlSamples/GridView/GridViewSortChangedViewModel.cs create mode 100644 src/DotVVM.Samples.Common/Views/ControlSamples/GridView/GridViewSortChanged.dothtml diff --git a/src/DotVVM.Framework/Controls/GridViewCheckBoxColumn.cs b/src/DotVVM.Framework/Controls/GridViewCheckBoxColumn.cs index a793c3afae..1a79478aa1 100644 --- a/src/DotVVM.Framework/Controls/GridViewCheckBoxColumn.cs +++ b/src/DotVVM.Framework/Controls/GridViewCheckBoxColumn.cs @@ -1,5 +1,6 @@ #nullable enable using DotVVM.Framework.Binding; +using DotVVM.Framework.Binding.Properties; using DotVVM.Framework.Hosting; namespace DotVVM.Framework.Controls @@ -48,5 +49,18 @@ private void CreateControlsCore(DotvvmControl container, bool enabled) Validator.Place(checkBox, container.Children, valueBinding, ValidatorPlacement); container.Children.Add(checkBox); } + + protected override string? GetSortExpression() + { + if (string.IsNullOrEmpty(SortExpression)) + { + return GetValueBinding(ValueBindingProperty)?.GetProperty()?.Code ?? + throw new DotvvmControlException(this, $"The 'ValueBinding' property must be set on the '{GetType()}' control!"); + } + else + { + return SortExpression; + } + } } } diff --git a/src/DotVVM.Samples.Common/ViewModels/ControlSamples/GridView/GridViewSortChangedViewModel.cs b/src/DotVVM.Samples.Common/ViewModels/ControlSamples/GridView/GridViewSortChangedViewModel.cs new file mode 100644 index 0000000000..603bb59a63 --- /dev/null +++ b/src/DotVVM.Samples.Common/ViewModels/ControlSamples/GridView/GridViewSortChangedViewModel.cs @@ -0,0 +1,66 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using DotVVM.Framework.Controls; +using DotVVM.Framework.ViewModel; +using DotVVM.Samples.BasicSamples.ViewModels.ControlSamples.GridView; + +namespace DotVVM.Samples.Common.ViewModels.ControlSamples.GridView +{ + public class GridViewSortChangedViewModel : DotvvmViewModelBase + { + + private static IQueryable GetData() + { + return new[] + { + new CustomerData() { CustomerId = 1, Name = "John Doe", BirthDate = DateTime.Parse("1976-04-01"), MessageReceived = false}, + new CustomerData() { CustomerId = 2, Name = "John Deer", BirthDate = DateTime.Parse("1984-03-02"), MessageReceived = false }, + new CustomerData() { CustomerId = 3, Name = "Johnny Walker", BirthDate = DateTime.Parse("1934-01-03"), MessageReceived = true}, + new CustomerData() { CustomerId = 4, Name = "Jim Hacker", BirthDate = DateTime.Parse("1912-11-04"), MessageReceived = true}, + new CustomerData() { CustomerId = 5, Name = "Joe E. Brown", BirthDate = DateTime.Parse("1947-09-05"), MessageReceived = false}, + new CustomerData() { CustomerId = 6, Name = "Jim Harris", BirthDate = DateTime.Parse("1956-07-06"), MessageReceived = false}, + new CustomerData() { CustomerId = 7, Name = "J. P. Morgan", BirthDate = DateTime.Parse("1969-05-07"), MessageReceived = false }, + new CustomerData() { CustomerId = 8, Name = "J. R. Ewing", BirthDate = DateTime.Parse("1987-03-08"), MessageReceived = false}, + new CustomerData() { CustomerId = 9, Name = "Jeremy Clarkson", BirthDate = DateTime.Parse("1994-04-09"), MessageReceived = false }, + new CustomerData() { CustomerId = 10, Name = "Jenny Green", BirthDate = DateTime.Parse("1947-02-10"), MessageReceived = false}, + new CustomerData() { CustomerId = 11, Name = "Joseph Blue", BirthDate = DateTime.Parse("1948-12-11"), MessageReceived = false}, + new CustomerData() { CustomerId = 12, Name = "Jack Daniels", BirthDate = DateTime.Parse("1968-10-12"), MessageReceived = true}, + new CustomerData() { CustomerId = 13, Name = "Jackie Chan", BirthDate = DateTime.Parse("1978-08-13"), MessageReceived = false}, + new CustomerData() { CustomerId = 14, Name = "Jasper", BirthDate = DateTime.Parse("1934-06-14"), MessageReceived = false}, + new CustomerData() { CustomerId = 15, Name = "Jumbo", BirthDate = DateTime.Parse("1965-06-15"), MessageReceived = false }, + new CustomerData() { CustomerId = 16, Name = "Junkie Doodle", BirthDate = DateTime.Parse("1977-05-16"), MessageReceived = false } + }.AsQueryable(); + } + + public GridViewDataSet CustomersDataSet { get; set; } = new GridViewDataSet() + { + PagingOptions = new PagingOptions() + { + PageSize = 10 + }, + SortingOptions = new SortingOptions() + { + SortExpression = nameof(CustomerData.CustomerId) + } + }; + + public override Task PreRender() + { + if (CustomersDataSet.IsRefreshRequired) + { + CustomersDataSet.LoadFromQueryable(GetData()); + } + + return base.PreRender(); + } + + public void CustomSort(string column) + { + CustomersDataSet.SortingOptions.SortExpression = column; + CustomersDataSet.SortingOptions.SortDescending = false; + } + } +} diff --git a/src/DotVVM.Samples.Common/Views/ControlSamples/GridView/GridViewSortChanged.dothtml b/src/DotVVM.Samples.Common/Views/ControlSamples/GridView/GridViewSortChanged.dothtml new file mode 100644 index 0000000000..39f8232e48 --- /dev/null +++ b/src/DotVVM.Samples.Common/Views/ControlSamples/GridView/GridViewSortChanged.dothtml @@ -0,0 +1,43 @@ +@viewModel DotVVM.Samples.Common.ViewModels.ControlSamples.GridView.GridViewSortChangedViewModel, DotVVM.Samples.Common + + + + Hello from DotVVM! + + +
+

GridView with default sorting logic

+
    +
  • Clicking on the column header sorts the data using the particular column in the ascending order.
  • +
  • Subsequent clicks on the column header alternates the changes the order from ascending to descending and vice versa.
  • +
+ + + + + + + + + +

GridView with custom sorting logic

+
    +
  • Clicking on the column header sorts the data using the particular column in the ascending order.
  • +
  • Subsequent clicks on the column header do nothing special - the order is always ascending.
  • +
+ + + + + + + + + +

+ Sort Expression: {{value: CustomersDataSet.SortingOptions.SortExpression}}
+ Sort Descending: {{value: CustomersDataSet.SortingOptions.SortDescending}} +

+
+ + \ No newline at end of file diff --git a/src/DotVVM.Samples.Tests/Control/GridViewTests.cs b/src/DotVVM.Samples.Tests/Control/GridViewTests.cs index a9d173b964..49a55d998f 100644 --- a/src/DotVVM.Samples.Tests/Control/GridViewTests.cs +++ b/src/DotVVM.Samples.Tests/Control/GridViewTests.cs @@ -614,5 +614,59 @@ public void Control_GridView_InvalidCssClass_CheckBox() browser.WaitFor(() => AssertUI.HasClass(gridview.First(".is-standalone > span"), "invalid"), 1000); }); } + + [Fact] + public void Control_GridView_GridViewSortChanged() + { + RunInAllBrowsers(browser => { + browser.NavigateToUrl(SamplesRouteUrls.ControlSamples_GridView_GridViewSortChanged); + browser.WaitUntilDotvvmInited(); + + var tables = browser.FindElements("table"); + var sortExpression = browser.Single(".result-sortexpression"); + var sortDescending = browser.Single(".result-sortdescending"); + + // click the Name column in the first table + tables[0].ElementAt("th", 1).Single("a").Click().Wait(); + AssertUI.TextEquals(sortExpression, "Name"); + AssertUI.TextEquals(sortDescending, "false"); + + // click the Name column in the first table again to change sort direction + tables[0].ElementAt("th", 1).Single("a").Click().Wait(); + AssertUI.TextEquals(sortExpression, "Name"); + AssertUI.TextEquals(sortDescending, "true"); + + // click the Message received column in the first table + tables[0].ElementAt("th", 3).Single("a").Click().Wait(); + AssertUI.TextEquals(sortExpression, "MessageReceived"); + AssertUI.TextEquals(sortDescending, "false"); + + // click the Message received column in the first table again to change sort direction + tables[0].ElementAt("th", 3).Single("a").Click().Wait(); + AssertUI.TextEquals(sortExpression, "MessageReceived"); + AssertUI.TextEquals(sortDescending, "true"); + + + // click the Name column in the second table + tables[1].ElementAt("th", 1).Single("a").Click().Wait(); + AssertUI.TextEquals(sortExpression, "Name"); + AssertUI.TextEquals(sortDescending, "false"); + + // click the Name column in the second table again - sort direction should remain unchanged + tables[1].ElementAt("th", 1).Single("a").Click().Wait(); + AssertUI.TextEquals(sortExpression, "Name"); + AssertUI.TextEquals(sortDescending, "false"); + + // click the Message received column in the first table + tables[1].ElementAt("th", 3).Single("a").Click().Wait(); + AssertUI.TextEquals(sortExpression, "MessageReceived"); + AssertUI.TextEquals(sortDescending, "false"); + + // click the Message received column in the second table again - sort direction should remain unchanged + tables[1].ElementAt("th", 3).Single("a").Click().Wait(); + AssertUI.TextEquals(sortExpression, "MessageReceived"); + AssertUI.TextEquals(sortDescending, "false"); + }); + } } } diff --git a/src/DotVVM.Testing.Abstractions/SamplesRouteUrls.designer.cs b/src/DotVVM.Testing.Abstractions/SamplesRouteUrls.designer.cs index 91952a7f5b..86f2b56fe7 100644 --- a/src/DotVVM.Testing.Abstractions/SamplesRouteUrls.designer.cs +++ b/src/DotVVM.Testing.Abstractions/SamplesRouteUrls.designer.cs @@ -17,6 +17,7 @@ public partial class SamplesRouteUrls public const string ComplexSamples_ChangedEvent_ChangedEvent = "ComplexSamples/ChangedEvent/ChangedEvent"; public const string ComplexSamples_ClassBindings_ClassBindings = "ComplexSamples/ClassBindings/ClassBindings"; public const string ComplexSamples_EmptyDataTemplate_RepeaterGridView = "ComplexSamples/EmptyDataTemplate/RepeaterGridView"; + public const string ComplexSamples_EventPropagation_EventPropagation = "ComplexSamples/EventPropagation/EventPropagation"; public const string ComplexSamples_FileUploadInRepeater_FileUploadInRepeater = "ComplexSamples/FileUploadInRepeater/FileUploadInRepeater"; public const string ComplexSamples_GridViewDataSet_GridViewDataSet = "ComplexSamples/GridViewDataSet/GridViewDataSet"; public const string ComplexSamples_InvoiceCalculator_InvoiceCalculator = "ComplexSamples/InvoiceCalculator/InvoiceCalculator"; @@ -80,6 +81,7 @@ public partial class SamplesRouteUrls public const string ControlSamples_GridView_GridViewPagingSorting = "ControlSamples/GridView/GridViewPagingSorting"; public const string ControlSamples_GridView_GridViewRowDecorators = "ControlSamples/GridView/GridViewRowDecorators"; public const string ControlSamples_GridView_GridViewServerRender = "ControlSamples/GridView/GridViewServerRender"; + public const string ControlSamples_GridView_GridViewSortChanged = "ControlSamples/GridView/GridViewSortChanged"; public const string ControlSamples_GridView_GridViewStaticCommand = "ControlSamples/GridView/GridViewStaticCommand"; public const string ControlSamples_GridView_InvalidCssClass = "ControlSamples/GridView/InvalidCssClass"; public const string ControlSamples_GridView_LargeGrid = "ControlSamples/GridView/LargeGrid";