Change Component Index at runtime - razor

How do i change index of Blazor's Component Index at runtime.
Here is code of razor
#page "/datagrid"
#namespace PS
<div class="input-group">
<DxDateEdit #bind-Date="#DateTimeValue"/>
<DxTextBox Text="This is textbox"/>
</div>
#code
{
DateTime DateTimeValue { get; set; } = new DateTime(2022, 1, 1);
public bool IsDisplayTextBoxFirst { get; set; } = false;
}
current Output
when user set value IsDisplayTextBoxFirst to true then Textbox should rendered at first like attached image
How do i change component index at runtime according to value of IsDisplayTextBoxFirst property.

You can use an if/else expression inside your .input-group, as follows:
<div class="input-group">
#if (IsDisplayTextBoxFirst)
{
<DxTextBox Text="This is textbox"/>
<DxDateEdit #bind-Date="#DateTimeValue"/>
}
else
{
<DxDateEdit #bind-Date="#DateTimeValue"/>
<DxTextBox Text="This is textbox"/>
}
</div>
Alternatively, define reusable RenderFragments in your code:
<div class="input-group">
#if (IsDisplayTextBoxFirst)
{
#TextBox
#DateEdit
}
else
{
#DateEdit
#TextBox
}
</div>
#code
{
// Parameters and other logic
private RenderFragment TextBox => __builder =>
{
<DxTextBox Text="This is textbox"/>
};
private RenderFragment DateEdit => __builder =>
{
<DxDateEdit #bind-Date="#DateTimeValue"/>
};
}
As suggested by #henk-holtermann, you could optionally implement the reusable RenderFragments using templated delegates (docs) and not need to reference the __builder at all:
#code
{
// Parameters and other logic
private RenderFragment TextBox => #<DxTextBox Text="This is textbox"/>;
private RenderFragment DateEdit => #<DxDateEdit #bind-Date="#DateTimeValue"/>;
}

You can use conditional rendering and have you code like this
<div class="input-group">
<DxDateEdit #bind-Date="#DateTimeValue"/>
#if(IsDisplayTextBoxFirst){
<DxTextBox Text="This is textbox"/>
}
</div>

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>

How do I restart a component in a Blazor page

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();
}
}

Creating a plain non-component class using a razor file

Is it possible to create a plain non-component class using a razor file? The idea is to take advantage of the simplified syntax for generating RenderFragments, from a class that is not intended to be a component or a page. e.g.
#inherits object
#code {
// some other stuff
public RenderFragment Foo => #<span class="foo"></span>;
public RenderFragment Render(string? name) => __builder =>
{
// some other stuff
<text>#name</text>
};
}
where the resulting class should be equivalent to:
public class Whatever /*: object*/
{
public RenderFragment Foo => __builder =>
{
__builder.OpenElement(0, "span");
// etc
};
public RenderFragment Render(string? name) => __builder =>
{
__builder.AddContent(0, name); // or whatever
};
}
Trying exactly the above results in the generated code reporting an error:
Whatever.BuildRenderTree(RenderTreeBuilder): no suitable method to override
which is not entirely surprising, since the generated code includes an override for that method, which of course does not exist in a plain class.
I was hoping that by not including anything outside of #code the compiler would not attempt to override that method, but sadly that does not appear to be the case. Is there some other trick that can make this work?
Revised Answer
Yes you can. You can create a minimum requirement class that the Razor compiler will accept.
It doesn't need to implement IComponent as there is no intention of using it as a component.
It needs a virtual BuildRenderTree(RenderTreeBuilder builder) method for the Razor compiler to override: it's where it compiles all the markup in the main block.
public abstract class MinRazor
{
protected virtual void BuildRenderTree(RenderTreeBuilder builder) {}
}
You're Razor file then inherits from it.
RenderStuff.razor
#inherits MinRazor
#code {
public string Message { get; set; } = "No one set me!";
public RenderFragment GiveMeADiv => __builder =>
{
<div class="p-2 m-2 bg-light">
<div class="p-2 m-2 bg-primary text-white">
#Message
</div>
</div>
};
}
The only baggage that the compiled code has is a blank BuildRenderTree(RenderTreeBuilder builder).
Here's the output file the compiler produces:
public partial class RenderStuff : MinRazor
{
protected override void BuildRenderTree(Microsoft.AspNetCore.Components.Rendering.RenderTreeBuilder __builder)
{
}
public string Message { get; set; } = "No one set me!";
public RenderFragment GiveMeADiv => __builder =>
{
__builder.OpenElement(0, "div");
__builder.AddAttribute(1, "class", "p-2 m-2 bg-light");
__builder.OpenElement(2, "div");
__builder.AddAttribute(3, "class", "p-2 m-2 bg-primary text-white");
__builder.AddContent(4, Message);
__builder.CloseElement();
__builder.CloseElement();
};
}
Your problem comes form #inherits object.
That does indeed make a "non-component class", but why?
Just leave that out and make your helpers static:
Whatever.razor
#inherits ComponentBase
#code {
// some other stuff
internal static RenderFragment Foo => #<span class="foo"></span>;
internal static RenderFragment Render(string? name) => __builder =>
{
// some other stuff
<text>#name</text>
};
}
And then you can use it like any static function:
#Whatever.Render("J Doe")
It appears that this is (currently at least) not possible; the closest working approach seems to be to implement it as a component anyway, but indicate that it is not intended to be used as such:
#code {
protected override void OnInitialized()
{
throw new NotSupportedException();
}
// some other stuff
public RenderFragment Foo => #<span class="foo"></span>;
public RenderFragment Render(string? name) => __builder =>
{
// some other stuff
<text>#name</text>
};
}
This unfortunately stills means that it will carry around some extra state baggage from inheriting ComponentBase, and it can't inherit any other (non-component) base class.
Making the render methods static and simply calling them from the "real" class as Henk Holterman suggests is probably the best compromise, although it would still need something like the OnInitialized override to discourage misuse.
Ideally you'd mark the static methods component as #internal too, to further reduce the scope of possible mis-use, but sadly that's not implemented yet.

Styling Blazor EditForm components - focus on InputText

I am learning Blazor framework and it has it's own HTML elements. I tried to style InputText element, but however it does not seem to work with some CSS properties. For example "Focus".
My CSS:
input:focus, select:focus, .inputstyle:focus {
border: 1px solid #ff6801;
}
HTML Input works fine, but InputText does not. My code:
<input type="text" name="address" id="address" />
<InputText class="inputstyle" #bind-Value="#Advert.Title" />
So how could I put css Focus on InputText?
InputText is a Blazor component, so it can't get input focus. But you can set focus on its internal input element... You can do something like this:
You can derives from InputText, and use ElementReference.FocusAsync()
Note: This feature is only available in .Net 5.0
InputTextSetFocus.razor
#inherits InputText
<input #ref="inputElement"
#attributes="AdditionalAttributes"
class="#CssClass"
value="#CurrentValue"
#oninput="EventCallback.Factory.CreateBinder<string>(this, __value => CurrentValueAsString = __value, CurrentValueAsString)" />
#code {
private ElementReference inputElement;
protected override void OnAfterRender(bool firstRender)
{
base.OnInitialized();
inputElement.FocusAsync();
}
}
Usage
#page "/"
<EditForm Model="employee">
<InputTextSetFocus #bind-Value="#employee.ID" />
</EditForm>
#code
{
private Employee employee = new Employee();
public class Employee
{
public string ID { get; set; }
public string Name { get; set; }
}
}

How to reproduce #using(Html.BeginForm()){ ... } in Razor [duplicate]

In WebForms, I could create a component where I could embed my own content
Example
<uc:BootstrapModal Title="Hello World" Size="Large">
<h1>Hello World</h1>
</uc:BootstrapModal>
<!--generates this...-->
<div class="modal fade bs-example-modal-lg" tabindex="-1" role="dialog">
<div class="modal-dialog modal-lg">
<div class="modal-content">
<h1>Hello World</h1>
</div>
</div>
</div>
How can I do this in MVC?
You can create a HtmlHelper extension method to generate the enclosing html, similar to the way BeginForm() generates enclosing <form></form> tags.
using System;
using System.Web.Mvc;
namespace YourAssembly.Html
{
public class Dialog : IDisposable
{
private readonly ViewContext _viewContext;
private bool _disposed;
public Dialog(ViewContext viewContext)
{
if (viewContext == null)
{
throw new ArgumentNullException("viewContext");
}
_viewContext = viewContext;
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (!_disposed)
{
_disposed = true;
DialogExtensions.EndDialog(_viewContext);
}
}
public void EndDialog()
{
Dispose(true);
}
}
public static class DialogExtensions
{
public static Dialog BeginDialog(this HtmlHelper htmlHelper)
{
return DialogHelper(htmlHelper);
}
private static Dialog DialogHelper(this HtmlHelper htmlHelper)
{
TagBuilder div = new TagBuilder("div");
div.AddCssClass("modal fade bs-example-modal-lg");
div.MergeAttribute("tabindex", "-1");
div.MergeAttribute("role", "dialog");
htmlHelper.ViewContext.Writer.Write(div.ToString(TagRenderMode.StartTag));
div = new TagBuilder("div");
div.AddCssClass("modal-dialog modal-lg");
htmlHelper.ViewContext.Writer.Write(div.ToString(TagRenderMode.StartTag));
div = new TagBuilder("div");
div.AddCssClass("modal-content");
htmlHelper.ViewContext.Writer.Write(div.ToString(TagRenderMode.StartTag));
Dialog modal = new Dialog(htmlHelper.ViewContext);
return modal;
}
public static void EndDialog(this HtmlHelper htmlHelper)
{
EndDialog(htmlHelper.ViewContext);
}
internal static void EndDialog(ViewContext viewContext)
{
viewContext.Writer.Write("</div>");
viewContext.Writer.Write("</div>");
viewContext.Writer.Write("</div>");
}
}
}
and in the view use it as
#using (Html.BeginDialog())
{
// add the content to be rendered in the dialog here
}
Note: In the web.config file add the namespace of your assembly so that you do not have to include #using statements in the view.
<namespaces>
<add namespace="System.Web.Mvc" />
....
<add namespace="YourAssembly.Html" /> <!--add-->
</namespaces>
And you can then extend this by creating additional overloads, for example you might also have parameters for string title and a ButtonType buttons (an enum) to render a title bar and footer buttons in the dialog