/* * 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; } } } }; };