Skip to content

Commit ebbe896

Browse files
authored
Merge pull request #1837 from riganti/feature/datapager-templating
DataPager styling & templating
2 parents f0bde9c + abe1e11 commit ebbe896

7 files changed

Lines changed: 321 additions & 18 deletions

File tree

src/Framework/Framework/Controls/DataPager.cs

Lines changed: 90 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@ namespace DotVVM.Framework.Controls
2121
public class DataPager : HtmlGenericControl
2222
{
2323
private readonly GridViewDataSetBindingProvider gridViewDataSetBindingProvider;
24-
private readonly BindingCompilationService bindingCompilationService;
2524

2625
private DataPagerBindings? pagerBindings;
2726

@@ -30,7 +29,6 @@ public DataPager(GridViewDataSetBindingProvider gridViewDataSetBindingProvider,
3029
: base("ul", false)
3130
{
3231
this.gridViewDataSetBindingProvider = gridViewDataSetBindingProvider;
33-
this.bindingCompilationService = bindingCompilationService;
3432
}
3533

3634
/// <summary>
@@ -93,6 +91,20 @@ public ITemplate? NextPageTemplate
9391
public static readonly DotvvmProperty NextPageTemplateProperty =
9492
DotvvmProperty.Register<ITemplate?, DataPager>(c => c.NextPageTemplate, null);
9593

94+
/// <summary>
95+
/// Gets or sets the template of the button which moves the user to the numbered page.
96+
/// </summary>
97+
[MarkupOptions(AllowBinding = false, MappingMode = MappingMode.InnerElement)]
98+
[ConstantDataContextChange(typeof(int[]), order: 0)]
99+
[CollectionElementDataContextChange(order: 1)]
100+
public ITemplate? PageNumberTemplate
101+
{
102+
get { return (ITemplate?)GetValue(PageNumberTemplateProperty); }
103+
set { SetValue(PageNumberTemplateProperty, value); }
104+
}
105+
public static readonly DotvvmProperty PageNumberTemplateProperty =
106+
DotvvmProperty.Register<ITemplate?, DataPager>(c => c.PageNumberTemplate, null);
107+
96108
/// <summary>
97109
/// Gets or sets whether a hyperlink should be rendered for the current page number. If set to false, only a plain text is rendered.
98110
/// </summary>
@@ -126,6 +138,27 @@ public bool Enabled
126138
public static readonly DotvvmProperty EnabledProperty =
127139
DotvvmPropertyWithFallback.Register<bool, DataPager>(nameof(Enabled), FormControls.EnabledProperty);
128140

141+
/// <summary>
142+
/// Gets or sets styles for the list item element (&lt;li&gt;) rendered by the component.
143+
/// </summary>
144+
public HtmlCapability ListItemHtmlCapability
145+
{
146+
get => (HtmlCapability)ListItemHtmlCapabilityProperty.GetValue(this);
147+
set => ListItemHtmlCapabilityProperty.SetValue(this, value);
148+
}
149+
public static readonly DotvvmCapabilityProperty ListItemHtmlCapabilityProperty = DotvvmCapabilityProperty.RegisterCapability<HtmlCapability, DataPager>("ListItem");
150+
151+
/// <summary>
152+
/// Gets or sets styles for the link buttons rendered by the component.
153+
/// </summary>
154+
public HtmlCapability LinkHtmlCapability
155+
{
156+
get => (HtmlCapability)LinkHtmlCapabilityProperty.GetValue(this);
157+
set => LinkHtmlCapabilityProperty.SetValue(this, value);
158+
}
159+
public static readonly DotvvmCapabilityProperty LinkHtmlCapabilityProperty = DotvvmCapabilityProperty.RegisterCapability<HtmlCapability, DataPager>("Link");
160+
161+
129162
/// <summary>
130163
/// Gets or sets the (static) command that will be triggered when the DataPager needs to load data (when navigating to different page).
131164
/// The command accepts one argument of type <see cref="GridViewDataSetOptions{TFilteringOptions, TSortingOptions, TPagingOptions}" /> and should return a new <see cref="GridViewDataSet{T}" /> or <see cref="GridViewDataSetResult{TItem, TFilteringOptions, TSortingOptions, TPagingOptions}" />.
@@ -138,14 +171,36 @@ public ICommandBinding? LoadData
138171
public static readonly DotvvmProperty LoadDataProperty =
139172
DotvvmProperty.Register<ICommandBinding?, DataPager>(nameof(LoadData));
140173

174+
/// <summary>
175+
/// Gets or sets the CSS class to be applied to the currently active page number.
176+
/// </summary>
177+
[MarkupOptions(AllowBinding = false)]
178+
public string ActiveItemCssClass
179+
{
180+
get { return (string)GetValue(ActiveItemCssClassProperty)!; }
181+
set { SetValue(ActiveItemCssClassProperty, value); }
182+
}
183+
public static readonly DotvvmProperty ActiveItemCssClassProperty
184+
= DotvvmProperty.Register<string, DataPager>(c => c.ActiveItemCssClass, "active");
185+
186+
/// <summary>
187+
/// Gets or sets the CSS class that to be applied to the disabled items.
188+
/// </summary>
189+
[MarkupOptions(AllowBinding = false)]
190+
public string DisabledItemCssClass
191+
{
192+
get { return (string)GetValue(DisabledItemCssClassProperty)!; }
193+
set { SetValue(DisabledItemCssClassProperty, value); }
194+
}
195+
public static readonly DotvvmProperty DisabledItemCssClassProperty
196+
= DotvvmProperty.Register<string, DataPager>(c => c.DisabledItemCssClass, "disabled");
197+
141198
protected HtmlGenericControl? ContentWrapper { get; set; }
142199
protected HtmlGenericControl? GoToFirstPageButton { get; set; }
143200
protected HtmlGenericControl? GoToPreviousPageButton { get; set; }
144201
protected Repeater? NumberButtonsRepeater { get; set; }
145202
protected HtmlGenericControl? GoToNextPageButton { get; set; }
146203
protected HtmlGenericControl? GoToLastPageButton { get; set; }
147-
protected virtual string ActiveItemCssClass => "active";
148-
protected virtual string DisabledItemCssClass => "disabled";
149204

150205
protected internal override void OnLoad(IDotvvmRequestContext context)
151206
{
@@ -196,7 +251,7 @@ protected virtual void DataBind(Hosting.IDotvvmRequestContext context)
196251
if (pagerBindings.PageNumbers is {})
197252
{
198253
// number fields
199-
var liTemplate = CreatePageNumberButton(globalEnabled, pagerBindings, context);
254+
var liTemplate = CreatePageNumberButton(globalEnabled, PageNumberTemplate, pagerBindings, context);
200255
AddItemCssClass(liTemplate, context);
201256

202257
NumberButtonsRepeater = new Repeater() {
@@ -250,22 +305,23 @@ protected virtual HtmlGenericControl CreateWrapperList()
250305

251306
protected override void AddVisibleAttributeOrBinding(in RenderState r, IHtmlWriter writer) { } // handled by the wrapper list
252307

253-
protected virtual HtmlGenericControl CreatePageNumberButton(ValueOrBinding<bool> globalEnabled, DataPagerBindings pagerBindings, IDotvvmRequestContext context)
308+
protected virtual HtmlGenericControl CreatePageNumberButton(ValueOrBinding<bool> globalEnabled, ITemplate? userDefinedContentTemplate, DataPagerBindings pagerBindings, IDotvvmRequestContext context)
254309
{
255-
var liTemplate = new HtmlGenericControl("li");
310+
var liTemplate = new HtmlGenericControl("li", ListItemHtmlCapability);
256311
liTemplate.CssClasses.Add(ActiveItemCssClass, new ValueOrBinding<bool>(pagerBindings.NotNull().IsActivePage.NotNull()));
257-
var link = new LinkButton();
312+
313+
var link = new LinkButton().SetCapability(LinkHtmlCapability);
314+
258315
link.SetBinding(ButtonBase.ClickProperty, pagerBindings.NotNull().GoToPage.NotNull());
259-
SetPageNumberButtonContent(link, pagerBindings, context);
316+
SetPageNumberButtonContent(context, link, pagerBindings, userDefinedContentTemplate);
260317
if (!RenderLinkForCurrentPage) link.SetBinding(IncludeInPageProperty, pagerBindings.IsActivePage.NotNull().Negate());
261318
if (!true.Equals(globalEnabled)) link.SetValue(ButtonBase.EnabledProperty, globalEnabled);
262319
liTemplate.Children.Add(link);
263320

264321
if (!RenderLinkForCurrentPage)
265322
{
266-
var notLink = new Literal();
267-
SetPageNumberSpanContent(notLink, pagerBindings, context);
268-
notLink.RenderSpanElement = true;
323+
var notLink = new HtmlGenericControl("span").SetCapability(LinkHtmlCapability);
324+
SetPageNumberSpanContent(context, notLink, pagerBindings, userDefinedContentTemplate);
269325
notLink.SetBinding(IncludeInPageProperty, pagerBindings.IsActivePage);
270326
liTemplate.Children.Add(notLink);
271327
}
@@ -274,8 +330,10 @@ protected virtual HtmlGenericControl CreatePageNumberButton(ValueOrBinding<bool>
274330

275331
protected virtual HtmlGenericControl CreateNavigationButton(string defaultText, ITemplate? userDefinedContentTemplate, object enabledValue, ICommandBinding clickCommandBindingExpression,IDotvvmRequestContext context)
276332
{
277-
var li = new HtmlGenericControl("li");
278-
var link = new LinkButton();
333+
var li = new HtmlGenericControl("li", ListItemHtmlCapability);
334+
335+
var link = new LinkButton().SetCapability(LinkHtmlCapability);
336+
279337
SetNavigationButtonContent(context, link, defaultText, userDefinedContentTemplate);
280338
link.SetBinding(ButtonBase.ClickProperty, clickCommandBindingExpression);
281339
if (!true.Equals(enabledValue)) link.SetValue(ButtonBase.EnabledProperty, enabledValue);
@@ -295,14 +353,28 @@ protected virtual void SetNavigationButtonContent(IDotvvmRequestContext context,
295353
}
296354
}
297355

298-
protected virtual void SetPageNumberSpanContent(Literal notLink, DataPagerBindings pagerBindings, IDotvvmRequestContext context)
356+
protected virtual void SetPageNumberSpanContent(IDotvvmRequestContext context, HtmlGenericControl span, DataPagerBindings pagerBindings, ITemplate? userDefinedContentTemplate)
299357
{
300-
notLink.SetBinding(Literal.TextProperty, pagerBindings.PageNumberText.NotNull());
358+
if (userDefinedContentTemplate != null)
359+
{
360+
userDefinedContentTemplate.BuildContent(context, span);
361+
}
362+
else
363+
{
364+
span.SetBinding(HtmlGenericControl.InnerTextProperty, pagerBindings.PageNumberText.NotNull());
365+
}
301366
}
302367

303-
protected virtual void SetPageNumberButtonContent(LinkButton link, DataPagerBindings pagerBindings, IDotvvmRequestContext context)
368+
protected virtual void SetPageNumberButtonContent(IDotvvmRequestContext context, LinkButton link, DataPagerBindings pagerBindings, ITemplate? userDefinedContentTemplate)
304369
{
305-
link.SetBinding(ButtonBase.TextProperty, pagerBindings.PageNumberText.NotNull());
370+
if (userDefinedContentTemplate != null)
371+
{
372+
userDefinedContentTemplate.BuildContent(context, link);
373+
}
374+
else
375+
{
376+
link.SetBinding(ButtonBase.TextProperty, pagerBindings.PageNumberText.NotNull());
377+
}
306378
}
307379

308380
protected virtual void AddItemCssClass(HtmlGenericControl item, IDotvvmRequestContext context)
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
using DotVVM.Framework.Controls;
2+
using DotVVM.Framework.ViewModel;
3+
using System.Collections.Generic;
4+
using System.Linq;
5+
using System.Threading.Tasks;
6+
7+
namespace DotVVM.Samples.BasicSamples.ViewModels.ControlSamples.DataPager
8+
{
9+
public class DataPagerTemplatesViewModel : DotvvmViewModelBase
10+
{
11+
public GridViewDataSet<Data> DataSet { get; set; }
12+
13+
public string[] RomanNumerals { get; set; } = new[] { "I", "II", "III", "IV", "V", "VI", "VII", "VIII", "IX", "X", "XI", "XII", "XIII", "XIV", "XV", "XVI", "XVII", "XVIII", "XIX", "XX" };
14+
15+
public override Task Init()
16+
{
17+
DataSet = new GridViewDataSet<Data>()
18+
{
19+
PagingOptions = new PagingOptions()
20+
{
21+
PageSize = 3
22+
}
23+
};
24+
return base.Init();
25+
}
26+
27+
public override Task PreRender()
28+
{
29+
if (DataSet.IsRefreshRequired)
30+
{
31+
DataSet.LoadFromQueryable(FakeDB(50));
32+
}
33+
34+
return base.PreRender();
35+
}
36+
37+
private IQueryable<Data> FakeDB(int itemsCreatorCounter)
38+
{
39+
var dbdata = new List<Data>();
40+
for (var i = 0; i < itemsCreatorCounter; i++)
41+
{
42+
dbdata.Add(new Data
43+
{
44+
Text = $"Item {i}"
45+
});
46+
}
47+
return dbdata.AsQueryable();
48+
}
49+
50+
public class Data
51+
{
52+
public string Text { get; set; }
53+
}
54+
}
55+
}

src/Samples/Common/ViewModels/ControlSamples/DataPager/DataPagerViewModel.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ public class DataPagerViewModel : DotvvmViewModelBase
1010
{
1111
public GridViewDataSet<Data> DataSet { get; set; }
1212

13+
public string[] RomanNumerals { get; set; } = new[] { "", "I", "II", "III", "IV", "V", "VI", "VII", "VIII", "IX", "X", "XI", "XII", "XIII", "XIV", "XV", "XVI", "XVII", "XVIII", "XIX", "XX" };
14+
1315
public bool Enabled { get; set; } = true;
1416

1517
public int ItemsInDatabaseCount { get; set; } = 2;
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
@viewModel DotVVM.Samples.BasicSamples.ViewModels.ControlSamples.DataPager.DataPagerTemplatesViewModel, DotVVM.Samples.Common
2+
3+
<!DOCTYPE html>
4+
5+
<html lang="en" xmlns="http://www.w3.org/1999/xhtml">
6+
<head>
7+
<meta charset="utf-8" />
8+
<title></title>
9+
<style>
10+
.pagination {
11+
list-style-type: none;
12+
display: flex;
13+
align-items: center;
14+
}
15+
.page-item {
16+
margin: 0 1em;
17+
padding: 1em;
18+
border: solid 1px black;
19+
}
20+
.page-link {
21+
text-decoration: none;
22+
}
23+
.disabled {
24+
opacity: 0.3;
25+
}
26+
</style>
27+
</head>
28+
<body>
29+
30+
<h1>Templates in DataPager</h1>
31+
32+
<dot:Repeater DataSource="{value: DataSet}" WrapperTagName="ul">
33+
<ItemTemplate>
34+
<li>{{value: Text}}</li>
35+
</ItemTemplate>
36+
</dot:Repeater>
37+
38+
<dot:DataPager DataSet="{value: DataSet}"
39+
class="pagination"
40+
ListItemclass="page-item"
41+
Linkclass="page-link">
42+
<FirstPageTemplate>◀️◀️</FirstPageTemplate>
43+
<PreviousPageTemplate>◀️</PreviousPageTemplate>
44+
<NextPageTemplate>▶️</NextPageTemplate>
45+
<LastPageTemplate>▶️▶️</LastPageTemplate>
46+
<PageNumberTemplate>
47+
{{value: _root.RomanNumerals[_this]}}
48+
</PageNumberTemplate>
49+
</dot:DataPager>
50+
51+
</body>
52+
</html>
53+
54+

src/Samples/Tests/Abstractions/SamplesRouteUrls.designer.cs

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/Samples/Tests/Tests/Control/DataPagerTests.cs

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -295,5 +295,27 @@ IElementWrapper GetPageIndex(int index)
295295
CheckNearPageIndexes(Enumerable.Range(11, 7));
296296
});
297297
}
298+
299+
[Fact]
300+
public void Control_DataPager_DataPagerTemplates()
301+
{
302+
RunInAllBrowsers(browser => {
303+
browser.NavigateToUrl(SamplesRouteUrls.ControlSamples_DataPager_DataPagerTemplates);
304+
305+
var pager = browser.Single(".pagination");
306+
307+
var items = pager.FindElements(".page-item");
308+
items.ThrowIfDifferentCountThan(10);
309+
310+
var content = new[] { "◀️◀️", "◀️", "I", "II", "III", "IV", "V", "VI", "▶️", "▶️▶️" };
311+
for (var i = 0; i < items.Count; i++)
312+
{
313+
var item = items[i];
314+
315+
var link = item.Single(".page-link");
316+
AssertUI.TextEquals(link, content[i]);
317+
}
318+
});
319+
}
298320
}
299321
}

0 commit comments

Comments
 (0)