﻿var Void=function(){};if(!window.console){window.console={warn:Void,log:Void,error:Void,info:Void,group:Void,groupEnd:Void,groupCollapsed:Void}}
var cloneObj=function(o){var c={};for(p in o){if(o[p]!==undefined){if(typeof o[p]=="object"){c[p]=cloneObj(o[p]);}else{c[p]=o[p];}}}return c;};
/*************************************************************************
 * jquery.TrackIt.js - Version 1.0
 *************************************************************************
 * @author Aaron Lisman (Aaron.Lisman@ogilvy.com)
 * @author Adam S. Kirschner (AdamS.Kirschner@ogilvy.com)
 * @date April 27, 2009
 *************************************************************************
 * ChangeLog: 
 *	- Initial version :-)
 *************************************************************************
 */
(function($) {	
	/**
	 * Create a new instance of the tracker, "this" should be the data grid itself
	 * 
	 * @param {string} trackerModule the Tracking Module to use, either ga or omniture (other aliases exist)
	 * @param {object} options a set of options that can be used to override $.Tracker.defaults
	 */
	$.fn.initTracker = function(trackerModule, options){
		return new $.Tracker(this, trackerModule, options);
	};
	
	/**
	 * Create a new instance of the tracker, this constructor is typically used with
	 * a combination XML file as apposed to a pre-existing JSON object in the above
	 * implementation.
	 *
	 * @param {string} trackerModule the Tracking Module to use, either ga or omniture (other aliases exist)
	 * @param {object} options a set of options that can be used to override $.Tracker.defaults
	 */
	$.initTracker = function(trackerModule, options){
		return new $.Tracker({}, trackerModule, options);
	};
		
	/**
	 * Main Constructor. Initializes the main Tracker object, merges in the correct tracking module and returns
	 * an instance of the tracker itself. This function will also attach a click event using jQuery.live
	 * 
	 * @param {object} data the tracking data itself
	 * @param {string} trackerModule the Tracking Module to use, either ga or omniture (other aliases exist)
	 * @param {object} options a set of options that can be used to override $.Tracker.defaults
	 */
	$.Tracker = function(data, trackerModule, options) {
		// jQuery sucks sometimes, keep "this"
		var that = this;
		
		// we just started, so we're not ready yet
		this.__DATA_READY = false;
		
		// this is a Tracker object, extend this 
		$.extend( this, $.Tracker );
		
		// merge the tracking Module object settings into this object
		this.loadTrackingModule(trackerModule);

		// if there was an xml file specified, load it	
		if( options.XmlUrl ) {
			// load the track data from an Xml File, send in an extra data that is set (if any)
			this.loadXml(options.XmlUrl, options.ExtraTrackData);
		} else {
			// set the track data
			this.TrackData = data.get(0);
			this.__DATA_READY = true;
		}
		
		if( options.ExtraGlobalHolders ) { $.extend( this.BuiltInHolders, options.ExtraGlobalHolders ); }
				
		// allow options to override default settings
		this.settings = $.extend($.Tracker.defaults, options);
		
		// use live to set global click listener
		$("*").live('click', function(){
			// if the tracking attribute has a valid value in it, then this element should be tracked
			if (typeof $(this).attr(that.settings.TrackKeyAttribute) !== 'undefined') {
				var trackEle = this;
				
				if( $(this).parent().get(0).tagName.toLowerCase() == "a" ) {
					trackEle = $(this).parent().get(0);
				};

				// this key should corespond to the data
				var key = $(this).attr(that.settings.TrackKeyAttribute);	
				
				// track it and send the link ele as an option
				that.track(key, { ele: trackEle });
			}
		});
		
		// callback functions should run when this module has done its basic initialization
		if( this.IsReady() ) { this.Ready(); }
		
		this.DudHtmlLink = $("<a></a>")
			.css("display","none")
			.attr("href","javascript:Void()");
			
		$(document.body).append( this.DudHtmlLink );
			
		
		// some debug info
		if( this.settings.TestMode && this.settings.ShowDebugInfo ) {
			console.info( "$.Tracker() - Test Mode is Enabled, Tracking Disabled!" );
		} else if( this.settings.ShowDebugInfo ) {
			console.info( "$.Tracker() - Tracking Enabled. Debug Mode On." );
		}
	};

	/**
     * $.Tracker Methods
     */
	$.extend($.Tracker, {
		/**
		 * These are the basic options that can be overridden by $.Tracker to help debugging
		 * and other customizations that may be necessary. Every option is set to false by default.
		 *
		 * @param TestMode {bool} whether or not reporting is actually sent to service.
		 * @param TrackKeyAttribute {string} the html attribute that is checked for a track key when an item is clicked on
		 * @param EnableUrlMappingWithDeepLink {bool} whether or not the tracker should be disabled if a "#" is present on pageload 
		 * @param ShowMissingHolderWarnings {bool} a warning will show up in Firebug when a holder ([some holder]) is evaluated based on no specified input
		 * @param ShowDebugInfo {bool} whether or not to show detailed tracker debug information to track stack trace and other useful information while testing
		 * @param SanityCheckEnabled {bool} whether or not to show a detailed report that verifies the existance (or non existance) of all holders in all track keys	
		 * @param SanityCheckMissingOnly {bool} whether or not to only show holders that are not defined during a data sanity check
		 * @param ShowOnlyReportedData {bool} regardless of other variables that are set, this option will ensure that reported data will show in console
		 */
		defaults: { 
			TestMode: false,
			TrackKeyAttribute: "trackKey",
			EnableUrlMappingWithDeepLink: false,
			ShowMissingHolderWarnings: false,
			ShowDebugInfo: false,
			SanityCheckEnabled: false,
			SanityCheckMissingOnly: false,
			ShowOnlyReportedData: false,
			DuplicatePropToEVar: true
		},
		
		/**
		 * This function will merge in the proper tracking module, whether it is Omniture or Google Analytics
		 *
		 * @param {string} module a string representation of what module to load
		 */
		loadTrackingModule: function( module ) {
			switch( module.toLowerCase() ) {
				case "googleanalytics":
				case "google":
				case "ga":
					$.extend( this, TrackingModules.GoogleAnalytics );
					break;
				case "omniture":
				case "omni":
					$.extend( this, TrackingModules.Omniture );
					break;
				default:
					console.error("WARNING: No valid tracking module was specified!"); 
			}
		},
		
		/**
		 * This function is useful when loading an external XML file as the basis for the tracking spec. 
		 * A xml file path is passed and is processed via an XmlHttpRequest. An optional object can be passed
		 * that will be merged with the XML document that is loaded. The option object can be typically used
		 * for passing extra keys that will be holders within a variables value. 
		 * 
		 * @param xmlUrl {string} the URL of where the trackData XML file lives. 
		 * @param extraTrackData {obj} an object that will be merged into the XML file once it is loaded, useful for custom functions as holders
		 */
		loadXml: function( xmlUrl, extraTrackData ) {
		
			// again jquery sucks
			var that = this;
			
			// lets go, ajax time
			$.ajax({
				'url': xmlUrl,
				'complete': function(xml) {
				
					// use xml2json plugin to parse it immediately to JSON, TODO: integrate this functionallity into tracker
					var json = $.xml2json(xml.responseText); 
					
					// since the XML is a little different than the data, we have a little manipulation to do, make the "eventName" the trackKey
					var newJson = {};
					$(json.trackEvent).each( function() {
						newJson[ this["eventName"] ] = this;
						
						if( extraTrackData && this["EventName"] in extraTrackData ) {
							newJson[ this["eventName"] ] = $.extend( newJson[ this["eventName"] ], extraTrackData[ this["eventName"] ] );
						}
						
						delete newJson[ this["eventName"] ]["eventName"];
					});	
				
					// set processed data 
					that.TrackData = newJson;
					
					// since we're ready now, go, this should change to a custom event
					that.__DATA_READY = true;
					that.Ready();
				}
			});		
		},
		/**
		 * Once the tracker has been loaded, it begins by checking the URL and the XML to see if any
		 * pageview event should fire. This function has a recognized options "EnableUrlMappingWithDeepLink"
		 * in the event that you want the deep linking controler to control what should be reported. This is 
		 * useful when using deep linking with JavaScript or Flash.
		 */
		CheckUrlMapping: function() { 
			var that = this;
			if( this.settings.ShowDebugInfo ) { console.group("$.Tracker.CheckUrlMapping()"); }

			// if the option "EnableUrlMappingWithDeepLink" is set and there's a match URL map, then skip UrlMapping for this load
			if( document.location.hash.length > 0 && ! this.settings.EnableUrlMappingWithDeepLink ) {
				if( this.settings.ShowDebugInfo ) { console.info("$.Tracker.CheckUrlMapping() - Deep Link Detected, Disabling Url Mapping"); }
			} else {	
				// the feature is enabled, so show info that it's going to process
				if( this.settings.ShowDebugInfo ) { console.info("$.Tracker.CheckUrlMapping() - Enabled"); }
				
				// go through each track key
				for( var trackKey in this.TrackData ) {
					var trackObj = this.TrackData[trackKey]
					// ensure that a url mapping exists
					if( trackObj.urlMap ) {
						var urlMappings = trackObj.urlMap.split("|");
						$.each( urlMappings, function() { 
							// check the URL and the url mapping as a regex, do some regex non-friendly manipulation first
							var mapping = this.replace("[", "\\["); mapping = mapping.replace("]","\\]");                           
							if( (new RegExp( '^' + mapping + '$' )).test( document.location.pathname ) ) {
								// if the test pasts as a regex match, then track this event
								if( that.settings.ShowDebugInfo ) { console.info("$.Tracker.CheckUrlMapping() - Found Key: '" + trackKey + "'"); }
								
								that.track( trackKey, {});
							}
						});
					}
				}
			}
			
			if( this.settings.ShowDebugInfo ) { console.groupEnd(); }
		},
		
		/**
		 * Checks to see whether or not the track module is ready to begin tracking.
		 */
		IsReady: function() { return this.__DATA_READY; },
		
		/**
		 * This function is simply a list of callback functions that should be run when the tracker object
		 * has been fully initialized and is ready to begin tracking. This function will eventually be an 
		 * event that will be listened upon so that plugins may extend this function.
		 */
		Ready: function() {
			if( this.settings.SanityCheckEnabled ) { this.DataSanityCheck(); }
			if( this.settings.DuplicatePropToEVar ) { this.CopyPropToEVar(); }
			this.CheckUrlMapping();
			
		},
		
		/**
		 * Function that will track an event. A key is used to map to the proper parameters that will
		 * be passed to the tracker itself. The options is an object where each key is a holder. This is
		 * when it is necesary to pass dynamic values into a tracking string.
		 * 
		 * @param {string} key the name of the event to get the data from
		 * @param {object} options an object where each key will be processed as a holder, options.ele can also be a HtmlLinkElement
		 */
		track: function(key, options) {
		
			// if a string comes back as options, this is most likely flash, so eval it
			if( typeof options == "string" ) {
			
				// this was in the jquery library, line 3725
				options = window["eval"]("(" + options + ")");
			}
		
			// log that a track event has occured, show options and key that was caught
			if( this.settings.ShowDebugInfo ) { console.group( "$.Tracker.track() - key='", key, "' options=", options); }
			
			// if the key is a valid track key
			if (key in this.TrackData) {
			
				// get the parsed version of the data (if there are placeholders) 
				var parsedData = this.GetParsedData(key, options);

				// detect what kind of an event that occured and use the loaded module to track the event
				if (parsedData.event && parsedData.event.toLowerCase() == "pageview") {
					if( this.settings.ShowDebugInfo ) { console.info('$.Tracker.track()-->DoTrackPageView("' + key + '")'); }
					this.DoTrackPageView(parsedData, options)
				} else {
					if( this.settings.ShowDebugInfo ) { console.info('$.Tracker.track()-->DoTrackEvent("' + key + '")'); }
					this.DoTrackEvent(parsedData, options);	
				}
			}
			
			if( this.settings.ShowDebugInfo ) { console.groupEnd(); }
		},
		
		/**
		 * This will return parsed data from the TrackData object. Key and eventType are used to retrieve the 
		 * raw data. ele is passed in so that we can retrieve data from the element itself and use it in the reporting.
		 * 
		 * @param {string} key the name of the event to get the data from
		 * @param {object} options contains extra information that will help with integrating the holders
		 */
		GetParsedData: function( key, options ) {
			var retVal = null;
			
			// find out if the key exists in TrackData
			if (key in this.TrackData) {
			
				// retrieve it and replace all the holders in each data element
				retVal = cloneObj( this.TrackData[key] );
			
				for (var varName in retVal) {
					if (typeof retVal[varName] != 'function') {
						retVal[varName] = this.ReplaceHolders(retVal[varName], key, options);
					}
				}
			}

			return retVal;
		},
		
		/**
		 * Return an array of all the Holders that exist in this string. A holder is the part of the string that has {}.
		 * 
		 * @param {string} str a string with or without a Holder
		 */
		GetPlaceHolderArray: function(str) { 
			return str.match(/\[[^\]]+\]/g); 
		},
		
		BuiltInHolders: {
			'TEXT': function() { return $(this).text(); },
			'TITLE': function() { return $('title').text(); },
			'H1': function() { return $("H1").text(); },
			'ALT': function() { return $(this).attr('alt'); },
			'HREF': function() { return $(this).attr('href'); },
			'ATTR': function(value) { return $(this).attr(value); },
			'ATTR+': function(value) { 
				var pars = $(this).parents('*[' + value + ']'); 								
				if (pars.length > 0) {
					return  $(pars[0]).attr(value); // get the attribute		
				}
			},
		    'PAGENAME': function() { 
				var fn = window.location.toString().split('/');
				return fn[fn.length-1];
			},
		    'DOWNLOADFILENAME': function() { 
				var fn = $(this).attr('href').toString().split('/');
				return fn[fn.length-1];
			}
		},
		/**
		 * This plugin comes with a variety of pre-existing holders that may come convenient to the implementer. 
		 *	- [TEXT] - "ele.text()" of a HtmlElement
		 *  - [TITLE] - title tag of the page
		 *  - [H1] - the "ele.text()" of the first HtmlHeadingElement
		 *  - [ALT] - the "ele.alt" attribute of a HtmlElement
		 *  - [HREF] - the desintation url of a HtmlLinkElement
		 *  - [ATTR:value] - returns the value of what the HtmlAttribute "value" is for the current HtmlElement
		 *  - [ATTR+:value] - similar to ATTR however, will check parent elements and returns the first occurance
		 *
		 * @param comamnd {string} the holder command value
		 * @param value {string} a holder can have a value, this is after the colan (:)
		 * @param options {obj} the options that were set, this will typically be options.ele for the HtmlElement that was clicked on
		 * @param key {string} the track key event that was fired
		 */
		 
		HandleBuiltInHolder: function(command, value, options, key) {
			var out = "not found - " + command;
			
			if( command in this.BuiltInHolders ) { 
				out = this.BuiltInHolders[command].apply( options.ele || null, [value] );
			} else {
				if( this.settings.ShowMissingHolderWarnings && this.settings.ShowDebugInfo ) {
					console.warn( "$.Tracker.HandleBuiltInHolder() - Missing Holder Detected - '" + command + "' in key '" + key + "'" );
				}
			}
			
			return out;
		},
		
		/**
		 * This function will get all of the holders within a string and replace it based on the holder. The set of options
		 * that was passed will be processed here
		 * 
		 * @param str {string} a preparsed string that main contain holders
		 * @param key {string} the track key event that is being tracked
		 * @param options {object} an object where each key will coorespond to a holder
		 */
		ReplaceHolders: function(str, key, options){
			// get all Holders that exist in this string
			var holders = this.GetPlaceHolderArray(str);

			if( holders ) { 
				// go through each holder
				for (var index = 0; index < holders.length; index++) {
				
					// get the holder
					var holder = holders[index];
					
					// reset the parsed holder
					var parsedHolder = "";
					
					// get rid of the curly braces and split at colon
					var splitArr = holder.toString().substring(1,(holder.length-1)).toString().split(":");
					var command = splitArr[0]; var value = splitArr[1];
					
					// process the existing track key properties first, recognizes functions and string values
					if ( typeof this.TrackData[key][command] == "function" ) {
						parsedHolder = this.TrackData[key][command].call(options, this); 
					} else if( typeof this.TrackData[key][command] == "string" ) {
						parsedHolder = this.TrackData[key][command];
					}	
					// rerun same rules on the passed options
					else if( options && typeof options[ command ] == "function" ) {
						parsedHolder = options[ command ].call( options, this );
					} else if( options && typeof options[ command ] == "string" ) {
						parsedHolder = options[ command ];
					} else {
						// if still not found, maybe its a built in function
						parsedHolder = this.HandleBuiltInHolder(command, value, options, key);
					}
					
					// replace the parsed value of the holder with the holder itself
					str = str.replace(holder, parsedHolder); 
				}
			}
			
			return str;
		},
		/*/
		 * The data sanity checker will iterate through each track key and process all the holders within 
		 * each variable. It will detect whether or not a valid holder replacement was found or not. An
		 * extra option "SanityCheckMissingOnly" can be used to only report on those holders that are missing.
		 * This will show up in the console regardless of the other options that are set.
		 */
		DataSanityCheck: function(invalidHolders) {
			if( ! invalidHolders ) { invalidHolders = {}; }
			var builtInCommands = ["TEXT","TITLE","H1","ALT","HREF","ATTR","ATTR+"];
			for( var key in this.TrackData ) {
				if( ! this.settings.SanityCheckMissingOnly ) { invalidHolders[key] = {}; }
				keyData = cloneObj( this.TrackData[key] );
				for( var varName in keyData ) {
					var str = keyData[varName];
					if (typeof str != 'function') {
						var holders = this.GetPlaceHolderArray(str);
						if( holders ) {
							for( var i = 0; i < holders.length; i++ ) {
								var holder = holders[i];
								var splitArr = holder.toString().substring(1,(holder.length-1)).toString().split(":");
								var command = splitArr[0]; var value = splitArr[1];
								if( ! ( command in builtInCommands ) &&
									! (command in keyData )  ) {
									if( ! invalidHolders[key] ) { invalidHolders[key] = {}; }
									invalidHolders[key][command] = false;
								} else {
									if( ! this.settings.SanityCheckMissingOnly ) { 
										invalidHolders[key][command] = true; 
									} else if( invalidHolders[key] && invalidHolders[key][command] ) {
										delete invalidHolders[key][command]; 
									}
								}
							}
						}
						
					}
				}
			}
			console.info( "$.Tracker.DataSanityCheck() - Results - ", invalidHolders);
		},
		
		CopyPropToEVar: function() {
			var regex = new RegExp("(\\d+)","g");
			var newEVars = {};
			for( var key in this.TrackData ) {
				keyData = this.TrackData[key];
				for( var varName in keyData ) {
					if( varName.indexOf( "prop" ) > -1 ) {
						var i = varName.match(regex)[0];
						keyData["eVar" + i] = keyData["prop" + i];
						if( ! newEVars[ key ] ) { newEVars[key] = {}; }
						newEVars[key]["eVar" + i] = keyData[varName];
					}
				}
			}
			
			if( this.settings.ShowDebugInfo ) { console.info( "$.Tracker.CopyPropToEVar() - Results - ", newEVars ); }
		}
	});	
})(jQuery);

/**
 * A tracking module needs to declare at least 2 kinds of tracking methods, "TrackEvent" and "TrackPageView". The outter 
 * shell of $.Tracker is essentially a wrapper for both of these functions. Each function will receieve the data node
 * of the correct event that was passed. The TrackEvent function will also receive the element that the event fired from.
 */
var TrackingModules = {
	/*
	 * !!!!!!!!!!!!!!!!!!!!!! WARNING !!!!!!!!!!!!!!!!!!!!!!!!!!
	 * THIS IMPLEMENTATION WAS NOT TESTED WITH VERSION 1.0 
	 * THESE CALLBACKS SHOULD BE CHECKED AND CONFIRMED WITH 
	 * GOOGLE ANALYTICS!
	 * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
	 */
	GoogleAnalytics: {
		Type: "Google Analytics",
		DoTrackEvent: function(data, ele){	
					
			if( this.settings.ShowDebugInfo ) {
				console.info(data);
			} else {
				pageTracker._trackEvent(data.category, data.action, data.opt_label, data.opt_value);
			}
		},
		DoTrackPageView: function(data){
			if (this.settings.ShowDebugInfo) {
				console.info(data);
			} else {
				pageTracker._trackPageview(data.pageName);
			}
		}
	},
	Omniture: {
		Type: "Omniture",
		/**
		 * This is the omniture implementation of the TrackEvent. This handles all special cases in terms of being able
		 * to track the proper variables.
		 * 
		 * @param {Object} data the parsed data that should be reported.
		 * @param {HtmlElement} ele the HtmlElement that the event fired from
		 */
		DoTrackEvent: function(data, options){
			// create new omniture instance
			var s = s_gi(s_account);

			// merge all the data into "s"
			$.extend( s, data );
			
			// these are TrackIt specific attributes used, we don't want this to merge with the tracker at all.
			var excludeVars = [ "type", "event", "urlMap", "customLinkName" ];
			$.each( excludeVars, function() { delete s[this] } );
			
			// omniture needs to know all the variables we are settings asside
			// from just setting it, add all the variable names to an array
			var linkTrackVars = [];
			for( var varName in data ) { 
				if( !( varName in excludeVars ) ) { linkTrackVars.push( varName ); }
			}
			
			// join the array with and tell omniture these are the variables
			s.linkTrackVars = linkTrackVars.join(',');
			
			// not only do we need set the s.events variable, but also s.linkTrackEvents
			// if an event does get thrown
			if( s.events && s.events.length > 0 ) {
				s.linkTrackEvents = s.events;
			}
            
            var linkName = null;
            if ("customLinkName" in data) {
                linkName = data.customLinkName;
            }   
            
            if( options["link text"] ) { 
				$(this.DudHtmlLink).text( options["link text"] );
			} else {
				if( options.ele ) { 
					$(this.DudHtmlLink).text( $(options.ele).text() );
					$(this.DudHtmlLink).attr( 'href', $(options.ele).attr('href') );
				}
			}

			// if in test mode just show what would get reported
			if( this.settings.TestMode && ( this.settings.ShowOnlyReportedData || this.settings.ShowDebugInfo ) ) {
				console.info("Track Link Skipped: ", {"s": s, "data": data});
			} else { 
				
				// either send in the HtmlLinkElement that was passed or false, a generic name will be used
				s.tl( this.DudHtmlLink, 'o', linkName);
				if( this.settings.ShowDebugInfo || this.settings.ShowOnlyReportedData ) { 
					console.info("Reported Track Link: ", {"s":s,"data":data});
				}
			}
			
			// clean up, clear out all the values that were set
			for( var key in data ) { delete s[key]  };
		},
		DoTrackPageView: function( data ) {
			// use existing s object from current page
			$.extend( s, data );
			// if in test mode AND we're showing some sort of information, we show the data
			if( this.settings.TestMode && ( this.settings.ShowOnlyReportedData || this.settings.ShowDebugInfo ) ) {
				console.info("Page View Skipped: ", {"s": s, "data": data});
			} 
			
			// if not in test mode, then actually track it
			if( ! this.settings.TestMode ) {	
				s.t();
				
				// decipher whether or not the tracked information should show
				if( this.settings.ShowDebugInfo || this.settings.ShowOnlyReportedData ) { 
					console.info("Reported Page View: ", {"s": s, "data": data });
				}
			}
			
			// clean up, clear out all the values that were set
			for( var key in data ) { delete s[key]  };
		}
	}
}