Skip to content

Commit

Permalink
Merge pull request #870 from riganti/Grid-SortChanged-precedence
Browse files Browse the repository at this point in the history
GridView.SortChange has precedence over default sorting
  • Loading branch information
tomasherceg committed Aug 23, 2020
2 parents 0082076 + 6d09c50 commit fc6c96b
Show file tree
Hide file tree
Showing 6 changed files with 206 additions and 24 deletions.
52 changes: 28 additions & 24 deletions src/DotVVM.Framework/Controls/GridView.cs
Expand Up @@ -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<string?>)SortChangedCommand
: null;

sortCommand ??= s =>
throw new DotvvmControlException(this, "Cannot sort when DataSource is null.");

CreateHeaderRow(context, sortCommand);

Expand Down Expand Up @@ -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<string?>? sortCommand)
{
head = new HtmlGenericControl("thead");
Expand Down
14 changes: 14 additions & 0 deletions 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
Expand Down Expand Up @@ -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<OriginalStringBindingProperty>()?.Code ??
throw new DotvvmControlException(this, $"The 'ValueBinding' property must be set on the '{GetType()}' control!");
}
else
{
return SortExpression;
}
}
}
}
@@ -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<CustomerData> 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<CustomerData> CustomersDataSet { get; set; } = new GridViewDataSet<CustomerData>()
{
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;
}
}
}
@@ -0,0 +1,43 @@
@viewModel DotVVM.Samples.Common.ViewModels.ControlSamples.GridView.GridViewSortChangedViewModel, DotVVM.Samples.Common

<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>Hello from DotVVM!</title>
</head>
<body>
<div class="container">
<h1>GridView with default sorting logic</h1>
<ul>
<li>Clicking on the column header sorts the data using the particular column in the ascending order.</li>
<li>Subsequent clicks on the column header alternates the changes the order from ascending to descending and vice versa.</li>
</ul>
<dot:GridView DataSource="{value: CustomersDataSet}" class="table table-bordered">
<Columns>
<dot:GridViewTextColumn HeaderText="Id" ValueBinding="{value: CustomerId}" AllowSorting="True" />
<dot:GridViewTextColumn HeaderText="Name" ValueBinding="{value: Name}" AllowSorting="True" />
<dot:GridViewTextColumn HeaderText="Birth Date" ValueBinding="{value: BirthDate}" FormatString="g" AllowSorting="True" />
<dot:GridViewCheckBoxColumn HeaderText="Message received" ValueBinding="{value: MessageReceived}" AllowSorting="True" />
</Columns>
</dot:GridView>

<h1>GridView with custom sorting logic</h1>
<ul>
<li>Clicking on the column header sorts the data using the particular column in the ascending order.</li>
<li>Subsequent clicks on the column header do nothing special - the order is always ascending.</li>
</ul>
<dot:GridView DataSource="{value: CustomersDataSet}" SortChanged="{command: CustomSort}" class="table table-bordered">
<Columns>
<dot:GridViewTextColumn HeaderText="Id" ValueBinding="{value: CustomerId}" AllowSorting="True" />
<dot:GridViewTextColumn HeaderText="Name" ValueBinding="{value: Name}" AllowSorting="True" />
<dot:GridViewTextColumn HeaderText="Birth Date" ValueBinding="{value: BirthDate}" FormatString="g" AllowSorting="True" />
<dot:GridViewCheckBoxColumn HeaderText="Message received" ValueBinding="{value: MessageReceived}" AllowSorting="True" />
</Columns>
</dot:GridView>

<p>
Sort Expression: <span class="result-sortexpression">{{value: CustomersDataSet.SortingOptions.SortExpression}}</span><br />
Sort Descending: <span class="result-sortdescending">{{value: CustomersDataSet.SortingOptions.SortDescending}}</span>
</p>
</div>
</body>
</html>
54 changes: 54 additions & 0 deletions src/DotVVM.Samples.Tests/Control/GridViewTests.cs
Expand Up @@ -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");
});
}
}
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit fc6c96b

Please sign in to comment.