Cloudkiller
Cloudkiller

Reputation: 1636

jQuery replacing relative links

Hopefully someone can explain some odd behavior I've encountered with jQuery. The following script is looking for relative links on my page and replacing them with absolute links.

$(document).ready(function() {
  $("a[href^='/']").each(function(){ 
    var cur_href = $(this).prop("href");
    $(this).prop("href", 'http://www.mysite.com'+cur_href);
  });
});

I'm using this script on a page that will be served up over https but I don't want all of my navigation to link to https pages. Since my navigation are global includes, this seemed like the easiest way to fix the problem.

The issue I'm encountering comes in the actual replacement. The second line of the script correctly matches all relative links on the page and then runs the replacement part of the script. It is in the replacement, line 4, where I get some weird results. After this part of the script runs, my URLs end up looking like this:

http://www.mysite.comhttps//www.mysite.com/mypage.htm

Obviously isn't doing what I want. It seems like the first part of the script is matching the relative URL but when the replacement part fires the browser has already tacked on the domain information.

The only thing I've found so far that actually does what I want is to write the replacement anticipating what the browser has tacked on:

this.href = this.href.replace(/^https:\/\/www\.mysite\.com\//, "http://www.mysite.com/");

Is there a better way to do this?


Edit: here is a jsfiddle of the problem.

Upvotes: 4

Views: 5045

Answers (6)

aknosis
aknosis

Reputation: 4308

The code:

$(function() {
    $('input').click(function() {
        $('a').not('[href^="http"],[href^="https"],[href^="mailto:"],[href^="#"]').each(function() {
            $(this).attr('href', function(index, value) {
                if (value.substr(0,1) !== "/") {
                    value = window.location.pathname + value;
                }
                return "http://mynewurl.com" + value;
            });
        });
    });
});

See this jsfiddle: http://jsfiddle.net/aknosis/kWrjr/

Here's a link to my solution to this problem: http://aknosis.com/2011/07/17/using-jquery-to-rewrite-relative-urls-to-absolute-urls-revisited/

Upvotes: 2

Jordan Running
Jordan Running

Reputation: 106027

jQuery isn't causing a problem here. The issue is that the href property of HTMLAnchorElement (the type of object jQuery is returning), per the spec, always contains an absolute URI.

In HTML5 href is a composite attribute and you can just swap the protocol (the part before //) at will by modifying href.protocol, e.g.:

var link = $( '<a href="https://example.com/foo">bar</a>' )[0];
console.log( link.href );
// => https://example.com/foo

link.href.protocol = 'http:';
console.log( link.href );
// => http://example.com/foo

For older browsers without the composite href you'll just have to make do with a regex:

console.log( link.href );
// => https://example.com/foo

link.href = link.href.replace( /^https:/, 'http:' );
console.log( link.href );
// => http://example.com/foo

TLDR: Your code should look something like this:

$( "a[href^='/']" ).prop( "href",
  function( _idx, oldHref ) {
    return oldHref.replace( /^https:/, 'http:' );
  }
);

P.S. You'll notice that I elided your $.each call. That's because prop automatically acts on every element in the matched set, i.e. it already does what you were doing with each.


.prop( propertyName, function(index, oldPropertyValue) )

  • propertyName The name of the property to set.
  • function(index, oldPropertyValue) A function returning the value to set. Receives the index position of the element in the set and the old property value as arguments. Within the function, the keyword this refers to the current element.

Upvotes: 3

Fabrizio Calderan
Fabrizio Calderan

Reputation: 123367

try simply with

$(document).ready(function() {
  $("a[href^='/']").each(function(){ 
    var cur_href = this.href;  /* <-- access to the href attribute 
                                      through the DOM reference */  
    $(this).prop("href", 'http://www.mysite.com'+cur_href);
  });
});

Upvotes: 1

John Fable
John Fable

Reputation: 1091

This is not pretty, but it should work across browsers:

$(document).ready(function() {
  $("a[href^='/']").each(function(){ 
    var cur_href = $(this).attr("href");
    if( cur_href.indexOf( "http" ) !== -1 ) {
        $(this).attr("href", cur_href);
    } else {
        $(this).attr("href", 'http://www.mysite.com'+cur_href);
    }  
  });
});

Upvotes: 1

maxedison
maxedison

Reputation: 17553

.prop() grabs the property value as opposed to the attribute value. While similar, there are some subtle, important differences -- one of which applies here. The href property on an element is always the full path. For example:

<a id="myAnchor" href="/foo.html"></a>

<script>
elem = document.getElementById('myAnchor');
$elem = $(elem);
alert(elem.href); //alerts the full path, http://www.example.com/foo.html
alert($elem.prop('href')); //same as above
alert($elem.attr('href')); //alerts just "/foo.html"
</script>

In other words, you're appending the domain to a value that is already an absolute path. Just use .attr() rather than .prop() and you'll be fine.

To see an example, open your console and go to this page: http://jsfiddle.net/JUcHU/

Upvotes: 0

charlietfl
charlietfl

Reputation: 171679

Try something like this:

if(! /http/.test( cur_href) ){
   $(this).attr("href", 'http://www.mysite.com'+cur_href);

}

Upvotes: 0

Related Questions