diff --git a/.gitignore b/.gitignore index 86ed346..2495d53 100644 --- a/.gitignore +++ b/.gitignore @@ -1,343 +1,344 @@ -## Ignore Visual Studio temporary files, build results, and -## files generated by popular Visual Studio add-ons. -## -## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore - -# User-specific files -*.suo -*.user -*.userosscache -*.sln.docstates -*.ncrunchproject -*.ncrunchsolution -*.swp - -# Project XML documentation -BlazorWebFormsComponents.xml - -# User-specific files (MonoDevelop/Xamarin Studio) -*.userprefs -.ionide/ -.vscode/ - -# Build results -[Dd]ebug/ -[Dd]ebugPublic/ -[Rr]elease/ -[Rr]eleases/ -x64/ -x86/ -bld/ -[Bb]in/ -[Oo]bj/ -[Ll]og/ - -# Visual Studio 2015/2017 cache/options directory -.vs/ -# Uncomment if you have tasks that create the project's static files in wwwroot -#wwwroot/ - -# Visual Studio 2017 auto generated files -Generated\ Files/ - -# MSTest test Results -[Tt]est[Rr]esult*/ -[Bb]uild[Ll]og.* - -# NUNIT -*.VisualState.xml -TestResult.xml - -# Build Results of an ATL Project -[Dd]ebugPS/ -[Rr]eleasePS/ -dlldata.c - -# Benchmark Results -BenchmarkDotNet.Artifacts/ - -# .NET Core -project.lock.json -project.fragment.lock.json -artifacts/ -**/Properties/launchSettings.json - -# StyleCop -StyleCopReport.xml - -# Files built by Visual Studio -*_i.c -*_p.c -*_i.h -*.ilk -*.meta -*.obj -*.iobj -*.pch -*.pdb -*.ipdb -*.pgc -*.pgd -*.rsp -*.sbr -*.tlb -*.tli -*.tlh -*.tmp -*.tmp_proj -*.log -*.vspscc -*.vssscc -.builds -*.pidb -*.svclog -*.scc - -# Chutzpah Test files -_Chutzpah* - -# Visual C++ cache files -ipch/ -*.aps -*.ncb -*.opendb -*.opensdf -*.sdf -*.cachefile -*.VC.db -*.VC.VC.opendb - -# Visual Studio profiler -*.psess -*.vsp -*.vspx -*.sap - -# Visual Studio Trace Files -*.e2e - -# TFS 2012 Local Workspace -$tf/ - -# Guidance Automation Toolkit -*.gpState - -# ReSharper is a .NET coding add-in -_ReSharper*/ -*.[Rr]e[Ss]harper -*.DotSettings.user - -# JustCode is a .NET coding add-in -.JustCode - -# TeamCity is a build add-in -_TeamCity* - -# DotCover is a Code Coverage Tool -*.dotCover - -# AxoCover is a Code Coverage Tool -.axoCover/* -!.axoCover/settings.json - -# Visual Studio code coverage results -*.coverage -*.coveragexml - -# NCrunch -_NCrunch_* -.*crunch*.local.xml -nCrunchTemp_* - -# MightyMoose -*.mm.* -AutoTest.Net/ - -# Web workbench (sass) -.sass-cache/ - -# Installshield output folder -[Ee]xpress/ - -# DocProject is a documentation generator add-in -DocProject/buildhelp/ -DocProject/Help/*.HxT -DocProject/Help/*.HxC -DocProject/Help/*.hhc -DocProject/Help/*.hhk -DocProject/Help/*.hhp -DocProject/Help/Html2 -DocProject/Help/html - -# Click-Once directory -publish/ - -# Publish Web Output -*.[Pp]ublish.xml -*.azurePubxml -# Note: Comment the next line if you want to checkin your web deploy settings, -# but database connection strings (with potential passwords) will be unencrypted -*.pubxml -*.publishproj - -# Microsoft Azure Web App publish settings. Comment the next line if you want to -# checkin your Azure Web App publish settings, but sensitive information contained -# in these scripts will be unencrypted -PublishScripts/ - -# NuGet Packages -*.nupkg -# The packages folder can be ignored because of Package Restore -**/[Pp]ackages/* -# except build/, which is used as an MSBuild target. -!**/[Pp]ackages/build/ -# Uncomment if necessary however generally it will be regenerated when needed -#!**/[Pp]ackages/repositories.config -# NuGet v3's project.json files produces more ignorable files -*.nuget.props -*.nuget.targets - -# Microsoft Azure Build Output -csx/ -*.build.csdef - -# Microsoft Azure Emulator -ecf/ -rcf/ - -# Windows Store app package directories and files -AppPackages/ -BundleArtifacts/ -Package.StoreAssociation.xml -_pkginfo.txt -*.appx - -# Visual Studio cache files -# files ending in .cache can be ignored -*.[Cc]ache -# but keep track of directories ending in .cache -!*.[Cc]ache/ - -# Others -ClientBin/ -~$* -*~ -*.dbmdl -*.dbproj.schemaview -*.jfm -*.pfx -*.publishsettings -orleans.codegen.cs - -# Including strong name files can present a security risk -# (https://github.com/github/gitignore/pull/2483#issue-259490424) -#*.snk - -# Since there are multiple workflows, uncomment next line to ignore bower_components -# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) -#bower_components/ - -# RIA/Silverlight projects -Generated_Code/ - -# Backup & report files from converting an old project file -# to a newer Visual Studio version. Backup files are not needed, -# because we have git ;-) -_UpgradeReport_Files/ -Backup*/ -UpgradeLog*.XML -UpgradeLog*.htm -ServiceFabricBackup/ -*.rptproj.bak - -# SQL Server files -*.mdf -*.ldf -*.ndf - -# Business Intelligence projects -*.rdl.data -*.bim.layout -*.bim_*.settings -*.rptproj.rsuser - -# Microsoft Fakes -FakesAssemblies/ - -# GhostDoc plugin setting file -*.GhostDoc.xml - -# Node.js Tools for Visual Studio -.ntvs_analysis.dat -node_modules/ - -# Visual Studio 6 build log -*.plg - -# Visual Studio 6 workspace options file -*.opt - -# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) -*.vbw - -# Visual Studio LightSwitch build output -**/*.HTMLClient/GeneratedArtifacts -**/*.DesktopClient/GeneratedArtifacts -**/*.DesktopClient/ModelManifest.xml -**/*.Server/GeneratedArtifacts -**/*.Server/ModelManifest.xml -_Pvt_Extensions - -# Paket dependency manager -.paket/paket.exe -paket-files/ - -# FAKE - F# Make -.fake/ - -# JetBrains Rider -.idea/ -*.sln.iml - -# CodeRush -.cr/ - -# Python Tools for Visual Studio (PTVS) -__pycache__/ -*.pyc - -# Cake - Uncomment if you are using it -# tools/** -# !tools/packages.config - -# Tabs Studio -*.tss - -# Telerik's JustMock configuration file -*.jmconfig - -# BizTalk build output -*.btp.cs -*.btm.cs -*.odx.cs -*.xsd.cs - -# OpenCover UI analysis results -OpenCover/ - -# Azure Stream Analytics local run output -ASALocalRun/ - -# MSBuild Binary and Structured Log -*.binlog - -# NVidia Nsight GPU debugger configuration file -*.nvuser - -# MFractors (Xamarin productivity tool) working folder -.mfractor/ - -# macOS -.DS_Store -.AppleDouble -.LSOverride +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. +## +## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore + +# User-specific files +*.suo +*.swo +*.user +*.userosscache +*.sln.docstates +*.ncrunchproject +*.ncrunchsolution +*.swp + +# Project XML documentation +BlazorWebFormsComponents.xml + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs +.ionide/ +.vscode/ + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +bld/ +[Bb]in/ +[Oo]bj/ +[Ll]og/ + +# Visual Studio 2015/2017 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# Visual Studio 2017 auto generated files +Generated\ Files/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUNIT +*.VisualState.xml +TestResult.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# Benchmark Results +BenchmarkDotNet.Artifacts/ + +# .NET Core +project.lock.json +project.fragment.lock.json +artifacts/ +**/Properties/launchSettings.json + +# StyleCop +StyleCopReport.xml + +# Files built by Visual Studio +*_i.c +*_p.c +*_i.h +*.ilk +*.meta +*.obj +*.iobj +*.pch +*.pdb +*.ipdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# Visual Studio Trace Files +*.e2e + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# JustCode is a .NET coding add-in +.JustCode + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# AxoCover is a Code Coverage Tool +.axoCover/* +!.axoCover/settings.json + +# Visual Studio code coverage results +*.coverage +*.coveragexml + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# Note: Comment the next line if you want to checkin your web deploy settings, +# but database connection strings (with potential passwords) will be unencrypted +*.pubxml +*.publishproj + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ + +# NuGet Packages +*.nupkg +# The packages folder can be ignored because of Package Restore +**/[Pp]ackages/* +# except build/, which is used as an MSBuild target. +!**/[Pp]ackages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/[Pp]ackages/repositories.config +# NuGet v3's project.json files produces more ignorable files +*.nuget.props +*.nuget.targets + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directories and files +AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt +*.appx + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.jfm +*.pfx +*.publishsettings +orleans.codegen.cs + +# Including strong name files can present a security risk +# (https://github.com/github/gitignore/pull/2483#issue-259490424) +#*.snk + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) +#bower_components/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm +ServiceFabricBackup/ +*.rptproj.bak + +# SQL Server files +*.mdf +*.ldf +*.ndf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings +*.rptproj.rsuser + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat +node_modules/ + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) +*.vbw + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe +paket-files/ + +# FAKE - F# Make +.fake/ + +# JetBrains Rider +.idea/ +*.sln.iml + +# CodeRush +.cr/ + +# Python Tools for Visual Studio (PTVS) +__pycache__/ +*.pyc + +# Cake - Uncomment if you are using it +# tools/** +# !tools/packages.config + +# Tabs Studio +*.tss + +# Telerik's JustMock configuration file +*.jmconfig + +# BizTalk build output +*.btp.cs +*.btm.cs +*.odx.cs +*.xsd.cs + +# OpenCover UI analysis results +OpenCover/ + +# Azure Stream Analytics local run output +ASALocalRun/ + +# MSBuild Binary and Structured Log +*.binlog + +# NVidia Nsight GPU debugger configuration file +*.nvuser + +# MFractors (Xamarin productivity tool) working folder +.mfractor/ + +# macOS +.DS_Store +.AppleDouble +.LSOverride diff --git a/README.md b/README.md index 921785f..d1ebfbc 100644 --- a/README.md +++ b/README.md @@ -20,6 +20,8 @@ Portions of the [original .NET Framework](https://github.com/microsoft/reference There are a significant number of controls in ASP.NET Web Forms, and we will focus on creating components in the following order: + - Editor Controls + - [Button](docs/Button.md) - Data Controls - Chart(?) - [DataList](docs/DataList.md) diff --git a/docs/Button.md b/docs/Button.md new file mode 100644 index 0000000..a7c1b17 --- /dev/null +++ b/docs/Button.md @@ -0,0 +1,3 @@ +# Button + +It may seem strange that we have a Button component when there already is an HTML button and Blazor has features that enable C# interactions with that button, but we need to activate other features that were once present in Web Forms diff --git a/samples/AfterBlazorServerSide/Pages/ControlSamples/Button/Index.razor b/samples/AfterBlazorServerSide/Pages/ControlSamples/Button/Index.razor new file mode 100644 index 0000000..542355e --- /dev/null +++ b/samples/AfterBlazorServerSide/Pages/ControlSamples/Button/Index.razor @@ -0,0 +1,37 @@ +@page "/ControlSamples/Button" + +

Button component home page

+ + + +@TheContent + +@code { + + [Parameter] + public string TheContent { get; set; } = "Not clicked yet!"; + public string bar = "bar"; + +/* + protected override void OnInitialized() { + TheContent = "Initialized"; + Console.WriteLine("Initialized"); + } +*/ + + void OnClick() { + + TheContent = "I've been clicked"; + + } + + void OnCommand(CommandEventArgs args) { + + Console.WriteLine("Command fired"); + TheContent = $"Command '{args.CommandName}'"; + + } + + + +} diff --git a/samples/AfterBlazorServerSide/Shared/NavMenu.razor b/samples/AfterBlazorServerSide/Shared/NavMenu.razor index 55a4ae5..1224e11 100644 --- a/samples/AfterBlazorServerSide/Shared/NavMenu.razor +++ b/samples/AfterBlazorServerSide/Shared/NavMenu.razor @@ -14,6 +14,10 @@ + + + + diff --git a/src/BlazorWebFormsComponents.Test/BaseWebFormsComponent/Parent.razor b/src/BlazorWebFormsComponents.Test/BaseWebFormsComponent/Parent.razor new file mode 100644 index 0000000..53e873a --- /dev/null +++ b/src/BlazorWebFormsComponents.Test/BaseWebFormsComponent/Parent.razor @@ -0,0 +1,41 @@ +@inherits TestComponentBase + + + + @* + @Item.Name + +
+ Header +
+ @itemPlaceholder +
+
+
+ *@ + + + +
+
+ +@code { + +Button MyButton { get; set; } + +void FirstTest() { + + var cut = GetComponentUnderTest(); + cut.FindAll("button").Count().ShouldNotBe(0); + + MyButton.ShouldNotBeNull(); + MyButton.Parent.ShouldNotBeNull("Cannot find the Parent component"); + +} + +} diff --git a/src/BlazorWebFormsComponents.Test/Button/Click.razor b/src/BlazorWebFormsComponents.Test/Button/Click.razor new file mode 100644 index 0000000..29dbbad --- /dev/null +++ b/src/BlazorWebFormsComponents.Test/Button/Click.razor @@ -0,0 +1,37 @@ +@inherits TestComponentBase + + + + + + + + + +@code { + + public string TheContent { get; set; } = "Not clicked yet!"; + + void OnClick() + { + + TheContent = "I've been clicked"; + + } + + void FirstTest() + { + // Given + var cut = GetComponentUnderTest(); + + TheContent.ShouldBe("Not clicked yet!"); + + // When + cut.Find("button").Click(); + + // Then + TheContent.ShouldBe("I've been clicked"); + + } + +} diff --git a/src/BlazorWebFormsComponents.Test/Button/Command.razor b/src/BlazorWebFormsComponents.Test/Button/Command.razor new file mode 100644 index 0000000..5320d78 --- /dev/null +++ b/src/BlazorWebFormsComponents.Test/Button/Command.razor @@ -0,0 +1,38 @@ +@inherits TestComponentBase + + + + + + + + + +@code { + + public string TheContent { get; set; } = "No Command yet!"; + public string CommandArgument = "bar"; + + void OnCommand(CommandEventArgs args) + { + + TheContent = $"Command '{args.CommandName}' : '{args.CommandArgument.ToString()}'"; + + } + + void FirstTest() + { + // Given + var cut = GetComponentUnderTest(); + + TheContent.ShouldBe("No Command yet!"); + + // When + cut.Find("button").Click(); + + // Then + TheContent.ShouldBe("Command 'Foo' : 'bar'"); + + } + +} diff --git a/src/BlazorWebFormsComponents/BaseWebFormsComponent.cs b/src/BlazorWebFormsComponents/BaseWebFormsComponent.cs index 35eb21e..caaf4ca 100644 --- a/src/BlazorWebFormsComponents/BaseWebFormsComponent.cs +++ b/src/BlazorWebFormsComponents/BaseWebFormsComponent.cs @@ -1,248 +1,242 @@ -using Microsoft.AspNetCore.Components; -using System; -using System.Collections.Generic; -using System.ComponentModel; -using System.Text; -using System.Threading.Tasks; - -namespace BlazorWebFormsComponents -{ - - public abstract class BaseWebFormsComponent : ComponentBase, IAsyncDisposable - { - - #region Obsolete Attributes / Properties - - /// - /// 🚨🚨 Use @ref instead of ID 🚨🚨 - /// - [Parameter, Obsolete("Use @ref instead of ID")] - public string ID { get; set; } - - /// - /// While ViewState is supported by this library, this parameter does nothing - /// - [Parameter(), Obsolete("ViewState is supported, but EnableViewState does nothing")] - public bool EnableViewState { get; set; } - - /// - /// 🚨🚨 runat is not available in Blazor 🚨🚨 - /// - [Parameter(), Obsolete("runat is not available in Blazor")] - public string runat { get; set; } - - /// - /// 🚨🚨 DataKeys are not used in Blazor 🚨🚨 - /// - [Parameter(), Obsolete("DataKeys are not used in Blazor")] - public string DataKeys { get; set; } - - /// - /// 🚨🚨 DataSource controls are not used in Blazor 🚨🚨 - /// - [Parameter, Obsolete("DataSource controls are not used in Blazor")] - public string DataSourceID { get; set; } - - /// - /// 🚨🚨 Theming is not available in Blazor 🚨🚨 - /// - [Parameter, Obsolete("Theming is not available in Blazor")] - public bool EnableTheming { get; set; } - - /// - /// 🚨🚨 Theming is not available in Blazor 🚨🚨 - /// - [Parameter, Obsolete("Theming is not available in Blazor")] - public bool SkinID { get; set; } - - #endregion - - [Parameter] - public bool Enabled { get; set; } = true; - - [Parameter] - public short TabIndex { get; set; } - - /// - /// ViewState is supported for compatibility with those components and pages that add and retrieve items from ViewState.!-- It is not binary compatible, but is syntax compatible - /// - /// - [Obsolete("ViewState is supported for compatibility and is discouraged for future use")] - public Dictionary ViewState { get; } = new Dictionary(); - - /// - /// Is the content of this component rendered and visible to your users? - /// - [Parameter] - public bool Visible { - get - { - return visible; - } - set - { - visible = value; - StateHasChanged(); - } - } - - [Obsolete("This method doesn't do anything in Blazor")] - public void DataBind() { } - - /// - /// 🚨🚨 Placeholders are not available in Blazor 🚨🚨 - /// - [Parameter, Obsolete("Placeholders are not available in Blazor")] - - public string ItemPlaceholderID { get; set; } - - - [Parameter(CaptureUnmatchedValues = true)] - public Dictionary AdditionalAttributes { get; set; } - - #region Custom Events - - /// - /// Event handler to mimic the Web Forms OnInit handler, and is triggered at the beginning of the OnInitialize Blazor event - /// - [Parameter] - public EventCallback OnInit { get; set; } - - /// - /// Event handler to mimic the Web Forms OnLoad handler, and is triggered at the end of the OnInitialize Blazor event - /// - [Parameter] - public EventCallback OnLoad { get; set; } - - /// - /// Event handler to mimic the Web Forms OnPreRender handler, and is triggered at the end of the OnInitialize Blazor event after Load - /// - [Parameter] - public EventCallback OnPreRender { get; set; } - - /// - /// Event handler to mimic the Web Forms OnUnload handler, and is triggered at the end of the OnAfterRender Blazor event - /// - [Parameter] - public EventCallback OnUnload { get; set; } - private bool _UnloadTriggered = false; - - /// - /// Event handler to mimic the Web Forms OnDisposed handler and triggered in the Dispose method of this class - /// - [Parameter] - public EventCallback OnDisposed { get; set; } - - #endregion - - #region Blazor Events - - protected override void OnInitialized() - { - // Adds this component to its parent's children - ParentControl?.Controls.Add(this); - base.OnInitialized(); - } - - protected override async Task OnInitializedAsync() - { - - if (OnInit.HasDelegate) - await OnInit.InvokeAsync(EventArgs.Empty); - - await base.OnInitializedAsync(); - - if (OnLoad.HasDelegate) - await OnLoad.InvokeAsync(EventArgs.Empty); - - if (OnPreRender.HasDelegate) - await OnPreRender.InvokeAsync(EventArgs.Empty); - - } - - protected override async Task OnAfterRenderAsync(bool firstRender) - { - - await base.OnAfterRenderAsync(firstRender); - - if (OnUnload.HasDelegate && !_UnloadTriggered) - { - await OnUnload.InvokeAsync(EventArgs.Empty); - _UnloadTriggered = true; - } - - if (firstRender) - { - - HandleUnknownAttributes(); - StateHasChanged(); - - } - - } - - protected virtual void HandleUnknownAttributes() { } - - - #endregion - - #region IDisposable Support - private bool disposedValue = false; // To detect redundant calls - private bool visible = true; - - protected virtual async ValueTask Dispose(bool disposing) - { - if (!disposedValue) - { - if (disposing) - { - if (OnDisposed.HasDelegate) - { - await OnDisposed.InvokeAsync(EventArgs.Empty); - } - } - // Remove this control from its parent control - ParentControl?.Controls.Remove(this); - disposedValue = true; - } - } - - ~BaseWebFormsComponent() - { - // Do not change this code. Put cleanup code in Dispose(bool disposing) above. - Dispose(false).GetAwaiter().GetResult(); - } - - // This code added to correctly implement the disposable pattern. - public ValueTask DisposeAsync() - { - GC.SuppressFinalize(this); - return Dispose(true); - } - #endregion - - public bool LayoutTemplateRendered { get; set; } = false; - - - /// - /// The component's parent - /// - [CascadingParameter(Name = "ParentControl")] public BaseWebFormsComponent ParentControl { get; set; } - - /// - /// The list of child controls - /// - public List Controls { get; set; } = new List(); - - /// - /// Finds a child control by its ID - /// - /// the ID of the child - /// - public BaseWebFormsComponent FindControl(string controlId) - { - return Controls.Find(control => control.ID == controlId); - } - } - -} +using Microsoft.AspNetCore.Components; +using Microsoft.AspNetCore.Components.Rendering; +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Text; +using System.Threading.Tasks; +using System.Reflection; +using IComponent = Microsoft.AspNetCore.Components.IComponent; + +namespace BlazorWebFormsComponents +{ + + public abstract class BaseWebFormsComponent : ComponentBase, IAsyncDisposable + { + + #region Obsolete Attributes / Properties + + /// + /// 🚨🚨 Use @ref instead of ID 🚨🚨 + /// + [Parameter, Obsolete("Use @ref instead of ID")] + public string ID { get; set; } + + /// + /// While ViewState is supported by this library, this parameter does nothing + /// + [Parameter(), Obsolete("ViewState is supported, but EnableViewState does nothing")] + public bool EnableViewState { get; set; } + + /// + /// 🚨🚨 runat is not available in Blazor 🚨🚨 + /// + [Parameter(), Obsolete("runat is not available in Blazor")] + public string runat { get; set; } + + /// + /// 🚨🚨 DataKeys are not used in Blazor 🚨🚨 + /// + [Parameter(), Obsolete("DataKeys are not used in Blazor")] + public string DataKeys { get; set; } + + /// + /// 🚨🚨 DataSource controls are not used in Blazor 🚨🚨 + /// + [Parameter, Obsolete("DataSource controls are not used in Blazor")] + public string DataSourceID { get; set; } + + /// + /// 🚨🚨 Theming is not available in Blazor 🚨🚨 + /// + [Parameter, Obsolete("Theming is not available in Blazor")] + public bool EnableTheming { get; set; } + + /// + /// 🚨🚨 Theming is not available in Blazor 🚨🚨 + /// + [Parameter, Obsolete("Theming is not available in Blazor")] + public bool SkinID { get; set; } + + #endregion + + [Parameter] + public bool Enabled { get; set; } = true; + + [CascadingParameter(Name= PARENTCOMPONENTNAME)] + public virtual BaseWebFormsComponent Parent { get; set; } + + [Parameter] + public short TabIndex { get; set; } + + /// + /// ViewState is supported for compatibility with those components and pages that add and retrieve items from ViewState.!-- It is not binary compatible, but is syntax compatible + /// + /// + [Obsolete("ViewState is supported for compatibility and is discouraged for future use")] + public Dictionary ViewState { get; } = new Dictionary(); + + /// + /// Is the content of this component rendered and visible to your users? + /// + [Parameter] + public bool Visible { get; set; } = true; + + [Obsolete("This method doesn't do anything in Blazor")] + public void DataBind() { } + + /// + /// 🚨🚨 Placeholders are not available in Blazor 🚨🚨 + /// + [Parameter, Obsolete("Placeholders are not available in Blazor")] + + public string ItemPlaceholderID { get; set; } + + + [Parameter(CaptureUnmatchedValues = true)] + public Dictionary AdditionalAttributes { get; set; } + + #region Custom Events + + /// + /// Event handler to mimic the Web Forms OnInit handler, and is triggered at the beginning of the OnInitialize Blazor event + /// + [Parameter] + public EventCallback OnInit { get; set; } + + /// + /// Event handler to mimic the Web Forms OnLoad handler, and is triggered at the end of the OnInitialize Blazor event + /// + [Parameter] + public EventCallback OnLoad { get; set; } + + /// + /// Event handler to mimic the Web Forms OnPreRender handler, and is triggered at the end of the OnInitialize Blazor event after Load + /// + [Parameter] + public EventCallback OnPreRender { get; set; } + + /// + /// Event handler to mimic the Web Forms OnUnload handler, and is triggered at the end of the OnAfterRender Blazor event + /// + [Parameter] + public EventCallback OnUnload { get; set; } + private bool _UnloadTriggered = false; + + /// + /// Event handler to mimic the Web Forms OnDisposed handler and triggered in the Dispose method of this class + /// + [Parameter] + public EventCallback OnDisposed { get; set; } + + #endregion + + [Parameter] + public RenderFragment ChildComponents { get; set; } + + #region Blazor Events + + protected override async Task OnInitializedAsync() + { + + if (OnInit.HasDelegate) + await OnInit.InvokeAsync(EventArgs.Empty); + + await base.OnInitializedAsync(); + + if (OnLoad.HasDelegate) + await OnLoad.InvokeAsync(EventArgs.Empty); + + if (OnPreRender.HasDelegate) + await OnPreRender.InvokeAsync(EventArgs.Empty); + + } + + protected override async Task OnAfterRenderAsync(bool firstRender) + { + + await base.OnAfterRenderAsync(firstRender); + + if (OnUnload.HasDelegate && !_UnloadTriggered) + { + await OnUnload.InvokeAsync(EventArgs.Empty); + _UnloadTriggered = true; + } + + if (firstRender) + { + + HandleUnknownAttributes(); + StateHasChanged(); + + } + + } + + protected virtual void HandleUnknownAttributes() { } + + + #endregion + + #region IDisposable Support + private bool disposedValue = false; // To detect redundant calls + + protected virtual async ValueTask Dispose(bool disposing) + { + if (!disposedValue) + { + if (disposing) + { + if (OnDisposed.HasDelegate) + { + await OnDisposed.InvokeAsync(EventArgs.Empty); + } + } + + disposedValue = true; + } + } + + ~BaseWebFormsComponent() + { + // Do not change this code. Put cleanup code in Dispose(bool disposing) above. + Dispose(false).GetAwaiter().GetResult(); + } + + // This code added to correctly implement the disposable pattern. + public ValueTask DisposeAsync() + { + GC.SuppressFinalize(this); + return Dispose(true); + } + #endregion + + public bool LayoutTemplateRendered { get; set; } = false; + private const string BASEFRAGMENTFIELDNAME = "_renderFragment"; + private const string PARENTCOMPONENTNAME = "ParentComponent"; + + // Get Access to the ComponentBase field we need to wrap every component in a CascadingValue + private static readonly FieldInfo _renderFragmentField = typeof(ComponentBase).GetField(BASEFRAGMENTFIELDNAME, BindingFlags.NonPublic | BindingFlags.Instance); + private readonly RenderFragment _baseRenderFragment; + + public BaseWebFormsComponent () + { + // Grab a copy of the default RenderFragment to go into the CascadingValue + _baseRenderFragment = (RenderFragment)_renderFragmentField.GetValue(this); + + // Override the default RenderFragment with our Special Sauce version + _renderFragmentField.SetValue(this, (RenderFragment)ParentWrappingBuildRenderTree); + + void ParentWrappingBuildRenderTree(RenderTreeBuilder builder) + { + builder.OpenComponent(1, typeof(CascadingValue)); + builder.AddAttribute(2, nameof(CascadingValue.Name), PARENTCOMPONENTNAME); + builder.AddAttribute(3, nameof(CascadingValue.Value), this); + builder.AddAttribute(4, nameof(CascadingValue.ChildContent), _baseRenderFragment); + builder.AddAttribute(5, nameof(CascadingValue.IsFixed), true); + builder.CloseComponent(); + } + } + } + +} diff --git a/src/BlazorWebFormsComponents/BlazorWebFormsComponents.csproj b/src/BlazorWebFormsComponents/BlazorWebFormsComponents.csproj index 50b84fe..1714eda 100644 --- a/src/BlazorWebFormsComponents/BlazorWebFormsComponents.csproj +++ b/src/BlazorWebFormsComponents/BlazorWebFormsComponents.csproj @@ -1,33 +1,33 @@ - - - - netstandard2.1 - 3.0 - 8.0 - BlazorWebFormsComponents.xml - - - - Fritz.BlazorWebFormsComponents - Jeffrey T. Fritz - A collection of Blazor components based on the default controls that ship with ASP.NET Web Forms - Copyright Jeffrey T. Fritz 2019,2020 - MIT - https://github.com/FritzAndFriends/BlazorWebFormsComponents - blazor - - - - - - - - - - - - - - - - + + + + netstandard2.1 + 3.0 + 8.0 + BlazorWebFormsComponents.xml + + + + Fritz.BlazorWebFormsComponents + Jeffrey T. Fritz + A collection of Blazor components based on the default controls that ship with ASP.NET Web Forms + Copyright Jeffrey T. Fritz 2019,2020 + MIT + https://github.com/FritzAndFriends/BlazorWebFormsComponents + blazor + + + + + + + + + + + + + + + + diff --git a/src/BlazorWebFormsComponents/Button.razor b/src/BlazorWebFormsComponents/Button.razor index b950da4..18dd414 100644 --- a/src/BlazorWebFormsComponents/Button.razor +++ b/src/BlazorWebFormsComponents/Button.razor @@ -1,4 +1 @@ -@inherits BaseWebFormsComponent - - - + \ No newline at end of file diff --git a/src/BlazorWebFormsComponents/Button.razor.cs b/src/BlazorWebFormsComponents/Button.razor.cs index 38cc41f..1d58043 100644 --- a/src/BlazorWebFormsComponents/Button.razor.cs +++ b/src/BlazorWebFormsComponents/Button.razor.cs @@ -1,21 +1,36 @@ -using Microsoft.AspNetCore.Components; -using System; -using System.Threading.Tasks; - -namespace BlazorWebFormsComponents -{ - public partial class Button : BaseWebFormsComponent - { - [Parameter] public string Text { get; set; } - - [Parameter] - public Action OnClick{ get; set; } - - private async Task TriggerClick() - { - OnClick?.Invoke(); - } - - - } -} +using Microsoft.AspNetCore.Components; +using Microsoft.AspNetCore.Components.Web; +using System; + +namespace BlazorWebFormsComponents { + + public partial class Button : BaseWebFormsComponent { + + [Parameter] + public EventCallback OnCommand { get; set; } + + [Parameter] + public string CommandName { get; set; } + + [Parameter] + public object CommandArgument { get; set; } + + [Parameter] + public EventCallback OnClick { get; set; } + + [Parameter] + public RenderFragment ChildContent { get; set; } + + protected void Click() { + + if (OnCommand.HasDelegate) { + OnCommand.InvokeAsync(new CommandEventArgs(CommandName, CommandArgument)); + } else { + OnClick.InvokeAsync(new MouseEventArgs()); + } + + } + + } + +} \ No newline at end of file diff --git a/src/BlazorWebFormsComponents/CommandEventArgs.cs b/src/BlazorWebFormsComponents/CommandEventArgs.cs new file mode 100644 index 0000000..ca83277 --- /dev/null +++ b/src/BlazorWebFormsComponents/CommandEventArgs.cs @@ -0,0 +1,27 @@ +using System; + +namespace BlazorWebFormsComponents { + + public class CommandEventArgs : EventArgs { + + public CommandEventArgs(string commandName, object commandArgument) { + + CommandName = commandName; + CommandArgument = commandArgument; + + } + + public CommandEventArgs(CommandEventArgs args) { + + this.CommandName = args.CommandName; + this.CommandArgument = args.CommandArgument; + + } + + public string CommandName { get; set; } + + public object CommandArgument { get; set; } + + } + +} diff --git a/src/BlazorWebFormsComponents/TreeNode.razor b/src/BlazorWebFormsComponents/TreeNode.razor index 49b7ebf..45dbe13 100644 --- a/src/BlazorWebFormsComponents/TreeNode.razor +++ b/src/BlazorWebFormsComponents/TreeNode.razor @@ -1,90 +1,91 @@ -@{ - string nodeText = Expanded ? $"Collapse {Text}" : $"Expand {Text}"; -} -@if ((Parent?.Expanded) ?? true) -{ - - - @{ - var currentParent = Parent; - var indentBuilder = new System.Text.StringBuilder(); - var indentItems = new List(); - } - @while (currentParent != null) - { - indentBuilder.Append(""); - indentItems.Add(indentBuilder.ToString()); - indentBuilder.Clear(); - currentParent = currentParent.Parent; - } - @((MarkupString)string.Join("",indentItems.ToArray().Reverse())) - - - @if ((IsRoot || IsParent) && ParentTreeView.ShowExpandCollapse && ChildContent != null) - { - - } - else if (!(Parent is null)) // && ParentTreeView.ShowExpandCollapse) - { - - } - - @if (!string.IsNullOrEmpty(ImageUrl)) - { - - if (string.IsNullOrEmpty(NavigateUrl)) - { - - } - else - { - - } - } - - - -
"); - if (!ParentTreeView.ShowLines || currentParent.Parent == null) - { - indentBuilder.Append(@"
"); - } - else if (!currentParent?.IsLast() ?? false) - { - indentBuilder.Append($""); - } else - { - indentBuilder.Append(@"
"); - } - indentBuilder.Append("
- - @nodeText - - - @ImageToolTip - - - @ImageToolTip - - - - @if (ParentTreeView.ShowCheckBoxes != Enums.TreeNodeTypes.None) - { - - var isRoot = (ParentTreeView.ShowCheckBoxes.HasFlag(Enums.TreeNodeTypes.Root) && Depth == 0); - var isParent = ParentTreeView.ShowCheckBoxes.HasFlag(Enums.TreeNodeTypes.Parent) && (Depth > 0 && !(ChildContent is null)); - var isLeaf = ParentTreeView.ShowCheckBoxes.HasFlag(Enums.TreeNodeTypes.Leaf) && (ChildContent is null); - - @if (ShowCheckBox && (isRoot || isParent || isLeaf)) - { - - } - - } - - @Text -
- - @ChildContent - -} +ο»Ώ@inherits ComponentBase +@{ + string nodeText = Expanded ? $"Collapse {Text}" : $"Expand {Text}"; +} +@if ((Parent?.Expanded) ?? true) +{ + + + @{ + var currentParent = Parent; + var indentBuilder = new System.Text.StringBuilder(); + var indentItems = new List(); + } + @while (currentParent != null) + { + indentBuilder.Append(""); + indentItems.Add(indentBuilder.ToString()); + indentBuilder.Clear(); + currentParent = currentParent.Parent; + } + @((MarkupString)string.Join("",indentItems.ToArray().Reverse())) + + + @if ((IsRoot || IsParent) && ParentTreeView.ShowExpandCollapse && ChildContent != null) + { + + } + else if (!(Parent is null)) // && ParentTreeView.ShowExpandCollapse) + { + + } + + @if (!string.IsNullOrEmpty(ImageUrl)) + { + + if (string.IsNullOrEmpty(NavigateUrl)) + { + + } + else + { + + } + } + + + +
"); + if (!ParentTreeView.ShowLines || currentParent.Parent == null) + { + indentBuilder.Append(@"
"); + } + else if (!currentParent?.IsLast() ?? false) + { + indentBuilder.Append($""); + } else + { + indentBuilder.Append(@"
"); + } + indentBuilder.Append("
+ + @nodeText + + + @ImageToolTip + + + @ImageToolTip + + + + @if (ParentTreeView.ShowCheckBoxes != Enums.TreeNodeTypes.None) + { + + var isRoot = (ParentTreeView.ShowCheckBoxes.HasFlag(Enums.TreeNodeTypes.Root) && Depth == 0); + var isParent = ParentTreeView.ShowCheckBoxes.HasFlag(Enums.TreeNodeTypes.Parent) && (Depth > 0 && !(ChildContent is null)); + var isLeaf = ParentTreeView.ShowCheckBoxes.HasFlag(Enums.TreeNodeTypes.Leaf) && (ChildContent is null); + + @if (ShowCheckBox && (isRoot || isParent || isLeaf)) + { + + } + + } + + @Text +
+ + @ChildContent + +} diff --git a/src/BlazorWebFormsComponents/TreeNode.razor.cs b/src/BlazorWebFormsComponents/TreeNode.razor.cs index 2f51f84..85159eb 100644 --- a/src/BlazorWebFormsComponents/TreeNode.razor.cs +++ b/src/BlazorWebFormsComponents/TreeNode.razor.cs @@ -1,215 +1,214 @@ -ο»Ώusing Microsoft.AspNetCore.Components; -using System; -using System.ComponentModel; -using System.Threading.Tasks; -using BlazorWebFormsComponents.Enums; -using System.Collections.Generic; -using System.Linq; -using System.Runtime.CompilerServices; - -namespace BlazorWebFormsComponents -{ - public partial class TreeNode : BaseWebFormsComponent - { - - public const string ImageLocation = "_content/Fritz.BlazorWebFormsComponents/TreeView/"; - - [Parameter] - public bool Checked { get; set; } = false; - - // TODO: Implement - [Parameter] - public byte Depth { get; set; } = 0; - - [Parameter] - public bool Expanded { get; set; } = true; - - // TODO: Implement - [Parameter] - public string FormatString { get; set; } - - [Parameter] - public string ImageToolTip { get; set; } - - private string _ImageUrl; - [Parameter] - public string ImageUrl { - get { return !String.IsNullOrEmpty(_ImageUrl) ? _ImageUrl : - string.IsNullOrEmpty(ParentTreeView.ImageSet.RootNode) ? "" : - (IsRoot ? $"{ImageLocation}{ParentTreeView.ImageSet.RootNode}" : - IsParent ? $"{ImageLocation}{ParentTreeView.ImageSet.ParentNode}" : - $"{ImageLocation}{ParentTreeView.ImageSet.LeafNode}"); - } - set { _ImageUrl = value; } - } - - [Parameter] - public string NavigateUrl { get; set; } - - // TODO: Implement - [Parameter] - public bool PopulateOnDemand { get; set; } - - // TODO: Implement - [Parameter] - public TreeNodeSelectAction SelectAction { get; set; } - - // TODO: Implement - [Parameter] - public bool Selected { get; set; } - - [Parameter] - public bool ShowCheckBox { get; set; } = true; - - [Parameter] - public string Target { get; set; } - - [Parameter] - public string Text { get; set; } - - [Parameter] - public string ToolTip { get; set; } = null; - - [Parameter] - public string Value { get; set; } - - [Parameter] - public RenderFragment ChildContent { get; set; } - - private TreeNode _Parent; - [CascadingParameter(Name = "ParentTreeNode")] - public TreeNode Parent { - get { return _Parent; } - set { - _Parent = value; - Depth = (byte)((_Parent?.Depth ?? 0) + 1); - } - } - - [CascadingParameter(Name = "ParentTreeView")] - public TreeView ParentTreeView { get; set; } - - private HashSet _ChildNodes = new HashSet(); - - protected void AddChildNode(TreeNode node) - { - - _ChildNodes.Add(node); - } - - protected bool IsLast() - { - return (Parent?._ChildNodes?.Last() == this); - } - - - protected bool IsRoot - { - get { return Parent is null; } - } - - protected bool IsParent - { - get { return !IsRoot && (ChildContent != null); } - } - - protected string NodeImage - { - get - { - - if (IsRoot) - { - - if (ParentTreeView.ShowLines && ParentTreeView.ShowExpandCollapse) - { - return (Expanded ? $"{ImageLocation}Default_DashCollapse.gif" : $"{ImageLocation}Default_DashExpand.gif"); - } - - if (ParentTreeView.ShowLines) - { - return $"{ImageLocation}Default_Dash.gif"; - } - - return string.IsNullOrEmpty(ParentTreeView.ImageSet.Collapse) ? $"{ImageLocation}Default_NoExpand.gif" : (Expanded ? $"{ImageLocation}{ParentTreeView.ImageSet.Collapse}" : $"{ImageLocation}{ParentTreeView.ImageSet.Expand}"); - //return $"{ImageLocation}Default_NoExpand.gif"; - - } - - else if (IsParent) - { - - if (ParentTreeView.ShowLines && ParentTreeView.ShowExpandCollapse && IsLast()) - { - return (Expanded ? $"{ImageLocation}Default_LCollapse.gif" : $"{ImageLocation}Default_LExpand.gif"); - } - - if (ParentTreeView.ShowLines && ParentTreeView.ShowExpandCollapse) - { - return (Expanded ? $"{ImageLocation}Default_TCollapse.gif" : $"{ImageLocation}Default_TExpand.gif"); - } - - return string.IsNullOrEmpty(ParentTreeView.ImageSet.Collapse) ? $"{ImageLocation}Default_NoExpand.gif" : (Expanded ? $"{ImageLocation}{ParentTreeView.ImageSet.Collapse}" : $"{ImageLocation}{ParentTreeView.ImageSet.Expand}"); - - } - - return string.IsNullOrEmpty(ParentTreeView.ImageSet.Collapse) ? $"{ImageLocation}Default_NoExpand.gif" : (Expanded ? $"{ImageLocation}{ParentTreeView.ImageSet.Collapse}" : $"{ImageLocation}{ParentTreeView.ImageSet.Expand}"); - } - } - - protected string NoExpandImage - { - get - { - - if (ParentTreeView.ShowLines) - { - - return IsLast() ? $"{ImageLocation}Default_L.gif" : $"{ImageLocation}Default_T.gif"; - - } - - return string.IsNullOrEmpty(ParentTreeView.ImageSet.Collapse) ? "" : $"{ImageLocation}{ParentTreeView.ImageSet.NoExpand}"; - } - } - - #region Event Handlers - - protected override Task OnInitializedAsync() - { - - Parent?.AddChildNode(this); - - return base.OnInitializedAsync(); - } - - protected override void OnParametersSet() { - - if (!ParentTreeView.Nodes.Contains(this)) - { - ParentTreeView.Nodes.Add(this); - } - - } - - public void HandleNodeExpand() { - - Expanded = !Expanded; - - if (Expanded) ParentTreeView.OnTreeNodeExpanded.InvokeAsync(new TreeNodeEventArgs(this)); - else ParentTreeView.OnTreeNodeCollapsed.InvokeAsync(new TreeNodeEventArgs(this)); - - } - - public void HandleCheckbox(object sender, ChangeEventArgs args) { - - this.Checked = (bool)args.Value; - - ParentTreeView.OnTreeNodeCheckChanged.InvokeAsync(new TreeNodeEventArgs(this)); - - } - - #endregion - - } -} +ο»Ώusing Microsoft.AspNetCore.Components; +using System; +using System.ComponentModel; +using System.Threading.Tasks; +using BlazorWebFormsComponents.Enums; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.CompilerServices; + +namespace BlazorWebFormsComponents +{ + public partial class TreeNode : ComponentBase + { + + public const string ImageLocation = "_content/Fritz.BlazorWebFormsComponents/TreeView/"; + + [Parameter] + public bool Checked { get; set; } = false; + + [Parameter] + public byte Depth { get; set; } = 0; + + [Parameter] + public bool Expanded { get; set; } = true; + + // TODO: Implement + [Parameter] + public string FormatString { get; set; } + + [Parameter] + public string ImageToolTip { get; set; } + + private string _ImageUrl; + [Parameter] + public string ImageUrl { + get { return !String.IsNullOrEmpty(_ImageUrl) ? _ImageUrl : + string.IsNullOrEmpty(ParentTreeView.ImageSet.RootNode) ? "" : + (IsRoot ? $"{ImageLocation}{ParentTreeView.ImageSet.RootNode}" : + IsParent ? $"{ImageLocation}{ParentTreeView.ImageSet.ParentNode}" : + $"{ImageLocation}{ParentTreeView.ImageSet.LeafNode}"); + } + set { _ImageUrl = value; } + } + + [Parameter] + public string NavigateUrl { get; set; } + + // TODO: Implement + [Parameter] + public bool PopulateOnDemand { get; set; } + + // TODO: Implement + [Parameter] + public TreeNodeSelectAction SelectAction { get; set; } + + // TODO: Implement + [Parameter] + public bool Selected { get; set; } + + [Parameter] + public bool ShowCheckBox { get; set; } = true; + + [Parameter] + public string Target { get; set; } + + [Parameter] + public string Text { get; set; } + + [Parameter] + public string ToolTip { get; set; } = null; + + [Parameter] + public string Value { get; set; } + + [Parameter] + public RenderFragment ChildContent { get; set; } + + private TreeNode _Parent; + [CascadingParameter(Name = "ParentTreeNode")] + public TreeNode Parent { + get { return _Parent; } + set { + _Parent = value; + Depth = (byte)((_Parent?.Depth ?? 0) + 1); + } + } + + [CascadingParameter(Name = "ParentTreeView")] + public TreeView ParentTreeView { get; set; } + + private HashSet _ChildNodes = new HashSet(); + + protected void AddChildNode(TreeNode node) + { + + _ChildNodes.Add(node); + } + + protected bool IsLast() + { + return (Parent?._ChildNodes?.Last() == this); + } + + + protected bool IsRoot + { + get { return Parent is null; } + } + + protected bool IsParent + { + get { return !IsRoot && (ChildContent != null); } + } + + protected string NodeImage + { + get + { + + if (IsRoot) + { + + if (ParentTreeView.ShowLines && ParentTreeView.ShowExpandCollapse) + { + return (Expanded ? $"{ImageLocation}Default_DashCollapse.gif" : $"{ImageLocation}Default_DashExpand.gif"); + } + + if (ParentTreeView.ShowLines) + { + return $"{ImageLocation}Default_Dash.gif"; + } + + return string.IsNullOrEmpty(ParentTreeView.ImageSet.Collapse) ? $"{ImageLocation}Default_NoExpand.gif" : (Expanded ? $"{ImageLocation}{ParentTreeView.ImageSet.Collapse}" : $"{ImageLocation}{ParentTreeView.ImageSet.Expand}"); + //return $"{ImageLocation}Default_NoExpand.gif"; + + } + + else if (IsParent) + { + + if (ParentTreeView.ShowLines && ParentTreeView.ShowExpandCollapse && IsLast()) + { + return (Expanded ? $"{ImageLocation}Default_LCollapse.gif" : $"{ImageLocation}Default_LExpand.gif"); + } + + if (ParentTreeView.ShowLines && ParentTreeView.ShowExpandCollapse) + { + return (Expanded ? $"{ImageLocation}Default_TCollapse.gif" : $"{ImageLocation}Default_TExpand.gif"); + } + + return string.IsNullOrEmpty(ParentTreeView.ImageSet.Collapse) ? $"{ImageLocation}Default_NoExpand.gif" : (Expanded ? $"{ImageLocation}{ParentTreeView.ImageSet.Collapse}" : $"{ImageLocation}{ParentTreeView.ImageSet.Expand}"); + + } + + return string.IsNullOrEmpty(ParentTreeView.ImageSet.Collapse) ? $"{ImageLocation}Default_NoExpand.gif" : (Expanded ? $"{ImageLocation}{ParentTreeView.ImageSet.Collapse}" : $"{ImageLocation}{ParentTreeView.ImageSet.Expand}"); + } + } + + protected string NoExpandImage + { + get + { + + if (ParentTreeView.ShowLines) + { + + return IsLast() ? $"{ImageLocation}Default_L.gif" : $"{ImageLocation}Default_T.gif"; + + } + + return string.IsNullOrEmpty(ParentTreeView.ImageSet.Collapse) ? "" : $"{ImageLocation}{ParentTreeView.ImageSet.NoExpand}"; + } + } + + #region Event Handlers + + protected override Task OnInitializedAsync() + { + + Parent?.AddChildNode(this); + + return base.OnInitializedAsync(); + } + + protected override void OnParametersSet() { + + if (!ParentTreeView.Nodes.Contains(this)) + { + ParentTreeView.Nodes.Add(this); + } + + } + + public void HandleNodeExpand() { + + Expanded = !Expanded; + + if (Expanded) ParentTreeView.OnTreeNodeExpanded.InvokeAsync(new TreeNodeEventArgs(this)); + else ParentTreeView.OnTreeNodeCollapsed.InvokeAsync(new TreeNodeEventArgs(this)); + + } + + public void HandleCheckbox(object sender, ChangeEventArgs args) { + + this.Checked = (bool)args.Value; + + ParentTreeView.OnTreeNodeCheckChanged.InvokeAsync(new TreeNodeEventArgs(this)); + + } + + #endregion + + } +}