Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Hash routing to named element #8393

Closed
MovGP0 opened this issue Mar 10, 2019 · 41 comments · Fixed by #47320
Closed

Hash routing to named element #8393

MovGP0 opened this issue Mar 10, 2019 · 41 comments · Fixed by #47320
Assignees
Labels
affected-most This issue impacts most of the customers area-blazor Includes: Blazor, Razor Components enhancement This issue represents an ask for new feature or an enhancement to an existing one feature-blazor-builtin-components Features related to the built in components we ship or could ship in the future Priority:1 Work that is critical for the release, but we could probably ship without severity-minor This label is used by an internal tool
Milestone

Comments

@MovGP0
Copy link

MovGP0 commented Mar 10, 2019

Describe the bug

I cannot use an html anchor to navigate to a specific html element of a page.

To Reproduce

Steps to reproduce the behavior:

Given there is a Blazor page that looks like the following:

@layout MainLayout
@page "/foo"

<nav>
    <!-- One version I've tried -->
    <a href="#bar">Bar</a>

    <!-- Another version I've tried -->
    <NavLink href="#bar">Bar</NavLink>

    @* ... *@
</nav>

@* ... *@

<section>
    <h2 id="bar">Bar</h2>
     @* ... *@
</section>

When I click the link to Bar, i get redirected to the route http://localhost:5000/foo#bar, but will be at the top of the page. The fragment of the route is not used for selection of a specific HTML element.

Expected behavior

The browser should scroll down to the proper element, as specified by the Element Selector.

In cases where the fragment of the URL is used for other purposes, ie. for OAuth, it should be possible to overwrite the default Blazor router behavior. The behavior should be set on a per-page and per-app level.

Workaround

The proposed workaround does not work, since the browser crashes and is only for illustration.

The current workaround is to write explicit code that interprets the URL. In the example above, we could use a little bit of JavaScript and then call that from our Blazor code:

<!-- in index.html -->
<script>
        window.scrollToElementId = (elementId) => {
            console.info('scrolling to element', elementId);
            var element = document.getElementById(elementId);
            if(!element)
            {
                console.warn('element was not found', elementId);
                return false;
            }
            element.scrollIntoView();
            return true;
        }
</script>
@inject Microsoft.AspNetCore.Components.Services.IUriHelper UriHelper

@functions {
    protected override void OnInit()
    {
        NavigateToElement();
        UriHelper.OnLocationChanged += OnLocationChanges;
    }

    private void OnLocationChanges(object sender, string location) => NavigateToElement();

    private void NavigateToElement()
    {
        var url = UriHelper.GetAbsoluteUri();
        var fragment = new Uri(url).Fragment;

        if(string.IsNullOrEmpty(fragment))
        {
            return;
        }

        var elementId = fragment.StartsWith("#") ? fragment.Substring(1) : fragment;

        if(string.IsNullOrEmpty(elementId))
        {
            return;
        }
    
        ScrollToElementId(elementId);
    }

    private static bool ScrollToElementId(string elementId)
    {
        return JSRuntime.Current.InvokeAsync<bool>("scrollToElementId", elementId).GetAwaiter().GetResult();
    }
}

Additional context

blazor 0.8.0-preview-19104-04

.NET Core SDK (reflecting any global.json):
 Version:   3.0.100-preview-010184
 Commit:    c57bde4593

Runtime Environment:
 OS Name:     Windows
 OS Version:  10.0.17763
 OS Platform: Windows
 RID:         win10-x64
 Base Path:   C:\Program Files\dotnet\sdk\3.0.100-preview-010184\

Host (useful for support):
  Version: 3.0.0-preview-27324-5
  Commit:  63a01b08e5

.NET Core SDKs installed:
  2.1.600-preview-009426 [C:\Program Files\dotnet\sdk]
  3.0.100-preview-010184 [C:\Program Files\dotnet\sdk]

.NET Core runtimes installed:
  Microsoft.AspNetCore.All 2.1.6 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.All]
  Microsoft.AspNetCore.App 2.1.6 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
  Microsoft.AspNetCore.App 3.0.0-preview-19075-0444 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
  Microsoft.NETCore.App 2.1.6 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
  Microsoft.NETCore.App 3.0.0-preview-27324-5 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
  Microsoft.WindowsDesktop.App 3.0.0-preview-27325-3 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App]
@MovGP0
Copy link
Author

MovGP0 commented Mar 10, 2019

see also #6175

@Eilon Eilon added the area-mvc Includes: MVC, Actions and Controllers, Localization, CORS, most templates label Mar 11, 2019
@MovGP0 MovGP0 closed this as completed Mar 19, 2019
@MovGP0 MovGP0 reopened this Mar 19, 2019
@mkArtakMSFT mkArtakMSFT added the area-blazor Includes: Blazor, Razor Components label Mar 25, 2019
@mkArtakMSFT mkArtakMSFT added this to the Backlog milestone Apr 1, 2019
@mkArtakMSFT mkArtakMSFT added the bug This issue describes a behavior which is not expected - a bug. label Apr 1, 2019
@mkArtakMSFT mkArtakMSFT removed area-mvc Includes: MVC, Actions and Controllers, Localization, CORS, most templates labels May 9, 2019
@Psimatrix
Copy link

This also happens when using the basic functionality of a tabbed panel.
For example: paste into a blazor page

<div class="tab-content">
    <div id="home" class="tab-pane fade in active">
        <h3>HOME</h3>
        <p>Some content.</p>
    </div>
    <div id="menu1" class="tab-pane fade">
        <h3>Menu 1</h3>
        <p>Some content in menu 1.</p>
    </div>
</div>

Clicking any of the panels will try to navigate you to a page instead of the named tab element.

@egil
Copy link
Contributor

egil commented Aug 16, 2019

I want to note that this is still an issue with Preview 8 (only testet in Server Side Blazor). Consider the following example

@page "/some/page"
<a href="#">#blank</a>
<a href="#example">#exmaple</a>
<h1 id="example">Example</h1>

Clicking either of the two links takes the browser to the url /# or /#example, which is clearly not the expected behaviour. The expected behaviour is to stay on the same page, e.g. /some/page# or /some/page#example, but have the relevant "hash" section appended/replaced in the url.

@SQL-MisterMagoo
Copy link
Contributor

Here is a temporary workaround that seems to work for internal clicks, but not deep linking

<a href="#Bar" onclick="event.stopPropagation();">Bar</a>

@egil
Copy link
Contributor

egil commented Aug 30, 2019

Here is a temporary workaround that seems to work for internal clicks, but not deep linking

<a href="#Bar" onclick="event.stopPropagation();">Bar</a>

@SQL-MisterMagoo the problem with that workaround is that it also stops any other @onclick=... attached events from firing (they are listened to at the document level).

I do not know what the implications are, but it looks like a fix for this bug could be adding a check around this line in NavigationManager.ts#L47, that checks if the anchor's href starts with a #, and if it does, don't trigger the navigation logic.

@SQL-MisterMagoo
Copy link
Contributor

@egil Yes, it does, but you can easily use @onMouseUp or another event if you need Blazor code to fire as well as the internal browser scroll.

See this example https://blazorfiddle.com/s/jtufkw80

this is a workaround - of course the real fix is needed.

@Andrzej-W
Copy link

I haven't tested this but I believe it should work:
https://community.devexpress.com/blogs/aspnet/archive/2019/08/28/blazor-components-free-anchor-navigation-tool.aspx

@TheGrandUser
Copy link

This is still a problem in the release version

@SQL-MisterMagoo
Copy link
Contributor

I was just reading the source and realised if you simply include target="_top" in your anchor, it will avoid the navigation interception and just work - not found a workaround for deep linking yet though.

This line of code in NavigationManager returns directly out of the click handler before preventDefault can be called - if there is a target and it doesn't equal "_self".

https://github.com/aspnet/AspNetCore/blob/32a2cc594363672ccfe7644a649f77a8bfc9c4a8/src/Components/Web.JS/src/Services/NavigationManager.ts#L59

Example:

<a id="bottom" href="#top" target="_top">Jump to top</a>

@TheGrandUser
Copy link

I just tried that in my project and it's still redirecting to the root url, not scrolling to the element.

@SQL-MisterMagoo
Copy link
Contributor

I just tried that in my project and it's still redirecting to the root url, not scrolling to the element.

Can you share the link?

@TheGrandUser
Copy link

TheGrandUser commented Nov 30, 2019

It's <a href="#k28" target="_top">#28</a>

And the element it refers to on that page is

<h3 id="k28">...</h3>

It's as straight forward as it can be

@SQL-MisterMagoo
Copy link
Contributor

@TheGrandUser is this server side Blazor or web assembly?

@TheGrandUser
Copy link

TheGrandUser commented Dec 1, 2019

This is server side, using .net core 3.1 preview 3 in the VS preview

@SQL-MisterMagoo
Copy link
Contributor

@TheGrandUser if the page is not the root of your site you will need to include the page route in the anchor href because Blazor uses root relative link resolution. Have you tried that?
href=mypage#k28

@TheGrandUser
Copy link

Ok, that works

@Grauenwolf
Copy link

Updated work-around:

    window.scrollToElementId = (elementId) => {
        console.info('scrolling to element', elementId);
        var element = document.getElementById(elementId);
        if (!element) {
            console.warn('element was not found', elementId);
            return false;
        }
        element.scrollIntoView();
        return true;
    }

    async protected sealed override Task OnAfterRenderAsync(bool firstRender)
    {
        await NavigateToElementAsync();
    }

    async Task NavigateToElementAsync()
    {
        var fragment = new Uri(Navigation.Uri).Fragment;

        if (string.IsNullOrEmpty(fragment))
            return;

        var elementId = fragment.StartsWith("#") ? fragment.Substring(1) : fragment;

        if (string.IsNullOrEmpty(elementId))
            return;

        await JSRuntime.InvokeAsync<bool>("scrollToElementId", elementId);
    }

@ramkiranhota
Copy link

ramkiranhota commented Jul 3, 2020

@TheGrandUser if the page is not the root of your site you will need to include the page route in the anchor href because Blazor uses root relative link resolution. Have you tried that?
href=mypage#k28

This worked for me...thank you so much

@chrdlx
Copy link

chrdlx commented Jul 5, 2020

@TheGrandUser if the page is not the root of your site you will need to include the page route in the anchor href because Blazor uses root relative link resolution. Have you tried that?
href=mypage#k28

This worked for me...thank you so much

Be careful with this.... if you use a component that uses anchors with this approach, and that component is used in different routes, then you'll have a problem since the URL is not static anymore.
Regards

@Bosnianflajter2
Copy link

Any news regarding hash routing? I have many existing (from my previous project) html files containing anchor which I want to use in blazor ServerApp but as this doesn't work... have to stop my project...
A simple approach would be OK as an additional future (update) to the existing net 6 version. Waiting for 8 v. -where it maybe will be implemented- could take years..

@mkArtakMSFT mkArtakMSFT added Priority:1 Work that is critical for the release, but we could probably ship without and removed Priority:2 Work that is important, but not critical for the release labels Nov 10, 2022
@fardarter
Copy link

How has this still not been addressed?

@MarcelDevG
Copy link

It's not just the scrolling to the html element, handling of the routing, but also synchronisation with the navigation.
When the user hit back, it returns where it came from on the same page.
But indeed : "How has this still not been addressed?"

@fardarter
Copy link

@MarcelDevG I take the point but every JS framework has figured this out. That's who Blazor is competing with.

@MarcelDevG
Copy link

Why do I have to pull in (yet another JS framework) to fix this...

@zHaytam
Copy link
Contributor

zHaytam commented Dec 26, 2022

I'm surprised the team still didn't address this...

@vflame
Copy link

vflame commented Dec 31, 2022

The proposed workaround has a shortcoming that can be hopefully be addressed--when the brower's DOM is mutating due to renders, the position of the target element may be stale when the JS is invoked to scroll.

The thinking is to somehow wait for everything in the browser's DOM to be "stable", and then scroll to the element. (How do you define what is "stable" per the brower's DOM?) You can try to design their pages to know the size of the content in advance so that the height/width is constant, but this isn't very realistic.

We ended up just adding an arbitrary delay before scrolling to ensure the DOM is "stable" enough. But even then, if a end-user has a very slow connection (blazor server)/slow device, the JS can still scroll before the DOM is "stable".

@MarvinKlein1508
Copy link

It would be great if this works out of the box for the NavigationManager.Navigate method as well.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
affected-most This issue impacts most of the customers area-blazor Includes: Blazor, Razor Components enhancement This issue represents an ask for new feature or an enhancement to an existing one feature-blazor-builtin-components Features related to the built in components we ship or could ship in the future Priority:1 Work that is critical for the release, but we could probably ship without severity-minor This label is used by an internal tool
Projects
None yet
Development

Successfully merging a pull request may close this issue.