diff --git a/BlazorBootstrap.Demo.RCL/Components/Layout/DemosMainLayout.razor.cs b/BlazorBootstrap.Demo.RCL/Components/Layout/DemosMainLayout.razor.cs index ff436eebf..e6d066b2f 100644 --- a/BlazorBootstrap.Demo.RCL/Components/Layout/DemosMainLayout.razor.cs +++ b/BlazorBootstrap.Demo.RCL/Components/Layout/DemosMainLayout.razor.cs @@ -23,14 +23,15 @@ internal override IEnumerable GetNavItems() new (){ Id = "403", Text = "Date Input", Href = DemoRouteConstants.Demos_URL_DateInput, IconName = IconName.CalendarDate, ParentId = "4" }, new (){ Id = "404", Text = "Enum Input", Href = DemoRouteConstants.Demos_URL_EnumInput, IconName = IconName.MenuButtonWideFill, ParentId = "4" }, new (){ Id = "405", Text = "Number Input", Href = DemoRouteConstants.Demos_URL_NumberInput, IconName = IconName.InputCursor, ParentId = "4" }, - new (){ Id = "406", Text = "Password Input", Href = DemoRouteConstants.Demos_URL_PasswordInput, IconName = IconName.EyeSlashFill, ParentId = "4" }, - new (){ Id = "407", Text = "Radio Input", Href = DemoRouteConstants.Demos_URL_RadioInput, IconName = IconName.RecordCircle, ParentId = "4" }, - new (){ Id = "408", Text = "Range Input", Href = DemoRouteConstants.Demos_URL_RangeInput, IconName = IconName.Sliders, ParentId = "4" }, + new (){ Id = "406", Text = "OTP Input", Href = DemoRouteConstants.Demos_URL_OTPInput, IconName = IconName.Asterisk, ParentId = "4" }, + new (){ Id = "407", Text = "Password Input", Href = DemoRouteConstants.Demos_URL_PasswordInput, IconName = IconName.EyeSlashFill, ParentId = "4" }, + new (){ Id = "408", Text = "Radio Input", Href = DemoRouteConstants.Demos_URL_RadioInput, IconName = IconName.RecordCircle, ParentId = "4" }, + new (){ Id = "409", Text = "Range Input", Href = DemoRouteConstants.Demos_URL_RangeInput, IconName = IconName.Sliders, ParentId = "4" }, //new (){ Id = "404", Text = "Select Input", Href = DemoRouteConstants.Demos_URL_SelectInput, IconName = IconName.MenuButtonWideFill, ParentId = "4" }, - new (){ Id = "409", Text = "Switch", Href = DemoRouteConstants.Demos_URL_Switch, IconName = IconName.ToggleOn, ParentId = "4" }, - new (){ Id = "410", Text = "Text Input", Href = DemoRouteConstants.Demos_URL_TextInput, IconName = IconName.InputCursorText, ParentId = "4" }, - new (){ Id = "411", Text = "Text Area Input", Href = DemoRouteConstants.Demos_URL_TextAreaInput, IconName = IconName.InputCursorText, ParentId = "4" }, - new (){ Id = "412", Text = "Time Input", Href = DemoRouteConstants.Demos_URL_TimeInput, IconName = IconName.ClockFill, ParentId = "4" }, + new (){ Id = "410", Text = "Switch", Href = DemoRouteConstants.Demos_URL_Switch, IconName = IconName.ToggleOn, ParentId = "4" }, + new (){ Id = "411", Text = "Text Input", Href = DemoRouteConstants.Demos_URL_TextInput, IconName = IconName.InputCursorText, ParentId = "4" }, + new (){ Id = "412", Text = "Text Area Input", Href = DemoRouteConstants.Demos_URL_TextAreaInput, IconName = IconName.InputCursorText, ParentId = "4" }, + new (){ Id = "413", Text = "Time Input", Href = DemoRouteConstants.Demos_URL_TimeInput, IconName = IconName.ClockFill, ParentId = "4" }, new (){ Id = "5", Text = "Components", IconName = IconName.GearFill, IconColor = IconColor.Danger }, new (){ Id = "500", Text = "Accordion", Href = DemoRouteConstants.Demos_URL_Accordion, IconName = IconName.ChevronBarExpand, ParentId = "5" }, diff --git a/BlazorBootstrap.Demo.RCL/Components/Layout/DocsMainLayout.razor.cs b/BlazorBootstrap.Demo.RCL/Components/Layout/DocsMainLayout.razor.cs index c1b5e241b..d05756698 100644 --- a/BlazorBootstrap.Demo.RCL/Components/Layout/DocsMainLayout.razor.cs +++ b/BlazorBootstrap.Demo.RCL/Components/Layout/DocsMainLayout.razor.cs @@ -22,14 +22,15 @@ internal override IEnumerable GetNavItems() new (){ Id = "403", Text = "Date Input", Href = DemoRouteConstants.Docs_URL_DateInput, IconName = IconName.CalendarDate, ParentId = "4" }, new (){ Id = "404", Text = "Enum Input", Href = DemoRouteConstants.Docs_URL_EnumInput, IconName = IconName.MenuButtonWideFill, ParentId = "4" }, new (){ Id = "405", Text = "Number Input", Href = DemoRouteConstants.Docs_URL_NumberInput, IconName = IconName.InputCursor, ParentId = "4" }, - new (){ Id = "406", Text = "Password Input", Href = DemoRouteConstants.Docs_URL_PasswordInput, IconName = IconName.EyeSlashFill, ParentId = "4" }, - new (){ Id = "407", Text = "Radio Input", Href = DemoRouteConstants.Docs_URL_RadioInput, IconName = IconName.RecordCircle, ParentId = "4" }, - new (){ Id = "408", Text = "Range Input", Href = DemoRouteConstants.Docs_URL_RangeInput, IconName = IconName.Sliders, ParentId = "4" }, + new (){ Id = "406", Text = "OTP Input", Href = DemoRouteConstants.Docs_URL_OTPInput, IconName = IconName.Asterisk, ParentId = "4" }, + new (){ Id = "407", Text = "Password Input", Href = DemoRouteConstants.Docs_URL_PasswordInput, IconName = IconName.EyeSlashFill, ParentId = "4" }, + new (){ Id = "408", Text = "Radio Input", Href = DemoRouteConstants.Docs_URL_RadioInput, IconName = IconName.RecordCircle, ParentId = "4" }, + new (){ Id = "409", Text = "Range Input", Href = DemoRouteConstants.Docs_URL_RangeInput, IconName = IconName.Sliders, ParentId = "4" }, //new (){ Id = "404", Text = "Select Input", Href = DemoRouteConstants.Docs_URL_SelectInput, IconName = IconName.MenuButtonWideFill, ParentId = "4" }, - new (){ Id = "409", Text = "Switch", Href = DemoRouteConstants.Docs_URL_Switch, IconName = IconName.ToggleOn, ParentId = "4" }, - new (){ Id = "410", Text = "Text Input", Href = DemoRouteConstants.Docs_URL_TextInput, IconName = IconName.InputCursorText, ParentId = "4" }, - new (){ Id = "411", Text = "Text Area Input", Href = DemoRouteConstants.Docs_URL_TextAreaInput, IconName = IconName.InputCursorText, ParentId = "4" }, - new (){ Id = "412", Text = "Time Input", Href = DemoRouteConstants.Docs_URL_TimeInput, IconName = IconName.ClockFill, ParentId = "4" }, + new (){ Id = "410", Text = "Switch", Href = DemoRouteConstants.Docs_URL_Switch, IconName = IconName.ToggleOn, ParentId = "4" }, + new (){ Id = "411", Text = "Text Input", Href = DemoRouteConstants.Docs_URL_TextInput, IconName = IconName.InputCursorText, ParentId = "4" }, + new (){ Id = "412", Text = "Text Area Input", Href = DemoRouteConstants.Docs_URL_TextAreaInput, IconName = IconName.InputCursorText, ParentId = "4" }, + new (){ Id = "413", Text = "Time Input", Href = DemoRouteConstants.Docs_URL_TimeInput, IconName = IconName.ClockFill, ParentId = "4" }, new (){ Id = "5", Text = "Components", IconName = IconName.GearFill, IconColor = IconColor.Danger }, new (){ Id = "500", Text = "Accordion", Href = DemoRouteConstants.Docs_URL_Accordion, IconName = IconName.ChevronBarExpand, ParentId = "5" }, diff --git a/BlazorBootstrap.Demo.RCL/Components/Pages/Demos/Form/OTPInput/OTPInputDocumentation.razor b/BlazorBootstrap.Demo.RCL/Components/Pages/Demos/Form/OTPInput/OTPInputDocumentation.razor new file mode 100644 index 000000000..7b5d29bb6 --- /dev/null +++ b/BlazorBootstrap.Demo.RCL/Components/Pages/Demos/Form/OTPInput/OTPInputDocumentation.razor @@ -0,0 +1,57 @@ +@page "/otp-input" +@attribute [Route(pageUrl)] +@layout DemosMainLayout + + + + + +
+
+ The OTPInput component provides a user-friendly interface for entering one-time passwords (OTP), commonly used for authentication and verification flows. +

+ How to use: +
+
    +
  1. Add the OTPInput component to your page.
  2. +
  3. Handle the OnOTPChanged event to capture the OTP value as the user types.
  4. +
  5. Handle the OnOTPCompleted event to respond when the user has entered the complete OTP.
  6. +
  7. Bind the entered OTP to a variable for display or further processing as needed.
  8. +
+
+ This demo illustrates the basic usage of the OTPInput component, including event handling for OTP entry and completion. +
+ +
+ +
+
+ The OTPInput component allows you to specify the required OTP length, adapting the number of input fields accordingly. +

+ How to use: +
+
    +
  1. Set the Length parameter to define how many digits or characters the OTP should have (e.g., Length="5").
  2. +
  3. Handle the OnOTPChanged and OnOTPCompleted events as shown in the demo to process the OTP input.
  4. +
  5. Display or use the entered OTP value as needed in your application.
  6. +
+
+ This demo demonstrates how to configure the OTPInput component for a custom OTP length and handle user input accordingly. +
+ +
+ +@code { + private const string componentName = nameof(OTPInput); + private const string pageUrl = DemoRouteConstants.Demos_URL_OTPInput; + private const string pageTitle = componentName; + private const string pageDescription = $"The {componentName} component allows users to enter a one-time password (OTP) in a secure and user-friendly manner. The component is designed to enhance the user experience by providing a visually appealing and functional input field for OTP entry."; + private const string metaTitle = $"Blazor {componentName} Component"; + private const string metaDescription = $"The {componentName} component allows users to enter a one-time password (OTP) in a secure and user-friendly manner. The component is designed to enhance the user experience by providing a visually appealing and functional input field for OTP entry."; + private const string imageUrl = DemoScreenshotSrcConstants.Demos_URL_OTPInput; +} diff --git a/BlazorBootstrap.Demo.RCL/Components/Pages/Demos/Form/OTPInput/OTPInput_Demo_01_How_it_works.razor b/BlazorBootstrap.Demo.RCL/Components/Pages/Demos/Form/OTPInput/OTPInput_Demo_01_How_it_works.razor new file mode 100644 index 000000000..824373cf4 --- /dev/null +++ b/BlazorBootstrap.Demo.RCL/Components/Pages/Demos/Form/OTPInput/OTPInput_Demo_01_How_it_works.razor @@ -0,0 +1,19 @@ + + +
Entered OTP: @enteredOTP
+ +@code { + private string? enteredOTP = null; + + private void HandleOtpChanged(string otp) + { + enteredOTP = otp; + } + + private void HandleOtpCompleted(string otp) + { + Console.WriteLine($"OTP Completed: {otp}"); + enteredOTP = otp; + } +} \ No newline at end of file diff --git a/BlazorBootstrap.Demo.RCL/Components/Pages/Demos/Form/OTPInput/OTPInput_Demo_02_Length.razor b/BlazorBootstrap.Demo.RCL/Components/Pages/Demos/Form/OTPInput/OTPInput_Demo_02_Length.razor new file mode 100644 index 000000000..7194b3841 --- /dev/null +++ b/BlazorBootstrap.Demo.RCL/Components/Pages/Demos/Form/OTPInput/OTPInput_Demo_02_Length.razor @@ -0,0 +1,20 @@ + + +
Entered OTP: @enteredOTP
+ +@code { + private string? enteredOTP = null; + + private void HandleOtpChanged(string otp) + { + enteredOTP = otp; + } + + private void HandleOtpCompleted(string otp) + { + Console.WriteLine($"OTP Completed: {otp}"); + enteredOTP = otp; + } +} \ No newline at end of file diff --git a/BlazorBootstrap.Demo.RCL/Components/Pages/Docs/Form/OTPInput/OTPInput_Doc_01_Documentation.razor b/BlazorBootstrap.Demo.RCL/Components/Pages/Docs/Form/OTPInput/OTPInput_Doc_01_Documentation.razor new file mode 100644 index 000000000..1efb8d2cc --- /dev/null +++ b/BlazorBootstrap.Demo.RCL/Components/Pages/Docs/Form/OTPInput/OTPInput_Doc_01_Documentation.razor @@ -0,0 +1,37 @@ +@attribute [Route(pageUrl)] +@layout DocsMainLayout + + + + + +
+ @metaTitle +
+ +
+ +
+ +
+ +
+ +
+ +
+ +@code { + private const string componentName = nameof(OTPInput); + private const string pageUrl = DemoRouteConstants.Docs_URL_OTPInput; + private const string pageTitle = componentName; + private const string pageDescription = $"This documentation provides a comprehensive reference for the {componentName} component, guiding you through its configuration options."; + private const string metaTitle = $"Blazor {componentName} Component"; + private const string metaDescription = $"This documentation provides a comprehensive reference for the {componentName} component, guiding you through its configuration options."; + private const string imageUrl = DemoScreenshotSrcConstants.Demos_URL_NumberInput; +} \ No newline at end of file diff --git a/BlazorBootstrap.Demo.RCL/Components/Pages/Home/Index.razor b/BlazorBootstrap.Demo.RCL/Components/Pages/Home/Index.razor index 392b6ecd6..b4a94d512 100644 --- a/BlazorBootstrap.Demo.RCL/Components/Pages/Home/Index.razor +++ b/BlazorBootstrap.Demo.RCL/Components/Pages/Home/Index.razor @@ -155,6 +155,11 @@

Number Input

+ +

Password Input

@@ -519,7 +529,7 @@ protected override void OnInitialized() { - version = $"v{Configuration["version"]}"; // example: v0.6.1 + version = $"v{Configuration["version"]}"; // example: v4.0.1 releaseShortDescription = Configuration["release:short_description"]!; base.OnInitialized(); diff --git a/BlazorBootstrap.Demo.RCL/Components/Shared/Demo.razor.cs b/BlazorBootstrap.Demo.RCL/Components/Shared/Demo.razor.cs index d2b282d24..226158eda 100644 --- a/BlazorBootstrap.Demo.RCL/Components/Shared/Demo.razor.cs +++ b/BlazorBootstrap.Demo.RCL/Components/Shared/Demo.razor.cs @@ -24,7 +24,7 @@ public partial class Demo : BlazorBootstrapComponentBase protected override async Task OnAfterRenderAsync(bool firstRender) { if (firstRender) - await JS.InvokeVoidAsync("highlightCode"); + await SafeInvokeVoidAsync("highlightCode"); await base.OnAfterRenderAsync(firstRender); } @@ -97,7 +97,7 @@ public void ResetCopyStatusJS() StateHasChanged(); } - private async Task CopyToClipboardAsync() => await JS.InvokeVoidAsync("copyToClipboard", snippet, objRef); + private async Task CopyToClipboardAsync() => await SafeInvokeVoidAsync("copyToClipboard", snippet, objRef); #endregion @@ -105,8 +105,6 @@ public void ResetCopyStatusJS() protected override string? ClassNames => BuildClassNames(Class, ("bd-example-snippet bd-code-snippet", true)); - [Inject] protected IJSRuntime JS { get; set; } = default!; - [Parameter] public LanguageCode LanguageCode { get; set; } = LanguageCode.Razor; [Parameter] public bool ShowCodeOnly { get; set; } diff --git a/BlazorBootstrap.Demo.RCL/Components/Shared/Snippet.cs b/BlazorBootstrap.Demo.RCL/Components/Shared/Snippet.cs index 929ff8d96..e4600d8ec 100644 --- a/BlazorBootstrap.Demo.RCL/Components/Shared/Snippet.cs +++ b/BlazorBootstrap.Demo.RCL/Components/Shared/Snippet.cs @@ -1,10 +1,11 @@ namespace BlazorBootstrap.Demo.RCL; -public class Snippet : ComponentBase +public class Snippet : BlazorBootstrapComponentBase { #region Members private string? snippet; + private bool isJsRuntimeAvailable = true; #endregion @@ -31,7 +32,7 @@ protected override void BuildRenderTree(RenderTreeBuilder builder) protected override async Task OnAfterRenderAsync(bool firstRender) { if (firstRender) - await JS.InvokeVoidAsync("highlightCode"); + await SafeInvokeVoidAsync("highlightCode"); await base.OnAfterRenderAsync(firstRender); } @@ -69,8 +70,6 @@ protected override async Task OnParametersSetAsync() #region Properties - [Inject] protected IJSRuntime JS { get; set; } = null!; - [Parameter] public LanguageCode LanguageCode { get; set; } = LanguageCode.Razor; [Parameter] public string? FilePath { get; set; } diff --git a/BlazorBootstrap.Demo.RCL/Constants/DemoRouteConstants.cs b/BlazorBootstrap.Demo.RCL/Constants/DemoRouteConstants.cs index 594c680fb..e4b1a7138 100644 --- a/BlazorBootstrap.Demo.RCL/Constants/DemoRouteConstants.cs +++ b/BlazorBootstrap.Demo.RCL/Constants/DemoRouteConstants.cs @@ -32,6 +32,7 @@ public static class DemoRouteConstants public const string Demos_URL_DateInput = Demos_URL_Forms_Prefix + "/date-input"; public const string Demos_URL_EnumInput = Demos_URL_Forms_Prefix + "/enum-input"; public const string Demos_URL_NumberInput = Demos_URL_Forms_Prefix + "/number-input"; + public const string Demos_URL_OTPInput = Demos_URL_Forms_Prefix + "/otp-input"; public const string Demos_URL_PasswordInput = Demos_URL_Forms_Prefix + "/password-input"; public const string Demos_URL_RadioInput = Demos_URL_Forms_Prefix + "/radio-input"; public const string Demos_URL_RangeInput = Demos_URL_Forms_Prefix + "/range-input"; @@ -143,6 +144,7 @@ public static class DemoRouteConstants public const string Docs_URL_DateInput = Docs_URL_Forms_Prefix + "/date-input"; public const string Docs_URL_EnumInput = Docs_URL_Forms_Prefix + "/enum-input"; public const string Docs_URL_NumberInput = Docs_URL_Forms_Prefix + "/number-input"; + public const string Docs_URL_OTPInput = Docs_URL_Forms_Prefix + "/otp-input"; public const string Docs_URL_PasswordInput = Docs_URL_Forms_Prefix + "/password-input"; public const string Docs_URL_RadioInput = Docs_URL_Forms_Prefix + "/radio-input"; public const string Docs_URL_RangeInput = Docs_URL_Forms_Prefix + "/range-input"; diff --git a/BlazorBootstrap.Demo.RCL/Constants/DemoScreenshotSrcConstants.cs b/BlazorBootstrap.Demo.RCL/Constants/DemoScreenshotSrcConstants.cs index ddfd69234..3db0516fe 100644 --- a/BlazorBootstrap.Demo.RCL/Constants/DemoScreenshotSrcConstants.cs +++ b/BlazorBootstrap.Demo.RCL/Constants/DemoScreenshotSrcConstants.cs @@ -24,6 +24,7 @@ public class DemoScreenshotSrcConstants public const string Demos_URL_DateInput = DemoScreenshotSrcPrefix + "date-input.png"; public const string Demos_URL_EnumInput = DemoScreenshotSrcPrefix + "enum-input.png"; // TODO: pending public const string Demos_URL_NumberInput = DemoScreenshotSrcPrefix + "number-input.png"; + public const string Demos_URL_OTPInput = DemoScreenshotSrcPrefix + "otp-input.png"; public const string Demos_URL_PasswordInput = DemoScreenshotSrcPrefix + "password-input.png"; public const string Demos_URL_RadioInput = DemoScreenshotSrcPrefix + "radio-input.png"; public const string Demos_URL_RangeInput = DemoScreenshotSrcPrefix + "range-input.png"; diff --git a/blazorbootstrap/Components/Alert/Alert.razor.cs b/blazorbootstrap/Components/Alert/Alert.razor.cs index 758db0329..ca1ba48b9 100644 --- a/blazorbootstrap/Components/Alert/Alert.razor.cs +++ b/blazorbootstrap/Components/Alert/Alert.razor.cs @@ -18,7 +18,7 @@ protected override async ValueTask DisposeAsyncCore(bool disposing) try { if (IsRenderComplete) - await JSRuntime.InvokeVoidAsync("window.blazorBootstrap.alert.dispose", Id); + await SafeInvokeVoidAsync("window.blazorBootstrap.alert.dispose", Id); } catch (JSDisconnectedException) { @@ -34,7 +34,7 @@ protected override async ValueTask DisposeAsyncCore(bool disposing) protected override async Task OnAfterRenderAsync(bool firstRender) { if (firstRender) - await JSRuntime.InvokeVoidAsync("window.blazorBootstrap.alert.initialize", Id, objRef); + await SafeInvokeVoidAsync("window.blazorBootstrap.alert.initialize", Id, objRef); await base.OnAfterRenderAsync(firstRender); } @@ -57,7 +57,7 @@ protected override async Task OnInitializedAsync() /// [AddedVersion("1.0.0")] [Description("Closes an alert by removing it from the DOM.")] - public async Task CloseAsync() => await JSRuntime.InvokeVoidAsync("window.blazorBootstrap.alert.close", Id); + public async Task CloseAsync() => await SafeInvokeVoidAsync("window.blazorBootstrap.alert.close", Id); #endregion diff --git a/blazorbootstrap/Components/Carousel/Carousel.razor.cs b/blazorbootstrap/Components/Carousel/Carousel.razor.cs index 482f6966d..bbf5fff67 100644 --- a/blazorbootstrap/Components/Carousel/Carousel.razor.cs +++ b/blazorbootstrap/Components/Carousel/Carousel.razor.cs @@ -30,7 +30,7 @@ protected override async ValueTask DisposeAsyncCore(bool disposing) try { if (IsRenderComplete) - await JSRuntime.InvokeVoidAsync(CarouselInterop.Dispose, Id); + await SafeInvokeVoidAsync(CarouselInterop.Dispose, Id); } catch (JSDisconnectedException) { @@ -48,7 +48,7 @@ protected override async Task OnAfterRenderAsync(bool firstRender) if (firstRender) { CarouselOptions options = new() { Interval = Interval, Keyboard = Keyboard, Ride = Autoplay.ToCarouselAutoPlayString(), Touch = Touch }; - await JSRuntime.InvokeVoidAsync(CarouselInterop.Initialize, Id, options, objRef); + await SafeInvokeVoidAsync(CarouselInterop.Initialize, Id, options, objRef); StateHasChanged(); // Required } @@ -87,7 +87,7 @@ public ValueTask ShowItemByIndexAsync(int index) if (!isDefaultActiveCarouselItemSet) isDefaultActiveCarouselItemSet = true; - return JSRuntime.InvokeVoidAsync(CarouselInterop.To, Id, index); + return new(SafeInvokeVoidAsync(CarouselInterop.To, Id, index)); } internal void AddItem(CarouselItem carouselItem) @@ -103,7 +103,7 @@ internal void AddItem(CarouselItem carouselItem) /// [AddedVersion("3.0.0")] [Description("Shows next CarouselItem.")] - public ValueTask PauseCarouselAsync() => JSRuntime.InvokeVoidAsync(CarouselInterop.Pause, Id); + public ValueTask PauseCarouselAsync() => new(SafeInvokeVoidAsync(CarouselInterop.Pause, Id)); /// /// Shows next . @@ -115,7 +115,7 @@ public ValueTask ShowNextItemAsync() var nextIndex = activeIndex + 1; activeIndex = nextIndex > items.Count - 1 ? 0 : nextIndex; - return JSRuntime.InvokeVoidAsync(CarouselInterop.Next, Id); + return new(SafeInvokeVoidAsync(CarouselInterop.Next, Id)); } /// @@ -128,7 +128,7 @@ public ValueTask ShowPreviousItemAsync() var previousIndex = activeIndex - 1; activeIndex = previousIndex < 0 ? items.Count - 1 : previousIndex; - return JSRuntime.InvokeVoidAsync(CarouselInterop.Previous, Id); + return new(SafeInvokeVoidAsync(CarouselInterop.Previous, Id)); } #endregion diff --git a/blazorbootstrap/Components/Charts/BarChart.razor.cs b/blazorbootstrap/Components/Charts/BarChart.razor.cs index c48a934f2..26db0541c 100644 --- a/blazorbootstrap/Components/Charts/BarChart.razor.cs +++ b/blazorbootstrap/Components/Charts/BarChart.razor.cs @@ -35,7 +35,7 @@ public override async Task AddDataAsync(ChartData chartData, string d if (data is BarChartDatasetData barChartDatasetData) barChartDataset.Data?.Add(barChartDatasetData.Data as double?); - await JSRuntime.InvokeVoidAsync($"{_jsObjectName}.addDatasetData", Id, dataLabel, data); + await SafeInvokeVoidAsync($"{_jsObjectName}.addDatasetData", Id, dataLabel, data); return chartData; } @@ -80,7 +80,7 @@ public override async Task AddDataAsync(ChartData chartData, string d barChartDataset.Data?.Add(barChartDatasetData.Data as double?); } - await JSRuntime.InvokeVoidAsync($"{_jsObjectName}.addDatasetsData", Id, dataLabel, data?.Select(x => (BarChartDatasetData)x)); + await SafeInvokeVoidAsync($"{_jsObjectName}.addDatasetsData", Id, dataLabel, data?.Select(x => (BarChartDatasetData)x)); return chartData; } @@ -99,7 +99,7 @@ public override async Task AddDatasetAsync(ChartData chartData, IChar if (chartDataset is BarChartDataset) { chartData.Datasets.Add(chartDataset); - await JSRuntime.InvokeVoidAsync($"{_jsObjectName}.addDataset", Id, (BarChartDataset)chartDataset); + await SafeInvokeVoidAsync($"{_jsObjectName}.addDataset", Id, (BarChartDataset)chartDataset); } return chartData; @@ -111,7 +111,7 @@ public override async Task InitializeAsync(ChartData chartData, IChartOptions ch { var datasets = chartData.Datasets.OfType(); var data = new { chartData.Labels, Datasets = datasets }; - await JSRuntime.InvokeVoidAsync($"{_jsObjectName}.initialize", Id, GetChartType(), data, (BarChartOptions)chartOptions, plugins); + await SafeInvokeVoidAsync($"{_jsObjectName}.initialize", Id, GetChartType(), data, (BarChartOptions)chartOptions, plugins); } } @@ -121,7 +121,7 @@ public override async Task UpdateAsync(ChartData chartData, IChartOptions chartO { var datasets = chartData.Datasets.OfType(); var data = new { chartData.Labels, Datasets = datasets }; - await JSRuntime.InvokeVoidAsync($"{_jsObjectName}.update", Id, GetChartType(), data, (BarChartOptions)chartOptions); + await SafeInvokeVoidAsync($"{_jsObjectName}.update", Id, GetChartType(), data, (BarChartOptions)chartOptions); } } diff --git a/blazorbootstrap/Components/Charts/BlazorBootstrapChart.cs b/blazorbootstrap/Components/Charts/BlazorBootstrapChart.cs index 0bfa8781d..d68d90ea5 100644 --- a/blazorbootstrap/Components/Charts/BlazorBootstrapChart.cs +++ b/blazorbootstrap/Components/Charts/BlazorBootstrapChart.cs @@ -47,11 +47,11 @@ public virtual async Task InitializeAsync(ChartData chartData, IChartOptions cha var _data = GetChartDataObject(chartData); if (chartType == ChartType.Bar) - await JSRuntime.InvokeVoidAsync("window.blazorChart.bar.initialize", Id, GetChartType(), _data, (BarChartOptions)chartOptions, plugins); + await SafeInvokeVoidAsync("window.blazorChart.bar.initialize", Id, GetChartType(), _data, (BarChartOptions)chartOptions, plugins); else if (chartType == ChartType.Line) - await JSRuntime.InvokeVoidAsync("window.blazorChart.line.initialize", Id, GetChartType(), _data, (LineChartOptions)chartOptions, plugins); + await SafeInvokeVoidAsync("window.blazorChart.line.initialize", Id, GetChartType(), _data, (LineChartOptions)chartOptions, plugins); else - await JSRuntime.InvokeVoidAsync("window.blazorChart.initialize", Id, GetChartType(), _data, chartOptions, plugins); + await SafeInvokeVoidAsync("window.blazorChart.initialize", Id, GetChartType(), _data, chartOptions, plugins); } } @@ -70,7 +70,7 @@ public async Task ResizeAsync(int width, int height, Unit widthUnit = Unit.Px, U { var widthWithUnit = $"width:{width.ToString(CultureInfo.InvariantCulture)}{widthUnit.ToCssString()}"; var heightWithUnit = $"height:{height.ToString(CultureInfo.InvariantCulture)}{heightUnit.ToCssString()}"; - await JSRuntime.InvokeVoidAsync("window.blazorChart.resize", Id, widthWithUnit, heightWithUnit); + await SafeInvokeVoidAsync("window.blazorChart.resize", Id, widthWithUnit, heightWithUnit); } /// @@ -86,11 +86,11 @@ public virtual async Task UpdateAsync(ChartData chartData, IChartOptions chartOp var data = GetChartDataObject(chartData); if (chartType == ChartType.Bar) - await JSRuntime.InvokeVoidAsync("window.blazorChart.bar.update", Id, GetChartType(), data, (BarChartOptions)chartOptions); + await SafeInvokeVoidAsync("window.blazorChart.bar.update", Id, GetChartType(), data, (BarChartOptions)chartOptions); else if (chartType == ChartType.Line) - await JSRuntime.InvokeVoidAsync("window.blazorChart.line.update", Id, GetChartType(), data, (LineChartOptions)chartOptions); + await SafeInvokeVoidAsync("window.blazorChart.line.update", Id, GetChartType(), data, (LineChartOptions)chartOptions); else - await JSRuntime.InvokeVoidAsync("window.blazorChart.update", Id, GetChartType(), data, chartOptions); + await SafeInvokeVoidAsync("window.blazorChart.update", Id, GetChartType(), data, chartOptions); } } @@ -106,11 +106,11 @@ public virtual async Task UpdateValuesAsync(ChartData chartData) var data = GetChartDataObject(chartData); if (chartType == ChartType.Bar) - await JSRuntime.InvokeVoidAsync("window.blazorChart.bar.updateDataValues", Id, data); + await SafeInvokeVoidAsync("window.blazorChart.bar.updateDataValues", Id, data); else if (chartType == ChartType.Line) - await JSRuntime.InvokeVoidAsync("window.blazorChart.line.updateDataValues", Id, data); + await SafeInvokeVoidAsync("window.blazorChart.line.updateDataValues", Id, data); else - await JSRuntime.InvokeVoidAsync("window.blazorChart.updateDataValues", Id, data); + await SafeInvokeVoidAsync("window.blazorChart.updateDataValues", Id, data); } } diff --git a/blazorbootstrap/Components/Charts/DoughnutChart.razor.cs b/blazorbootstrap/Components/Charts/DoughnutChart.razor.cs index 6119d6da7..1e28b6c52 100644 --- a/blazorbootstrap/Components/Charts/DoughnutChart.razor.cs +++ b/blazorbootstrap/Components/Charts/DoughnutChart.razor.cs @@ -38,7 +38,7 @@ public override async Task AddDataAsync(ChartData chartData, string d doughnutChartDataset.BackgroundColor?.Add(doughnutChartDatasetData.BackgroundColor!); } - await JSRuntime.InvokeVoidAsync($"{_jsObjectName}.addDatasetData", Id, dataLabel, data); + await SafeInvokeVoidAsync($"{_jsObjectName}.addDatasetData", Id, dataLabel, data); return chartData; } @@ -86,7 +86,7 @@ public override async Task AddDataAsync(ChartData chartData, string d } } - await JSRuntime.InvokeVoidAsync($"{_jsObjectName}.addDatasetsData", Id, dataLabel, data?.Select(x => (DoughnutChartDatasetData)x)); + await SafeInvokeVoidAsync($"{_jsObjectName}.addDatasetsData", Id, dataLabel, data?.Select(x => (DoughnutChartDatasetData)x)); return chartData; } @@ -105,7 +105,7 @@ public override async Task AddDatasetAsync(ChartData chartData, IChar if (chartDataset is DoughnutChartDataset doughnutChartDataset) { chartData.Datasets.Add(doughnutChartDataset); - await JSRuntime.InvokeVoidAsync($"{_jsObjectName}.addDataset", Id, doughnutChartDataset); + await SafeInvokeVoidAsync($"{_jsObjectName}.addDataset", Id, doughnutChartDataset); } return chartData; @@ -117,7 +117,7 @@ public override async Task InitializeAsync(ChartData chartData, IChartOptions ch { var datasets = chartData.Datasets.OfType(); var data = new { chartData.Labels, Datasets = datasets }; - await JSRuntime.InvokeVoidAsync($"{_jsObjectName}.initialize", Id, GetChartType(), data, (DoughnutChartOptions)chartOptions, plugins); + await SafeInvokeVoidAsync($"{_jsObjectName}.initialize", Id, GetChartType(), data, (DoughnutChartOptions)chartOptions, plugins); } } @@ -127,7 +127,7 @@ public override async Task UpdateAsync(ChartData chartData, IChartOptions chartO { var datasets = chartData.Datasets.OfType(); var data = new { chartData.Labels, Datasets = datasets }; - await JSRuntime.InvokeVoidAsync($"{_jsObjectName}.update", Id, GetChartType(), data, (DoughnutChartOptions)chartOptions); + await SafeInvokeVoidAsync($"{_jsObjectName}.update", Id, GetChartType(), data, (DoughnutChartOptions)chartOptions); } } diff --git a/blazorbootstrap/Components/Charts/LineChart.razor.cs b/blazorbootstrap/Components/Charts/LineChart.razor.cs index 765fa000e..a68334578 100644 --- a/blazorbootstrap/Components/Charts/LineChart.razor.cs +++ b/blazorbootstrap/Components/Charts/LineChart.razor.cs @@ -29,7 +29,7 @@ public override async Task AddDataAsync(ChartData chartData, string d if (data is LineChartDatasetData lineChartDatasetData) lineChartDataset.Data?.Add(lineChartDatasetData.Data as double?); - await JSRuntime.InvokeVoidAsync("window.blazorChart.line.addDatasetData", Id, dataLabel, data); + await SafeInvokeVoidAsync("window.blazorChart.line.addDatasetData", Id, dataLabel, data); return chartData; } @@ -74,7 +74,7 @@ public override async Task AddDataAsync(ChartData chartData, string d lineChartDataset.Data?.Add(lineChartDatasetData.Data as double?); } - await JSRuntime.InvokeVoidAsync("window.blazorChart.line.addDatasetsData", Id, dataLabel, data?.Select(x => (LineChartDatasetData)x)); + await SafeInvokeVoidAsync("window.blazorChart.line.addDatasetsData", Id, dataLabel, data?.Select(x => (LineChartDatasetData)x)); return chartData; } @@ -93,7 +93,7 @@ public override async Task AddDatasetAsync(ChartData chartData, IChar if (chartDataset is LineChartDataset) { chartData.Datasets.Add(chartDataset); - await JSRuntime.InvokeVoidAsync("window.blazorChart.line.addDataset", Id, (LineChartDataset)chartDataset); + await SafeInvokeVoidAsync("window.blazorChart.line.addDataset", Id, (LineChartDataset)chartDataset); } return chartData; @@ -112,7 +112,7 @@ public override async Task InitializeAsync(ChartData chartData, IChartOptions ch var datasets = chartData.Datasets.OfType(); var data = new { chartData.Labels, Datasets = datasets }; - await JSRuntime.InvokeVoidAsync("window.blazorChart.line.initialize", Id, GetChartType(), data, (LineChartOptions)chartOptions, plugins); + await SafeInvokeVoidAsync("window.blazorChart.line.initialize", Id, GetChartType(), data, (LineChartOptions)chartOptions, plugins); } public override async Task UpdateAsync(ChartData chartData, IChartOptions chartOptions) @@ -128,7 +128,7 @@ public override async Task UpdateAsync(ChartData chartData, IChartOptions chartO var datasets = chartData.Datasets.OfType(); var data = new { chartData.Labels, Datasets = datasets }; - await JSRuntime.InvokeVoidAsync("window.blazorChart.line.update", Id, GetChartType(), data, (LineChartOptions)chartOptions); + await SafeInvokeVoidAsync("window.blazorChart.line.update", Id, GetChartType(), data, (LineChartOptions)chartOptions); } #endregion diff --git a/blazorbootstrap/Components/Charts/PieChart.razor.cs b/blazorbootstrap/Components/Charts/PieChart.razor.cs index 86a4eeb5e..9c9f6d52b 100644 --- a/blazorbootstrap/Components/Charts/PieChart.razor.cs +++ b/blazorbootstrap/Components/Charts/PieChart.razor.cs @@ -38,7 +38,7 @@ public override async Task AddDataAsync(ChartData chartData, string d pieChartDataset.BackgroundColor?.Add(pieChartDatasetData.BackgroundColor!); } - await JSRuntime.InvokeVoidAsync($"{_jsObjectName}.addDatasetData", Id, dataLabel, data); + await SafeInvokeVoidAsync($"{_jsObjectName}.addDatasetData", Id, dataLabel, data); return chartData; } @@ -86,7 +86,7 @@ public override async Task AddDataAsync(ChartData chartData, string d } } - await JSRuntime.InvokeVoidAsync($"{_jsObjectName}.addDatasetsData", Id, dataLabel, data?.Select(x => (PieChartDatasetData)x)); + await SafeInvokeVoidAsync($"{_jsObjectName}.addDatasetsData", Id, dataLabel, data?.Select(x => (PieChartDatasetData)x)); return chartData; } @@ -105,7 +105,7 @@ public override async Task AddDatasetAsync(ChartData chartData, IChar if (chartDataset is PieChartDataset pieChartDataset) { chartData.Datasets.Add(pieChartDataset); - await JSRuntime.InvokeVoidAsync($"{_jsObjectName}.addDataset", Id, pieChartDataset); + await SafeInvokeVoidAsync($"{_jsObjectName}.addDataset", Id, pieChartDataset); } return chartData; @@ -117,7 +117,7 @@ public override async Task InitializeAsync(ChartData chartData, IChartOptions ch { var datasets = chartData.Datasets.OfType(); var data = new { chartData.Labels, Datasets = datasets }; - await JSRuntime.InvokeVoidAsync($"{_jsObjectName}.initialize", Id, GetChartType(), data, (PieChartOptions)chartOptions, plugins); + await SafeInvokeVoidAsync($"{_jsObjectName}.initialize", Id, GetChartType(), data, (PieChartOptions)chartOptions, plugins); } } @@ -127,7 +127,7 @@ public override async Task UpdateAsync(ChartData chartData, IChartOptions chartO { var datasets = chartData.Datasets.OfType(); var data = new { chartData.Labels, Datasets = datasets }; - await JSRuntime.InvokeVoidAsync($"{_jsObjectName}.update", Id, GetChartType(), data, (PieChartOptions)chartOptions); + await SafeInvokeVoidAsync($"{_jsObjectName}.update", Id, GetChartType(), data, (PieChartOptions)chartOptions); } } diff --git a/blazorbootstrap/Components/Charts/PolarAreaChart.razor.cs b/blazorbootstrap/Components/Charts/PolarAreaChart.razor.cs index 615c9b624..993ec1f99 100644 --- a/blazorbootstrap/Components/Charts/PolarAreaChart.razor.cs +++ b/blazorbootstrap/Components/Charts/PolarAreaChart.razor.cs @@ -36,7 +36,7 @@ public override async Task AddDataAsync(ChartData chartData, string d if (data is PolarAreaChartDatasetData barChartDatasetData) barChartDataset.Data?.Add(barChartDatasetData.Data as double?); - await JSRuntime.InvokeVoidAsync($"{_jsObjectName}.addDatasetData", Id, dataLabel, data); + await SafeInvokeVoidAsync($"{_jsObjectName}.addDatasetData", Id, dataLabel, data); return chartData; } @@ -81,7 +81,7 @@ public override async Task AddDataAsync(ChartData chartData, string d barChartDataset.Data?.Add(barChartDatasetData.Data as double?); } - await JSRuntime.InvokeVoidAsync($"{_jsObjectName}.addDatasetsData", Id, dataLabel, data?.Select(x => (PolarAreaChartDatasetData)x)); + await SafeInvokeVoidAsync($"{_jsObjectName}.addDatasetsData", Id, dataLabel, data?.Select(x => (PolarAreaChartDatasetData)x)); return chartData; } @@ -100,7 +100,7 @@ public override async Task AddDatasetAsync(ChartData chartData, IChar if (chartDataset is PolarAreaChartDataset) { chartData.Datasets.Add(chartDataset); - await JSRuntime.InvokeVoidAsync($"{_jsObjectName}.addDataset", Id, (PolarAreaChartDataset)chartDataset); + await SafeInvokeVoidAsync($"{_jsObjectName}.addDataset", Id, (PolarAreaChartDataset)chartDataset); } return chartData; @@ -112,7 +112,7 @@ public override async Task InitializeAsync(ChartData chartData, IChartOptions ch { var datasets = chartData.Datasets.OfType(); var data = new { chartData.Labels, Datasets = datasets }; - await JSRuntime.InvokeVoidAsync($"{_jsObjectName}.initialize", Id, GetChartType(), data, (PolarAreaChartOptions)chartOptions, plugins); + await SafeInvokeVoidAsync($"{_jsObjectName}.initialize", Id, GetChartType(), data, (PolarAreaChartOptions)chartOptions, plugins); } } @@ -122,7 +122,7 @@ public override async Task UpdateAsync(ChartData chartData, IChartOptions chartO { var datasets = chartData.Datasets.OfType(); var data = new { chartData.Labels, Datasets = datasets }; - await JSRuntime.InvokeVoidAsync($"{_jsObjectName}.update", Id, GetChartType(), data, (PolarAreaChartOptions)chartOptions); + await SafeInvokeVoidAsync($"{_jsObjectName}.update", Id, GetChartType(), data, (PolarAreaChartOptions)chartOptions); } } diff --git a/blazorbootstrap/Components/Charts/RadarChart.razor.cs b/blazorbootstrap/Components/Charts/RadarChart.razor.cs index 360607416..d727a2e6f 100644 --- a/blazorbootstrap/Components/Charts/RadarChart.razor.cs +++ b/blazorbootstrap/Components/Charts/RadarChart.razor.cs @@ -36,7 +36,7 @@ public override async Task AddDataAsync(ChartData chartData, string d if (data is RadarChartDatasetData radarChartDatasetData) radarChartDataset.Data?.Add(radarChartDatasetData.Data as double?); - await JSRuntime.InvokeVoidAsync($"{_jsObjectName}.addDatasetData", Id, dataLabel, data); + await SafeInvokeVoidAsync($"{_jsObjectName}.addDatasetData", Id, dataLabel, data); return chartData; } @@ -81,7 +81,7 @@ public override async Task AddDataAsync(ChartData chartData, string d radarChartDataset.Data?.Add(radarChartDatasetData.Data as double?); } - await JSRuntime.InvokeVoidAsync($"{_jsObjectName}.addDatasetsData", Id, dataLabel, data?.Select(x => (RadarChartDatasetData)x)); + await SafeInvokeVoidAsync($"{_jsObjectName}.addDatasetsData", Id, dataLabel, data?.Select(x => (RadarChartDatasetData)x)); return chartData; } @@ -100,7 +100,7 @@ public override async Task AddDatasetAsync(ChartData chartData, IChar if (chartDataset is RadarChartDataset) { chartData.Datasets.Add(chartDataset); - await JSRuntime.InvokeVoidAsync($"{_jsObjectName}.addDataset", Id, (RadarChartDataset)chartDataset); + await SafeInvokeVoidAsync($"{_jsObjectName}.addDataset", Id, (RadarChartDataset)chartDataset); } return chartData; @@ -112,7 +112,7 @@ public override async Task InitializeAsync(ChartData chartData, IChartOptions ch { var datasets = chartData.Datasets.OfType(); var data = new { chartData.Labels, Datasets = datasets }; - await JSRuntime.InvokeVoidAsync($"{_jsObjectName}.initialize", Id, GetChartType(), data, (RadarChartOptions)chartOptions, plugins); + await SafeInvokeVoidAsync($"{_jsObjectName}.initialize", Id, GetChartType(), data, (RadarChartOptions)chartOptions, plugins); } } @@ -122,7 +122,7 @@ public override async Task UpdateAsync(ChartData chartData, IChartOptions chartO { var datasets = chartData.Datasets.OfType(); var data = new { chartData.Labels, Datasets = datasets }; - await JSRuntime.InvokeVoidAsync($"{_jsObjectName}.update", Id, GetChartType(), data, (RadarChartOptions)chartOptions); + await SafeInvokeVoidAsync($"{_jsObjectName}.update", Id, GetChartType(), data, (RadarChartOptions)chartOptions); } } diff --git a/blazorbootstrap/Components/Charts/ScatterChart.razor.cs b/blazorbootstrap/Components/Charts/ScatterChart.razor.cs index 02f22c924..df7271d7c 100644 --- a/blazorbootstrap/Components/Charts/ScatterChart.razor.cs +++ b/blazorbootstrap/Components/Charts/ScatterChart.razor.cs @@ -36,7 +36,7 @@ public override async Task AddDataAsync(ChartData chartData, string d if (data is ScatterChartDatasetData scatterChartDatasetData && scatterChartDatasetData.Data is ScatterChartDataPoint scatterChartDataPoint) scatterChartDataset.Data?.Add(scatterChartDataPoint); - await JSRuntime.InvokeVoidAsync($"{_jsObjectName}.addDatasetData", Id, dataLabel, data); + await SafeInvokeVoidAsync($"{_jsObjectName}.addDatasetData", Id, dataLabel, data); return chartData; } @@ -81,7 +81,7 @@ public override async Task AddDataAsync(ChartData chartData, string d scatterChartDataset.Data?.Add(scatterChartDataPoint); } - await JSRuntime.InvokeVoidAsync($"{_jsObjectName}.addDatasetsData", Id, dataLabel, data?.Select(x => (ScatterChartDatasetData)x)); + await SafeInvokeVoidAsync($"{_jsObjectName}.addDatasetsData", Id, dataLabel, data?.Select(x => (ScatterChartDatasetData)x)); return chartData; } @@ -100,7 +100,7 @@ public override async Task AddDatasetAsync(ChartData chartData, IChar if (chartDataset is ScatterChartDataset) { chartData.Datasets.Add(chartDataset); - await JSRuntime.InvokeVoidAsync($"{_jsObjectName}.addDataset", Id, (ScatterChartDataset)chartDataset); + await SafeInvokeVoidAsync($"{_jsObjectName}.addDataset", Id, (ScatterChartDataset)chartDataset); } return chartData; @@ -119,7 +119,7 @@ public override async Task InitializeAsync(ChartData chartData, IChartOptions ch var datasets = chartData.Datasets.OfType(); var data = new { chartData.Labels, Datasets = datasets }; - await JSRuntime.InvokeVoidAsync($"{_jsObjectName}.initialize", Id, GetChartType(), data, (ScatterChartOptions)chartOptions, plugins); + await SafeInvokeVoidAsync($"{_jsObjectName}.initialize", Id, GetChartType(), data, (ScatterChartOptions)chartOptions, plugins); } public override async Task UpdateAsync(ChartData chartData, IChartOptions chartOptions) @@ -135,7 +135,7 @@ public override async Task UpdateAsync(ChartData chartData, IChartOptions chartO var datasets = chartData.Datasets.OfType(); var data = new { chartData.Labels, Datasets = datasets }; - await JSRuntime.InvokeVoidAsync($"{_jsObjectName}.update", Id, GetChartType(), data, (ScatterChartOptions)chartOptions); + await SafeInvokeVoidAsync($"{_jsObjectName}.update", Id, GetChartType(), data, (ScatterChartOptions)chartOptions); } #endregion diff --git a/blazorbootstrap/Components/Collapse/Collapse.razor.cs b/blazorbootstrap/Components/Collapse/Collapse.razor.cs index 8f1855a78..351785540 100644 --- a/blazorbootstrap/Components/Collapse/Collapse.razor.cs +++ b/blazorbootstrap/Components/Collapse/Collapse.razor.cs @@ -18,7 +18,7 @@ protected override async ValueTask DisposeAsyncCore(bool disposing) try { if (IsRenderComplete) - await JSRuntime.InvokeVoidAsync("window.blazorBootstrap.collapse.dispose", Id); + await SafeInvokeVoidAsync("window.blazorBootstrap.collapse.dispose", Id); } catch (JSDisconnectedException) { @@ -34,7 +34,7 @@ protected override async ValueTask DisposeAsyncCore(bool disposing) protected override async Task OnAfterRenderAsync(bool firstRender) { if (firstRender) - await JSRuntime.InvokeVoidAsync("window.blazorBootstrap.collapse.initialize", Id, Parent, Toggle, objRef); + await SafeInvokeVoidAsync("window.blazorBootstrap.collapse.initialize", Id, Parent, Toggle, objRef); await base.OnAfterRenderAsync(firstRender); } @@ -63,21 +63,21 @@ protected override async Task OnInitializedAsync() /// [AddedVersion("1.7.0")] [Description("Hides a collapsible element.")] - public async Task HideAsync() => await JSRuntime.InvokeVoidAsync("window.blazorBootstrap.collapse.hide", Id); + public async Task HideAsync() => await SafeInvokeVoidAsync("window.blazorBootstrap.collapse.hide", Id); /// /// Shows a collapsible element. /// [AddedVersion("1.7.0")] [Description("Shows a collapsible element.")] - public async Task ShowAsync() => await JSRuntime.InvokeVoidAsync("window.blazorBootstrap.collapse.show", Id); + public async Task ShowAsync() => await SafeInvokeVoidAsync("window.blazorBootstrap.collapse.show", Id); /// /// Toggles a collapsible element to shown or hidden. /// [AddedVersion("1.7.0")] [Description("Toggles a collapsible element to shown or hidden.")] - public async Task ToggleAsync() => await JSRuntime.InvokeVoidAsync("window.blazorBootstrap.collapse.toggle", Id); + public async Task ToggleAsync() => await SafeInvokeVoidAsync("window.blazorBootstrap.collapse.toggle", Id); #endregion diff --git a/blazorbootstrap/Components/ConfirmDialog/ConfirmDialog.razor.cs b/blazorbootstrap/Components/ConfirmDialog/ConfirmDialog.razor.cs index e97daaf40..9a4a23a9a 100644 --- a/blazorbootstrap/Components/ConfirmDialog/ConfirmDialog.razor.cs +++ b/blazorbootstrap/Components/ConfirmDialog/ConfirmDialog.razor.cs @@ -77,7 +77,7 @@ private void Hide() StateHasChanged(); - Task.Run(() => JSRuntime.InvokeVoidAsync("window.blazorBootstrap.confirmDialog.hide", Id)); + Task.Run(async () => await SafeInvokeVoidAsync("window.blazorBootstrap.confirmDialog.hide", Id)); } private void OnNoClick() @@ -122,7 +122,7 @@ private Task Show(string title, string? message1, string? message2, Type? StateHasChanged(); - Task.Run(() => JSRuntime.InvokeVoidAsync("window.blazorBootstrap.confirmDialog.show", Id)); + Task.Run(async () => await SafeInvokeVoidAsync("window.blazorBootstrap.confirmDialog.show", Id)); return task; } diff --git a/blazorbootstrap/Components/Core/BlazorBootstrapComponentBase.cs b/blazorbootstrap/Components/Core/BlazorBootstrapComponentBase.cs index 87dc2375e..8a238a63e 100644 --- a/blazorbootstrap/Components/Core/BlazorBootstrapComponentBase.cs +++ b/blazorbootstrap/Components/Core/BlazorBootstrapComponentBase.cs @@ -8,6 +8,8 @@ public abstract class BlazorBootstrapComponentBase : ComponentBase, IDisposable, private bool isDisposed; + private bool isJsRuntimeAvailable = true; + internal Queue> queuedTasks = new(); #endregion @@ -30,6 +32,27 @@ protected override void OnInitialized() Id ??= IdUtility.GetNextId(); } + protected async Task SafeInvokeVoidAsync(string identifier, params object?[] args) + { + if (!isJsRuntimeAvailable) + return; + + try + { + await JSRuntime.InvokeVoidAsync(identifier, args); + } + catch (TaskCanceledException) + { + // Component/DOM likely got removed (navigation, conditional render, etc.) + // Treat as benign for focus/value updates; do not mark JS runtime as unavailable. + } + catch (JSDisconnectedException) + { + // JS runtime no longer available (more common on Server, but safe here too). + isJsRuntimeAvailable = false; + } + } + public static string BuildClassNames(params (string? cssClass, bool when)[] cssClassList) { var list = new HashSet(); @@ -189,6 +212,8 @@ protected virtual ValueTask DisposeAsyncCore(bool disposing) protected bool IsRenderComplete { get; private set; } + protected bool IsJsRuntimeAvailable => isJsRuntimeAvailable; + [Inject] protected IJSRuntime JSRuntime { get; set; } = default!; /// diff --git a/blazorbootstrap/Components/Core/JsInteropBase.cs b/blazorbootstrap/Components/Core/JsInteropBase.cs new file mode 100644 index 000000000..9c61e7dcc --- /dev/null +++ b/blazorbootstrap/Components/Core/JsInteropBase.cs @@ -0,0 +1,65 @@ +namespace BlazorBootstrap; + +public abstract class JsInteropBase : IAsyncDisposable +{ + #region Fields and Constants + + private readonly Lazy> moduleTask; + private bool isJsRuntimeAvailable = true; + + #endregion + + #region Constructors + + protected JsInteropBase(IJSRuntime jsRuntime, string modulePath) + { + moduleTask = new Lazy>(() => + jsRuntime.InvokeAsync("import", modulePath).AsTask()); + } + + #endregion + + #region Methods + + public async ValueTask DisposeAsync() + { + if (!moduleTask.IsValueCreated) + return; + + try + { + var module = await moduleTask.Value; + await module.DisposeAsync(); + } + catch (JSDisconnectedException) + { + // Circuit is gone; ignore cleanup. + } + catch (TaskCanceledException) + { + // Circuit is shutting down; ignore cleanup. + } + } + + protected async Task SafeInvokeVoidAsync(string identifier, params object?[] args) + { + if (!isJsRuntimeAvailable) + return; + + try + { + var module = await moduleTask.Value; + await module.InvokeVoidAsync(identifier, args); + } + catch (JSDisconnectedException) + { + isJsRuntimeAvailable = false; + } + catch (TaskCanceledException) + { + // do nothing + } + } + + #endregion +} diff --git a/blazorbootstrap/Components/Dropdown/Dropdown.razor.cs b/blazorbootstrap/Components/Dropdown/Dropdown.razor.cs index 550b3d549..86074c61d 100644 --- a/blazorbootstrap/Components/Dropdown/Dropdown.razor.cs +++ b/blazorbootstrap/Components/Dropdown/Dropdown.razor.cs @@ -15,15 +15,8 @@ protected override async ValueTask DisposeAsyncCore(bool disposing) { if (disposing) { - try - { - if (IsRenderComplete) - await JSRuntime.InvokeVoidAsync("window.blazorBootstrap.dropdown.dispose", Id); - } - catch (JSDisconnectedException) - { - // do nothing - } + if (IsRenderComplete) + await SafeInvokeVoidAsync("window.blazorBootstrap.dropdown.dispose", Id); objRef?.Dispose(); } @@ -34,7 +27,7 @@ protected override async ValueTask DisposeAsyncCore(bool disposing) protected override async Task OnAfterRenderAsync(bool firstRender) { if (firstRender) - await JSRuntime.InvokeVoidAsync("window.blazorBootstrap.dropdown.initialize", Id, objRef); + await SafeInvokeVoidAsync("window.blazorBootstrap.dropdown.initialize", Id, objRef); await base.OnAfterRenderAsync(firstRender); } @@ -64,7 +57,7 @@ protected override void OnInitialized() /// Task [AddedVersion("1.10.0")] [Description("Hides the dropdown menu of a given navbar or tabbed navigation.")] - public async Task HideAsync() => await JSRuntime.InvokeVoidAsync("window.blazorBootstrap.dropdown.hide", Id); + public async Task HideAsync() => await SafeInvokeVoidAsync("window.blazorBootstrap.dropdown.hide", Id); /// /// Shows the dropdown menu of a given navbar or tabbed navigation. @@ -72,7 +65,7 @@ protected override void OnInitialized() /// Task [AddedVersion("1.10.0")] [Description("Shows the dropdown menu of a given navbar or tabbed navigation.")] - public async Task ShowAsync() => await JSRuntime.InvokeVoidAsync("window.blazorBootstrap.dropdown.show", Id); + public async Task ShowAsync() => await SafeInvokeVoidAsync("window.blazorBootstrap.dropdown.show", Id); /// /// Toggles the dropdown menu of a given navbar or tabbed navigation. @@ -80,7 +73,7 @@ protected override void OnInitialized() /// Task [AddedVersion("1.10.0")] [Description("Toggles the dropdown menu of a given navbar or tabbed navigation.")] - public async Task ToggleAsync() => await JSRuntime.InvokeVoidAsync("window.blazorBootstrap.dropdown.toggle", Id); + public async Task ToggleAsync() => await SafeInvokeVoidAsync("window.blazorBootstrap.dropdown.toggle", Id); /// /// Updates the position of an element’s dropdown. @@ -88,7 +81,7 @@ protected override void OnInitialized() /// Task [AddedVersion("1.10.0")] [Description("Updates the position of an element’s dropdown.")] - public async Task UpdateAsync() => await JSRuntime.InvokeVoidAsync("window.blazorBootstrap.dropdown.update", Id); + public async Task UpdateAsync() => await SafeInvokeVoidAsync("window.blazorBootstrap.dropdown.update", Id); #endregion diff --git a/blazorbootstrap/Components/Form/AutoComplete/AutoComplete.razor.cs b/blazorbootstrap/Components/Form/AutoComplete/AutoComplete.razor.cs index d98eeca0b..15d8d7d15 100644 --- a/blazorbootstrap/Components/Form/AutoComplete/AutoComplete.razor.cs +++ b/blazorbootstrap/Components/Form/AutoComplete/AutoComplete.razor.cs @@ -34,7 +34,7 @@ protected override async ValueTask DisposeAsyncCore(bool disposing) try { if (IsRenderComplete) - await JSRuntime.InvokeVoidAsync("window.blazorBootstrap.autocomplete.dispose", Element); // NOTE: Always pass ElementRef + await SafeInvokeVoidAsync("window.blazorBootstrap.autocomplete.dispose", Element); // NOTE: Always pass ElementRef } catch (JSDisconnectedException) { @@ -50,7 +50,7 @@ protected override async ValueTask DisposeAsyncCore(bool disposing) protected override async Task OnAfterRenderAsync(bool firstRender) { if (firstRender) - await JSRuntime.InvokeVoidAsync("window.blazorBootstrap.autocomplete.initialize", Element, objRef); + await SafeInvokeVoidAsync("window.blazorBootstrap.autocomplete.initialize", Element, objRef); await base.OnAfterRenderAsync(firstRender); } @@ -198,7 +198,7 @@ private async Task HideAsync() if (AdditionalAttributes is not null && AdditionalAttributes.TryGetValue(BootstrapAttributes.DataBootstrapToggle, out _)) AdditionalAttributes.Remove(BootstrapAttributes.DataBootstrapToggle); - await JSRuntime.InvokeVoidAsync("window.blazorBootstrap.autocomplete.hide", Element); + await SafeInvokeVoidAsync("window.blazorBootstrap.autocomplete.hide", Element); } private async Task OnInputChangedAsync(ChangeEventArgs args) @@ -280,7 +280,7 @@ private async Task ShowAsync() if (AdditionalAttributes is not null && !AdditionalAttributes.TryGetValue(BootstrapAttributes.DataBootstrapToggle, out _)) AdditionalAttributes.Add(BootstrapAttributes.DataBootstrapToggle, "dropdown"); - await JSRuntime.InvokeVoidAsync("window.blazorBootstrap.autocomplete.show", Element); + await SafeInvokeVoidAsync("window.blazorBootstrap.autocomplete.show", Element); } #endregion diff --git a/blazorbootstrap/Components/Form/CurrencyInput/CurrencyInput.razor.cs b/blazorbootstrap/Components/Form/CurrencyInput/CurrencyInput.razor.cs index 99881011d..08d993584 100644 --- a/blazorbootstrap/Components/Form/CurrencyInput/CurrencyInput.razor.cs +++ b/blazorbootstrap/Components/Form/CurrencyInput/CurrencyInput.razor.cs @@ -20,7 +20,7 @@ protected override async Task OnAfterRenderAsync(bool firstRender) { if (firstRender) { - await JSRuntime.InvokeVoidAsync("window.blazorBootstrap.currencyInput.initialize", Id, isFloatingNumber(), AllowNegativeNumbers, cultureInfo.NumberFormat.CurrencyDecimalSeparator); + await SafeInvokeVoidAsync("window.blazorBootstrap.currencyInput.initialize", Id, isFloatingNumber(), AllowNegativeNumbers, cultureInfo.NumberFormat.CurrencyDecimalSeparator); var currentValue = Value; // object diff --git a/blazorbootstrap/Components/Form/DateInput/DateInput.razor.cs b/blazorbootstrap/Components/Form/DateInput/DateInput.razor.cs index 181d4239b..2c24410c5 100644 --- a/blazorbootstrap/Components/Form/DateInput/DateInput.razor.cs +++ b/blazorbootstrap/Components/Form/DateInput/DateInput.razor.cs @@ -230,7 +230,7 @@ private async Task SetValueAsync(TValue oldValue, object? newValue) formattedValue = GetFormattedValue(Value!); if (oldValue!.Equals(Value)) - await JSRuntime.InvokeVoidAsync("window.blazorBootstrap.dateInput.setValue", Id, formattedValue); + await SafeInvokeVoidAsync("window.blazorBootstrap.dateInput.setValue", Id, formattedValue); await ValueChanged.InvokeAsync(Value); diff --git a/blazorbootstrap/Components/Form/NumberInput/NumberInput.razor.cs b/blazorbootstrap/Components/Form/NumberInput/NumberInput.razor.cs index 74c2ca9a9..a462d5abe 100644 --- a/blazorbootstrap/Components/Form/NumberInput/NumberInput.razor.cs +++ b/blazorbootstrap/Components/Form/NumberInput/NumberInput.razor.cs @@ -18,7 +18,7 @@ protected override async Task OnAfterRenderAsync(bool firstRender) { if (firstRender) { - await JSRuntime.InvokeVoidAsync("window.blazorBootstrap.numberInput.initialize", Id, isFloatingNumber(), AllowNegativeNumbers, cultureInfo.NumberFormat.NumberDecimalSeparator); + await SafeInvokeVoidAsync("window.blazorBootstrap.numberInput.initialize", Id, isFloatingNumber(), AllowNegativeNumbers, cultureInfo.NumberFormat.NumberDecimalSeparator); var currentValue = Value; // object @@ -261,7 +261,7 @@ private async Task OnChange(ChangeEventArgs e) Value = value; if (oldValue!.Equals(Value)) - await JSRuntime.InvokeVoidAsync("window.blazorBootstrap.numberInput.setValue", Id, Value); + await SafeInvokeVoidAsync("window.blazorBootstrap.numberInput.setValue", Id, Value); await ValueChanged.InvokeAsync(Value); diff --git a/blazorbootstrap/Components/Form/OTPInput/OTPInput.razor b/blazorbootstrap/Components/Form/OTPInput/OTPInput.razor new file mode 100644 index 000000000..a9958dc7e --- /dev/null +++ b/blazorbootstrap/Components/Form/OTPInput/OTPInput.razor @@ -0,0 +1,20 @@ +@namespace BlazorBootstrap +@inherits BlazorBootstrapComponentBase + +
+ @for (int i = 0; i < Length; i++) + { + var index = i; + var inputId = GetInputId(index); + + + } +
\ No newline at end of file diff --git a/blazorbootstrap/Components/Form/OTPInput/OTPInput.razor.cs b/blazorbootstrap/Components/Form/OTPInput/OTPInput.razor.cs new file mode 100644 index 000000000..af0c38744 --- /dev/null +++ b/blazorbootstrap/Components/Form/OTPInput/OTPInput.razor.cs @@ -0,0 +1,234 @@ +namespace BlazorBootstrap; + +public partial class OTPInput : BlazorBootstrapComponentBase +{ + #region Fields and Constants + + private string[] otpValues = Array.Empty(); + + #endregion + + #region Methods + + // Auto focus + // Color + + protected override void OnParametersSet() + { + if (otpValues.Length != Length) + { + otpValues = new string[Length]; + Array.Fill(otpValues, string.Empty); + } + } + + /// + /// Clears the OTP input fields. + /// + [AddedVersion("4.0.0")] + [Description("Clears the OTP input fields.")] + public async Task ClearAsync() + { + otpValues = new string[Length]; + Array.Fill(otpValues, string.Empty); + await NotifyChangesAsync(); + + if (Length > 0) + await SafeInvokeVoidAsync(JsInteropUtils.FocusInputElement, GetInputId(0)); + + await InvokeAsync(StateHasChanged); + } + + private string GetInputId(int index) => $"{Id}-otp-input-{index}"; + + private async Task NotifyChangesAsync() + { + Console.WriteLine(">> NotifyChangesAsync called"); + var otpValue = string.Join(string.Empty, otpValues); + Console.WriteLine($">> otpValue: {otpValue}"); + await OnOTPChanged.InvokeAsync(otpValue); + + if (otpValue.Length == Length && !otpValues.Any(string.IsNullOrWhiteSpace)) + await OnOTPCompleted.InvokeAsync(otpValue); + } + + private async Task OnInput(ChangeEventArgs e, int index) + { + var rawValue = e.Value?.ToString(); + var numericValue = new string(rawValue?.Where(char.IsDigit).ToArray()); + + if (string.IsNullOrEmpty(numericValue)) + { + otpValues[index] = string.Empty; + + // Clear the input element if it contained invalid characters + if (!string.IsNullOrEmpty(rawValue)) + { + await SafeInvokeVoidAsync(JsInteropUtils.SetInputElementValue, GetInputId(index), string.Empty); + } + + await NotifyChangesAsync(); + return; + } + + // If multiple digits were entered (e.g. paste), distribute them across the input fields + if (numericValue.Length > 1) + { + var digits = numericValue.ToCharArray(); + var currentInputLength = digits.Length; + + for (int i = 0; i < currentInputLength; i++) + { + var targetIndex = index + i; + if (targetIndex < Length) + { + otpValues[targetIndex] = digits[i].ToString(); + + // Update the UI value for the current input and subsequent inputs + await SafeInvokeVoidAsync(JsInteropUtils.SetInputElementValue, GetInputId(targetIndex), otpValues[targetIndex]); + } + } + + // Move focus to the next input field after the last pasted digit + var nextIndex = index + currentInputLength; + if (nextIndex < Length) + await SafeInvokeVoidAsync(JsInteropUtils.FocusInputElement, GetInputId(nextIndex)); + else + await SafeInvokeVoidAsync(JsInteropUtils.FocusInputElement, GetInputId(Length - 1)); + + await NotifyChangesAsync(); + return; + } + + var digit = numericValue; + + otpValues[index] = digit; + + // Reset the input value on the client side if it doesn't match the sanitized digit + if (rawValue != digit) + { + await SafeInvokeVoidAsync(JsInteropUtils.SetInputElementValue, GetInputId(index), digit); + } + + // Move focus to the next input field + if (index < Length - 1) + { + await SafeInvokeVoidAsync(JsInteropUtils.FocusInputElement, GetInputId(index + 1)); + } + + await NotifyChangesAsync(); + } + + private async Task OnKeyUp(KeyboardEventArgs e, int index) + { + // Handle backspace key to clear the current input and focus on the previous one + if (e.Key == "Backspace" && index > 0) + { + otpValues[index] = string.Empty; + await SafeInvokeVoidAsync(JsInteropUtils.FocusInputElement, GetInputId(index - 1)); + + // Notify changes + await NotifyChangesAsync(); + } + + // Handle left arrow key to focus on the previous input + if (e.Key == "ArrowLeft" && index > 0) + await SafeInvokeVoidAsync(JsInteropUtils.FocusInputElement, GetInputId(index - 1)); + + // Handle right arrow key to focus on the next input + if (e.Key == "ArrowRight" && index < Length - 1) + await SafeInvokeVoidAsync(JsInteropUtils.FocusInputElement, GetInputId(index + 1)); + } + + #endregion + + #region Properties, Indexers + + protected override string? ClassNames => + BuildClassNames( + Class, + (BootstrapClass.FormControl, true), + (BootstrapClass.TextCenter, true), + (BootstrapClass.MarginEnd2, true) + ); + + protected override string? StyleNames => + BuildClassNames( + Style, + ("width:40px;", true), + ("height:40px;", true) + ); + + private string? ContainerClassNames => + BuildClassNames( + ContainerCssClass, + (BootstrapClass.Flex, true), + (BootstrapClass.FlexRow, true) + ); + + /// + /// Gets or sets the CSS class for the container element. + /// + /// Default value is . + /// + /// + [AddedVersion("4.0.0")] + [DefaultValue(null)] + [Description("Gets or sets the CSS class for the container element.")] + [Parameter] + public string? ContainerCssClass { get; set; } + + /// + /// Gets or sets the CSS style for the container element. + /// + /// Default value is . + /// + /// + [AddedVersion("4.0.0")] + [DefaultValue(null)] + [Description("Gets or sets the CSS style for the container element.")] + [Parameter] + public string? ContainerCssStyle { get; set; } + + private string? ContainerStyleNames => + BuildClassNames( + ContainerCssStyle + ); + + /// + /// Gets or sets the OTP input length. + /// + /// Default value is 6. + /// + /// + [AddedVersion("4.0.0")] + [DefaultValue(6)] + [Description("Gets or sets the OTP input length.")] + [Parameter] + public int Length { get; set; } = 6; + + /// + /// This event fires when the OTP input value changes. + /// + [AddedVersion("4.0.0")] + [Description("This event fires when the OTP input value changes.")] + [Parameter] + public EventCallback OnOTPChanged { get; set; } + + // Disabled + // Divider + + /// + /// This event fires when the OTP input is completed. + /// + [AddedVersion("4.0.0")] + [Description("This event fires when the OTP input is completed.")] + [Parameter] + public EventCallback OnOTPCompleted { get; set; } + + #endregion + + // Size + // Style + // Width +} diff --git a/blazorbootstrap/Components/Form/RadioInput/RadioInput.razor.cs b/blazorbootstrap/Components/Form/RadioInput/RadioInput.razor.cs index b0dffa725..4fb9c4fb3 100644 --- a/blazorbootstrap/Components/Form/RadioInput/RadioInput.razor.cs +++ b/blazorbootstrap/Components/Form/RadioInput/RadioInput.razor.cs @@ -16,7 +16,7 @@ protected override async Task OnAfterRenderAsync(bool firstRender) { if (firstRender) { - await JSRuntime.InvokeVoidAsync("window.blazorBootstrap.radioInput.initialize", Id, Name, objRef); + await SafeInvokeVoidAsync("window.blazorBootstrap.radioInput.initialize", Id, Name, objRef); } await base.OnAfterRenderAsync(firstRender); diff --git a/blazorbootstrap/Components/Form/RangeInput/RangeInput.razor.cs b/blazorbootstrap/Components/Form/RangeInput/RangeInput.razor.cs index f112e4511..d3661c438 100644 --- a/blazorbootstrap/Components/Form/RangeInput/RangeInput.razor.cs +++ b/blazorbootstrap/Components/Form/RangeInput/RangeInput.razor.cs @@ -28,7 +28,7 @@ protected override async Task OnAfterRenderAsync(bool firstRender) { if (firstRender) { - await JSRuntime.InvokeVoidAsync("window.blazorBootstrap.rangeInput.initialize", Id, objRef); + await SafeInvokeVoidAsync("window.blazorBootstrap.rangeInput.initialize", Id, objRef); var currentValue = Value; // object diff --git a/blazorbootstrap/Components/Form/TimeInput/TimeInput.razor.cs b/blazorbootstrap/Components/Form/TimeInput/TimeInput.razor.cs index ca48872d4..973814c2d 100644 --- a/blazorbootstrap/Components/Form/TimeInput/TimeInput.razor.cs +++ b/blazorbootstrap/Components/Form/TimeInput/TimeInput.razor.cs @@ -202,7 +202,7 @@ private async Task SetValueAsync(TValue oldValue, object? newValue) formattedValue = GetFormattedValue(Value!); if (oldValue!.Equals(Value)) - await JSRuntime.InvokeVoidAsync("window.blazorBootstrap.timeInput.setValue", Id, formattedValue); + await SafeInvokeVoidAsync("window.blazorBootstrap.timeInput.setValue", Id, formattedValue); await ValueChanged.InvokeAsync(Value); diff --git a/blazorbootstrap/Components/Grid/Grid.razor.cs b/blazorbootstrap/Components/Grid/Grid.razor.cs index 331e101e8..63b3b6cbf 100644 --- a/blazorbootstrap/Components/Grid/Grid.razor.cs +++ b/blazorbootstrap/Components/Grid/Grid.razor.cs @@ -331,7 +331,7 @@ internal async Task SortingChangedAsync(GridColumn column) await RefreshDataAsync(false); } - private async Task CheckOrUnCheckAll() => await JSRuntime.InvokeVoidAsync("window.blazorBootstrap.grid.checkOrUnCheckAll", $".bb-grid-form-check-{headerCheckboxId} > input.form-check-input", allItemsSelected); + private async Task CheckOrUnCheckAll() => await SafeInvokeVoidAsync("window.blazorBootstrap.grid.checkOrUnCheckAll", $".bb-grid-form-check-{headerCheckboxId} > input.form-check-input", allItemsSelected); /// /// Child selection template. @@ -534,7 +534,7 @@ private async Task OnRowCheckboxChanged(string id, TItem item, ChangeEventArgs a private async Task OnScroll(EventArgs e) { - await JSRuntime.InvokeVoidAsync("window.blazorBootstrap.grid.scroll", Id); + await SafeInvokeVoidAsync("window.blazorBootstrap.grid.scroll", Id); } private void PrepareCheckboxIds() @@ -617,7 +617,7 @@ private async Task SelectAllItemsInternalAsync(bool selectAll) private Task SetCheckboxStateAsync(string id, CheckboxState checkboxState) { - queuedTasks.Enqueue(async () => await JSRuntime.InvokeVoidAsync("window.blazorBootstrap.grid.setSelectAllCheckboxState", id, (int)checkboxState)); + queuedTasks.Enqueue(async () => await SafeInvokeVoidAsync("window.blazorBootstrap.grid.setSelectAllCheckboxState", id, (int)checkboxState)); return Task.CompletedTask; } diff --git a/blazorbootstrap/Components/Maps/GoogleMap.razor.cs b/blazorbootstrap/Components/Maps/GoogleMap.razor.cs index a0688a3c5..0eba255af 100644 --- a/blazorbootstrap/Components/Maps/GoogleMap.razor.cs +++ b/blazorbootstrap/Components/Maps/GoogleMap.razor.cs @@ -26,7 +26,7 @@ protected override async Task OnInitializedAsync() [Description("Adds a marker to the GoogleMap.")] public ValueTask AddMarkerAsync(GoogleMapMarker marker) { - JSRuntime.InvokeVoidAsync("window.blazorBootstrap.googlemaps.addMarker", Id, marker, objRef); + _ = SafeInvokeVoidAsync("window.blazorBootstrap.googlemaps.addMarker", Id, marker, objRef); return ValueTask.CompletedTask; } @@ -46,7 +46,7 @@ public async Task OnMarkerClickJS(GoogleMapMarker marker) [Description("Refreshes the Google Map component.")] public ValueTask RefreshAsync() { - JSRuntime.InvokeVoidAsync("window.blazorBootstrap.googlemaps.initialize", Id, Zoom, Center, Markers, Clickable, objRef); + _ = SafeInvokeVoidAsync("window.blazorBootstrap.googlemaps.initialize", Id, Zoom, Center, Markers, Clickable, objRef); return ValueTask.CompletedTask; } @@ -59,14 +59,14 @@ public ValueTask RefreshAsync() [Description("Updates the markers on the Google Map.")] public ValueTask UpdateMarkersAsync(IEnumerable markers) { - JSRuntime.InvokeVoidAsync("window.blazorBootstrap.googlemaps.updateMarkers", Id, markers, objRef); + _ = SafeInvokeVoidAsync("window.blazorBootstrap.googlemaps.updateMarkers", Id, markers, objRef); return ValueTask.CompletedTask; } private void OnScriptLoad() { - Task.Run(async () => await JSRuntime.InvokeVoidAsync("window.blazorBootstrap.googlemaps.initialize", Id, Zoom, Center, Markers, Clickable, objRef)); + Task.Run(() => SafeInvokeVoidAsync("window.blazorBootstrap.googlemaps.initialize", Id, Zoom, Center, Markers, Clickable, objRef)); } #endregion diff --git a/blazorbootstrap/Components/Modals/Modal.razor.cs b/blazorbootstrap/Components/Modals/Modal.razor.cs index c0b6b4991..02a81ed37 100644 --- a/blazorbootstrap/Components/Modals/Modal.razor.cs +++ b/blazorbootstrap/Components/Modals/Modal.razor.cs @@ -32,7 +32,7 @@ protected override async ValueTask DisposeAsyncCore(bool disposing) try { if (IsRenderComplete) - await JSRuntime.InvokeVoidAsync("window.blazorBootstrap.modal.dispose", Id); + await SafeInvokeVoidAsync("window.blazorBootstrap.modal.dispose", Id); } catch (JSDisconnectedException) { @@ -51,7 +51,7 @@ protected override async ValueTask DisposeAsyncCore(bool disposing) protected override async Task OnAfterRenderAsync(bool firstRender) { if (firstRender) - await JSRuntime.InvokeVoidAsync("window.blazorBootstrap.modal.initialize", Id, UseStaticBackdrop, CloseOnEscape, objRef); + await SafeInvokeVoidAsync("window.blazorBootstrap.modal.initialize", Id, UseStaticBackdrop, CloseOnEscape, objRef); await base.OnAfterRenderAsync(firstRender); } @@ -95,7 +95,7 @@ public async Task bsHiddenModal() public async Task HideAsync() { isVisible = false; - await JSRuntime.InvokeVoidAsync("window.blazorBootstrap.modal.hide", Id); + await SafeInvokeVoidAsync("window.blazorBootstrap.modal.hide", Id); } /// @@ -156,7 +156,7 @@ private async Task ShowAsync(string? title, string? message, Type? type, Diction await InvokeAsync(StateHasChanged); - await JSRuntime.InvokeVoidAsync("window.blazorBootstrap.modal.show", Id); + await SafeInvokeVoidAsync("window.blazorBootstrap.modal.show", Id); } #endregion diff --git a/blazorbootstrap/Components/Offcanvas/Offcanvas.razor.cs b/blazorbootstrap/Components/Offcanvas/Offcanvas.razor.cs index a56075b42..f84ebc3c8 100644 --- a/blazorbootstrap/Components/Offcanvas/Offcanvas.razor.cs +++ b/blazorbootstrap/Components/Offcanvas/Offcanvas.razor.cs @@ -24,7 +24,7 @@ protected override async ValueTask DisposeAsyncCore(bool disposing) try { if (IsRenderComplete) - await JSRuntime.InvokeVoidAsync("window.blazorBootstrap.offcanvas.dispose", Id); + await SafeInvokeVoidAsync("window.blazorBootstrap.offcanvas.dispose", Id); } catch (JSDisconnectedException) { @@ -40,7 +40,7 @@ protected override async ValueTask DisposeAsyncCore(bool disposing) protected override async Task OnAfterRenderAsync(bool firstRender) { if (firstRender) - await JSRuntime.InvokeVoidAsync("window.blazorBootstrap.offcanvas.initialize", Id, UseStaticBackdrop, CloseOnEscape, IsScrollable, objRef); + await SafeInvokeVoidAsync("window.blazorBootstrap.offcanvas.initialize", Id, UseStaticBackdrop, CloseOnEscape, IsScrollable, objRef); await base.OnAfterRenderAsync(firstRender); } @@ -71,7 +71,7 @@ protected override async Task OnInitializedAsync() /// [AddedVersion("1.0.0")] [Description("Hides an offcanvas.")] - public async Task HideAsync() => await JSRuntime.InvokeVoidAsync("window.blazorBootstrap.offcanvas.hide", Id); + public async Task HideAsync() => await SafeInvokeVoidAsync("window.blazorBootstrap.offcanvas.hide", Id); /// /// Shows an offcanvas. @@ -97,7 +97,7 @@ private async Task ShowAsync(string? title, Type? type, Dictionary> moduleTask; - - #endregion - #region Constructors public PdfViewerJsInterop(IJSRuntime jsRuntime) + : base(jsRuntime, "./_content/Blazor.Bootstrap/blazor.bootstrap.pdf.js") { - moduleTask = new Lazy>(() => jsRuntime.InvokeAsync("import", "./_content/Blazor.Bootstrap/blazor.bootstrap.pdf.js").AsTask()); } #endregion #region Methods - public async ValueTask DisposeAsync() - { - if (moduleTask.IsValueCreated) - { - var module = await moduleTask.Value; - await module.DisposeAsync(); - } - } - public async Task FirstPageAsync(object objRef, string elementId) { - var module = await moduleTask.Value; - await module.InvokeVoidAsync("firstPage", objRef, elementId); + await SafeInvokeVoidAsync("firstPage", objRef, elementId); } public async Task GotoPageAsync(object objRef, string elementId, int gotoPageNum) { - var module = await moduleTask.Value; - await module.InvokeVoidAsync("gotoPage", objRef, elementId, gotoPageNum); + await SafeInvokeVoidAsync("gotoPage", objRef, elementId, gotoPageNum); } public async Task InitializeAsync(object objRef, string elementId, double scale, double rotation, string url, string password) { - var module = await moduleTask.Value; - await module.InvokeVoidAsync("initialize", objRef, elementId, scale, rotation, url, password); + await SafeInvokeVoidAsync("initialize", objRef, elementId, scale, rotation, url, password); } public async Task LastPageAsync(object objRef, string elementId) { - var module = await moduleTask.Value; - await module.InvokeVoidAsync("lastPage", objRef, elementId); + await SafeInvokeVoidAsync("lastPage", objRef, elementId); } public async Task NextPageAsync(object objRef, string elementId) { - var module = await moduleTask.Value; - await module.InvokeVoidAsync("nextPage", objRef, elementId); + await SafeInvokeVoidAsync("nextPage", objRef, elementId); } public async Task PreviousPageAsync(object objRef, string elementId) { - var module = await moduleTask.Value; - await module.InvokeVoidAsync("previousPage", objRef, elementId); + await SafeInvokeVoidAsync("previousPage", objRef, elementId); } public async Task PrintAsync(object objRef, string elementId, string url) { - var module = await moduleTask.Value; - await module.InvokeVoidAsync("print", objRef, elementId, url); + await SafeInvokeVoidAsync("print", objRef, elementId, url); } public async Task RotateAsync(object objRef, string elementId, double rotation) { - var module = await moduleTask.Value; - await module.InvokeVoidAsync("rotate", objRef, elementId, rotation); + await SafeInvokeVoidAsync("rotate", objRef, elementId, rotation); } public async Task ZoomInOutAsync(object objRef, string elementId, double scale) { - var module = await moduleTask.Value; - await module.InvokeVoidAsync("zoomInOut", objRef, elementId, scale); + await SafeInvokeVoidAsync("zoomInOut", objRef, elementId, scale); } #endregion diff --git a/blazorbootstrap/Components/ScriptLoader/ScriptLoader.razor.cs b/blazorbootstrap/Components/ScriptLoader/ScriptLoader.razor.cs index 0683213f5..9c15eb4b8 100644 --- a/blazorbootstrap/Components/ScriptLoader/ScriptLoader.razor.cs +++ b/blazorbootstrap/Components/ScriptLoader/ScriptLoader.razor.cs @@ -24,7 +24,7 @@ public partial class ScriptLoader : BlazorBootstrapComponentBase protected override async Task OnAfterRenderAsync(bool firstRender) { if (firstRender) - await JSRuntime.InvokeVoidAsync("window.blazorBootstrap.scriptLoader.initialize", Id, Async, Defer, ScriptId, Source, ScriptType, objRef); + await SafeInvokeVoidAsync("window.blazorBootstrap.scriptLoader.initialize", Id, Async, Defer, ScriptId, Source, ScriptType, objRef); await base.OnAfterRenderAsync(firstRender); } diff --git a/blazorbootstrap/Components/Sidebar/Sidebar.razor.cs b/blazorbootstrap/Components/Sidebar/Sidebar.razor.cs index 7c9b77d61..0c05e404d 100644 --- a/blazorbootstrap/Components/Sidebar/Sidebar.razor.cs +++ b/blazorbootstrap/Components/Sidebar/Sidebar.razor.cs @@ -24,7 +24,7 @@ protected override async Task OnAfterRenderAsync(bool firstRender) { if (firstRender) { - await JSRuntime.InvokeVoidAsync("window.blazorBootstrap.sidebar.initialize", Id, objRef); + await SafeInvokeVoidAsync("window.blazorBootstrap.sidebar.initialize", Id, objRef); var width = await JSRuntime.InvokeAsync("window.blazorBootstrap.sidebar.windowSize"); diff --git a/blazorbootstrap/Components/SortableList/SortableListJsInterop.cs b/blazorbootstrap/Components/SortableList/SortableListJsInterop.cs index dbda19200..bf9c7aef7 100644 --- a/blazorbootstrap/Components/SortableList/SortableListJsInterop.cs +++ b/blazorbootstrap/Components/SortableList/SortableListJsInterop.cs @@ -1,37 +1,21 @@ namespace BlazorBootstrap; -public class SortableListJsInterop : IAsyncDisposable +public class SortableListJsInterop : JsInteropBase { - #region Fields and Constants - - private readonly Lazy> moduleTask; - - #endregion - #region Constructors public SortableListJsInterop(IJSRuntime jsRuntime) + : base(jsRuntime, "./_content/Blazor.Bootstrap/blazor.bootstrap.sortable-list.js") { - moduleTask = new Lazy>(() => jsRuntime.InvokeAsync("import", "./_content/Blazor.Bootstrap/blazor.bootstrap.sortable-list.js").AsTask()); } #endregion #region Methods - public async ValueTask DisposeAsync() - { - if (moduleTask.IsValueCreated) - { - var module = await moduleTask.Value; - await module.DisposeAsync(); - } - } - public async Task InitializeAsync(string elementId, string elementName, string handle, string group, bool allowSorting, object pull, object put, string filter, object objRef) { - var module = await moduleTask.Value; - await module.InvokeVoidAsync("initialize", elementId, elementName, handle, group, allowSorting, pull, put, filter, objRef); + await SafeInvokeVoidAsync("initialize", elementId, elementName, handle, group, allowSorting, pull, put, filter, objRef); } #endregion diff --git a/blazorbootstrap/Components/Tabs/Tab.razor.cs b/blazorbootstrap/Components/Tabs/Tab.razor.cs index 80048b65b..3751a9700 100644 --- a/blazorbootstrap/Components/Tabs/Tab.razor.cs +++ b/blazorbootstrap/Components/Tabs/Tab.razor.cs @@ -7,16 +7,9 @@ public partial class Tab : BlazorBootstrapComponentBase /// protected override async ValueTask DisposeAsyncCore(bool disposing) { - if (disposing && IsRenderComplete) + if (disposing && IsRenderComplete && IsJsRuntimeAvailable) { - try - { - await JSRuntime.InvokeVoidAsync("window.blazorBootstrap.tabs.dispose", Id); - } - catch (JSDisconnectedException) - { - // do nothing - } + await SafeInvokeVoidAsync("window.blazorBootstrap.tabs.dispose", Id); } await base.DisposeAsyncCore(disposing); diff --git a/blazorbootstrap/Components/Tabs/Tabs.razor.cs b/blazorbootstrap/Components/Tabs/Tabs.razor.cs index 573a49a92..2ae387515 100644 --- a/blazorbootstrap/Components/Tabs/Tabs.razor.cs +++ b/blazorbootstrap/Components/Tabs/Tabs.razor.cs @@ -24,7 +24,29 @@ public partial class Tabs : BlazorBootstrapComponentBase protected override async ValueTask DisposeAsyncCore(bool disposing) { if (disposing && tabs is not null) + { + foreach (var tab in tabs) + { + try + { + if (tab is IAsyncDisposable asyncDisposable) + { + await asyncDisposable.DisposeAsync(); + } + else if (tab is IDisposable disposable) + { + disposable.Dispose(); + } + } + catch (Exception ex) + { + System.Diagnostics.Debug.WriteLine($"Exception while disposing tab '{tab?.Name}': {ex}"); + } + } + + tabs.Clear(); tabs = null!; + } await base.DisposeAsyncCore(disposing); } @@ -32,7 +54,7 @@ protected override async ValueTask DisposeAsyncCore(bool disposing) protected override async Task OnAfterRenderAsync(bool firstRender) { if (firstRender) - await JSRuntime.InvokeVoidAsync("window.blazorBootstrap.tabs.initialize", Id, objRef); + await SafeInvokeVoidAsync("window.blazorBootstrap.tabs.initialize", Id, objRef); // Set active tab if (firstRender && !isDefaultActiveTabSet) @@ -283,7 +305,7 @@ private async Task ShowTabAsync(Tab tab) if (!isDefaultActiveTabSet) isDefaultActiveTabSet = true; - queuedTasks.Enqueue(async () => await JSRuntime.InvokeVoidAsync("window.blazorBootstrap.tabs.show", tab.Id)); + queuedTasks.Enqueue(async () => await SafeInvokeVoidAsync("window.blazorBootstrap.tabs.show", tab.Id)); if (tab?.OnClick.HasDelegate ?? false) await tab.OnClick.InvokeAsync(new TabEventArgs(tab.Name!, tab.Title!)); diff --git a/blazorbootstrap/Components/ThemeSwitcher/ThemeSwitcherJsInterop.cs b/blazorbootstrap/Components/ThemeSwitcher/ThemeSwitcherJsInterop.cs index 50f8f9c2d..53ece5576 100644 --- a/blazorbootstrap/Components/ThemeSwitcher/ThemeSwitcherJsInterop.cs +++ b/blazorbootstrap/Components/ThemeSwitcher/ThemeSwitcherJsInterop.cs @@ -1,44 +1,21 @@ namespace BlazorBootstrap; -public class ThemeSwitcherJsInterop : IAsyncDisposable +public class ThemeSwitcherJsInterop : JsInteropBase { - #region Fields and Constants - - private readonly Lazy> moduleTask; - - #endregion - #region Constructors public ThemeSwitcherJsInterop(IJSRuntime jsRuntime) + : base(jsRuntime, "./_content/Blazor.Bootstrap/blazor.bootstrap.theme-switcher.js") { - moduleTask = new Lazy>(() => jsRuntime.InvokeAsync("import", "./_content/Blazor.Bootstrap/blazor.bootstrap.theme-switcher.js").AsTask()); } #endregion #region Methods - public async ValueTask DisposeAsync() - { - try - { - if (moduleTask.IsValueCreated) - { - var module = await moduleTask.Value; - await module.DisposeAsync(); - } - } - catch (JSDisconnectedException) - { - // do nothing - } - } - public async Task InitializeAsync(DotNetObjectReference? objRef) { - var module = await moduleTask.Value; - await module.InvokeVoidAsync("initializeTheme", objRef); + await SafeInvokeVoidAsync("initializeTheme", objRef); } internal Task SetAutoThemeAsync(DotNetObjectReference? objRef) => SetThemeAsync(objRef, "system"); @@ -49,8 +26,7 @@ public async Task InitializeAsync(DotNetObjectReference? objRef) internal async Task SetThemeAsync(DotNetObjectReference? objRef, string themeName) { - var module = await moduleTask.Value; - await module.InvokeVoidAsync("setTheme", objRef, themeName); + await SafeInvokeVoidAsync("setTheme", objRef, themeName); } #endregion diff --git a/blazorbootstrap/Components/Toasts/SimpleToast.razor.cs b/blazorbootstrap/Components/Toasts/SimpleToast.razor.cs index 6bce92ce9..7b3040332 100644 --- a/blazorbootstrap/Components/Toasts/SimpleToast.razor.cs +++ b/blazorbootstrap/Components/Toasts/SimpleToast.razor.cs @@ -15,7 +15,8 @@ protected override async ValueTask DisposeAsyncCore(bool disposing) { if (disposing) { - await JSRuntime.InvokeVoidAsync("window.blazorBootstrap.toasts.dispose", Id); + await SafeInvokeVoidAsync("window.blazorBootstrap.toasts.dispose", Id); + objRef?.Dispose(); } @@ -52,12 +53,12 @@ protected override async Task OnInitializedAsync() /// /// Hides an element’s toast. /// - public async Task HideAsync() => await JSRuntime.InvokeVoidAsync("window.blazorBootstrap.toasts.hide", Id); + public async Task HideAsync() => await SafeInvokeVoidAsync("window.blazorBootstrap.toasts.hide", Id); /// /// Reveals an element’s toast. /// - public async Task ShowAsync() => await JSRuntime.InvokeVoidAsync("window.blazorBootstrap.toasts.show", Id, AutoHide, Delay, objRef); + public async Task ShowAsync() => await SafeInvokeVoidAsync("window.blazorBootstrap.toasts.show", Id, AutoHide, Delay, objRef); #endregion diff --git a/blazorbootstrap/Components/Toasts/Toast.razor.cs b/blazorbootstrap/Components/Toasts/Toast.razor.cs index 58f33c8cc..0df7a39c8 100644 --- a/blazorbootstrap/Components/Toasts/Toast.razor.cs +++ b/blazorbootstrap/Components/Toasts/Toast.razor.cs @@ -21,15 +21,8 @@ protected override async ValueTask DisposeAsyncCore(bool disposing) { if (disposing) { - try - { - if (IsRenderComplete) - await JSRuntime.InvokeVoidAsync("window.blazorBootstrap.toasts.dispose", Id); - } - catch (JSDisconnectedException) - { - // do nothing - } + if (IsRenderComplete) + await SafeInvokeVoidAsync("window.blazorBootstrap.toasts.dispose", Id); objRef?.Dispose(); } @@ -93,12 +86,12 @@ protected override async Task OnInitializedAsync() /// /// Hides an element’s toast. /// - public async Task HideAsync() => await JSRuntime.InvokeVoidAsync("window.blazorBootstrap.toasts.hide", Id); + public async Task HideAsync() => await SafeInvokeVoidAsync("window.blazorBootstrap.toasts.hide", Id); /// /// Reveals an element’s toast. /// - public async Task ShowAsync() => await JSRuntime.InvokeVoidAsync("window.blazorBootstrap.toasts.show", Id, AutoHide, Delay, objRef); + public async Task ShowAsync() => await SafeInvokeVoidAsync("window.blazorBootstrap.toasts.show", Id, AutoHide, Delay, objRef); private string GetIconClass() => ToastMessage.Type switch diff --git a/blazorbootstrap/Components/Toasts/Toasts.razor.cs b/blazorbootstrap/Components/Toasts/Toasts.razor.cs index 7cc1eb7e1..024358339 100644 --- a/blazorbootstrap/Components/Toasts/Toasts.razor.cs +++ b/blazorbootstrap/Components/Toasts/Toasts.razor.cs @@ -72,7 +72,7 @@ private async Task OnToastShownAsync(ToastEventArgs args) { Messages.Remove(message); if (!string.IsNullOrWhiteSpace(message.ElementId)) - await JSRuntime.InvokeVoidAsync("window.blazorBootstrap.toasts.hide", message.ElementId); + await SafeInvokeVoidAsync("window.blazorBootstrap.toasts.hide", message.ElementId); } } } diff --git a/blazorbootstrap/Components/Tooltip/Tooltip.razor.cs b/blazorbootstrap/Components/Tooltip/Tooltip.razor.cs index fb34e959c..0707015f4 100644 --- a/blazorbootstrap/Components/Tooltip/Tooltip.razor.cs +++ b/blazorbootstrap/Components/Tooltip/Tooltip.razor.cs @@ -21,7 +21,7 @@ protected override async ValueTask DisposeAsyncCore(bool disposing) try { if (IsRenderComplete) - await JSRuntime.InvokeVoidAsync("window.blazorBootstrap.tooltip.dispose", Element); + await SafeInvokeVoidAsync("window.blazorBootstrap.tooltip.dispose", Element); } catch (JSDisconnectedException) { @@ -38,7 +38,7 @@ protected override async Task OnAfterRenderAsync(bool firstRender) { if (firstRender) { - await JSRuntime.InvokeVoidAsync("window.blazorBootstrap.tooltip.initialize", Element); + await SafeInvokeVoidAsync("window.blazorBootstrap.tooltip.initialize", Element); isFirstRenderComplete = true; } @@ -65,14 +65,14 @@ protected override async Task OnParametersSetAsync() title = Title; color = Color; - await JSRuntime.InvokeVoidAsync("window.blazorBootstrap.tooltip.dispose", Element); - await JSRuntime.InvokeVoidAsync("window.blazorBootstrap.tooltip.update", Element); + await SafeInvokeVoidAsync("window.blazorBootstrap.tooltip.dispose", Element); + await SafeInvokeVoidAsync("window.blazorBootstrap.tooltip.update", Element); } } public async Task ShowAsync() { - await JSRuntime.InvokeVoidAsync("window.blazorBootstrap.tooltip.show", Element); + await SafeInvokeVoidAsync("window.blazorBootstrap.tooltip.show", Element); } #endregion diff --git a/blazorbootstrap/Constants/BootstrapClass.cs b/blazorbootstrap/Constants/BootstrapClass.cs index 7b92fb56a..d8ee08c56 100644 --- a/blazorbootstrap/Constants/BootstrapClass.cs +++ b/blazorbootstrap/Constants/BootstrapClass.cs @@ -103,6 +103,9 @@ public static class BootstrapClass public const string ImageFluid = "img-fluid"; public const string ImageThumbnail = "img-thumbnail"; + public const string MarginEnd1 = "me-1"; + public const string MarginEnd2 = "me-2"; + public const string Modal = "modal"; public const string ModalFade = "fade"; @@ -143,6 +146,7 @@ public static class BootstrapClass public const string TableResponsive = "table-responsive"; public const string TableSticky = "bb-table-sticky"; + public const string TextCenter = "text-center"; public const string TextNoWrap = "text-nowrap"; public const string Toast = "toast"; diff --git a/blazorbootstrap/Extensions/CharExtensions.cs b/blazorbootstrap/Extensions/CharExtensions.cs new file mode 100644 index 000000000..9aa38342b --- /dev/null +++ b/blazorbootstrap/Extensions/CharExtensions.cs @@ -0,0 +1,11 @@ +namespace BlazorBootstrap; + +public static class CharExtensions +{ + public static bool IsAlphanumeric(this char c) + { + return (c >= 'a' && c <= 'z') || + (c >= 'A' && c <= 'Z') || + (c >= '0' && c <= '9'); + } +} diff --git a/blazorbootstrap/Usings.cs b/blazorbootstrap/Usings.cs index 734a0407a..30d068b25 100644 --- a/blazorbootstrap/Usings.cs +++ b/blazorbootstrap/Usings.cs @@ -10,3 +10,4 @@ global using System.Linq.Expressions; global using System.Text.Json.Serialization; global using System.Text.RegularExpressions; +global using Microsoft.JSInterop; diff --git a/blazorbootstrap/Utils/JsInteropUtils.cs b/blazorbootstrap/Utils/JsInteropUtils.cs new file mode 100644 index 000000000..0124b8f4a --- /dev/null +++ b/blazorbootstrap/Utils/JsInteropUtils.cs @@ -0,0 +1,14 @@ +namespace BlazorBootstrap; + +public class JsInteropUtils +{ + #region Fields and Constants + + private const string Prefix = "window.blazorBootstrap."; + + public const string FocusInputElement = Prefix + "focusInputElement"; + + public const string SetInputElementValue = Prefix + "setInputElementValue"; + + #endregion +} diff --git a/blazorbootstrap/wwwroot/blazor.bootstrap.js b/blazorbootstrap/wwwroot/blazor.bootstrap.js index daefce205..fa50c95de 100644 --- a/blazorbootstrap/wwwroot/blazor.bootstrap.js +++ b/blazorbootstrap/wwwroot/blazor.bootstrap.js @@ -948,8 +948,11 @@ window.blazorBootstrap = { windowSize: () => window.innerWidth }, // global function - invokeMethodAsync: (callbackEventName, dotNetHelper) => { - dotNetHelper.invokeMethodAsync(callbackEventName); + focusInputElement(elementId) { + const element = document.getElementById(elementId); + if (element) { + element.focus(); + } }, hasInvalidChars: (input, validChars) => { if (input.length <= 0 || validChars.length <= 0) @@ -963,6 +966,9 @@ window.blazorBootstrap = { return false; }, + invokeMethodAsync: (callbackEventName, dotNetHelper) => { + dotNetHelper.invokeMethodAsync(callbackEventName); + }, scrollToElementBottom: (elementId) => { let el = document.getElementById(elementId); if (el) @@ -972,6 +978,12 @@ window.blazorBootstrap = { let el = document.getElementById(elementId); if (el) el.scrollTop = 0; + }, + setInputElementValue: (elementId, value) => { + const element = document.getElementById(elementId); + if (element) { + element.value = value; + } } } diff --git a/blazorbootstrap/wwwroot/blazor.bootstrap.pdf.js b/blazorbootstrap/wwwroot/blazor.bootstrap.pdf.js index a920e1782..3769918b8 100644 --- a/blazorbootstrap/wwwroot/blazor.bootstrap.pdf.js +++ b/blazorbootstrap/wwwroot/blazor.bootstrap.pdf.js @@ -195,7 +195,10 @@ pageRotateCcwButton.disabled = this.pagesCount === 0; */ export function initialize(dotNetHelper, elementId, scale, rotation, url, password = null) { - const pdf = new Pdf(elementId); + const canvas = getCanvas(elementId); + if (!canvas) return; + + const pdf = new Pdf(canvas); pdf.scale = scale; pdf.rotation = rotation;