181 lines
6.2 KiB
JavaScript
Executable file
181 lines
6.2 KiB
JavaScript
Executable file
/*
|
|
* JavaScript tracker for Snowplow: links.js
|
|
*
|
|
* Significant portions copyright 2010 Anthon Pang. Remainder copyright
|
|
* 2012-2014 Snowplow Analytics Ltd. All rights reserved.
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without
|
|
* modification, are permitted provided that the following conditions are
|
|
* met:
|
|
*
|
|
* * Redistributions of source code must retain the above copyright
|
|
* notice, this list of conditions and the following disclaimer.
|
|
*
|
|
* * Redistributions in binary form must reproduce the above copyright
|
|
* notice, this list of conditions and the following disclaimer in the
|
|
* documentation and/or other materials provided with the distribution.
|
|
*
|
|
* * Neither the name of Anthon Pang nor Snowplow Analytics Ltd nor the
|
|
* names of their contributors may be used to endorse or promote products
|
|
* derived from this software without specific prior written permission.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
|
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
|
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
|
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
|
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
|
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
|
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
|
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
|
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
*/
|
|
|
|
var isUndefined = require('lodash/isUndefined'),
|
|
helpers = require('./lib/helpers'),
|
|
object = typeof exports !== 'undefined' ? exports : this;
|
|
|
|
/**
|
|
* Object for handling automatic link tracking
|
|
*
|
|
* @param object core The tracker core
|
|
* @param string trackerId Unique identifier for the tracker instance, used to mark tracked links
|
|
* @param function contextAdder Function to add common contexts like PerformanceTiming to all events
|
|
* @return object linkTrackingManager instance
|
|
*/
|
|
object.getLinkTrackingManager = function (core, trackerId, contextAdder) {
|
|
|
|
// Filter function used to determine whether clicks on a link should be tracked
|
|
var linkTrackingFilter,
|
|
|
|
// Whether pseudo clicks are tracked
|
|
linkTrackingPseudoClicks,
|
|
|
|
// Whether to track the innerHTML of clicked links
|
|
linkTrackingContent,
|
|
|
|
// The context attached to link click events
|
|
linkTrackingContext,
|
|
|
|
// Internal state of the pseudo click handler
|
|
lastButton,
|
|
lastTarget;
|
|
|
|
/*
|
|
* Process clicks
|
|
*/
|
|
function processClick(sourceElement, context) {
|
|
|
|
var parentElement,
|
|
tag,
|
|
elementId,
|
|
elementClasses,
|
|
elementTarget,
|
|
elementContent;
|
|
|
|
while ((parentElement = sourceElement.parentNode) !== null &&
|
|
!isUndefined(parentElement) && // buggy IE5.5
|
|
((tag = sourceElement.tagName.toUpperCase()) !== 'A' && tag !== 'AREA')) {
|
|
sourceElement = parentElement;
|
|
}
|
|
|
|
if (!isUndefined(sourceElement.href)) {
|
|
// browsers, such as Safari, don't downcase hostname and href
|
|
var originalSourceHostName = sourceElement.hostname || helpers.getHostName(sourceElement.href),
|
|
sourceHostName = originalSourceHostName.toLowerCase(),
|
|
sourceHref = sourceElement.href.replace(originalSourceHostName, sourceHostName),
|
|
scriptProtocol = new RegExp('^(javascript|vbscript|jscript|mocha|livescript|ecmascript|mailto):', 'i');
|
|
|
|
// Ignore script pseudo-protocol links
|
|
if (!scriptProtocol.test(sourceHref)) {
|
|
|
|
elementId = sourceElement.id;
|
|
elementClasses = helpers.getCssClasses(sourceElement);
|
|
elementTarget = sourceElement.target;
|
|
elementContent = linkTrackingContent ? sourceElement.innerHTML : null;
|
|
|
|
// decodeUrl %xx
|
|
sourceHref = unescape(sourceHref);
|
|
core.trackLinkClick(sourceHref, elementId, elementClasses, elementTarget, elementContent, contextAdder(helpers.resolveDynamicContexts(context, sourceElement)));
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Return function to handle click event
|
|
*/
|
|
function getClickHandler(context) {
|
|
return function (evt) {
|
|
var button,
|
|
target;
|
|
|
|
evt = evt || window.event;
|
|
button = evt.which || evt.button;
|
|
target = evt.target || evt.srcElement;
|
|
|
|
// Using evt.type (added in IE4), we avoid defining separate handlers for mouseup and mousedown.
|
|
if (evt.type === 'click') {
|
|
if (target) {
|
|
processClick(target, context);
|
|
}
|
|
} else if (evt.type === 'mousedown') {
|
|
if ((button === 1 || button === 2) && target) {
|
|
lastButton = button;
|
|
lastTarget = target;
|
|
} else {
|
|
lastButton = lastTarget = null;
|
|
}
|
|
} else if (evt.type === 'mouseup') {
|
|
if (button === lastButton && target === lastTarget) {
|
|
processClick(target, context);
|
|
}
|
|
lastButton = lastTarget = null;
|
|
}
|
|
};
|
|
}
|
|
|
|
/*
|
|
* Add click listener to a DOM element
|
|
*/
|
|
function addClickListener(element) {
|
|
if (linkTrackingPseudoClicks) {
|
|
// for simplicity and performance, we ignore drag events
|
|
helpers.addEventListener(element, 'mouseup', getClickHandler(linkTrackingContext), false);
|
|
helpers.addEventListener(element, 'mousedown', getClickHandler(linkTrackingContext), false);
|
|
} else {
|
|
helpers.addEventListener(element, 'click', getClickHandler(linkTrackingContext), false);
|
|
}
|
|
}
|
|
|
|
return {
|
|
|
|
/*
|
|
* Configures link click tracking: how to filter which links will be tracked,
|
|
* whether to use pseudo click tracking, and what context to attach to link_click events
|
|
*/
|
|
configureLinkClickTracking: function (criterion, pseudoClicks, trackContent, context) {
|
|
linkTrackingContent = trackContent;
|
|
linkTrackingContext = context;
|
|
linkTrackingPseudoClicks = pseudoClicks;
|
|
linkTrackingFilter = helpers.getFilter(criterion, true);
|
|
},
|
|
|
|
/*
|
|
* Add click handlers to anchor and AREA elements, except those to be ignored
|
|
*/
|
|
addClickListeners: function () {
|
|
|
|
var linkElements = document.links,
|
|
i;
|
|
|
|
for (i = 0; i < linkElements.length; i++) {
|
|
// Add a listener to link elements which pass the filter and aren't already tracked
|
|
if (linkTrackingFilter(linkElements[i]) && !linkElements[i][trackerId]) {
|
|
addClickListener(linkElements[i]);
|
|
linkElements[i][trackerId] = true;
|
|
}
|
|
}
|
|
}
|
|
};
|
|
};
|