DotvvmChildEventCallback

Details

diff --git a/src/Web/Controls/WizardNavigation/WizardNavigation.cs b/src/Web/Controls/WizardNavigation/WizardNavigation.cs
new file mode 100644
index 0000000..0c0770e
--- /dev/null
+++ b/src/Web/Controls/WizardNavigation/WizardNavigation.cs
@@ -0,0 +1,74 @@
+namespace Web.Controls.WizardNavigation
+{
+    using DotVVM.Framework.Binding;
+    using DotVVM.Framework.Binding.Expressions;
+    using DotVVM.Framework.Controls;
+    using DotVVM.Framework.Hosting;
+    using System.Threading.Tasks;
+
+    public class WizardNavigation : DotvvmMarkupControl
+    {
+        public Command NextButtonCommand
+        {
+            get { return (Command)GetValue(NextButtonCommandProperty); }
+            set
+            {
+                SetValue(NextButtonCommandProperty, value);
+            }
+        }
+        public static readonly DotvvmProperty NextButtonCommandProperty
+            = DotvvmProperty.Register<Command, WizardNavigation>(c => c.NextButtonCommand, null);
+
+        public Command PreviousButtonCommand
+        {
+            get { return (Command)GetValue(PreviousButtonCommandProperty); }
+            set
+            {
+                SetValue(PreviousButtonCommandProperty, value);
+            }
+        }
+        public static readonly DotvvmProperty PreviousButtonCommandProperty
+            = DotvvmProperty.Register<Command, WizardNavigation>(c => c.PreviousButtonCommand, null);
+
+        protected override void OnInit(IDotvvmRequestContext context)
+        {
+            var dataContext = DataContext as WizardNavigationViewModel;
+            if (dataContext == null)
+                throw new DotvvmControlException("Data Context is null.");
+
+            if(dataContext.Total <= 0)
+                throw new DotvvmControlException("Initialization was not called.");
+
+            base.OnInit(context);
+        }
+
+        public async Task NextCommand()
+        {
+            var binding = GetCommandBinding(NextButtonCommandProperty);
+
+            if (binding == null)
+                return;
+
+            var dataContext = DataContext as WizardNavigationViewModel;
+            if (dataContext == null)
+                throw new DotvvmControlException("Data Context is null.");
+
+            await dataContext.OnNextClickAsync(NextButtonCommand);
+        }
+
+        public async Task PreviousCommand()
+        {
+            var binding = GetCommandBinding(PreviousButtonCommandProperty);
+
+            if (binding == null)
+                return;
+
+            var dataContext = DataContext as WizardNavigationViewModel;
+            if (dataContext == null)
+                throw new DotvvmControlException("Data Context is null.");
+
+            await dataContext.OnPreviousClickAsync(PreviousButtonCommand);
+        }
+    }
+}
+
diff --git a/src/Web/Controls/WizardNavigation/WizardNavigation.dotcontrol b/src/Web/Controls/WizardNavigation/WizardNavigation.dotcontrol
new file mode 100644
index 0000000..fdf7f2f
--- /dev/null
+++ b/src/Web/Controls/WizardNavigation/WizardNavigation.dotcontrol
@@ -0,0 +1,29 @@
+@viewModel Web.Controls.WizardNavigation.WizardNavigationViewModel, Web
+@baseType Web.Controls.WizardNavigation.WizardNavigation, Web
+
+<div class="container mt-3" IncludeInPage="{value: WizardNavigationVisible}">
+    <div class="row">
+        <div class="col-md-2">
+            <bs:Button ButtonTagName="button" Click="{controlCommand:  PreviousCommand()}" Enabled="{value: PreviousButtonEnabled}" IncludeInPage="{value: PreviousButtonVisible}" Type="Light">
+                Previous
+            </bs:Button>
+        </div>
+        <div class="col-md-8">
+            Step {{value: Step}} out of {{value: Total}}.
+            <bs:ProgressBar Color="Info" VisualStyle="AnimatedStriped" Value="{value: Percentage}" />
+            {{value: Percentage.ToString("#.##")}}% Complete
+        </div>
+        <div class="col-md-2">
+            <bs:Button ButtonTagName="button" Click="{controlCommand: NextCommand()}" Enabled="{value: NextButtonEnabled}" IncludeInPage="{value: NextButtonVisible}" IsSubmitButton="true" Type="Primary">
+                Next
+            </bs:Button>
+        </div>
+    </div>
+</div>
+<div class="container">
+    <div class="row">
+        <div class="offset-md-5 col-md-4">
+            <h4 class="lead">{{value: Title}}</h4>
+        </div>
+    </div>
+</div>
diff --git a/src/Web/Controls/WizardNavigation/WizardNavigationConfigurationExtensions.cs b/src/Web/Controls/WizardNavigation/WizardNavigationConfigurationExtensions.cs
new file mode 100644
index 0000000..be65d26
--- /dev/null
+++ b/src/Web/Controls/WizardNavigation/WizardNavigationConfigurationExtensions.cs
@@ -0,0 +1,18 @@
+namespace Web.Controls.WizardNavigation
+{
+    using DotVVM.Framework.Configuration;
+    using Helper;
+
+    public static class WizardNavigationConfigurationExtensions
+    {
+        public static void AddWizardNavigationConfiguration(this DotvvmConfiguration config)
+        {
+            config.Markup.Controls.Add(new DotvvmControlConfiguration()
+            {
+                Assembly = typeof(WizardNavigation).Assembly.GetName().Name,
+                Namespace = typeof(WizardNavigation).Namespace,
+                TagPrefix = Constants.TagPrefix
+            });
+        }
+    }
+}
diff --git a/src/Web/Controls/WizardNavigation/WizardNavigationViewModel.cs b/src/Web/Controls/WizardNavigation/WizardNavigationViewModel.cs
new file mode 100644
index 0000000..b66a0b6
--- /dev/null
+++ b/src/Web/Controls/WizardNavigation/WizardNavigationViewModel.cs
@@ -0,0 +1,80 @@
+namespace Web.Controls.WizardNavigation
+{
+    using DotVVM.Framework.Binding.Expressions;
+    using DotVVM.Framework.ViewModel;
+    using System;
+    using System.Collections.Generic;
+    using System.Threading.Tasks;
+
+    public class WizardNavigationViewModel : DotvvmViewModelBase
+    {
+        public int MinimumStep { get; set; }
+        public bool NextButtonEnabled { get; set; }
+        public bool NextButtonVisible { get; set; }
+        public double Percentage => ((float)Step / Total) * 100.0;
+        public int PreviousStep { get; set; }
+        public bool PreviousButtonEnabled { get; set; }
+        public bool PreviousButtonVisible { get; set; }
+        public int Step { get; set; }
+        public string Title => Titles[Step - 1];
+        public string[] Titles { get; set; }
+        public int Total => Titles.Length;
+        public bool WizardNavigationVisible { get; set; }
+
+        public WizardNavigationViewModel(List<string> titles) : this(titles, 1)
+        {
+        }
+
+        public WizardNavigationViewModel(List<string> titles, int minimumStep)
+        {
+            if (titles == null)
+                throw new ArgumentNullException($"{nameof(titles)} is required.");
+
+            if (titles.Count == 0)
+                throw new ArgumentOutOfRangeException($"{nameof(titles)} is empty.");
+
+            MinimumStep = minimumStep;
+            NextButtonVisible = true;
+            PreviousButtonVisible = true;
+            Step = 1;
+            Titles = titles.ToArray();
+
+            if (Total > 1)
+                NextButtonEnabled = true;
+        }
+
+        public void Hide() => WizardNavigationVisible = false;
+
+        public async Task OnNextClickAsync(Command command)
+        {
+            if (Step >= Total)
+                return;
+
+            PreviousStep = Step;
+            Step++;
+            SetButtonStatus();
+            await command.Invoke();
+        }
+
+        public async Task OnPreviousClickAsync(Command command)
+        {
+            if (Step <= MinimumStep)
+                return;
+
+            PreviousStep = Step;
+            Step--;
+            SetButtonStatus();
+            await command.Invoke();
+        }
+
+        public void Show() => WizardNavigationVisible = true;
+
+        #region Private Methods
+        private void SetButtonStatus()
+        {
+            NextButtonEnabled = Step < Total;
+            PreviousButtonEnabled = Step > MinimumStep;
+        }
+        #endregion
+    }
+}
diff --git a/src/Web/DotvvmStartup.cs b/src/Web/DotvvmStartup.cs
index 9728645..49063b3 100644
--- a/src/Web/DotvvmStartup.cs
+++ b/src/Web/DotvvmStartup.cs
@@ -1,6 +1,7 @@
 namespace Web
 {
     using Controls.ConfirmationModal;
+    using Controls.WizardNavigation;
     using DotVVM.Framework.Configuration;
     using DotVVM.Framework.Controls.Bootstrap4;
     using DotVVM.Framework.ResourceManagement;
@@ -35,6 +36,7 @@ namespace Web
         private void ConfigureControls(DotvvmConfiguration config, string applicationPath)
         {
             config.AddConfirmationModalConfiguration();
+            config.AddWizardNavigationConfiguration();
             config.Markup.AutoDiscoverControls(new DefaultControlRegistrationStrategy(config, Constants.TagPrefix, "Controls"));
         }
 
diff --git a/src/Web/ViewModels/DefaultViewModel.cs b/src/Web/ViewModels/DefaultViewModel.cs
index e4409d7..47f015b 100644
--- a/src/Web/ViewModels/DefaultViewModel.cs
+++ b/src/Web/ViewModels/DefaultViewModel.cs
@@ -1,7 +1,8 @@
 namespace Web.ViewModels
 {
-    using System;
+    using Controls.WizardNavigation;
     using System.Collections.Generic;
+    using System.Threading.Tasks;
     using static DefaultViewModelEvents;
 
     public static class DefaultViewModelEvents
@@ -32,14 +33,20 @@ namespace Web.ViewModels
             if (!First.IsInitialized)
                 First.Initialize(ToggleNextButtonEnabled);
 
-            Wizard.NextButtonEnabled = false; // default off until user checks to continue
-            Wizard.ShowNavigation();
+            Wizard = new WizardNavigationViewModel(new List<string>
+            {
+                "Tale of Two Cities",
+                "Moby Dick"
+            }, 1)
+            {
+                NextButtonEnabled = false // default off until user checks to continue
+            };
+
+            Wizard.Show();
         }
 
-        public void OnNextClick()
+        public async Task OnNextClick()
         {
-            Wizard.OnNextClick();
-
             if (Wizard.Step == 2)
             {
                 if (Second == null)  // TODO resolve with IOC
@@ -48,11 +55,13 @@ namespace Web.ViewModels
                 if (!Second.IsInitialized)
                     Second.Initialize();
             }
+
+            await Task.CompletedTask;
         }
 
-        public void OnPreviousClick()
+        public async Task OnPreviousClick()
         {
-            Wizard.OnPreviousClick();
+            await Task.CompletedTask;
         }
 
         #region Private Methods
@@ -110,76 +119,4 @@ namespace Web.ViewModels
         }
     }
     #endregion
-
-    #region Wizard Navigation
-    public class WizardNavigationViewModel
-    {
-        public int MinimumStep { get; set; }
-        public bool NextButtonEnabled { get; set; }
-        public bool NextButtonVisible { get; set; }
-        public double Percentage => ((float)Step / Total) * 100.0;
-        public int PreviousStep { get; set; }
-        public bool PreviousButtonEnabled { get; set; }
-        public bool PreviousButtonVisible { get; set; }
-        public int Step { get; set; }
-        public string Title => Titles[Step - 1];
-        public string[] Titles { get; set; }
-        public int Total => Titles.Length;
-        public bool NavigationVisible { get; set; }
-
-        public WizardNavigationViewModel(List<string> titles) : this(titles, 1)
-        {
-        }
-
-        public WizardNavigationViewModel(List<string> titles, int minimumStep)
-        {
-            if (titles == null)
-                throw new ArgumentNullException($"{nameof(titles)} is required.");
-
-            if (titles.Count == 0)
-                throw new ArgumentOutOfRangeException($"{nameof(titles)} is empty.");
-
-            MinimumStep = minimumStep;
-            NextButtonVisible = true;
-            PreviousButtonVisible = true;
-            Step = 1;
-            Titles = titles.ToArray();
-
-            if (Total > 1)
-                NextButtonEnabled = true;
-        }
-
-        public void HideNavigation() => NavigationVisible = false;
-
-        public void OnNextClick()
-        {
-            if (Step >= Total)
-                return;
-
-            PreviousStep = Step;
-            Step++;
-            SetButtonStatus();
-        }
-
-        public void OnPreviousClick()
-        {
-            if (Step <= MinimumStep)
-                return;
-
-            PreviousStep = Step;
-            Step--;
-            SetButtonStatus();
-        }
-
-        public void ShowNavigation() => NavigationVisible = true;
-
-        #region Private Methods
-        private void SetButtonStatus()
-        {
-            NextButtonEnabled = Step < Total;
-            PreviousButtonEnabled = Step > MinimumStep;
-        }
-        #endregion
-    }
-    #endregion
 }
diff --git a/src/Web/Views/Default.dothtml b/src/Web/Views/Default.dothtml
index bf86c98..401b8ae 100644
--- a/src/Web/Views/Default.dothtml
+++ b/src/Web/Views/Default.dothtml
@@ -2,46 +2,7 @@
 @masterPage Views/Site.dotmaster
 
 <dot:Content ContentPlaceHolderID="Body">
-    <div DataContext="{value: Wizard}">
-        <div class="container mt-3" IncludeInPage="{value: NavigationVisible}">
-            <div class="row">
-                <div class="col-md-2">
-                    <bs:Button ButtonTagName="button" Click="{command:  _root.OnPreviousClick()}" Enabled="{value: PreviousButtonEnabled}" IncludeInPage="{value: PreviousButtonVisible}" Type="Light">
-                        Previous Button
-                    </bs:Button>
-                </div>
-                <div class="col-md-8">
-                    <div class="row">
-                        <div class="col-md-3">
-                            Step {{value: Step}} out of {{value: Total}}.
-                        </div>
-                    </div>
-                    <div class="row">
-                        <div class="col-md-12">
-                            <bs:ProgressBar Color="Primary" VisualStyle="AnimatedStriped" Value="{value: Percentage}" />
-                        </div>
-                    </div>
-                    <div class="row">
-                        <div class="col-md-3">
-                            {{value: Percentage.ToString("#.##")}}% Complete
-                        </div>
-                    </div>
-                </div>
-                <div class="col-md-2">
-                    <bs:Button ButtonTagName="button" Click="{command: _root.OnNextClick()}" Enabled="{value: NextButtonEnabled}" IncludeInPage="{value: NextButtonVisible}" IsSubmitButton="true" Type="Primary">
-                        Next Button
-                    </bs:Button>
-                </div>
-            </div>
-        </div>
-        <div class="container mt-3">
-            <div class="row">
-                <div class="offset-md-2 col-md-8">
-                    <h5 class="text-muted">{{value: Title}}</h5>
-                </div>
-            </div>
-        </div>
-    </div>
+    <cl:WizardNavigation DataContext="{value:  Wizard}" NextButtonCommand="{command: _parent.OnNextClick()}" PreviousButtonCommand="{command: _parent.OnPreviousClick()}" />
     <div class="container mt-4">
         <div class="row">
             <div class="col-md-12">
diff --git a/src/Web/Web.csproj b/src/Web/Web.csproj
index 45b43f9..8deca42 100644
--- a/src/Web/Web.csproj
+++ b/src/Web/Web.csproj
@@ -193,6 +193,9 @@
   <ItemGroup>
     <Compile Include="Controls\ConfirmationModal\ConfirmationModal.cs" />
     <Compile Include="Controls\ConfirmationModal\ConfirmationModalConfigurationExtensions.cs" />
+    <Compile Include="Controls\WizardNavigation\WizardNavigation.cs" />
+    <Compile Include="Controls\WizardNavigation\WizardNavigationConfigurationExtensions.cs" />
+    <Compile Include="Controls\WizardNavigation\WizardNavigationViewModel.cs" />
     <Compile Include="Helper\Constants.cs" />
     <Compile Include="Properties\AssemblyInfo.cs" />
     <Compile Include="Startup.cs" />
@@ -232,6 +235,7 @@
     <Content Include="wwwroot\js\toastr\toastr.min.css" />
     <Content Include="wwwroot\js\toastr\toastr.min.js" />
     <Content Include="Controls\ConfirmationModal\ConfirmationModal.dotcontrol" />
+    <Content Include="Controls\WizardNavigation\WizardNavigation.dotcontrol" />
     <None Include="packages.config" />
     <Content Include="Views\Site.dotmaster" />
     <Content Include="Views\Default.dothtml" />