0

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.

3 Answers 3

2

This is a known issue and it looks like it will be solved for .NET8: https://github.com/dotnet/aspnetcore/pull/47320

Sign up to request clarification or add additional context in comments.

1 Comment

Hi Rogier, I'm working on a workaround and it seems to work. I will post my solution as soon as it is ready.
0

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

Here is the test result: enter image description here

1 Comment

Hi Xiaotian, I know this blazor component. It's described in the following post link. But it is not what I'm looking for. The anchor tag is still present in the url
0

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!

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.