-
Notifications
You must be signed in to change notification settings - Fork 97
Commit
- Loading branch information
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,74 @@ | ||
using System; | ||
using System.Collections.Generic; | ||
using System.Text; | ||
using DotVVM.Framework.Binding.Expressions; | ||
using DotVVM.Framework.Binding; | ||
using DotVVM.Framework.Hosting; | ||
|
||
namespace DotVVM.Framework.Controls | ||
{ | ||
/// <summary> | ||
/// An invisible control that periodically invokes a command. | ||
/// </summary> | ||
public class Timer : DotvvmControl | ||
{ | ||
/// <summary> | ||
/// Gets or sets the command binding that will be invoked on every tick. | ||
/// </summary> | ||
[MarkupOptions(AllowHardCodedValue = false, Required = true)] | ||
public ICommandBinding Command | ||
{ | ||
get { return (ICommandBinding)GetValue(CommandProperty); } | ||
Check failure on line 21 in src/Framework/Framework/Controls/Timer.cs GitHub Actions / Build published projects without warnings (Release)
Check failure on line 21 in src/Framework/Framework/Controls/Timer.cs GitHub Actions / Build published projects without warnings (Release)
Check failure on line 21 in src/Framework/Framework/Controls/Timer.cs GitHub Actions / Build published projects without warnings (Release)
Check failure on line 21 in src/Framework/Framework/Controls/Timer.cs GitHub Actions / Build published projects without warnings (Release)
Check failure on line 21 in src/Framework/Framework/Controls/Timer.cs GitHub Actions / Build published projects without warnings (Debug)
Check failure on line 21 in src/Framework/Framework/Controls/Timer.cs GitHub Actions / Build published projects without warnings (Debug)
Check failure on line 21 in src/Framework/Framework/Controls/Timer.cs GitHub Actions / Build published projects without warnings (Debug)
|
||
set { SetValue(CommandProperty, value); } | ||
} | ||
public static readonly DotvvmProperty CommandProperty | ||
= DotvvmProperty.Register<ICommandBinding, Timer>(c => c.Command, null); | ||
|
||
/// <summary> | ||
/// Gets or sets the interval in milliseconds. | ||
/// </summary> | ||
[MarkupOptions(AllowBinding = false)] | ||
public int Interval | ||
{ | ||
get { return (int)GetValue(IntervalProperty); } | ||
Check failure on line 33 in src/Framework/Framework/Controls/Timer.cs GitHub Actions / Build published projects without warnings (Release)
Check failure on line 33 in src/Framework/Framework/Controls/Timer.cs GitHub Actions / Build published projects without warnings (Release)
Check failure on line 33 in src/Framework/Framework/Controls/Timer.cs GitHub Actions / Build published projects without warnings (Debug)
|
||
set { SetValue(IntervalProperty, value); } | ||
} | ||
public static readonly DotvvmProperty IntervalProperty | ||
= DotvvmProperty.Register<int, Timer>(c => c.Interval, 30000); | ||
|
||
/// <summary> | ||
/// Gets or sets whether the timer is enabled. | ||
/// </summary> | ||
public bool Enabled | ||
{ | ||
get { return (bool)GetValue(EnabledProperty); } | ||
Check failure on line 44 in src/Framework/Framework/Controls/Timer.cs GitHub Actions / Build published projects without warnings (Release)
Check failure on line 44 in src/Framework/Framework/Controls/Timer.cs GitHub Actions / Build published projects without warnings (Release)
Check failure on line 44 in src/Framework/Framework/Controls/Timer.cs GitHub Actions / Build published projects without warnings (Debug)
|
||
set { SetValue(EnabledProperty, value); } | ||
} | ||
public static readonly DotvvmProperty EnabledProperty | ||
= DotvvmProperty.Register<bool, Timer>(c => c.Enabled, true); | ||
|
||
public Timer() | ||
{ | ||
SetValue(Validation.EnabledProperty, false); | ||
SetValue(PostBack.ConcurrencyProperty, PostbackConcurrencyMode.Queue); | ||
} | ||
|
||
protected override void RenderBeginTag(IHtmlWriter writer, IDotvvmRequestContext context) | ||
{ | ||
var group = new KnockoutBindingGroup(); | ||
group.Add("command", KnockoutHelper.GenerateClientPostbackLambda("Command", Command, this)); | ||
group.Add("interval", Interval.ToString()); | ||
group.Add("enabled", this, EnabledProperty); | ||
writer.WriteKnockoutDataBindComment("timer", group.ToString()); | ||
|
||
base.RenderBeginTag(writer, context); | ||
} | ||
|
||
protected override void RenderEndTag(IHtmlWriter writer, IDotvvmRequestContext context) | ||
{ | ||
base.RenderEndTag(writer, context); | ||
|
||
writer.WriteKnockoutDataBindEndComment(); | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
type TimerProps = { | ||
interval: number, | ||
enabled: KnockoutObservable<boolean>, | ||
command: () => Promise<DotvvmAfterPostBackEventArgs> | ||
} | ||
|
||
ko.virtualElements.allowedBindings["timer"] = true; | ||
|
||
export default { | ||
"timer": { | ||
init: (element: HTMLElement, valueAccessor: () => TimerProps) => { | ||
const prop = valueAccessor(); | ||
let timer: number | null = null; | ||
|
||
if (ko.isObservable(prop.enabled)) { | ||
prop.enabled.subscribe(newValue => createOrDestroyTimer(newValue)); | ||
} | ||
createOrDestroyTimer(ko.unwrap(prop.enabled)); | ||
|
||
function createOrDestroyTimer(enabled: boolean) { | ||
if (enabled) { | ||
if (timer) { | ||
window.clearInterval(timer); | ||
} | ||
|
||
timer = window.setInterval(() => { | ||
prop.command.bind(element)(); | ||
}, prop.interval); | ||
|
||
} else if (timer) { | ||
window.clearInterval(timer); | ||
} | ||
}; | ||
|
||
ko.utils.domNodeDisposal.addDisposeCallback(element, () => { | ||
if (timer) { | ||
window.clearInterval(timer); | ||
} | ||
}); | ||
} | ||
} | ||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
using System; | ||
using System.Collections.Generic; | ||
using System.Linq; | ||
using System.Text; | ||
using System.Threading.Tasks; | ||
using DotVVM.Framework.ViewModel; | ||
using DotVVM.Framework.Hosting; | ||
|
||
namespace DotVVM.Samples.Common.ViewModels.ControlSamples.Timer | ||
{ | ||
public class TimerViewModel : DotvvmViewModelBase | ||
{ | ||
public int Value1 { get; set; } | ||
public int Value2 { get; set; } | ||
public int Value3 { get; set; } | ||
|
||
public bool Enabled1 { get; set; } = true; | ||
public bool Enabled2 { get; set; } | ||
} | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
@viewModel DotVVM.Samples.Common.ViewModels.ControlSamples.Timer.TimerViewModel, DotVVM.Samples.Common | ||
|
||
<!DOCTYPE html> | ||
|
||
<html lang="en" xmlns="http://www.w3.org/1999/xhtml"> | ||
<head> | ||
<meta charset="utf-8" /> | ||
<title></title> | ||
</head> | ||
<body> | ||
|
||
<h1>Timer</h1> | ||
|
||
<div> | ||
<h2>Timer 1 - enabled from the start</h2> | ||
<p> | ||
Value: <span data-ui="value1">{{value: Value1}}</span> | ||
</p> | ||
<p> | ||
<dot:CheckBox data-ui="enabled1" Checked="{value: Enabled1}" Text="Timer enabled" /> | ||
</p> | ||
|
||
<dot:Timer Interval="1000" Command="{command: Value1 = Value1 + 1}" Enabled="{value: Enabled1}" /> | ||
</div> | ||
|
||
<div> | ||
<h2>Timer 2 - disabled from the start</h2> | ||
<p> | ||
Value: <span data-ui="value2">{{value: Value2}}</span> | ||
</p> | ||
<p> | ||
<dot:CheckBox data-ui="enabled2" Checked="{value: Enabled2}" Text="Timer enabled" /> | ||
</p> | ||
|
||
<dot:Timer Interval="2000" Command="{command: Value2 = Value2 + 1}" Enabled="{value: Enabled2}" /> | ||
</div> | ||
|
||
<div> | ||
<h2>Timer 3 - without Enabled property</h2> | ||
<p> | ||
Value: <span data-ui="value3">{{value: Value3}}</span> | ||
</p> | ||
|
||
<dot:Timer Interval="3000" Command="{command: Value3 = Value3 + 1}" /> | ||
</div> | ||
|
||
</body> | ||
</html> | ||
|
||
|
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,102 @@ | ||
using System; | ||
using DotVVM.Samples.Tests.Base; | ||
using Riganti.Selenium.Core; | ||
using DotVVM.Testing.Abstractions; | ||
using Xunit; | ||
using Xunit.Abstractions; | ||
using OpenQA.Selenium.Interactions; | ||
using OpenQA.Selenium; | ||
using Riganti.Selenium.Core.Abstractions; | ||
|
||
namespace DotVVM.Samples.Tests.Control | ||
{ | ||
public class TimerTests : AppSeleniumTest | ||
{ | ||
public TimerTests(ITestOutputHelper output) : base(output) | ||
{ | ||
} | ||
|
||
[Fact] | ||
public void Control_Timer_Timer_Timer1() | ||
{ | ||
RunInAllBrowsers(browser => { | ||
browser.NavigateToUrl(SamplesRouteUrls.ControlSamples_Timer_Timer); | ||
|
||
var value = browser.Single("[data-ui=value1]"); | ||
|
||
// ensure the first timer is running | ||
Assert.True(EqualsWithTolerance(0, int.Parse(value.GetInnerText()), 1)); | ||
browser.Wait(3000); | ||
Assert.True(EqualsWithTolerance(3, int.Parse(value.GetInnerText()), 1)); | ||
browser.Wait(3000); | ||
Assert.True(EqualsWithTolerance(6, int.Parse(value.GetInnerText()), 1)); | ||
|
||
// stop the first timer | ||
browser.Single("[data-ui=enabled1]").Click(); | ||
browser.Wait(3000); | ||
Assert.True(EqualsWithTolerance(6, int.Parse(value.GetInnerText()), 1)); | ||
browser.Wait(3000); | ||
Assert.True(EqualsWithTolerance(6, int.Parse(value.GetInnerText()), 1)); | ||
|
||
// restart the timer | ||
browser.Single("[data-ui=enabled1]").Click(); | ||
browser.Wait(3000); | ||
Assert.True(EqualsWithTolerance(9, int.Parse(value.GetInnerText()), 1)); | ||
browser.Wait(3000); | ||
Assert.True(EqualsWithTolerance(12, int.Parse(value.GetInnerText()), 1)); | ||
}); | ||
} | ||
|
||
[Fact] | ||
public void Control_Timer_Timer_Timer2() | ||
{ | ||
RunInAllBrowsers(browser => { | ||
browser.NavigateToUrl(SamplesRouteUrls.ControlSamples_Timer_Timer); | ||
|
||
var value = browser.Single("[data-ui=value2]"); | ||
|
||
// ensure the timer is not running | ||
Assert.True(EqualsWithTolerance(0, int.Parse(value.GetInnerText()), 1)); | ||
browser.Wait(3000); | ||
Assert.True(EqualsWithTolerance(0, int.Parse(value.GetInnerText()), 1)); | ||
browser.Wait(3000); | ||
Assert.True(EqualsWithTolerance(0, int.Parse(value.GetInnerText()), 1)); | ||
|
||
// start the second timer | ||
browser.Single("[data-ui=enabled2]").Click(); | ||
browser.Wait(4000); | ||
Assert.True(EqualsWithTolerance(2, int.Parse(value.GetInnerText()), 1)); | ||
browser.Wait(4000); | ||
Assert.True(EqualsWithTolerance(4, int.Parse(value.GetInnerText()), 1)); | ||
|
||
// stop the second timer | ||
browser.Single("[data-ui=enabled2]").Click(); | ||
browser.Wait(4000); | ||
Assert.True(EqualsWithTolerance(4, int.Parse(value.GetInnerText()), 1)); | ||
browser.Wait(4000); | ||
Assert.True(EqualsWithTolerance(4, int.Parse(value.GetInnerText()), 1)); | ||
}); | ||
} | ||
|
||
|
||
[Fact] | ||
public void Control_Timer_Timer_Timer3() | ||
{ | ||
RunInAllBrowsers(browser => { | ||
browser.NavigateToUrl(SamplesRouteUrls.ControlSamples_Timer_Timer); | ||
|
||
var value = browser.Single("[data-ui=value3]"); | ||
|
||
// ensure the timer is running | ||
Assert.True(EqualsWithTolerance(0, int.Parse(value.GetInnerText()), 1)); | ||
browser.Wait(3000); | ||
Assert.True(EqualsWithTolerance(1, int.Parse(value.GetInnerText()), 1)); | ||
browser.Wait(3000); | ||
Assert.True(EqualsWithTolerance(2, int.Parse(value.GetInnerText()), 1)); | ||
}); | ||
} | ||
|
||
private static bool EqualsWithTolerance(int expected, int actual, int tolerance) | ||
=> Math.Abs(expected - actual) <= tolerance; | ||
} | ||
} |