2

I am trying to have all external links on my website raise a confirm saying "You are being redirected to an external site." I know how to write the JS to check for if a link is external and how to raise the confirm, but is there a way I can apply this to every link in my site without going through individually? The links will always have the format <a href=URL> Link </a>. An angular script would check if the URL subdomain is the same as my site, and if not it will add onclick=return confirm('You will now be redirected to an exteral site') and target="_blank" to the link HTML.

laminarflow
  • 55
  • 1
  • 10

4 Answers4

2

As you already said, this can be achieved by using confirm onclick, you can easily add an EventListener to all a Elements that are external (l.hostname !== location.hostname) in your page and only redirect after the user accepts the message, just like so:

Array.from(document.querySelectorAll("a")).filter(l => l.hostname !== location.hostname).forEach(el => el.addEventListener("click", evtL));

function evtL(e) {
    if (confirm("Are you sure you want to leave the page?")) {
      return; //redirect
    } else {
      e.preventDefault();
      return false; //don't redirect
    }
}
<a href="/foo/bar">internal</a>
<a href="https://example.com">external</a>

<a href="https://stacksnippets.net/js">also internal</a>
Luca Kiebel
  • 9,790
  • 7
  • 29
  • 44
  • I was writing my comment at the same time you were posting you're answer so I couldn't see it. Why not filtering the result of the querySelectorAll so not all links have an eventListener, but only the ones who need to have one ? – Zyigh Jun 08 '18 at 14:49
  • You're right, that would be more efficient, thanks for the suggestions – Luca Kiebel Jun 08 '18 at 14:50
  • 1
    Edited my post! – Luca Kiebel Jun 08 '18 at 14:54
0

Since you are using Angular.js, I would recommend taking a look at how they work with <a> since they already have a directive applied to all <a> to make ngHref work. The source would be here: <a> directive.

The basic idea is to put the logic that you use to change the href to the warning or display a modal or whatever in the element.on('click', function(event) {...}).

Because Angular.js already defined an <a> directive, you may need to fiddle with the priority of your directive so that you don't accidentally break the way Angular fiddles with <a>.

The directive would look something like this:

// Logic that dictactes whether the URL should show a warning
function isSuspectUrl(l) {
    return false;
}

const app = angular
    .module("aWarn", [])
    .directive("a", function() {
        return {
            restrict: "E",
            compile: function(el, attr) {
                element.on("click", function(e) {
                    if (isSuspectUrl(attr.href)) {
                        // Logic that would display a warning
                    }
                });
            }
        }
    });
zero298
  • 25,467
  • 10
  • 75
  • 100
0
  1. It can be achieved by the regex matching.
  2. Instead of adding multiple event listeners, single event listener can be added on the document object and detect the external links as follows. It will give more efficiency.

For reference, Handling events for multiple elements in a single listener

var regexp = /https?:\/\/(www.){0,1}((?:[\w\d-]+\.)+[\w\d]{2,})/i;
function isExternal(url)
{
    return regexp.exec(location.href)[2] !== regexp.exec(url)[2];
}

document.addEventListener("click",function(){
    if(event.target.tagName.toLowerCase()==="a")
    {
        if(isExternal(event.target.href))
        {
            if(confirm("Redirecting to an external site...."))
            {
                console.log("Going to external site...");
                return;
            }
            else
            {
                event.preventDefault();
                return;
            }
        }
        else
        {
            console.log("Internal URL Clicked.")
        }
    }
});
<a href="https://www.google.com" target="_blank">External Site 1</a> - https://www.google.com<br>
<a href="https://www.stackoverflow.com" target="_blank">External Site 2</a> - https://www.stackoverflow.com<br>
<a href="http://www.facebook.com" target="_blank">External Site 3</a> - http://www.facebook.com (Using HTTP)<br>
<a href="http://www.stacksnippets.net" target="_blank">Internal Site (Using HTTP)</a> - http://www.stacksnippets.net<br>
<a href="http://stacksnippets.net" target="_blank">Internal Site (without www.)</a> - http://stacksnippets.net<br>
<a href="/path/to/something" target="_blank">Internal reference</a> - /path/to/something (Internal reference)<br>

Thanks @pseudosavant for the regex.

Though @Luca's answer works, the hostname is not supported in IE, Safari and Opera. Browser Compatibility for reference.

Vignesh Raja
  • 7,927
  • 1
  • 33
  • 42
0

2021 version

  • es2015+
  • Works with SPA
  • Handles local links that look external
  • Subdomains support

const baseUrl = window.location.origin;
const absoluteUrlRegex = new RegExp('^(?:[a-z]+:)?//', 'i');

const isAbsoluteUrl = (url) => absoluteUrlRegex.test(url);
const isLocalUrl = (url) => url.startsWith(baseUrl) || !isAbsoluteUrl(url);

// https://gist.github.com/ -> github.com
// https://stackoverflow.com/ -> stackoverflow.com
const getDomain = (url) => {
    const urlInstance = new URL(url);
    const dividedUrl = urlInstance.hostname.split('.');
    const urlDomainsCount = dividedUrl.length;
    return urlDomainsCount === 2
        ? urlInstance.hostname
        : `${dividedUrl[urlDomainsCount - 2]}.${dividedUrl[urlDomainsCount - 1]}`;
};

// example whitelist
const whitelist = [
    'twitter.com',
    'github.com',
];

// url has same domain or whitelisted
const isWhitelistedUrl = (url) => {
    const domain = getDomain(url);
    return domain === window.location.hostname || whitelist.includes(domain);
};

// bind listener
const confirmExternalLinks = (confirmationFn) => {
    document.addEventListener('click', async (e) => {
        const linkElement = e.target.closest('a');
        if (!linkElement) return;
        const link = linkElement.getAttribute('href');
        if (isLocalUrl(link) || isWhitelistedUrl(link)) return;
        e.preventDefault();
        const confirmation = await confirmationFn(link);
        if (confirmation) window.open(link);
    });
};

// tip: replace confirmationFn with your custom handler which returns Promise
confirmExternalLinks((link) => confirm(`Proceed to ${link}?`));
<a target="_blank" href="https://stacksnippets.net/">https://stacksnippets.net/</a>  is local for this snippet iframe
<br>
<a target="_blank" href="https://twitter.com/">https://twitter.com/</a> is whitelisted
<br>
<a target="_blank" href="https://developer.mozilla.org/">https://developer.mozilla.org/</a>  is external
Georgiy Bukharov
  • 356
  • 3
  • 10