Styling Blazor EditForm components - focus on InputText - html

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

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

Change Component Index at runtime

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>

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

How to postback a checkbox in MVC

I have a controller:
using System;
using System.Collections.Generic;
using System.Web.Mvc;
using MvcApplication5.Models;
namespace MvcApplication5.Controllers
{
public class HomeController : Controller
{
public ActionResult Index()
{
var car = new Car { Sold = true };
return View(car);
}
[HttpPost]
public ActionResult Index(Car car)
{
return View(car);
}
public ActionResult About()
{
return View();
}
}
}
Model:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
namespace MvcApplication5.Models
{
public class Car
{
public bool Sold { get; set; }
}
}
Here's the view:
#model MvcApplication5.Models.Car
#using (Html.BeginForm())
{
#Html.CheckBoxFor(m => m.Sold, new { #class = "sold" })
#Html.HiddenFor(m => m.Sold)
<div class="disable">Disable</div>
<button type="submit" value="Submit">Submit</button>
}
<script type="text/javascript">
$(function () {
$('.disable').click(function () {
$('.sold').attr("disabled", "disabled");
});
})
</script>
So what I'm trying to achieve here is you come in and the checkbox is checked. If you click submit the value is posted back fine. However if you click disable the checkbox becomes disabled. You then click submit. I was expecting because the checkbox is still checked and I've added a hidden field the value posted back for sold would be true. That's the whole point of why I added the hidden field.
Can anyone tell me why the checkbox when checked but disabled posts back false for Sold when there is a hidden field that should be maintaining the value?
disabled=disabled doesn't send values, what you want to preserve the value by to disable it from being updated is readonly=readonly. i.e.
$('.sold').attr("readonly", "readonly");
You'll also need to apply some CSS to make it look disabled.