Reputation: 473
to develop a new website I've used a template, based on HTML, CSS and JS. Making out of it a classical ASP.NET Core (MVC) project works fine. Everything is running as a charm. But I've decided to "convert" it to a Blazor Webassembly project. My Blazor Webassembly App is running fine, except that if I navigate to a page section using an anchor tag, the localization component does not work any more.
My CultureSelector.razor component is:
@using System.Globalization
@inject IJSRuntime JS
@inject NavigationManager Navigation
<ul class="list-inline s-header__action s-header__action--lb">
<select class="localeSettings" @bind="Culture">
@foreach (var culture in supportedCultures)
{
<option class="localeSettingsOption" value="@culture">@culture.DisplayName</option>
}
</select>
</ul>
@code
{
private CultureInfo[] supportedCultures = new[]
{
new CultureInfo("en-US"),
new CultureInfo("it-IT"),
new CultureInfo("de-DE"),
};
private CultureInfo Culture
{
get => CultureInfo.CurrentCulture;
set
{
if (CultureInfo.CurrentCulture != value)
{
var js = (IJSInProcessRuntime)JS;
js.InvokeVoid("blazorCulture.set", value.Name);
Navigation.NavigateTo(Navigation.Uri, forceLoad: true);
}
}
}
}
My Program.cs file is:
using myProject;
using System.Globalization;
using Microsoft.JSInterop;
using Microsoft.AspNetCore.Components.Web;
using Microsoft.AspNetCore.Components.WebAssembly.Hosting;
var builder = WebAssemblyHostBuilder.CreateDefault(args);
builder.RootComponents.Add<App>("#app");
builder.RootComponents.Add<HeadOutlet>("head::after");
builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) });
builder.Services.AddLocalization();
var host = builder.Build();
CultureInfo culture;
var js = host.Services.GetRequiredService<IJSRuntime>();
var result = await js.InvokeAsync<string>("blazorCulture.get");
if (result != null)
{
culture = new CultureInfo(result);
}
else
{
culture = new CultureInfo("en-US");
await js.InvokeVoidAsync("blazorCulture.set", "en-US");
}
CultureInfo.DefaultThreadCurrentCulture = culture;
CultureInfo.DefaultThreadCurrentUICulture = culture;
await host.RunAsync();
On my Index.razor page I have the following anchor element:
<a href="#js__scroll-to-section" class="s-scroll-to-section-v1--bc g-margin-b-15--xs">
<span class="g-font-size-18--xs g-color--white ti-angle-double-down"></span>
<p class="text-uppercase g-color--white g-letter-spacing--3 g-margin-b-0--xs">@Loc["Learn More"]</p>
</a>
To handle scrolling to the desired section I have the following script:
function handleScrollToSection() {
let scrollToElement = $('a[href*=#js__scroll-to-]:not([href=#js__scroll-to-])')
scrollToElement[0].addEventListener('click', (event) => {
event.stopImmediatePropagation()
if (location.pathname.replace(/^\//, '') == scrollToElement[0].pathname.replace(/^\//, '') && location.hostname == scrollToElement[0].hostname) {
var target = $(scrollToElement[0].hash);
target = target.length ? target : $('[name=' + scrollToElement[0].hash.slice(1) + ']');
if (target.length) {
$('html,body').animate({
scrollTop: target.offset().top - 90
}, 1000);
return false;
}
}
})
}
This script makes it possible on a ASP.NET Core MVC (NOT Blazor WASM) that the anchor #js__scroll-to-section
is removed from the url.
In the case of my Blazor app, this does not work and the anchor remains in the url https://localhost:7123/#js__scroll-to-section
If that is the case, my localization component does not work any more. I select a language from the dropdown menu and nothing happens.
Upvotes: 0
Views: 858
Reputation: 473
Basically I've found 2 ways to get it running. They share the same concept and in the second case I've added the cosmetic feature to rewrite the url after clicking to the scroll-to-element link, so that the anchor tag disappear. But keep in mind: disappeared means that it is not more visible, but still in the Navigation.Uri
So, first of all, substitute the line of code Navigation.NavigateTo(Navigation.Uri, forceLoad: true);
with this one: Navigation.NavigateTo(Navigation.BaseUri, forceLoad: true);
in the CultureSelector.razor component.
This makes the whole magic!
If you need also the cosmetic feature to let the anchor tag disappear from the url while scrolling to the desired page element, than you can add a @onfocusout="ChangeUrl"
on your anchor element, like I did here in my Index.razor page:
<a href="#js__scroll-to-section" @onfocusout="ChangeUrl" class="s-scroll-to-section-v1--bc g-margin-b-15--xs">
<span class="g-font-size-18--xs g-color--white ti-angle-double-down"></span>
<p class="text-uppercase g-color--white g-letter-spacing--3 g-margin-b-0--xs">@Loc["Learn More"]</p>
</a>
Do not modify href="#js__scroll-to-section"
as it handles scrolling to the desired element. That is the reason why I'm using @onfocusout
.
In the @code
section of the Index.razor add the ChangeUrl
function:
void ChangeUrl()
{
JsRuntime.InvokeVoidAsync("ChangeUrl", NavigationManager.BaseUri);
}
Finally, in your javascript file define the ChangeUrl
function as follows:
function ChangeUrl(url) {
history.pushState(null, '', url)
}
This works fine for me and solved my problem.
Happy coding!
Upvotes: 0
Reputation: 594
In a Blazor WebAssembly, it first loads the .NET runtime and application dll before doing any rendering. The anchor behavior doesn't work because Blazor handles the navigation events for routing purposes.
But I find a way you can try. You can build a component to do that. First write the JS code in index.html for Scrolling the web page:
<script>
function BlazorScrollToId(id) {
const element = document.getElementById(id);
if (element instanceof HTMLElement) {
element.scrollIntoView({
behavior: "smooth",
block: "start",
inline: "nearest"
});
}
}
</script>
Then build a component to navigate the location and invoke the JS function:
@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('#'))
{
var elementId = fragment.Substring(1);
if (!string.IsNullOrEmpty(elementId))
{
await JSRuntime.InvokeVoidAsync("BlazorScrollToId", elementId);
}
}
}
}
And here is the index page code:
@page "/"
<ul>
@for (int i = 1; i <= 5; i++)
{
<li><a href="@GetHref(i)">Header @i</a></li>
}
</ul>
@for (int i = 1; i <= 5; i++)
{
<h1 id="@GetId(i)">Header @i</h1>
<h3 id="@GetId(i)">This is Paragraph @i</h3>
<p style="word-break:break-word;">
The component class is usually written in the form of a Razor markup page with a .razor file extension. Components in Blazor are formally referred to as Razor components, informally as Blazor components. Razor is a syntax for combining HTML markup with C# code designed for developer productivity. Razor allows you to switch between HTML markup and C# in the same file with IntelliSense programming support in Visual Studio. Razor Pages and MVC also use Razor. Unlike Razor Pages and MVC, which are built around a request/response model, components are used specifically for client-side UI logic and composition.
</p>
}
<AnchorNavigation />
@code {
string GetId(int i) => "header-" + i;
string GetHref(int i) => "#" + GetId(i);
}
Upvotes: 0
Reputation: 1240
This is a known issue and it looks like it will be solved for .NET8: https://github.com/dotnet/aspnetcore/pull/47320
Upvotes: 2