jQuery indexOf error for xdomain.js cross-domain l

2019-09-17 08:23发布

问题:

I'm using a script to detect cross-domain links for google analytics cross-domain tracking. The original script (xdomain.js) was provided by the great folks at Luna Metrics. Here is the script with my modifications, hat-tip to educardocereto here on StackOverflow for the suggested changes to enable setAllowAnchor in the GATC (I've commented line 40 where the console error first points to):

var jQueryXD = jQuery.noConflict();
/* I added var because page loads 2 versions 
    of jquery - not the source of the problem.*/

function listenToClicks()
{
    var domains=["domain1.com", "domain2.com"];
    var fileTypes=[".pdf"];

    jQueryXD('a').each(function(index) {
        var link = jQueryXD(this);
        var href = link.attr('href');

        jQueryXD.each(fileTypes, function(i) {
            if(jQueryXD(link).attr('href').indexOf(this)!=-1){ //this is line 40
                valid = false;
                jQueryXD(link).bind('click', function(c) {
                    c.preventDefault();
                    _gat._getTrackerByName()._trackEvent('Download', 'Click - ' +      jQueryXD(link).attr('href'));
                    setTimeout('document.location = "' + jQueryXD(link).attr('href') + '"', 100);
                });
            }
        });

        var valid = false;
        jQueryXD.each(domains, function(j) {
            try
            {
                if((jQueryXD(link).attr('href').indexOf(this)!=-1)&&(window.location.href.indexOf(this)==-1)){  
                    valid = true;

                    if (valid)
                    {
                        jQueryXD(link).bind('click', function(l) {
                            if(typeof(_gat)=="object"){
                                l.preventDefault();
                                if (jQueryXD(link).attr('target') != "_blank")
                                {                               // _gaq.push(['_link',jQueryXD(link).attr('href')]);
                                    _gaq.push(['_link',jQueryXD(link).attr('href'), true]); // mod
                                }
                                else
                                {
                                    var tracker = _gat._getTrackerByName();
                                    //var fullUrl = tracker._getLinkerUrl(jQueryXD(link).attr('href'));
                                    var fullUrl = tracker._getLinkerUrl(jQueryXD(link).attr('href'), true); //mod
                                    window.open(fullUrl);
                                }
                            }
                        });
                    }
                }

            }
            catch(e)
            {
                //Bad A tag
            }           
        });

        var rootDomain = document.domain.split(".")[document.domain.split(".").length - 2] + "." + document.domain.split(".")[document.domain.split(".").length - 1];

        if ( (href.match(/^http/)) && (href.indexOf(rootDomain) == -1) && !valid) {
            jQueryXD(link).bind('click', function(d) {
                    d.preventDefault();
                    _gat._getTrackerByName()._trackEvent('Outbound Link', href);
                    setTimeout('document.location = "' + href + '"', 100);
                });            
        }
    });

}

jQueryXD(document).ready(function() {
    listenToClicks();
});

The output from the Chrome javascript console:

 Uncaught TypeError: 
Cannot call method 'indexOf' of undefined        xdomain-nfi-nfs-anchormod-noconflict.js:40
    jQueryXD.each.valid                          xdomain-nfi-nfs-anchormod-noconflict.js:40
    jQuery.extend.each                           jquery-1.2.6.min.js:21
    (anonymous function)                         xdomain-nfi-nfs-anchormod-noconflict.js:39
    jQuery.extend.each                           jquery-1.2.6.min.js:21
    jQuery.fn.jQuery.each                        jquery-1.2.6.min.js:12
    listenToClicks                               xdomain-nfi-nfs-anchormod-noconflict.js:35
    (anonymous function)                         xdomain-nfi-nfs-anchormod-noconflict.js:100
    jQuery.fn.extend.ready                       jquery-1.2.6.min.js:27
    jQuery.extend.ready.jQuery.readyList         jquery-1.2.6.min.js:27
    jQuery.extend.each                           jquery-1.2.6.min.js:21
    jQuery.extend.ready                          jquery-1.2.6.min.js:27

So, atleast it seems to be not mixing up the two jquery instances. I've also tried it with jquery 1.7.1. I'm using 1.2.6 because the script seems to have been moste tested on that version.

回答1:

Here you cache both the jQuerified element and the href attr.

var link = jQueryXD(this);
var href = link.attr('href');

Than why do you later do this:

jQueryXD(link).attr('href').indexOf(this)

You could either call link.attr('href').indexOf(this) since link is already a jQuery object or you could use directly the href you cached and do this href.indexOf(this).

Still I think the error you see happens when a link doesn't have an href attribute. So you better check if the href is not undefined before continuing your logic.

I tested it on both jQuery 1.2.6 and 1.7. It seems to be working fine.

Here's the finished script.

var jQueryXD = jQuery.noConflict();
/* I added var because page loads 2 versions 
    of jquery - not the source of the problem.*/

function listenToClicks() {
    var domains = ["domain1.com", "domain2.com"];
    var fileTypes = [".pdf"];

    jQueryXD('a').each(function(index) {
        var link = jQueryXD(this);
        var href = link.attr('href');
        if(!href){
            // This element doesnt have a href
            return true;
        }

        var valid = false;
        jQueryXD.each(fileTypes, function(i) {
            if (href.indexOf(this) != -1) { //this is line 40
                valid = false;
                link.bind('click', function(c) {
                    c.preventDefault();
                    _gat._getTrackerByName()._trackEvent('Download', 'Click - ' + link.attr('href'));
                    setTimeout('document.location = "' + href + '"', 100);
                });
            }
        });

        jQueryXD.each(domains, function(j) {
            try {
                if ((href.indexOf(this) != -1) && (window.location.href.indexOf(this) == -1)) {
                    valid = true;

                    if (valid) {
                        link.bind('click', function(l) {
                            if (typeof(_gat) == "object") {
                                l.preventDefault();
                                if (link.attr('target') != "_blank") { // _gaq.push(['_link',jQueryXD(link).attr('href')]);
                                    _gaq.push(['_link', href, true]); // mod
                                }
                                else {
                                    var tracker = _gat._getTrackerByName();
                                    //var fullUrl = tracker._getLinkerUrl(href);
                                    var fullUrl = tracker._getLinkerUrl(href, true); //mod
                                    window.open(fullUrl);
                                }
                            }
                        });
                    }
                }

            }
            catch (e) {
                //Bad A tag
            }
        });

        var rootDomain = document.domain.split(".")[document.domain.split(".").length - 2] + "." + document.domain.split(".")[document.domain.split(".").length - 1];

        if ((href.match(/^http/)) && (href.indexOf(rootDomain) == -1) && !valid) {
            jQueryXD(link).bind('click', function(d) {
                d.preventDefault();
                _gat._getTrackerByName()._trackEvent('Outbound Link', href);
                setTimeout('document.location = "' + href + '"', 100);
            });
        }
    });

}

jQueryXD(document).ready(function() {
    listenToClicks();
});

But you might be reinventing the wheel here. There are some better scripts out there to achieve the same thing. I think you might be interested in looking into GAS. It's a wrapper around ga.js that extends and add a bunch of stuff including crossDomain and downloadTracking.

Spoiler: I'm the main developer of GAS.

https://github.com/CardinalPath/gas