visionmedia / page.js

Micro client-side router inspired by the Express router
http://visionmedia.github.com/page.js
7.67k stars 687 forks source link

Handling onpage hash links #586

Open rkyoku opened 3 years ago

rkyoku commented 3 years ago

Hi,

I am having trouble with simple hash links : <a href="#my-section">link</a>

I specified {click: false} in the Page.js options, because there are some links I do not want handled by Page.js.

However, in my click handler, I detect the hashtag, and I return true (using jQuery) so that the browser updates the URL and scrolls to the section with that ID.

In that scenario, I am not calling page($this.attr('href')) like I do for "normal" links.

Strangely enough, it seems that Page.js still intercepts that link (or that change of URL) and matches the route again, which causes a "reload" (SPA-like) of my page.

How should I handle onpage hash links?

$(document.body)
    .on('click', 'a', function(e) {
        const $a = $(this);

        console.log('click A', this, $a.attr('href'));

        /** previous code (not working)
        if ($a.attr('href').charAt(0) === '#' || $a.hasClass('api-link') || $a.hasClass('api-download'))
            return true;
        */

        // New attempt (not working either)
        if ($a.attr('href').charAt(0) === '#' || $a.hasClass('api-link') || $a.hasClass('api-download')) {
            console.log('follow link!');

            e.preventDefault();
            e.stopImmediatePropagation();

            // Not working either
            // const url = location.href;
            // location.href = $a.attr('href');
            // history.pushState(null,null, url + $a.attr('href'));

            // Still not working
            window.location.hash = $a.attr('href');

            return false;
        }

        //
        // Standard behavior for `<a href="/my/new/url">` links
        //
        console.log('Clicking on link', $a.attr('href'));
        e.preventDefault();
        page($a.attr('href'));
    })
;
rkyoku commented 2 years ago

Any input on this?

rkyoku commented 2 years ago

Still nothing?

rkyoku commented 2 years ago

The project is dead.

For those with the same issue, you can:

<span class="api-link" data-href="{url}">{label}</span>
// We need to override the click handler that would otherwise have been defined by page.js,
// so that we can fine-tune the behavior
$(document.body).on('click', 'a', function(e) {
    // "api-link" links don't need to be handled here, it's just to show the reason for using {click: false}
    if ($(this).attr('href').charAt(0) === '#' || $(this).hasClass('api-link')) {
        // same-page anchors DO need to be handled here though, because they would pushstate,
        // and then page.js would execute yet again the route callback of the page we're on...
        // we need to prevent that: disable page.js just for a tiny moment
        page.stop();
        setTimeout(page.start.bind(page, {click: false, dispatch: false}), 100);
    }
    return true;
});

It works for me as it is, but there might of course be room for improvement.

Feel free to improve this! 😉👍

rkyoku commented 2 years ago

Another issue I encountered right after:

There is a bug in page.js that does not reset _running to true, which in turn causes the stop() method to return too early, and not unbind listeners.

Just be sure to modify page.js lines 551 and 552 so that this._running = true; is executed before the function returning (because we gave the option {dispatch: false}):

this._running = true;

// Execute this AFTER setting _running back to true, otherwise the stop() method won't work properly
if (false === opts.dispatch) return;
AshfordN commented 1 year ago

I don't think your original issue is caused by page.js intercepting click events on links. Looking at the page.js source, there's no reason to believe that the click handlers were installed if you specified {click: false}. However, specifying {hashbang: true} would cause page.js to install hashchange event handlers, and since the links in question would change the URL's fragment identifier, page.js would detect those changes and respond. Now, while you haven't provided details on your initial page.js configuration object, I suspect that this might be the case. If you need to handle multiple levels of fragment identifiers (e.g. example.com/#!/route1#header1), you can simply install a middleware handler in front of your routes, to break the dispatch sequence when you detect these kinds of URLs. For example:

page((ctx, next) => {
    // find the index of the hashbang defined by page.js
    let hashIdx = ctx.pathname.indexOf("#!");

    // check for additional hash marks
    if (ctx.pathname.includes("#", hashIdx + 2)) {
        doSomethingElse(ctx.pathname); // dispatch the link to a custom handler
        return; // early return prevents page.js from continuing the dispatch operation
    }

    next();
});

Now, with that being said, URLs containing multiple levels of fragment identifiers are not compliant with the current web standards. You have to remember that specifying {hashbang: true} is already causing page.js to occupy the fragment component of the URL. Attempting to define sub-fragments is not advised and rightly results in undefined behavior.