7

Well in my (react) single page application I have a lot of "internal links". These links work by changing the url using a router store. (Mobx+react).

As html standards warn against using href without an actual href, and if one uses on_click event a button -styled to look like a link- should be used.

This works perfectly fine. Except now that I wish to improve use, I wish to allow consumers to press ctrl+click, middle mouse or whatever the keybind is of the esoteric browser the user uses, to "open link in a new tab".

Is there a way to get the "intention" of a click by the browser? - Ie to see if the user's intention was to open the link (button) in a new tab?

Without letting the user reload the page if clicked on the link normally?

Or from the other side: how can I prevent a full page reload when clicking on a link that is a relative url and opens in the same window?

paul23
  • 8,799
  • 12
  • 66
  • 149

4 Answers4

0

Try using Link, it works both ways:

import {Link} from 'mobx-router';
Krzysztof Atłasik
  • 21,985
  • 6
  • 54
  • 76
  • mobx-router’s `Link` does the same thing, just missing `shiftKey` (commonly open in new window) and `altKey` (commonly download). https://github.com/kitze/mobx-router/blob/57bd155310e82a4325da4ab2eaa2cb1e1ef9397b/src/components/Link.js#L28-L29 – Ry- Jul 09 '19 at 19:40
0

So.. React-router-dom has a component called "Link". Which handles this use case perfectly..

Here's the code snippet from the Link component copied from react-routerrepo.

function isModifiedEvent(event) {
  return !!(event.metaKey || event.altKey || event.ctrlKey || event.shiftKey);
}

function LinkAnchor({ innerRef, navigate, onClick, ...rest }) {
  const { target } = rest;

  return (
    <a
      {...rest}
      ref={innerRef} // TODO: Use forwardRef instead
      onClick={event => {
        try {
          if (onClick) onClick(event);
        } catch (ex) {
          event.preventDefault();
          throw ex;
        }

        if (
          !event.defaultPrevented && // onClick prevented default
          event.button === 0 && // ignore everything but left clicks
          (!target || target === "_self") && // let browser handle "target=_blank" etc.
          !isModifiedEvent(event) // ignore clicks with modifier keys
        ) {
          event.preventDefault();
          navigate();
        }
      }}
    />
  );
}

You can just modify this component a bit to suit your use case. Try and make this an HOC or may be use renderProps pattern to render your content inside this anchor link

Hope this helps..

  • Unfortunately essentially the same thing though. :/ I guess a link activation intent sort of feature really still doesn’t exist. – Ry- Jul 15 '19 at 13:01
0

Like stated in other answers, the easiest thing is to not preventDefault when shift or control is present. However in my case another thirdparty component (PatternFly) handles the actual navigation events and implement preventDefault, so I had to come up with a global solution.

I solved this by stopping event propagation at the capture phase of the document.

This will stop custom link events (with preventDefault calls) further down the DOM and let the default link logic kick in.

Here is a typescript example, which also takes into account if the link is external:

document.addEventListener('click', (event: MouseEvent) => {  
  const target = event.target;
  if (target instanceof Element
    && (target as Element).baseURI
    && !isExternalUrl((target as Element).baseURI)
  ) {
    if (event.ctrlKey || event.shiftKey) {
      event.stopPropagation();
    }
  }
}, true);

and isExternalUrl being something like

function isExternalUrl (url: string): boolean {
  const host = window.location.hostname;

  // Initialize as Relative URL
  var linkHost = host;
  if (/^https?:\/\//.test(url)) {
    // Absolute URL.
    // The easy way to parse an URL, is to create <a> element.
    // @see: https://gist.github.com/jlong/2428561
    var parser = document.createElement('a');
    parser.href = url;
    linkHost = parser.hostname;
  }

  return host !== linkHost;
}
mhbuur
  • 175
  • 1
  • 13
-1
function Anchor(props) {
    const {href, children} = props;
    function handleClick(e) {
        if (e.ctrlKey || e.shiftKey || e.metaKey || e.altKey) return; // let the browser handle
        e.preventDefault(); // take over
        try {
            window.history.pushState(null, null, href);
        } catch {
            window.location.assign(href);
        }
    }
    return <a {...props} onClick={handleClick}>{children}</a>
}

Usage, just like an <a> tag:

<Anchor href="/other-page">Go to Other Page</Anchor>
Arash Motamedi
  • 9,284
  • 5
  • 34
  • 43
  • This doesn’t seem better than checking those properties (plus the ones you missed: `altKey` and `button`) in the `click` listener, from both code cleanliness and reliability (in the face of focus changing while holding modifier keys) standpoints. – Ry- Jul 09 '19 at 19:37
  • @Ry- You are right! Don't know why I'd implemented it that way; maybe I'd thought that the key event info won't come through the click event. Will update. Thanks! – Arash Motamedi Jul 09 '19 at 20:06
  • 1
    Wel this is not a bad answer, it is equally good as the answer using buttons - Both provide an equally hacky solution. Just starting from a different position. I might even end up using this, since it might be easier for search engines to understand. – paul23 Jul 14 '19 at 13:44