/* globals define */ (function (root, factory) { 'use strict'; if (typeof define === 'function' && define.amd) { define(['angular'], factory); } else if (typeof module === 'object' && module.exports) { module.exports = factory(require('angular')); } else { factory(root.angular); } }(this, function (angular, undefined) { 'use strict'; angular.module('angular-google-analytics', []) .provider('Analytics', function () { var accounts, analyticsJS = true, cookieConfig = 'auto', // DEPRECATED created = false, crossDomainLinker = false, crossLinkDomains, currency = 'USD', debugMode = false, delayScriptTag = false, displayFeatures = false, disableAnalytics = false, domainName, ecommerce = false, enhancedEcommerce = false, enhancedLinkAttribution = false, experimentId, ignoreFirstPageLoad = false, logAllCalls = false, hybridMobileSupport = false, offlineMode = false, pageEvent = '$routeChangeSuccess', readFromRoute = false, removeRegExp, testMode = false, traceDebuggingMode = false, trackPrefix = '', trackRoutes = true, trackUrlParams = false; this.log = []; this.offlineQueue = []; /** * Configuration Methods **/ this.setAccount = function (tracker) { if (angular.isUndefined(tracker) || tracker === false) { accounts = undefined; } else if (angular.isArray(tracker)) { accounts = tracker; } else if (angular.isObject(tracker)) { accounts = [tracker]; } else { // In order to preserve an existing behavior with how the _trackEvent function works, // the trackEvent property must be set to true when there is only a single tracker. accounts = [{ tracker: tracker, trackEvent: true }]; } return this; }; this.trackPages = function (val) { trackRoutes = !!val; return this; }; this.trackPrefix = function (prefix) { trackPrefix = prefix; return this; }; this.setDomainName = function (domain) { domainName = domain; return this; }; this.useDisplayFeatures = function (val) { displayFeatures = !!val; return this; }; this.useAnalytics = function (val) { analyticsJS = !!val; return this; }; this.useEnhancedLinkAttribution = function (val) { enhancedLinkAttribution = !!val; return this; }; this.useCrossDomainLinker = function (val) { crossDomainLinker = !!val; return this; }; this.setCrossLinkDomains = function (domains) { crossLinkDomains = domains; return this; }; this.setPageEvent = function (name) { pageEvent = name; return this; }; /* DEPRECATED */ this.setCookieConfig = function (config) { cookieConfig = config; return this; }; this.useECommerce = function (val, enhanced) { ecommerce = !!val; enhancedEcommerce = !!enhanced; return this; }; this.setCurrency = function (currencyCode) { currency = currencyCode; return this; }; this.setRemoveRegExp = function (regex) { if (regex instanceof RegExp) { removeRegExp = regex; } return this; }; this.setExperimentId = function (id) { experimentId = id; return this; }; this.ignoreFirstPageLoad = function (val) { ignoreFirstPageLoad = !!val; return this; }; this.trackUrlParams = function (val) { trackUrlParams = !!val; return this; }; this.disableAnalytics = function (val) { disableAnalytics = !!val; return this; }; this.setHybridMobileSupport = function (val) { hybridMobileSupport = !!val; return this; }; this.startOffline = function (val) { offlineMode = !!val; if (offlineMode === true) { this.delayScriptTag(true); } return this; }; this.delayScriptTag = function (val) { delayScriptTag = !!val; return this; }; this.logAllCalls = function (val) { logAllCalls = !!val; return this; }; this.enterTestMode = function () { testMode = true; return this; }; this.enterDebugMode = function (enableTraceDebugging) { debugMode = true; traceDebuggingMode = !!enableTraceDebugging; return this; }; // Enable reading page url from route object this.readFromRoute = function(val) { readFromRoute = !!val; return this; }; /** * Public Service */ this.$get = ['$document', // To read title '$location', // '$log', // '$rootScope',// '$window', // '$injector', // To access ngRoute module without declaring a fixed dependency function ($document, $location, $log, $rootScope, $window, $injector) { var that = this; /** * Side-effect Free Helper Methods **/ var isPropertyDefined = function (key, config) { return angular.isObject(config) && angular.isDefined(config[key]); }; var isPropertySetTo = function (key, config, value) { return isPropertyDefined(key, config) && config[key] === value; }; var generateCommandName = function (commandName, config) { if (angular.isString(config)) { return config + '.' + commandName; } return isPropertyDefined('name', config) ? (config.name + '.' + commandName) : commandName; }; // Try to read route configuration and log warning if not possible var $route = {}; if (readFromRoute) { if (!$injector.has('$route')) { $log.warn('$route service is not available. Make sure you have included ng-route in your application dependencies.'); } else { $route = $injector.get('$route'); } } // Get url for current page var getUrl = function () { // Using ngRoute provided tracking urls if (readFromRoute && $route.current && ('pageTrack' in $route.current)) { return $route.current.pageTrack; } // Otherwise go the old way var url = trackUrlParams ? $location.url() : $location.path(); return removeRegExp ? url.replace(removeRegExp, '') : url; }; var getUtmParams = function () { var utmToCampaignVar = { utm_source: 'campaignSource', utm_medium: 'campaignMedium', utm_term: 'campaignTerm', utm_content: 'campaignContent', utm_campaign: 'campaignName' }; var object = {}; angular.forEach($location.search(), function (value, key) { var campaignVar = utmToCampaignVar[key]; if (angular.isDefined(campaignVar)) { object[campaignVar] = value; } }); return object; }; /** * get ActionFieldObject * https://developers.google.com/analytics/devguides/collection/analyticsjs/enhanced-ecommerce#action-data * @param id * @param affliation * @param revenue * @param tax * @param shipping * @param coupon * @param list * @param step * @param option */ var getActionFieldObject = function (id, affiliation, revenue, tax, shipping, coupon, list, step, option) { var obj = {}; if (id) { obj.id = id; } if (affiliation) { obj.affiliation = affiliation; } if (revenue) { obj.revenue = revenue; } if (tax) { obj.tax = tax; } if (shipping) { obj.shipping = shipping; } if (coupon) { obj.coupon = coupon; } if (list) { obj.list = list; } if (step) { obj.step = step; } if (option) { obj.option = option; } return obj; }; /** * Private Methods */ var _gaJs = function (fn) { if (!analyticsJS && $window._gaq && typeof fn === 'function') { fn(); } }; var _gaq = function () { var args = Array.prototype.slice.call(arguments); if (offlineMode === true) { that.offlineQueue.push([_gaq, args]); return; } if (!$window._gaq) { $window._gaq = []; } if (logAllCalls === true) { that._log.apply(that, args); } $window._gaq.push(args); }; var _analyticsJs = function (fn) { if (analyticsJS && $window.ga && typeof fn === 'function') { fn(); } }; var _ga = function () { var args = Array.prototype.slice.call(arguments); if (offlineMode === true) { that.offlineQueue.push([_ga, args]); return; } if (typeof $window.ga !== 'function') { that._log('warn', 'ga function not set on window'); return; } if (logAllCalls === true) { that._log.apply(that, args); } $window.ga.apply(null, args); }; var _gaMultipleTrackers = function (includeFn) { // Drop the includeFn from the arguments and preserve the original command name var args = Array.prototype.slice.call(arguments, 1), commandName = args[0], trackers = []; if (typeof includeFn === 'function') { accounts.forEach(function (account) { if (includeFn(account)) { trackers.push(account); } }); } else { // No include function indicates that all accounts are to be used trackers = accounts; } // To preserve backwards compatibility fallback to _ga method if no account // matches the specified includeFn. This preserves existing behaviors by // performing the single tracker operation. if (trackers.length === 0) { _ga.apply(that, args); return; } trackers.forEach(function (tracker) { // Check tracker 'select' function, if it exists, for whether the tracker should be used with the current command. // If the 'select' function returns false then the tracker will not be used with the current command. if (isPropertyDefined('select', tracker) && typeof tracker.select === 'function' && !tracker.select(args)) { return; } args[0] = generateCommandName(commandName, tracker); _ga.apply(that, args); }); }; this._log = function () { var args = Array.prototype.slice.call(arguments); if (args.length > 0) { if (args.length > 1) { switch (args[0]) { case 'debug': case 'error': case 'info': case 'log': case 'warn': $log[args[0]](args.slice(1)); break; } } that.log.push(args); } }; this._createScriptTag = function () { if (!accounts || accounts.length < 1) { that._log('warn', 'No account id set to create script tag'); return; } if (accounts.length > 1) { that._log('warn', 'Multiple trackers are not supported with ga.js. Using first tracker only'); accounts = accounts.slice(0, 1); } if (created === true) { that._log('warn', 'ga.js or analytics.js script tag already created'); return; } if (disableAnalytics === true) { that._log('info', 'Analytics disabled: ' + accounts[0].tracker); $window['ga-disable-' + accounts[0].tracker] = true; } _gaq('_setAccount', accounts[0].tracker); if(domainName) { _gaq('_setDomainName', domainName); } if (enhancedLinkAttribution) { _gaq('_require', 'inpage_linkid', '//www.google-analytics.com/plugins/ga/inpage_linkid.js'); } if (trackRoutes && !ignoreFirstPageLoad) { if (removeRegExp) { _gaq('_trackPageview', getUrl()); } else { _gaq('_trackPageview'); } } var document = $document[0]; var scriptSource; if (displayFeatures === true) { scriptSource = ('https:' === document.location.protocol ? 'https://' : 'http://') + 'stats.g.doubleclick.net/dc.js'; } else { scriptSource = ('https:' === document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js'; } if (testMode !== true) { // If not in test mode inject the Google Analytics tag (function () { var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true; ga.src = scriptSource; var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s); })(); } else { // Log the source location for validation that._log('inject', scriptSource); } created = true; return true; }; this._createAnalyticsScriptTag = function () { if (!accounts) { that._log('warn', 'No account id set to create analytics script tag'); return; } if (created === true) { that._log('warn', 'ga.js or analytics.js script tag already created'); return; } if (disableAnalytics === true) { accounts.forEach(function (trackerObj) { that._log('info', 'Analytics disabled: ' + trackerObj.tracker); $window['ga-disable-' + trackerObj.tracker] = true; }); } var document = $document[0]; var protocol = hybridMobileSupport === true ? 'https:' : ''; var scriptSource = protocol + '//www.google-analytics.com/' + (debugMode ? 'analytics_debug.js' : 'analytics.js'); if (testMode !== true) { // If not in test mode inject the Google Analytics tag (function (i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function (){ (i[r].q=i[r].q||[]).push(arguments);},i[r].l=1*new Date();a=s.createElement(o), m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m); })(window,document,'script',scriptSource,'ga'); } else { if (typeof $window.ga !== 'function') { // In test mode create a ga function if none exists that is a noop sink. $window.ga = function () {}; } // Log script injection. that._log('inject', scriptSource); } if (traceDebuggingMode) { $window.ga_debug = { trace: true }; } accounts.forEach(function (trackerObj) { trackerObj.crossDomainLinker = isPropertyDefined('crossDomainLinker', trackerObj) ? trackerObj.crossDomainLinker : crossDomainLinker; trackerObj.crossLinkDomains = isPropertyDefined('crossLinkDomains', trackerObj) ? trackerObj.crossLinkDomains : crossLinkDomains; trackerObj.displayFeatures = isPropertyDefined('displayFeatures', trackerObj) ? trackerObj.displayFeatures : displayFeatures; trackerObj.enhancedLinkAttribution = isPropertyDefined('enhancedLinkAttribution', trackerObj) ? trackerObj.enhancedLinkAttribution : enhancedLinkAttribution; trackerObj.set = isPropertyDefined('set', trackerObj) ? trackerObj.set : {}; trackerObj.trackEcommerce = isPropertyDefined('trackEcommerce', trackerObj) ? trackerObj.trackEcommerce : ecommerce; trackerObj.trackEvent = isPropertyDefined('trackEvent', trackerObj) ? trackerObj.trackEvent : false; // Logic to choose the account fields to be used. // cookieConfig is being deprecated for a tracker specific property: fields. var fields = {}; if (isPropertyDefined('fields', trackerObj)) { fields = trackerObj.fields; } else if (isPropertyDefined('cookieConfig', trackerObj)) { if (angular.isString(trackerObj.cookieConfig)) { fields.cookieDomain = trackerObj.cookieConfig; } else { fields = trackerObj.cookieConfig; } } else if (angular.isString(cookieConfig)) { fields.cookieDomain = cookieConfig; } else if (cookieConfig) { fields = cookieConfig; } if (trackerObj.crossDomainLinker === true) { fields.allowLinker = true; } if (isPropertyDefined('name', trackerObj)) { fields.name = trackerObj.name; } trackerObj.fields = fields; _ga('create', trackerObj.tracker, trackerObj.fields); // Hybrid mobile application support // https://developers.google.com/analytics/devguides/collection/analyticsjs/tasks if (hybridMobileSupport === true) { _ga(generateCommandName('set', trackerObj), 'checkProtocolTask', null); } // Send all custom set commands from the trackerObj.set property for (var key in trackerObj.set) { if (trackerObj.set.hasOwnProperty(key)) { _ga(generateCommandName('set', trackerObj), key, trackerObj.set[key]); } } if (trackerObj.crossDomainLinker === true) { _ga(generateCommandName('require', trackerObj), 'linker'); if (angular.isDefined(trackerObj.crossLinkDomains)) { _ga(generateCommandName('linker:autoLink', trackerObj), trackerObj.crossLinkDomains); } } if (trackerObj.displayFeatures) { _ga(generateCommandName('require', trackerObj), 'displayfeatures'); } if (trackerObj.trackEcommerce) { if (!enhancedEcommerce) { _ga(generateCommandName('require', trackerObj), 'ecommerce'); } else { _ga(generateCommandName('require', trackerObj), 'ec'); _ga(generateCommandName('set', trackerObj), '&cu', currency); } } if (trackerObj.enhancedLinkAttribution) { _ga(generateCommandName('require', trackerObj), 'linkid'); } if (trackRoutes && !ignoreFirstPageLoad) { _ga(generateCommandName('send', trackerObj), 'pageview', trackPrefix + getUrl()); } }); if (experimentId) { var expScript = document.createElement('script'), s = document.getElementsByTagName('script')[0]; expScript.src = protocol + '//www.google-analytics.com/cx/api.js?experiment=' + experimentId; s.parentNode.insertBefore(expScript, s); } created = true; return true; }; this._ecommerceEnabled = function (warn, command) { var result = ecommerce && !enhancedEcommerce; if (warn === true && result === false) { if (ecommerce && enhancedEcommerce) { that._log('warn', command + ' is not available when Enhanced Ecommerce is enabled with analytics.js'); } else { that._log('warn', 'Ecommerce must be enabled to use ' + command + ' with analytics.js'); } } return result; }; this._enhancedEcommerceEnabled = function (warn, command) { var result = ecommerce && enhancedEcommerce; if (warn === true && result === false) { that._log('warn', 'Enhanced Ecommerce must be enabled to use ' + command + ' with analytics.js'); } return result; }; /** * Track page https://developers.google.com/analytics/devguides/collection/gajs/ https://developers.google.com/analytics/devguides/collection/analyticsjs/pages * @param url * @param title * @param custom * @private */ this._trackPage = function (url, title, custom) { url = url ? url : getUrl(); title = title ? title : $document[0].title; _gaJs(function () { // http://stackoverflow.com/questions/7322288/how-can-i-set-a-page-title-with-google-analytics _gaq('_set', 'title', title); _gaq('_trackPageview', (trackPrefix + url)); }); _analyticsJs(function () { var opt_fieldObject = { 'page': trackPrefix + url, 'title': title }; angular.extend(opt_fieldObject, getUtmParams()); if (angular.isObject(custom)) { angular.extend(opt_fieldObject, custom); } _gaMultipleTrackers(undefined, 'send', 'pageview', opt_fieldObject); }); }; /** * Track event https://developers.google.com/analytics/devguides/collection/gajs/eventTrackerGuide https://developers.google.com/analytics/devguides/collection/analyticsjs/events * @param category * @param action * @param label * @param value * @param noninteraction * @param custom * @private */ this._trackEvent = function (category, action, label, value, noninteraction, custom) { _gaJs(function () { _gaq('_trackEvent', category, action, label, value, !!noninteraction); }); _analyticsJs(function () { var opt_fieldObject = {}; var includeFn = function (trackerObj) { return isPropertySetTo('trackEvent', trackerObj, true); }; if (angular.isDefined(noninteraction)) { opt_fieldObject.nonInteraction = !!noninteraction; } if (angular.isObject(custom)) { angular.extend(opt_fieldObject, custom); } if (!angular.isDefined(opt_fieldObject.page)) { opt_fieldObject.page = getUrl(); } _gaMultipleTrackers(includeFn, 'send', 'event', category, action, label, value, opt_fieldObject); }); }; /** * Add transaction * https://developers.google.com/analytics/devguides/collection/gajs/methods/gaJSApiEcommerce#_gat.GA_Tracker_._addTrans * https://developers.google.com/analytics/devguides/collection/analyticsjs/ecommerce#addTrans * @param transactionId * @param affiliation * @param total * @param tax * @param shipping * @param city * @param state * @param country * @private */ this._addTrans = function (transactionId, affiliation, total, tax, shipping, city, state, country, currency) { _gaJs(function () { _gaq('_addTrans', transactionId, affiliation, total, tax, shipping, city, state, country); }); _analyticsJs(function () { if (that._ecommerceEnabled(true, 'addTrans')) { var includeFn = function (trackerObj) { return isPropertySetTo('trackEcommerce', trackerObj, true); }; _gaMultipleTrackers( includeFn, 'ecommerce:addTransaction', { id: transactionId, affiliation: affiliation, revenue: total, tax: tax, shipping: shipping, currency: currency || 'USD' }); } }); }; /** * Add item to transaction * https://developers.google.com/analytics/devguides/collection/gajs/methods/gaJSApiEcommerce#_gat.GA_Tracker_._addItem * https://developers.google.com/analytics/devguides/collection/analyticsjs/ecommerce#addItem * @param transactionId * @param sku * @param name * @param category * @param price * @param quantity * @private */ this._addItem = function (transactionId, sku, name, category, price, quantity) { _gaJs(function () { _gaq('_addItem', transactionId, sku, name, category, price, quantity); }); _analyticsJs(function () { if (that._ecommerceEnabled(true, 'addItem')) { var includeFn = function (trackerObj) { return isPropertySetTo('trackEcommerce', trackerObj, true); }; _gaMultipleTrackers( includeFn, 'ecommerce:addItem', { id: transactionId, name: name, sku: sku, category: category, price: price, quantity: quantity }); } }); }; /** * Track transaction * https://developers.google.com/analytics/devguides/collection/gajs/methods/gaJSApiEcommerce#_gat.GA_Tracker_._trackTrans * https://developers.google.com/analytics/devguides/collection/analyticsjs/ecommerce#sendingData * @private */ this._trackTrans = function () { _gaJs(function () { _gaq('_trackTrans'); }); _analyticsJs(function () { if (that._ecommerceEnabled(true, 'trackTrans')) { var includeFn = function (trackerObj) { return isPropertySetTo('trackEcommerce', trackerObj, true); }; _gaMultipleTrackers(includeFn, 'ecommerce:send'); } }); }; /** * Clear transaction * https://developers.google.com/analytics/devguides/collection/analyticsjs/ecommerce#clearingData * @private */ this._clearTrans = function () { _analyticsJs(function () { if (that._ecommerceEnabled(true, 'clearTrans')) { var includeFn = function (trackerObj) { return isPropertySetTo('trackEcommerce', trackerObj, true); }; _gaMultipleTrackers(includeFn, 'ecommerce:clear'); } }); }; /** * Enhanced Ecommerce */ /** * Add Product * https://developers.google.com/analytics/devguides/collection/analyticsjs/enhanced-ecommerce#product-data * @param productId * @param name * @param category * @param brand * @param variant * @param price * @param quantity * @param coupon * @param position * @param custom * @private */ this._addProduct = function (productId, name, category, brand, variant, price, quantity, coupon, position, custom) { _gaJs(function () { _gaq('_addProduct', productId, name, category, brand, variant, price, quantity, coupon, position); }); _analyticsJs(function () { if (that._enhancedEcommerceEnabled(true, 'addProduct')) { var includeFn = function (trackerObj) { return isPropertySetTo('trackEcommerce', trackerObj, true); }; var details = { id: productId, name: name, category: category, brand: brand, variant: variant, price: price, quantity: quantity, coupon: coupon, position: position }; if (angular.isObject(custom)) { angular.extend(details, custom); } _gaMultipleTrackers(includeFn, 'ec:addProduct', details); } }); }; /** * Add Impression * https://developers.google.com/analytics/devguides/collection/analyticsjs/enhanced-ecommerce#impression-data * @param id * @param name * @param list * @param brand * @param category * @param variant * @param position * @param price * @private */ this._addImpression = function (id, name, list, brand, category, variant, position, price){ _gaJs(function () { _gaq('_addImpression', id, name, list, brand, category, variant, position, price); }); _analyticsJs(function () { if (that._enhancedEcommerceEnabled(true, 'addImpression')) { var includeFn = function (trackerObj) { return isPropertySetTo('trackEcommerce', trackerObj, true); }; _gaMultipleTrackers( includeFn, 'ec:addImpression', { id: id, name: name, category: category, brand: brand, variant: variant, list: list, position: position, price: price }); } }); }; /** * Add Promo * https://developers.google.com/analytics/devguides/collection/analyticsjs/enhanced-ecommerce * @param productId * @param name * @param creative * @param position * @private */ this._addPromo = function (productId, name, creative, position) { _gaJs(function () { _gaq('_addPromo', productId, name, creative, position); }); _analyticsJs(function () { if (that._enhancedEcommerceEnabled(true, 'addPromo')) { var includeFn = function (trackerObj) { return isPropertySetTo('trackEcommerce', trackerObj, true); }; _gaMultipleTrackers( includeFn, 'ec:addPromo', { id: productId, name: name, creative: creative, position: position }); } }); }; /** * Set Action * https://developers.google.com/analytics/devguides/collection/analyticsjs/enhanced-ecommerce#measuring-actions * https://developers.google.com/analytics/devguides/collection/analyticsjs/enhanced-ecommerce#action-types * @param action * @param obj * @private */ this._setAction = function (action, obj){ _gaJs(function () { _gaq('_setAction', action, obj); }); _analyticsJs(function () { if (that._enhancedEcommerceEnabled(true, 'setAction')) { var includeFn = function (trackerObj) { return isPropertySetTo('trackEcommerce', trackerObj, true); }; _gaMultipleTrackers(includeFn, 'ec:setAction', action, obj); } }); }; /** * Track Transaction * https://developers.google.com/analytics/devguides/collection/analyticsjs/enhanced-ecommerce#measuring-transactions * @param transactionId * @param affiliation * @param revenue * @param tax * @param shipping * @param coupon * @param list * @param step * @param option * @private */ this._trackTransaction = function (transactionId, affiliation, revenue, tax, shipping, coupon, list, step, option) { this._setAction('purchase', getActionFieldObject(transactionId, affiliation, revenue, tax, shipping, coupon, list, step, option)); }; /** * Track Refund * https://developers.google.com/analytics/devguides/collection/analyticsjs/enhanced-ecommerce#measuring-refunds * @param transactionId * @private */ this._trackRefund = function (transactionId) { this._setAction('refund', getActionFieldObject(transactionId)); }; /** * Track Checkout * https://developers.google.com/analytics/devguides/collection/analyticsjs/enhanced-ecommerce#measuring-checkout * @param step * @param option * @private */ this._trackCheckOut = function (step, option) { this._setAction('checkout', getActionFieldObject(null, null, null, null, null, null, null, step, option)); }; /** * Track detail * @private */ this._trackDetail = function () { this._setAction('detail'); this._pageView(); }; /** * Track add/remove to cart * https://developers.google.com/analytics/devguides/collection/analyticsjs/enhanced-ecommerce#add-remove-cart * @param action * @param list * @private */ this._trackCart = function (action, listName) { if (['add', 'remove'].indexOf(action) !== -1) { this._setAction(action, { list: listName }); this._trackEvent('UX', 'click', action + (action === 'add' ? ' to cart' : ' from cart')); } }; /** * Track promo click * https://developers.google.com/analytics/devguides/collection/analyticsjs/enhanced-ecommerce#measuring-promo-clicks * @param promotionName * @private */ this._promoClick = function (promotionName) { this._setAction('promo_click'); this._trackEvent('Internal Promotions', 'click', promotionName); }; /** * Track product click * https://developers.google.com/analytics/devguides/collection/analyticsjs/enhanced-ecommerce#measuring-promo-clicks * @param promotionName * @private */ this._productClick = function (listName) { this._setAction('click', getActionFieldObject(null, null, null, null, null, null, listName, null, null)); this._trackEvent('UX', 'click', listName); }; /** * Send page view * @param trackerName * @private */ this._pageView = function (trackerName) { _analyticsJs(function () { _ga(generateCommandName('send', trackerName), 'pageview'); }); }; /** * Send custom events * https://developers.google.com/analytics/devguides/collection/analyticsjs/user-timings#implementation * https://developers.google.com/analytics/devguides/collection/analyticsjs/social-interactions#implementation * @private */ this._send = function () { var args = Array.prototype.slice.call(arguments); args.unshift('send'); _analyticsJs(function () { _ga.apply(that, args); }); }; /** * Set custom dimensions, metrics or experiment * https://developers.google.com/analytics/devguides/collection/analyticsjs/custom-dims-mets * https://developers.google.com/analytics/devguides/collection/analyticsjs/field-reference#customs * @param name (Required) * @param value (Required) * @param trackerName (Optional) * @private */ this._set = function (name, value, trackerName) { _analyticsJs(function () { _ga(generateCommandName('set', trackerName), name, value); }); }; /** * Track user timings * @param timingCategory (Required): A string for categorizing all user timing variables into logical groups(e.g jQuery). * @param timingVar (Required): A string to identify the variable being recorded(e.g. JavaScript Load). * @param timingValue (Required): The number of milliseconds in elapsed time to report to Google Analytics(e.g. 20). * @param timingLabel (Optional): A string that can be used to add flexibility in visualizing user timings in the reports(e.g. Google CDN). * @private */ this._trackTimings = function (timingCategory, timingVar, timingValue, timingLabel) { _analyticsJs(function () { _gaMultipleTrackers(undefined, 'send', 'timing', timingCategory, timingVar, timingValue, timingLabel); }); }; /** * Exception tracking * https://developers.google.com/analytics/devguides/collection/analyticsjs/exceptions * @param description (Optional): A description of the exception. * @param isFatal (Optional): true if the exception was fatal, false otherwise. * @private */ this._trackException = function (description, isFatal) { _analyticsJs(function () { _gaMultipleTrackers(undefined, 'send', 'exception', { exDescription: description, exFatal: !!isFatal}); }); }; // creates the Google Analytics tracker if (!delayScriptTag) { if (analyticsJS) { this._createAnalyticsScriptTag(); } else { this._createScriptTag(); } } // activates page tracking if (trackRoutes) { $rootScope.$on(pageEvent, function () { // Apply $route based filtering if configured if (readFromRoute) { // Avoid tracking undefined routes, routes without template (e.g. redirect routes) // and those explicitly marked as 'do not track' if (!$route.current || !$route.current.templateUrl || $route.current.doNotTrack) { return; } } that._trackPage(); }); } return { log: that.log, offlineQueue: that.offlineQueue, configuration: { accounts: accounts, universalAnalytics: analyticsJS, crossDomainLinker: crossDomainLinker, crossLinkDomains: crossLinkDomains, currency: currency, debugMode: debugMode, delayScriptTag: delayScriptTag, disableAnalytics: disableAnalytics, displayFeatures: displayFeatures, domainName: domainName, ecommerce: that._ecommerceEnabled(), enhancedEcommerce: that._enhancedEcommerceEnabled(), enhancedLinkAttribution: enhancedLinkAttribution, experimentId: experimentId, hybridMobileSupport: hybridMobileSupport, ignoreFirstPageLoad: ignoreFirstPageLoad, logAllCalls: logAllCalls, pageEvent: pageEvent, readFromRoute: readFromRoute, removeRegExp: removeRegExp, testMode: testMode, traceDebuggingMode: traceDebuggingMode, trackPrefix: trackPrefix, trackRoutes: trackRoutes, trackUrlParams: trackUrlParams }, getUrl: getUrl, /* DEPRECATED */ setCookieConfig: that._setCookieConfig, /* DEPRECATED */ getCookieConfig: function () { return cookieConfig; }, createAnalyticsScriptTag: function (config) { if (config) { cookieConfig = config; } return that._createAnalyticsScriptTag(); }, createScriptTag: function () { return that._createScriptTag(); }, offline: function (mode) { if (mode === true && offlineMode === false) { // Go to offline mode offlineMode = true; } if (mode === false && offlineMode === true) { // Go to online mode and process the offline queue offlineMode = false; while (that.offlineQueue.length > 0) { var obj = that.offlineQueue.shift(); obj[0].apply(that, obj[1]); } } return offlineMode; }, trackPage: function (url, title, custom) { that._trackPage.apply(that, arguments); }, trackEvent: function (category, action, label, value, noninteraction, custom) { that._trackEvent.apply(that, arguments); }, addTrans: function (transactionId, affiliation, total, tax, shipping, city, state, country, currency) { that._addTrans.apply(that, arguments); }, addItem: function (transactionId, sku, name, category, price, quantity) { that._addItem.apply(that, arguments); }, trackTrans: function () { that._trackTrans.apply(that, arguments); }, clearTrans: function () { that._clearTrans.apply(that, arguments); }, addProduct: function (productId, name, category, brand, variant, price, quantity, coupon, position, custom) { that._addProduct.apply(that, arguments); }, addPromo: function (productId, name, creative, position) { that._addPromo.apply(that, arguments); }, addImpression: function (productId, name, list, brand, category, variant, position, price) { that._addImpression.apply(that, arguments); }, productClick: function (listName) { that._productClick.apply(that, arguments); }, promoClick : function (promotionName) { that._promoClick.apply(that, arguments); }, trackDetail: function () { that._trackDetail.apply(that, arguments); }, trackCart: function (action, list) { that._trackCart.apply(that, arguments); }, trackCheckout: function (step, option) { that._trackCheckOut.apply(that, arguments); }, trackTimings: function (timingCategory, timingVar, timingValue, timingLabel) { that._trackTimings.apply(that, arguments); }, trackTransaction: function (transactionId, affiliation, revenue, tax, shipping, coupon, list, step, option) { that._trackTransaction.apply(that, arguments); }, trackException: function (description, isFatal) { that._trackException.apply(that, arguments); }, setAction: function (action, obj) { that._setAction.apply(that, arguments); }, pageView: function () { that._pageView.apply(that, arguments); }, send: function (obj) { that._send.apply(that, arguments); }, set: function (name, value, trackerName) { that._set.apply(that, arguments); } }; }]; }) .directive('gaTrackEvent', ['Analytics', '$parse', function (Analytics, $parse) { return { restrict: 'A', link: function (scope, element, attrs) { var options = $parse(attrs.gaTrackEvent); element.bind('click', function () { if(attrs.gaTrackEventIf){ if(!scope.$eval(attrs.gaTrackEventIf)){ return; // Cancel this event if we don't pass the ga-track-event-if condition } } if (options.length > 1) { Analytics.trackEvent.apply(Analytics, options(scope)); } }); } }; }]); return angular.module('angular-google-analytics'); }));