How do I restart a component in a Blazor page - razor

I have a Syncfusion SfDialog in my code and I need the component in the content to restart every time the dialog is open. So far I have tried this:
<SfDialog Visible="_dialogTripRunAutoRoute" Width="75%" ShowCloseIcon="true" IsModal="true" AllowPrerender="true">
<DialogEvents Closed="#CloseDialogTripRunAutoRoute"></DialogEvents>
<DialogTemplates>
<Content>
#_tripRunAutoRoute
</Content>
</DialogTemplates>
<DialogPositionData X="center" Y="top"></DialogPositionData>
</SfDialog>
private async Task ToggleDialogTripRunAutoRoute(){
_tripRunAutoRoute = new TripRunAutoRoute();
_tripRunAutoRoute.ModelTripRun = TripOps.TripRunAutoRouteFormModel;
await InvokeAsync(StateHasChanged);
_dialogTripRunAutoRoute = !_dialogTripRunAutoRoute;
}
The result is

Assumption. #_tripRunAutoRoute is just a plain old RenderFragment.
You can't "restart" a component. The Renderer controls the component's lifecycle, not you. Any attempt to reset its state internally will be reverted the next time the page renders.
Move the toggle parameter outside the control like this:
#if(_dialogTripRunAutoRoute)
{
SfDialog stuff
}
Now the Renderer will remove the component from the render tree when _dialogTripRunAutoRoute is false, and create a new instance when it's true.

You can use the Opened and Closed event of the Dialog control to re render your component added in the Dialog content. Refer the API and code below,
<div class=" col-lg-8 control-section sb-property-border" id="target" style="height:350px;">
<div>
#if (this.ShowButton)
{
<button class="e-btn" #onclick="#OnBtnClick">Open</button>
}
</div>
<SfDialog Width="335px" IsModal="true" #bind-Visible="Visibility" AllowPrerender="true" CssClass="dialog-medium">
<DialogTemplates>
<Header> Software Update </Header>
<Content>
#if(DialogBool)
{
#DialogContent
<div>#count</div>
}
</Content>
</DialogTemplates>
<DialogButtons>
<DialogButton Content="OK" IsPrimary="true" OnClick="#DlgButtonClick" />
</DialogButtons>
<DialogEvents OnOpen="#DialogOpen" Closed="#DialogClose"></DialogEvents>
<DialogAnimationSettings Effect="#DialogEffect.None"></DialogAnimationSettings>
</SfDialog>
#code {
SfCheckBox<bool> CheckboxObj;
public int count { get; set; } = 0;
public bool DialogBool { get; set; } = false;
public string DialogContent { get; set; } = "";
private bool Visibility { get; set; } = true;
private bool ShowButton { get; set; } = false;
private void DialogOpen(Object args)
{
this.ShowButton = false;
DialogBool = true;
}
private void DialogClose(Object args)
{
this.ShowButton = true;
DialogBool = false;
}
private void OnBtnClick()
{
this.Visibility = true;
DialogContent = "content added";
count++;
}
private void DlgButtonClick()
{
this.Visibility = false;
}
}
API Link: https://help.syncfusion.com/cr/blazor/Syncfusion.Blazor.Popups.DialogEvents.html#Syncfusion_Blazor_Popups_DialogEvents_Opened

I need the component in the content to restart every time the dialog is open
You can make Blazor restart the component by assigning the key attribute to the component. When you change the key, Blazor re-creates the component in the DOM, thus, you can call it as restarted or rebooted.
<ComponentInTheContent #key="#(componentId)">
</ComponentInTheContent>
#code {
private Guid componentId = Guid.NewGuid();
private async Task CalledWhenDialogIsOpened()
{
// stuff
// this change of id will make Blazor re-create
// the component in the DOM as it sees it as a new component.
componentId = Guid.NewGuid();
}
}

Related

Routing to named element in Blazor (use anchor to navigate to specific element)

I cannot use an HTML anchor to navigate to a specific HTML element of a page in the Blazor Server. For example:
#page "/test"
<nav>
<!-- One version I've tried -->
Section2
<!-- Another version I've tried -->
<NavLink href="#section2">Section2</NavLink>
</nav>
#* ... *#
<h2 id="section2">It's Section2.</h2>
#* ... *#
When I click the link to Section2, I get redirected to the route http://localhost:5000/test#section2, however, will be at the top of the page. In my opinion, the browser should scroll down to the proper element, as specified by the Element Selector, but it can't.
Does it have to be done in a special way in Blazor?
I use Blazor 6 in .Net6 with Visual Studio 2022 (ver:17.0.2).
After loading a page, a browser automatically scrolls to the element identified by its id in the fragment part of the URL. It does the same when you click on an anchor with an href of the kind #element-id.
The page load behavior doesn't work for a Blazor Server because the element doesn't exist yet on page load.
The solution is to manually create a scroller using javascript and a razor component:
First of all, create a razor component like this
#inject IJSRuntime JSRuntime
#inject NavigationManager NavigationManager
#implements IDisposable
#code {
protected override void OnInitialized()
{
NavigationManager.LocationChanged += OnLocationChanged;
}
protected override async Task OnAfterRenderAsync(bool firstRender)
{
await ScrollToFragment();
}
public void Dispose()
{
NavigationManager.LocationChanged -= OnLocationChanged;
}
private async void OnLocationChanged(object sender, LocationChangedEventArgs e)
{
await ScrollToFragment();
}
private async Task ScrollToFragment()
{
var uri = new Uri(NavigationManager.Uri, UriKind.Absolute);
var fragment = uri.Fragment;
if (fragment.StartsWith('#'))
{
// Handle text fragment (https://example.org/#test:~:text=foo)
// https://github.com/WICG/scroll-to-text-fragment/
var elementId = fragment.Substring(1);
var index = elementId.IndexOf(":~:", StringComparison.Ordinal);
if (index > 0)
{
elementId = elementId.Substring(0, index);
}
if (!string.IsNullOrEmpty(elementId))
{
await JSRuntime.InvokeVoidAsync("BlazorScrollToId", elementId);
}
}
}
}
Then add this javascript code somewhere before the Blazor script renders. You can wrap it with script tags and place it in the head.
function BlazorScrollToId(id) {
const element = document.getElementById(id);
if (element instanceof HTMLElement) {
element.scrollIntoView({
behavior: "smooth",
block: "start",
inline: "nearest"
});
}
}
Finally implement it in your pages if needed. You can also place it inside your layouts, so it will work for every page you create.
#page "/"
<PageTitle>Index</PageTitle>
<a href="#my-id">
<h1>Hello, world!</h1>
</a>
<SurveyPrompt Title="How is Blazor working for you?" />
<div style="height: 2000px">
</div>
<div id="my-id">
Hello!
</div>
<AnchorNavigation />
Source: link
You can also use an ElementReference and FocusAsync which uses the built in Blazor JS. To use it you need to use a small hack to make the component "Focusable" which is to set a tabindex. I've used a span but you can use what you like. I've used #alessandromanzini's code to get the element from the NavigationManager.
Here's a component:
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Rendering;
using Microsoft.AspNetCore.Components.Routing;
using System.Diagnostics.CodeAnalysis;
namespace SO75358165;
public class Bookmark : ComponentBase, IDisposable
{
private bool _setFocus;
[Inject] private NavigationManager NavManager { get; set; } = default!;
[Parameter] public RenderFragment? ChildContent { get; set; }
[Parameter] public string? BookmarkName { get; set; }
[DisallowNull] public ElementReference? Element { get; private set; }
protected override void BuildRenderTree(RenderTreeBuilder builder)
{
builder.OpenElement(0, "span");
builder.AddAttribute(2, "tabindex", "-1");
builder.AddContent(3, this.ChildContent);
builder.AddElementReferenceCapture(4, this.SetReference);
builder.CloseElement();
}
protected override void OnInitialized()
=> NavManager.LocationChanged += this.OnLocationChanged;
protected override void OnParametersSet()
=> _setFocus = this.IsMe();
private void SetReference(ElementReference reference)
=> this.Element = reference;
private void OnLocationChanged(object? sender, LocationChangedEventArgs e)
{
if (this.IsMe())
{
_setFocus = true;
this.StateHasChanged();
}
}
protected async override Task OnAfterRenderAsync(bool firstRender)
{
if (_setFocus)
await this.Element!.Value.FocusAsync(false);
_setFocus = false;
}
private bool IsMe()
{
string? elementId = null;
var uri = new Uri(this.NavManager.Uri, UriKind.Absolute);
if (uri.Fragment.StartsWith('#'))
{
elementId = uri.Fragment.Substring(1);
return elementId == BookmarkName;
}
return false;
}
public void Dispose()
=> NavManager.LocationChanged -= this.OnLocationChanged;
}
Here's my test page:
#page "/"
<PageTitle>Index</PageTitle>
<NavLink href="#me">To me</NavLink>
<h1>Hello, world!</h1>
<h1>Hello, world!</h1>
<h1>Hello, world!</h1>
//.....
<h1>Hello, world!</h1>
<Bookmark BookmarkName="me" >
<h1 id="me">Focus on Me</h1>
</Bookmark>

Review data section at end of long form (Blazor html)?

I have several long form sections in a Blazor html project. After a user has completed all of the fields, I would like there to be a section at the very end where they can double-check the data they have inputted before submitting. Can anyone give me the basic structure/advice of how to do this?
isFilled -> is just a variable to hold the current state.
#if(isFilled == false){
<<<<<<Here is the data entery>>>>>>>>
<button #OnClick="next">Next</button>
}
#if(isFilled == true){
<section>
<<<<<<<<<<<Review the data here>>>>>>>>>
<button #OnClick="confirm">Confirm</button>
<button #OnClick="edit">Edit</button>
</section>
}
Code section :
#Code{
private bool isFilled;
protected override void OnInitialized()
{
isFilled = false;
}
private void next(){
isFilled = ture;
}
private void edit(){
isFilled = false;
}
private void confirm(){
}
}

Options pattern, configuration, in Legacy .NET Application with Simple Injector

This article, https://medium.com/#dmitryzaets/legacy-net-applications-configuration-management-net-framework-4-5-1-68220335d9d8, describe how to use Options pattern together with Autofac. I have tried to translate this to use with Simple Injector. But I have no luck.
Here is my IOC code
public class IocBootstrap2
{
private Container Container { get; }
public IocBootstrap2()
{
Container = new Container();
var configurationBuilder = new ConfigurationBuilder()
.SetBasePath(Path.Combine(System.AppDomain.CurrentDomain.BaseDirectory, "Configuration"))
.AddJsonFile("settings.json", optional: false, reloadOnChange: true);
var configuration = configurationBuilder.Build();
//Register Options
Container.Register(typeof(IOptions<>), typeof(OptionsManager<>));
Container.Register(typeof(IOptionsMonitor<>), typeof(OptionsMonitor<>));
Container.Register(typeof(IOptionsFactory<>), typeof(OptionsFactory<>));
Container.Register(typeof(IOptionsMonitorCache<>), typeof(OptionsCache<>));
// Register ConfigurationOptions
Container.RegisterConfigurationOptions2<MailingOptions>(configuration.GetSection("mailing"));
#if DEBUG
Container.Verify();
#endif
}
}
public static class ConfigurationSetupExtensions2
{
public static void RegisterConfigurationOptions2<TOptions>(this Container container, IConfiguration config)
where TOptions : class
{
container.Register(typeof(IOptionsChangeTokenSource<TOptions>),
() => new ConfigurationChangeTokenSource<TOptions>(config), Lifestyle.Transient);
container.Register(typeof(IConfigureOptions<TOptions>),
() => new ConfigureFromConfigurationOptions<TOptions>(config), Lifestyle.Transient);
}
}
public class MailingOptions
{
public MailingOptions()
{
BatchSize = 1;
}
public int BatchSize { get; set; }
public int BatchDelay { get; set; }
}
settings.json
{
"mailing": {
"batchSize": 15,
"batchDelay": 1
}
}
Then I inject it in a ViewModel:s constructor like this
public class BlockViewModel
{
private readonly MailingOptions _options;
#region Constructor
public BlockViewModel(IOptions<MailingOptions> options)
{
_options = options.Value;
}
#endregion
}
When I run it I get Exceptions in Container.Verify.
The constructor of type OptionsFactory<MailingOptions> contains the parameter with name 'setups' and type IEnumerable<IConfigureOptions<MailingOptions>> that is not registered. Please ensure IEnumerable<IConfigureOptions<MailingOptions>> is registered, or change the constructor of OptionsFactory<MailingOptions>. There is, however, a registration for IConfigureOptions<MailingOptions>; Did you mean to depend on IConfigureOptions<MailingOptions>?
StackTrace:
at SimpleInjector.Container.ThrowParameterTypeMustBeRegistered(InjectionTargetInfo target)
How will I Register an IEnumerable<IConfigureOptions<MailingOptions>>?
Can someone tell my what I'm doing wrong, or more precise, what is it that I don't understand?
The short answer is: don't inject IOptions<T> into your application components. As explained here, that will only complicate your components, their unit tests, and, as you already noticed, your configuration.
Instead, let BlockViewModel depend on MailingOptions directly:
public class BlockViewModel
{
private readonly MailingOptions _options;
public BlockViewModel(MailingOptions options)
{
_options = options ?? throw new ArgumentNullException(nameof(options));
}
}
This allows you to simplify your configuration to the following:
Container = new Container();
var configutation = new ConfigurationBuilder()
.SetBasePath(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Configuration"))
.AddJsonFile("settings.json", optional: false);
.Build();
MailingOptions options = configuration.GetSection("mailing").Get<MailingOptions>();
Container.RegisterInstance<MailingOptions>(options);
// Register View Models
Container.Register<BlockViewModel>();
Container.Verify();

Bind an Action to a property of a UserControl in XAML

I have a user control which has a button and a dependency property for the action the button is to execute. The page which contains the control sets the action in XAML.
MyUserControl.cs
A Button, and dependency property ButtonAction, of type Action. When the button is clicked it executes the ButtonAction.
MainPage.xaml.cs
Action Action1
Action Action2
MainPage.xaml
Present an instance of MyUserControl, with ButtonAction=Action1
The problem: The ButtonAction property is not assigned from the XAML
MyUserControl.cs
public sealed partial class MyUserControl : UserControl
{
public Action ButtonAction {
get { return (Action)GetValue(ButtonActionProperty); }
set { SetValue(ButtonActionProperty, value); }
}
public static readonly DependencyProperty ButtonActionProperty =
DependencyProperty.Register("ButtonAction", typeof(Action), typeof(MyUserControl), new PropertyMetadata(null,ButtonAction_PropertyChanged));
private static void ButtonAction_PropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) {
Debug.WriteLine("ButtonAction_PropertyChanged");
// Is not called!
}
public MyUserControl() {
this.InitializeComponent();
}
private void Button_Click(object sender, RoutedEventArgs e) {
if (ButtonAction != null) {
// Never reaches here!
ButtonAction();
}
}
}
MyUserControl.xaml
<Grid>
<Button Click="Button_Click">Do The Attached Action!</Button>
</Grid>
MainPage.xaml.cs
Action Action1 = (
() => { Debug.WriteLine("Action1 called"); });
Action Action2 = (() => { Debug.WriteLine("Action2 called"); });
MainPage.xaml
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<local:MyUserControl x:Name="myUserControl" ButtonAction="{Binding Action1}"/>
</Grid>
It does work if in the code-behind for MainPage (MainPage.xaml.cs) I assign the action in the Loaded event.
private void Page_Loaded(object sender, RoutedEventArgs e) {
this.myUserControl.ButtonAction = Action1;
}
In this case the PropertyChanged callback in the user control is also called. (This handler is provided only for diagnostic purposes. I can't see how it can be used to support the property in practice).
The issue is in your data binding. The Action1 in ButtonAction="{Binding Action1}" should be a public property while you defined it as a private variable.
Also, you cannot just declare a normal property directly in the code behind like that. You will need either a dependency property, or more commonly, a public property inside a viewmodel which implements INotifyPropertyChanged.
If we go with the second approach, we will need to create a viewmodel class like the following with an Action1 property. Note the OnPropertyChanged stuff is just the standard way of implementing INotifyPropertyChanged.
public class ViewModel : INotifyPropertyChanged
{
private Action _action1;
public Action Action1
{
get { return _action1; }
set
{
_action1 = value;
OnPropertyChanged("Action1");
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string name)
{
var handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(name));
}
}
}
And then you just need to assign this to the DataContext of your main page.
public MainPage()
{
this.InitializeComponent();
var vm = new ViewModel();
vm.Action1 = (() =>
{
Debug.WriteLine("Action1 called");
});
this.DataContext = vm;
}
With these two changes, your ButtonAction callback should be firing now. :)

Vaadin Drag Drop Component

We are creating a web application using Vaadin. Our application contains alot of drag and drop features.
We have an object which is drag-able.
We can click on it to open its menu as well.
Sometimes that when we click that item it behaves as if it is dragged.
When this happens we are unable to open its menu because the component is in dragmode.
All components with the same functionality behave the same however in development environment, when we restart the tomcat the problem disappeared?
I noticed that when the components start showing me this behavior the webpage in FireFox the behavior is fine there?
A simple solution to this could be to introduce a drag mode/edit button which would allow the user to switch the drag mode on and off.
This would mean the user could interact with the components and then enter this "drag mode" when they wished to drag them. Hence reducing the frustration of trying to interact with the component and it starting to "drag" instead.
I've create a simple example program to try out below.
public class DemoUI extends UI {
HorizontalSplitPanel splitPanel;
DragAndDropWrapper wrapperA;
DragAndDropWrapper wrapperB;
DragAndDropWrapper splitPaneWrapper;
Button buttonA;
Button buttonB;
private boolean isDragMode = false;
#WebServlet(value = "/*", asyncSupported = true)
#VaadinServletConfiguration(productionMode = false, ui = DemoUI.class)
public static class Servlet extends VaadinServlet {
}
#Override
protected void init(VaadinRequest request) {
final HorizontalSplitPanel splitPanel = new HorizontalSplitPanel();
Button buttonA = new Button("Button A");
Button buttonB = new Button("Button B");
final DragAndDropWrapper wrapperA = new DragAndDropWrapper(buttonA);
final DragAndDropWrapper wrapperB = new DragAndDropWrapper(buttonB);
final VerticalLayout leftPanel = new VerticalLayout();
final VerticalLayout rightPanel = new VerticalLayout();
DragAndDropWrapper leftPanelWrapper = new DragAndDropWrapper(leftPanel);
DragAndDropWrapper rightPanelWrapper = new DragAndDropWrapper(rightPanel);
buttonA.addClickListener(new ClickListener() {
#Override
public void buttonClick(ClickEvent event) {
Notification.show("Button A was clicked");
}
});
buttonB.addClickListener(new ClickListener() {
#Override
public void buttonClick(ClickEvent event) {
Notification.show("Button B was clicked");
}
});
leftPanelWrapper.setDropHandler(new DropHandler() {
#Override
public void drop(DragAndDropEvent event) {
leftPanel.addComponent(event.getTransferable().getSourceComponent());
}
#Override
public AcceptCriterion getAcceptCriterion() {
return AcceptAll.get();
}
});
rightPanelWrapper.setDropHandler(new DropHandler() {
#Override
public void drop(DragAndDropEvent event) {
rightPanel.addComponent(event.getTransferable().getSourceComponent());
}
#Override
public AcceptCriterion getAcceptCriterion() {
return AcceptAll.get();
}
});
final Button dragMode = new Button("Drag Mode On");
dragMode.addClickListener(new ClickListener() {
#Override
public void buttonClick(ClickEvent event) {
isDragMode = !isDragMode;
if (isDragMode) {
dragMode.setCaption("Drag Mode Off");
wrapperA.setDragStartMode(DragStartMode.WRAPPER);
wrapperB.setDragStartMode(DragStartMode.WRAPPER);
} else {
dragMode.setCaption("Drag Mode On");
wrapperA.setDragStartMode(DragStartMode.NONE);
wrapperB.setDragStartMode(DragStartMode.NONE);
}
}
});
leftPanel.setSizeFull();
rightPanel.setSizeFull();
leftPanelWrapper.setSizeFull();
rightPanelWrapper.setSizeFull();
leftPanel.addComponent(wrapperA);
rightPanel.addComponent(wrapperB);
splitPanel.setFirstComponent(leftPanelWrapper);
splitPanel.setSecondComponent(rightPanelWrapper);
splitPanel.setSizeFull();
VerticalLayout layout = new VerticalLayout();
layout.addComponent(dragMode);
layout.addComponent(splitPanel);
layout.setSizeFull();
this.setContent(layout);
this.setSizeFull();
}
.
All the best.