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

Handling onpage hash links #586

Open
rkyoku opened this issue Mar 20, 2021 · 5 comments
Open

Handling onpage hash links #586

rkyoku opened this issue Mar 20, 2021 · 5 comments

Comments

@rkyoku
Copy link

rkyoku commented Mar 20, 2021

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
Copy link
Author

rkyoku commented Aug 16, 2021

Any input on this?

@rkyoku
Copy link
Author

rkyoku commented Jan 29, 2022

Still nothing?

@rkyoku
Copy link
Author

rkyoku commented Jan 29, 2022

The project is dead.

For those with the same issue, you can:

  • Either not use regular links for things like API calls, so that you don't need to use the {click: false} option (but it would not be ideal semantically speaking)
<span class="api-link" data-href="{url}">{label}</span>
  • Or, in your click handler, stop the router before returning true, and then call setTimeout to start it again an instant later:
// 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
Copy link
Author

rkyoku commented Jan 30, 2022

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
Copy link

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants