PageSourceSearch

https://modulemd.ai/wp-content/themes/newsite/assets/js/horizontal_timeline.2.0_v2.0.5.3.js

js modulemd.ai collected 2026-10-01 02:07:11 UTC 92,752 bytes, 2,159 lines download raw bytes

1/* --------------------------------
2
3Horizontal Timeline 2.0
4by Studocwho @ yCodeTech
5
6Version: 2.0.5.3
7
8Original Horizontal Timeline by CodyHouse
9
10Licensed under the MIT license
11
12Docs at http://horizontal-timeline.ycodetech.co.uk
13
14-------------------------------- */
15
16
17// the semi-colon before the function invocation is a safety
18// net against concatenated scripts and/or other plugins
19// that are not closed properly.
20;(function ( $, window, document, undefined ) {
21
22	// undefined is used here as the undefined global
23	// variable in ECMAScript 3 and is mutable (i.e. it can
24	// be changed by someone else). undefined isn't really
25	// being passed in so we can ensure that its value is
26	// truly undefined. In ES5, undefined can no longer be
27	// modified.
28
29	// window and document are passed through as local
30	// variables rather than as globals, because this (slightly)
31	// quickens the resolution process and can be more
32	// efficiently minified (especially when both are
33	// regularly referenced in your plugin).
34
35	// Create the defaults once
36	var pluginName = 'horizontalTimeline',
37		defaults = {
38			// ! Deprecate these individual options in favour of the object options. //
39
40			desktopDateIntervals: 200,   //************\\
41			tabletDateIntervals: 150,   // Minimum: 120 \\
42			mobileDateIntervals: 120,  //****************\\
43			minimalFirstDateInterval: true,
44
45			// ! End Deprecated options //
46
47			/* New object options... */
48			// If the deprecated single options exist in the user options, then use them,
49			// otherwise default to the new object options.
50
51			// Can not use in conjunction with the single options...
52			// If both single and object options are set in the options, the object will take precedence.
53
54			dateIntervals: {
55				"desktop": 200,   //************\\
56				"tablet": 150,   // Minimum: 120 \\
57				"mobile": 120,  //****************\\
58				"minimal": true
59			},
60
61			/* End new object options */
62
63			dateDisplay: "dateTime", // dateTime, date, time, dayMonth, monthYear, year
64			dateOrder: "normal", // normal, reverse
65
66			autoplay: false,
67			autoplaySpeed: 8, // Sec
68			autoplayPause_onHover: false,
69
70			useScrollWheel: false,
71			useTouchSwipe: true,
72			useKeyboardKeys: false,
73			addRequiredFile: true,
74			useFontAwesomeIcons: true,
75			useNavBtns: true,
76			useScrollBtns: true,
77
78			// ! Deprecate these individual options in favour of the object options. //
79
80			iconBaseClass: "fa", // Space separated class names
81			scrollLeft_iconClass: "fa-chevron-circle-left",
82			scrollRight_iconClass: "fa-chevron-circle-right",
83			prev_iconClass: "fa-arrow-left",
84			next_iconClass: "fa-arrow-right",
85			pause_iconClass: "fa-pause-circle",
86			play_iconClass: "fa-play-circle",
87
88			animation_baseClass: "animationSpeed", // Space separated class names
89			enter_animationClass: {
90				"left": "enter-left",
91				"right": "enter-right"
92			},
93			exit_animationClass: {
94				"left": "exit-left",
95				"right": "exit-right"
96			},
97
98			// ! End Deprecated options //
99
100			/* New object options... */
101			// If the deprecated single options exist in the user options, then use them,
102			// otherwise default to the new object options.
103
104			// Can not use in conjunction with the single options...
105			// If both single and object options are set in the options, the object will take precedence.
106
107			iconClass: {
108				"base": "fa", // Space separated class names
109				//"scrollLeft": "fa-chevron-circle-left",
110				//"scrollRight": "fa-chevron-circle-right",
111
112				"scrollLeft": "fa-angle-double-left",
113				"scrollRight": "fa-angle-double-right",
114
115				
116				"prev": "fa-chevron-left",
117				"next": "fa-chevron-right",
118				"pause": "fa-pause-circle",
119				"play": "fa-play-circle"
120			},
121			animationClass: {
122				"base": "animationSpeed", // Space separated class names,
123				"enter": {
124					"left": "enter-left",
125					"right": "enter-right"
126				},
127				"exit": {
128					"left": "exit-left",
129					"right": "exit-right"
130				}
131			}
132			/* End new object options */
133		};
134
135	// The actual plugin constructor
136	function Timeline( element, options ) {
137		this.element = element;
138
139		// jQuery has an extend method that merges the
140		// contents of two or more objects, storing the
141		// result in the first object. The first object
142		// is generally empty because we don't want to alter
143		// the default options for future instances of the plugin
144		// (deep recursive copy for nested objects, empty object, the defaults object, the options object)
145		this.settings = $.extend(true, {}, defaults, options);
146
147		this._defaults = defaults;
148		this._options = (options != undefined)? options : "Nothing overridden";
149		this._name = pluginName;
150		this.$element = $(element);
151
152		this.init();
153	}
154	Timeline.prototype = {
155		init: function () {
156			// You already have access to the DOM element and
157			// the options via the instance, e.g. this.element
158			// and this.settings
159			var dataAttribute = this._eventContentListData(),
160			    contentList = this.$element.find('li['+ dataAttribute +']');
161			if(contentList.length == 0) {
162				var text = "There are no events at this point in time. Please add some content.";
163
164				this.$element.css('opacity', 1).append('<h3>'+ text +'</h3>');
165				throw new Error(text);
166			}
167			
168			if (this.settings.useFontAwesomeIcons == true) {
169				var url = "https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.8.2/css/all.min.css";
170
171				// Function to load the file
172				// (url, type)
173				this._addFile(url, 'css');
174			}
175			this._create();
176
177			// Wait about 300s to make sure the all elements are created properly.
178			// Otherwise the width of the timeline would report as bigger than it actually is.
179			window.setTimeout($.proxy(function(){
180				var timelineTotalWidth,
181					timelineComponents = {};
182
183				this._addIdsAndClasses(timelineComponents);
184
185				this._timelineComponents(timelineComponents);
186
187				//** Select the correct event **//
188
189				// If any events-content has .selected class...
190				if (timelineComponents['eventsContentList'].hasClass('selected')) {
191						// Get date from data-attribute
192						var date = this._timelineData(timelineComponents['eventsContentSelected'], "date"),
193							// Find the event date matching the date
194							selectedDate = timelineComponents['eventsWrapper'].find("a").filter($.proxy(function(index, element) {
195								var data = this._timelineData($(element), "date");
196								if (data == date) return $(element);
197							}, this));
198
199					// Add .selected class to the matched element
200					selectedDate.addClass('selected');
201					// Update all previous dates for styling.
202					this._updateOlderEvents(selectedDate);
203				}
204				// If no class found at all...
205				else {
206					// If dateOrder is normal (Ascending)... start from the left.
207					if (this.settings.dateOrder == "normal") {
208						// Add .selected class to the first event.
209						timelineComponents['eventsWrapper'].find('a.first').addClass('selected');
210
211							// Find the selected event
212						var selectedEvent = timelineComponents['eventsWrapper'].find('a.selected'),
213							// Get the selected event's date.
214							selectedDate = this._timelineData(selectedEvent, "date");
215
216						// Find the selected event's content using the date and add selected class to the content.
217						timelineComponents['eventsContentList'].filter($.proxy(function(index, element) {
218							var data = this._timelineData($(element), "date");
219							if (data == selectedDate) $(element).addClass('selected');
220						}, this));
221					}
222					// Else dateOrder is reverse (Descending)... start from the right.
223					else if (this.settings.dateOrder == "reverse") {
224						// Add .selected class to the last event.
225						timelineComponents['eventsWrapper'].find('a.last').addClass('selected');
226
227							// Find the selected event
228						var selectedEvent = timelineComponents['eventsWrapper'].find('a.selected'),
229							// Get the selected event's date.
230							selectedDate = this._timelineData(selectedEvent, "date");
231
232						// Find the selected event's content using the date and add selected class to the content.
233						timelineComponents['eventsContentList'].filter($.proxy(function(index, element) {
234							var data = this._timelineData($(element), "date");
235							if (data == selectedDate) $(element).addClass('selected');
236						}, this));
237
238
239						this._updateOlderEvents(selectedEvent);
240					}
241				}
242
243				// Assign a left postion to the single events along the timeline
244				this._setDatePosition(timelineComponents);
245				// Assign a width to the timeline
246				timelineTotalWidth = this._setTimelineWidth(timelineComponents);
247				// Set the filling line to the selected event
248				this._updateFilling(timelineComponents['eventsWrapper']
249					.find('a.selected'), timelineComponents['fillingLine'], timelineTotalWidth);
250				// The timeline has been initialised - show it
251				this.$element.addClass('loaded');
252				
253				/* Custom namespaced event: initialised with the data passed to the event as the instance and timelineSelector (jQuery object). */
254				this.$element.trigger({
255					type: "initialised."+this._name,
256					instance: this,
257					timelineSelector: this.$element
258				});
259
260				this._setup(this, timelineComponents, timelineTotalWidth);
261			}, this), 300);
262
263		}, // End init function
264
265		_addIdsAndClasses: function (timelineComponents) {
266			//** Adding IDs and Classes **//
267			this._timelineComponents(timelineComponents);
268
269			if (timelineComponents['eventsContentList'].length == 1) {
270				timelineComponents['eventsContentList'].first().attr('id', 'first');
271				timelineComponents['timelineEvents'].first().addClass("first");
272			}
273			else {
274				// Check if the deprecated single options are defined in the user options, if they are use them,
275				// otherwise use the new object options.
276
277				// A variable to include in an if statement that queries if the single option is defined 
278				// AND the object option is also defined.
279				var bothDefined = (this._options.animation_baseClass != undefined && this._options.animationClass != undefined),
280
281					// If single option are undefined OR both single and object options are defined
282					// then default to the object options, otherwise use the deprecated single option.
283					animationObj = (this._options.animation_baseClass == undefined || bothDefined) ? this.settings.animationClass : this.settings,
284
285					// If animationObj equals the object options...
286					animationBase = (animationObj == this.settings.animationClass) ? animationObj.base : animationObj.animation_baseClass;
287
288				// Adds id to the first and last li of the event-content list respectively.
289				timelineComponents['eventsContentList'].addClass(animationBase)
290					.first().attr('id', 'first').end()
291					.last().attr('id', 'last');
292
293				// Adds class to the first and last timeline event dates respectively.
294				timelineComponents['timelineEvents']
295					.first().addClass("first").end()
296					.last().addClass("last");
297			}
298		}, // End _addIdsAndClasses
299
300		/* Dynamically creates the timeline according to the amount of events. */
301		_create: function () {
302			var timelineHTML = "",
303
304				// All buttons uses Font Awesome for the icons
305				// Icons require Font Awesome CSS
306				// The CSS file has been added to the document if not already present.
307
308				// Check if the deprecated single options are defined in the user options, if they are use them,
309				// otherwise use the new object options.
310
311				// Set the single options into an array to check against.
312				optionArray = [this._options.iconBaseClass, 
313					this._options.scrollLeft_iconClass, 
314					this._options.scrollRight_iconClass, 
315					this._options.prev_iconClass, 
316					this._options.next_iconClass, 
317					this._options.pause_iconClass],
318
319				// A variable to include in an if statement that queries if the single options are undefined.
320				singleUndefined = (optionArray[0] == undefined 
321					&& optionArray[1] == undefined 
322					&& optionArray[2] == undefined 
323					&& optionArray[3] == undefined 
324					&& optionArray[4] == undefined 
325					&& optionArray[5] == undefined),
326
327				// A variable to include in an if statement that queries if the single option is defined 
328				// AND the object option is also defined.
329				bothDefined = (optionArray[0] != undefined && this._options.iconClass != undefined)
330					|| (optionArray[1] != undefined && this._options.iconClass != undefined) 
331					|| (optionArray[2] != undefined && this._options.iconClass != undefined)
332					|| (optionArray[3] != undefined && this._options.iconClass != undefined)
333					|| (optionArray[4] != undefined && this._options.iconClass != undefined)
334					|| (optionArray[5] != undefined && this._options.iconClass != undefined),
335
336				// If single option are undefined OR both single and object options are defined
337				// then default to the object options, otherwise use the deprecated single option.
338				iconClass = (singleUndefined || bothDefined) ? this.settings.iconClass : this.settings,
339
340				// If iconClass equals the object options...
341				
342				iconBase = (iconClass == this.settings.iconClass) ? iconClass.base : iconClass.iconBaseClass,
343
344				iconScrollLeft = (iconClass == this.settings.iconClass) ? iconClass.scrollLeft : iconClass.scrollLeft_iconClass,
345				
346				iconScrollRight = (iconClass == this.settings.iconClass) ? iconClass.scrollRight : iconClass.scrollRight_iconClass,
347				
348				iconPrev = (iconClass == this.settings.iconClass) ? iconClass.prev : iconClass.prev_iconClass,
349				
350				iconNext = (iconClass == this.settings.iconClass) ? iconClass.next : iconClass.next_iconClass,
351				
352				iconPause = (iconClass == this.settings.iconClass) ? iconClass.pause : iconClass.pause_iconClass,
353
354				// Left Nav
355				$scrollLeftButton = '<a href="" class="'+ iconBase +' '+ iconScrollLeft +' scroll-left inactive"></a>',
356				$prevButton = '<a href="" class="'+ iconBase +' '+ iconPrev +' prev inactive"></a>',
357
358				// Right Nav
359				$nextButton = '<a href="" class="'+ iconBase +' '+ iconNext +' next"></a>',
360				$scrollRightButton = '<a href="" class="'+ iconBase +' '+ iconScrollRight +' scroll-right"></a>',
361
362				// Pause button
363				$pauseButton = '<a href="" class="'+ iconBase +' '+ iconPause +' pause"></a>';
364
365			//** Create the timeline HTML **//
366
367			timelineHTML += '<div class="timeline">';
368
369			if (this.settings.useNavBtns == true || this.settings.useScrollBtns == true) {
370				// Add the left nav.
371				timelineHTML += '<div class="timeline-navigation" id="leftNav">'
372
373				if (this.settings.useNavBtns == false && this.settings.useScrollBtns == true)
374					// Add the scroll left button.
375					timelineHTML += $scrollLeftButton;
376
377				else if (this.settings.useNavBtns == true && this.settings.useScrollBtns == false)
378					// Add the prev button.
379					timelineHTML += $prevButton;
380
381				else if (this.settings.useNavBtns == true && this.settings.useScrollBtns == true)
382					// Add the scroll left button and the prev button.
383					timelineHTML += $scrollLeftButton + $prevButton;
384
385				timelineHTML += '</div>'
386
387					+'<div class="events-wrapper"><div class="events"><span class="filling-line" aria-hidden="true"></span></div></div>'
388					
389					// Add the right nav.
390					+'<div class="timeline-navigation" id="rightNav">';
391
392				if (this.settings.useNavBtns == false && this.settings.useScrollBtns == true)
393					// Add the scroll right button.
394					timelineHTML += $scrollRightButton;
395
396				else if (this.settings.useNavBtns == true && this.settings.useScrollBtns == false)
397					// Add the next button.
398					timelineHTML += $nextButton;
399
400				else if (this.settings.useNavBtns == true && this.settings.useScrollBtns == true)
401					// Add the next button and the scroll right button.
402					timelineHTML += $nextButton + $scrollRightButton;
403
404				timelineHTML += '</div>';
405			}
406			else {
407				timelineHTML += '<div class="events-wrapper" style="min-width: 100%"><div class="events"><span class="filling-line" aria-hidden="true"></span></div></div>';
408			}
409			if (this.settings.autoplay == true)
410				timelineHTML += '<div class="timeline-navigation" id="pausePlay">'
411					+ $pauseButton
412					+'</div>';
413
414			timelineHTML +='</div>';
415
416			// Prepend the timeline HTML to the element (before the event content).
417			this.$element.prepend(timelineHTML);
418
419			//** Create the HTML for the event date display **//
420			this._createDate(this, 'append');
421
422		}, // End create() function
423
424		// (instance, insertMethod (append, before, after [last 2 for addEvent method]), date to insert before/after [from addEvent method])
425		_createDate: function (self, insertMethod, arrangementDate) {
426			var dataAttribute = this._eventContentListData();
427
428			// If dateOrder is normal (starting from the left).
429			if (self.settings.dateOrder == "normal") {
430				// Find the event content.
431				var $element = self.$element.children('.events-content').find('li['+ dataAttribute +']');
432			}
433			// Else if dateOrder is reverse (starting from the right).
434			else if (self.settings.dateOrder == "reverse") {
435				var $element = $(self.$element.children('.events-content').find('li['+ dataAttribute +']').get().reverse());
436			}
437
438			/* dateTime = the date and time */
439			if(self.settings.dateDisplay == "dateTime") {
440				$element.each(function() {
441					self._eventDateDisplay(self, $(this), "dateTime", insertMethod, arrangementDate);
442				});
443			}
444			/* date = the date only */
445			else if (self.settings.dateDisplay == "date") {
446				$element.each(function() {
447					self._eventDateDisplay(self, $(this), "date", insertMethod, arrangementDate);
448				});
449			}
450			/* time = the time only */
451			else if (self.settings.dateDisplay == "time") {
452				$element.each(function() {
453					self._eventDateDisplay(self, $(this), "time", insertMethod, arrangementDate);
454				});
455			}
456			/* dayMonth = the day and monthName only */
457			else if (self.settings.dateDisplay == "dayMonth") {
458				$element.each(function() {
459					self._eventDateDisplay(self, $(this), "dayMonth", insertMethod, arrangementDate);
460				});
461			}
462			/* monthYear = the monthName and year only */
463			else if (self.settings.dateDisplay == "monthYear") {
464				$element.each(function() {
465					self._eventDateDisplay(self, $(this), "monthYear", insertMethod, arrangementDate);
466				});
467			}
468			/* year = the year only */
469			else if (self.settings.dateDisplay == "year") {
470				$element.each(function() {
471					self._eventDateDisplay(self, $(this), "year", insertMethod, arrangementDate);
472				});
473			}
474		},// End _createDate
475
476		/* Function to create the event date display
477		(instance, element, displayType, insertMethod (append, before, after [last 2 for addEvent method]), date to insert before/after [from addEvent method])*/
478		_eventDateDisplay: function (self, eventElement, display, insertMethod, arrangementDate) {
479				// Get date from data-attribute
480			var dataDate = self._timelineData(eventElement, "date"),
481				// Check if element data-date format is DD/MM/YYYYTHH:MM by checking for 'T'
482				isDateTime = dataDate.includes("T"),
483				// Check if element data-date format is HH:MM by checking for ':' but doesn't have 'T'
484				isTime = !isDateTime && dataDate.includes(":"),
485				// Display type checks
486				dateTimeDisplay = display == "dateTime",
487				dateDisplay = display == "date",
488				timeDisplay = display == "time",
489				dayMonthDisplay = display == "dayMonth",
490				monthYearDisplay = display == "monthYear",
491				yearDisplay = display == "year",
492				// Find .events for the date display
493				$eventDateDisplay = self.$element.find('.events'),
494				dateLink = '<a href="" data-horizontal-timeline=\'{"date": "'+ dataDate +'"}\'>';
495
496				// For use with the addEvent public method.
497				// If arrangementDate isn't undefined or null...
498				if(typeof arrangementDate != 'undefined' || arrangementDate != null) {
499					// Finds the event with the specific date.
500					var $arrangementEvent = $eventDateDisplay.find("a").filter(function() {
501						var data = self._timelineData($(this), "date");
502						if (data == arrangementDate) return $(this);
503					});
504				}
505
506			// Function to add the number suffix st, nd, rd, th (eg: 1st, 2nd, 3rd, 4th)
507			// Part of answer on StackOverflow: https://stackoverflow.com/a/15397495/2358222
508			function numSuffix(num) {
509				if (num > 3 && num < 21) return 'th';
510				switch (num % 10) {
511					case 1:  return "st";
512					case 2:  return "nd";
513					case 3:  return "rd";
514					default: return "th";
515				}
516			}
517			// Function to get the month name according to a number supplied.
518			// Answer on StackOverflow: https://stackoverflow.com/a/10996297/2358222
519			function getMonthName(num) {
520				// Create an array of the months, with the index 0 = null,
521				// so that we can get the month by its corresponding index number.
522				var monthNames = [null, "January", "February", "March", "April", "May", "June",
523								"July", "August", "September", "October", "November", "December" ];
524				return monthNames[num];
525			}
526
527			var dateExists = $eventDateDisplay.children('a').map(function() {
528					if ($(this).data('horizontal-timeline')) {
529						var data = $(this).data('horizontal-timeline');
530
531						return data.date;
532					}
533					// data-date deprecated as of v2.0.5.alpha.3
534					// and will be removed in a later major version.
535					else {
536						var dataDate = $(this).data('date');
537
538						return dataDate;
539					}
540				}).get();
541
542			if(jQuery.inArray(dataDate, dateExists) == -1) {
543				// Date and Time format (DD/MM/YYYYTHH:MM)
544				if (isDateTime){
545						// Separate the date at point of T, to get individually date and time
546					var dateSplit = dataDate.split('T'),
547						// Date
548						date = dateSplit[0],
549						// Time
550						time = dateSplit[1],
551						// Separate the date at point of /, to get individual date parts
552						dateParts = date.split('/'),
553						// Remove the leading 0 (zero) from the day
554						dayPart = dateParts[0].replace(/^0+/, ''),
555						// Remove the leading 0 (zero) from the month
556						monthPart = dateParts[1].replace(/^0+/, ''),
557						yearPart = dateParts[2];
558
559					/* Add the event date displays according to the display types */
560
561					// Custom Date Display
562
563					// Get the custom text from the data-attribute object.
564					var customDisplay = self._timelineData(eventElement, "customDisplay");
565
566					// If customDisplay is defined in the data-attribute object...
567					if(typeof customDisplay !== 'undefined') {
568
569
570						// Add in the custom Text depending on which insertMethod used.
571						if (insertMethod == 'append') $eventDateDisplay.append(dateLink + customDisplay +'</a>');
572						// For use with the addEvent method... creates new timeline events and places them where specified.
573						else if (insertMethod == 'after') $arrangementEvent.after(dateLink + customDisplay +'</a>');
574						else $arrangementEvent.before(dateLink + customDisplay +'</a>');
575					}
576
577					else if(dateTimeDisplay) {
578						if (insertMethod == 'append') $eventDateDisplay.append(dateLink + date +'<br>'+ time +'</a>');
579						// For use with the addEvent method... creates new timeline events and places them where specified.
580						else if (insertMethod == 'after') $arrangementEvent.after(dateLink + date +'<br>'+ time +'</a>');
581						else $arrangementEvent.before(dateLink + date +'<br>'+ time +'</a>');
582					}
583					else if(dateDisplay) {
584						if (insertMethod == 'append') $eventDateDisplay.append(dateLink + date +'</a>');
585						// For use with the addEvent method... creates new timeline events and places them where specified.
586						else if (insertMethod == 'after') $arrangementEvent.after(dateLink + date +'</a>');
587						else $arrangementEvent.before(dateLink + date +'</a>');
588					}
589					else if(timeDisplay) {
590						if (insertMethod == 'append') $eventDateDisplay.append(dateLink + time +'</a>');
591						// For use with the addEvent method... creates new timeline events and places them where specified.
592						else if (insertMethod == 'after') $arrangementEvent.after(dateLink + time +'</a>');
593						else $arrangementEvent.before(dateLink + time +'</a>');
594					}
595					else if(dayMonthDisplay) {
596						if (insertMethod == 'append') $eventDateDisplay.append(dateLink + dayPart + numSuffix(dayPart) + '<br>' + getMonthName(monthPart) +'</a>');
597						// For use with the addEvent method... creates new timeline events and places them where specified.
598						else if (insertMethod == 'after') $arrangementEvent.after(dateLink + dayPart + numSuffix(dayPart) + '<br>' + getMonthName(monthPart) +'</a>');
599						else $arrangementEvent.before(dateLink + dayPart + numSuffix(dayPart) + '<br>' + getMonthName(monthPart) +'</a>');
600					}
601					else if(monthYearDisplay) {
602						if (insertMethod == 'append') $eventDateDisplay.append(dateLink + getMonthName(monthPart) + '<br>
602' + yearPart +'</a>');
603						// For use with the addEvent method... creates new timeline events and places them where specified.
604						else if (insertMethod == 'after') $arrangementEvent.after(dateLink + getMonthName(monthPart) + '<br>' + yearPart +'</a>');
605						else $arrangementEvent.before(dateLink + getMonthName(monthPart) + '<br>' + yearPart +'</a>');
606					}
607					else if(yearDisplay) {
608						if (insertMethod == 'append') $eventDateDisplay.append(dateLink + yearPart +'</a>');
609						// For use with the addEvent method... creates new timeline events and places them where specified.
610						else if (insertMethod == 'after') $arrangementEvent.after(dateLink + yearPart +'</a>');
611						else $arrangementEvent.before(dateLink + yearPart +'</a>');
612					}
613				}
614				// Time format (HH:MM)
615				else if (isTime) {
616					var time = dataDate;
617					/* Add the event date displays according to the display types */
618
619					// Custom Date Display
620
621					// Get the custom text from the data-attribute object.
622					var customDisplay = self._timelineData(eventElement, "customDisplay");
623
624					// If customDisplay is defined in the data-attribute object...
625					if(typeof customDisplay !== 'undefined') {
626
627						// Add in the custom Text depending on which insertMethod used.
628						if (insertMethod == 'append') $eventDateDisplay.append(dateLink + customDisplay +'</a>');
629						// For use with the addEvent method... creates new timeline events and places them where specified.
630						else if (insertMethod == 'after') $arrangementEvent.after(dateLink + customDisplay +'</a>');
631						else $arrangementEvent.before(dateLink + customDisplay +'</a>');
632					}
633
634					else if(dateTimeDisplay || timeDisplay) {
635						if (insertMethod == 'append') $eventDateDisplay.append(dateLink + time +'</a>');
636						// For use with the addEvent method... creates new timeline events and places them where specified.
637						else if (insertMethod == 'after') $arrangementEvent.after(dateLink + time +'</a>');
638						else $arrangementEvent.before(dateLink + time +'</a>');
639					}
640				}
641				// Date format (DD/MM/YYYY)
642				else {
643					var date = dataDate,
644						// Separate the date at point of /, to get individual date parts
645						dateParts = date.split('/'),
646						// Remove the leading 0 (zero) from the day
647						dayPart = dateParts[0].replace(/^0+/, ''),
648						// Remove the leading 0 (zero) from the month
649						monthPart = dateParts[1].replace(/^0+/, ''),
650						yearPart = dateParts[2];
651
652					/* Add the event date displays according to the display types */
653
654					// Custom Date Display
655
656					// Get the custom text from the data-attribute object.
657					var customDisplay = self._timelineData(eventElement, "customDisplay");
658
659					// If customDisplay is defined in the data-attribute object...
660					if(typeof customDisplay !== 'undefined') {
661
662						// Add in the custom Text depending on which insertMethod used.
663						if (insertMethod == 'append') $eventDateDisplay.append(dateLink + customDisplay +'</a>');
664						// For use with the addEvent method... creates new timeline events and places them where specified.
665						else if (insertMethod == 'after') $arrangementEvent.after(dateLink + customDisplay +'</a>');
666						else $arrangementEvent.before(dateLink + customDisplay +'</a>');
667					}
668
669					else if(dateTimeDisplay || dateDisplay) {
670						if (insertMethod == 'append') $eventDateDisplay.append(dateLink + date +'</a>');
671						// For use with the addEvent method... creates new timeline events and places them where specified.
672						else if (insertMethod == 'after') $arrangementEvent.after(dateLink + date +'</a>');
673						else $arrangementEvent.before(dateLink + date +'</a>');
674					}
675					else if(dayMonthDisplay) {
676						if (insertMethod == 'append') $eventDateDisplay.append(dateLink + dayPart + numSuffix(dayPart) + '<br>' + getMonthName(monthPart) +'</a>');
677						// For use with the addEvent method... creates new timeline events and places them where specified.
678						else if (insertMethod == 'after') $arrangementEvent.after(dateLink + dayPart + numSuffix(dayPart) + '<br>' + getMonthName(monthPart) +'</a>');
679						else $arrangementEvent.before(dateLink + dayPart + numSuffix(dayPart) + '<br>' + getMonthName(monthPart) +'</a>');
680					}
681					else if(monthYearDisplay) {
682						if (insertMethod == 'append') $eventDateDisplay.append(dateLink + getMonthName(monthPart) + '<br>
682' + yearPart +'</a>');
683						// For use with the addEvent method... creates new timeline events and places them where specified.
684						else if (insertMethod == 'after') $arrangementEvent.after(dateLink + getMonthName(monthPart) + '<br>' + yearPart +'</a>');
685						else $arrangementEvent.before(dateLink + getMonthName(monthPart) + '<br>' + yearPart +'</a>');
686					}
687					else if(yearDisplay) {
688						if (insertMethod == 'append') $eventDateDisplay.append(dateLink + yearPart +'</a>');
689						// For use with the addEvent method... creates new timeline events and places them where specified.
690						else if (insertMethod == 'after') $arrangementEvent.after(dateLink + yearPart +'</a>');
691						else $arrangementEvent.before(dateLink + yearPart +'</a>');
692					}
693				} // End else
694			} // End inArray
695		}, // End eventDateDisplay() function
696
697		_timelineComponents: function (timelineComponents) {
698			var dataAttribute = this._eventContentListData();
699			// Cache timeline components
700			timelineComponents['eventsContent'] = this.$element.children('.events-content');
701			timelineComponents['eventsContentList'] = timelineComponents['eventsContent'].find('li['+ dataAttribute +']');
702			timelineComponents['eventsContentSelected'] = timelineComponents['eventsContent'].find('li['+ dataAttribute +'].selected');
703
704			timelineComponents['timelineWrapper'] = timelineComponents['eventsContent'].parent().find('.events-wrapper');
705			timelineComponents['eventsWrapper'] = timelineComponents['timelineWrapper'].children('.events');
706			timelineComponents['fillingLine'] = timelineComponents['eventsWrapper'].children('.filling-line');
707			timelineComponents['timelineEvents'] = timelineComponents['eventsWrapper'].find('a');
708
709			timelineComponents['timelineNavigation'] = timelineComponents['timelineWrapper'].siblings('.timeline-navigation');
710		},
711
712		_setup: function (self, timelineComponents, timelineTotalWidth) {
713			/* Debounce function for resize events */
714
715			// Returns a function, that, as long as it continues to be invoked, will not
716			// be triggered. The function will be called after it stops being called for
717			// N milliseconds. If `immediate` is passed, trigger the function on the
718			// leading edge, instead of the trailing.
719			function debounce(func, wait, immediate) {
720				var timeout;
721				return function() {
722					var context = this, args = arguments;
723					var later = function() {
724						timeout = null;
725						if (!immediate) func.apply(context, args);
726					};
727					var callNow = immediate && !timeout;
728					clearTimeout(timeout);
729					timeout = setTimeout(later, wait);
730					if (callNow) func.apply(context, args);
731				};
732			};
733
734			var id = this.$element.attr('id');
735
736			function _mobileResizeFix(event) {
737				var windowWidth = this.$element.data('plugin_'+ this._name)['windowWidth'],
738					newWidth = $(window).width();
739
740				if(newWidth !== windowWidth){
741					this.refresh();
742				}
743				this.$element.data('plugin_'+ this._name)['windowWidth'] = newWidth;
744			}
745			this._setup.mobileResizeFix = _mobileResizeFix;
746			// On window resize, change the timeline accordingly.
747			$(window).on('resize.'+this._name+'_'+id, debounce($.proxy(this._setup.mobileResizeFix, this), 250));
748
749
750
751			//** Navigation button function **//
752
753			// Stop click events on the .inactive buttons
754			this.$element
755				.on('click.'+this._name, '.timeline-navigation .inactive', function(event){
756					event.stopImmediatePropagation();
757					return(false);
758				})
759				// Button on click...
760				.on('click.'+this._name, '.timeline-navigation:not(#pausePlay) a', $.proxy(function(event) {
761					event.preventDefault();
762					var $this = $(event.target);
763
764					this._timelineComponents(timelineComponents);
765
766					timelineTotalWidth = this._setTimelineWidth(timelineComponents);
767					// If next button clicked, shows next content
768					if($this.is('.next')) this._showNewContent(timelineComponents, timelineTotalWidth, 'next');
769					// If prev button clicked shows prev content
770					if($this.is('.prev')) this._showNewContent(timelineComponents, timelineTotalWidth, 'prev');
771					// If scroll-right button clicked, scrolls timeline right
772					if($this.is('.scroll-right')) this._updateSlide(timelineComponents, timelineTotalWidth, 'right');
773					// If scroll-left button clicked, scrolls timeline left
774					if($this.is('.scroll-left')) this._updateSlide(timelineComponents, timelineTotalWidth, 'left');
775				}, this))
776				//** Event date function **//
777				// Detect click on a single event date = show new event content
778				.on('click.'+this._name, '.events a', $.proxy(function(event) {
779					event.preventDefault();
780					var $this = $(event.target);
781
782					this._timelineComponents(timelineComponents);
783					// Remove selected class from all dates.
784					this.$element.find('.events').find('a').removeClass('selected');
785					// Add class to the event date clicked.
786					$this.addClass('selected');
787					// Update all other previous event dates for styling
788					this._updateOlderEvents($this);
789					// Set the timeline width.
790					timelineTotalWidth = this._setTimelineWidth(timelineComponents);
791					// Update the timeline width and filling line.
792					this._updateFilling($this, timelineComponents['fillingLine'], timelineTotalWidth);
793					// Change the event content to match the selected event.
794					this._updateVisibleContent($this, timelineComponents['eventsContent']);
795					// Translate (scroll) the timeline left or right according to the position of the targeted event date
796					this._updateTimelinePosition($this, timelineComponents, timelineTotalWidth);
797				}, this));
798
799			//** Autoplay **//
800
801			if (this.settings.autoplay == true){
802				// Define the progress bar html
803				var	$progressBar = '<div class="progressBarWrapper"><div class="progressBar"></div></div>',
804					// Find and get the pause button html.
805					$pauseButton = timelineComponents['timelineNavigation'].find('.pause')[0];
806				// Create the progress bar.
807				timelineComponents['eventsContent'].prepend($progressBar);
808
809				// Call the autoplay function.
810				this._autoplay(timelineComponents);
811
812				// On click
813				this.$element
814					// On click of the element pause button, pass data to the event [pausebtnClicked, pausebtnHtml, state] and call the changeButtons function.
815					.on('click.'+this._name, '.timeline-navigation .pause', [true, $pauseButton, 'paused'], $.proxy(this._autoplay.changeButtons, this))
816					// On click of the element play button, pass data to the event [pausebtnClicked, pausebtnHtml, state] and call the changeButtons function.
817					.on('click.'+this._name, '.timeline-navigation .play', [false, $pauseButton, 'playing'], $.proxy(this._autoplay.changeButtons, this));
818
819				// Hover
820				if (this.settings.autoplayPause_onHover == true) {
821					var checkMQ = this._checkMQ();
822
823					// Only execute hover code if device is tablet or desktop.
824					if (checkMQ == 'tablet' || checkMQ == 'desktop') {
825						// On hover
826						this.$element
827							// On mouseenter of the element events-content, pass data to the event [pausebtnClicked, pausebtnHtml, state] and call the changeButtons function.
828							.on('mouseenter.'+this._name, '.events-content', [false, $pauseButton, 'paused'], $.proxy(this._autoplay.changeButtons, this))
829							// On mouseleave of the element events-content, pass data to the event [pausebtnClicked, pausebtnHtml, state] and call the changeButtons function.
830							.on('mouseleave.'+this._name, '.events-content', [false, $pauseButton, 'playing'], $.proxy(this._autoplay.changeButtons, this));
831					} // End checkMQ is desktop
832				} // End autoplayPause_onHover this.settings
833
834
835
836			} // End Autoplay this.settings
837
838			//** Go-to timeline link function **//
839
840			// Linking to a specific date of a timeline
841
842			// Set the go-to selector in a variable
843			var goToTimelineLink = $('.goto-horizontal-timeline');
844
845			// If go-to selector exists...
846			if(goToTimelineLink.length > 0) {
847				// On click
848				goToTimelineLink.on('click.'+this._name, gotoTimeline);
849
850				function gotoTimeline(event) {
851					// Prevent default click
852					event.preventDefault();
853					// Prevent every instance of the plugin from firing the function, and concentrate on just the one.
854					event.stopImmediatePropagation();
855						// Reference the button
856					var	$this = $(event.target),
857						// Get the go-to href value of the button as the selector
858						href = $this.attr('href'),
859						// A check to see if href only contains a # (by itself)...
860						targetSelf = href == "#";
861
862					// We are using a lonely # to determine if a link is targetting the timeline it sits in (itself)
863					if(targetSelf) {
864						// We are targeting the timeline the link is in.
865							// Get the ID of the outer wrapper of the timeline, from which the link sits in
866						var gotoself = '#' + $this.parents('.horizontal-timeline').attr('id');
867							// Set the target variable as this timeline.
868							$target = $(gotoself);
869					}
870					// Otherwise we're targetting another timeline.
871					else var $target = $(href); // Reference the jQuery object selector only once
872
873					// Get the correct plugin instance from the target data.
874					var instanceRef = $target.data('plugin_horizontalTimeline').Timeline;
875
876						// Get the data-gototimeline options object
877					var datagoto = $this.data('gototimeline'),
878						// Set empty variables
879						date,
880						scrollSpeed,
881						scrollOffset,
882						scrollEasing,
883
884						// Get the keys from the data object
885						dataDate = datagoto.date,
886						dataScrollSpeed = datagoto.scrollspeed,
887						dataScrollOffset = datagoto.scrolloffset,
888						dataScrollEasing = datagoto.scrolleasing;
889
890					// If the data-gototimeline attribute exists...
891					if (typeof datagoto !== 'undefined') {
892						// Set the date from the data object
893						date = dataDate;
894
895						// The speed, offset, and easing data options are optional,
896						// so we need to check for their existence
897
898						// If speed option exists, set the speed from the data object
899						if (typeof dataScrollSpeed !== 'undefined') scrollSpeed = dataScrollSpeed;
900
901						// If offset option exists set offset from the data object
902						if (typeof dataScrollOffset !== 'undefined') scrollOffset = dataScrollOffset;
903
904						// If easing option exists set easing from the data object
905						if (typeof dataScrollEasing !== 'undefined') scrollEasing = dataScrollEasing;
906					}
907
908					// If a link is targetting the timeline it sits in (itself), then execute the public method interally to goTo the date.
909					if(targetSelf) instanceRef.goTo(date, instanceRef);
910					// If not, then use a smooth scroll and then execute the public method interally afterwards.
911					else instanceRef.goTo(date, {smoothScroll: true, speed: scrollSpeed, offset: scrollOffset, easing: scrollEasing}, instanceRef);
912
913				} // End gotoTimeline function
914			} // End if goToTimelineLink exists
915
916			//** Mouse wheel function **//
917			// Requires the jQuery plugin mouse wheel: https://github.com/jquery/jquery-mousewheel
918			// Mouse wheel support for "scrolling" the events content.
919			if(this.settings.useScrollWheel == true) {
920					// The URL to the plugin on CDN
921				var url = "https://cdnjs.cloudflare.com/ajax/libs/jquery-mousewheel/3.1.13/jquery.mousewheel.min.js";
922
923				// Set a global variable to equal the function.
924				this._setup.mousewheel = mousewheel;
925
926				// Function to load the Mousewheel plugin (url, type, callback)
927				this._addFile(url, 'js', $.proxy(function() {
928					// Wait 300ms whilst the Mousewheel script loads
929					window.setTimeout($.proxy(function() {
930						this.$element.on('mousewheel.'+this._name, '.events-content', $.proxy(this._setup.mousewheel, this));
931					}, this), 300); // End setTimeout function
932				}, this)); // End addFile function
933
934				/* Mousewheel function */
935				function mousewheel(e, delta) {
936					this._timelineComponents(timelineComponents);
937					timelineTotalWidth = this._setTimelineWidth(timelineComponents);
938
939					// Scroll Up = show previous content
940					if (e.deltaY > 0) this._showNewContent(timelineComponents, timelineTotalWidth, 'prev');
941					// Scroll Down = show next content
942					else this._showNewContent(timelineComponents, timelineTotalWidth, 'next');
943					// Prevent the normal document scroll
944					e.preventDefault();
945				}
946			} // End scrollWheel setting
947
948
949			//** TouchSwipe function **//
950			// Requires the jQuery plugin TouchSwipe: http://labs.rampinteractive.co.uk/touchSwipe/demos/index.html
951			// TouchSwipe has more events/options than jQuery Mobile
952			if(this.settings.useTouchSwipe == true){
953					// The URL to the plugin on CDN
954				var url = "https://cdnjs.cloudflare.com/ajax/libs/jquery.touchswipe/1.6.18/jquery.touchSwipe.min.js";
955
956				// Set a global variable to equal the function.
957				this._setup.swipe = swipe;
958
959				// Function to load the TouchSwipe plugin (url, type, callback)
960				this._addFile(url, 'js', $.proxy(function() {
961					// Wait 300ms whilst the TouchSwipe script loads
962					window.setTimeout($.proxy(function() {
963
964						// On swipe of .events-content, show next/prev event content
965						timelineComponents['eventsContent'].swipe({
966							// Swipe right to go left (previous)
967							swipeRight:$.proxy(function(event, direction, distance, duration, fingerCount) {
968								// Show previous content on swipeRight
969								this._setup.swipe(this, 'prev');
970							}, this),
971							// Swipe left to go right (next)
972							swipeLeft:$.proxy(function(event, direction, distance, duration, fingerCount) {
973								// Show next content on swipeLeft
974								this._setup.swipe(this, 'next');
975							}, this),
976							// Swipe distance... 0 = any distance in px
977							threshold:75,
978							preventDefaultEvents: false
979						}); // End TouchSwipe Event
980
981						/* Swipe function for the timeline wrapper*/
982						// So that we can scroll the timeline with a swipe.
983
984						timelineComponents['timelineWrapper'].swipe({
985							// Swipe right to scroll the timeline left
986							swipeRight:$.proxy(function(event, direction, distance, duration, fingerCount) {
987									// Get the current translate value
988								var translateValue = this._getTranslateValue(timelineComponents['eventsWrapper']),
989									// Get the width of the timeline wrapper.
990									wrapperWidth = Number(timelineComponents['timelineWrapper'].width());
991
992								// Translate the timeline to the left (also know as scroll left)
993								// according to the amount of distance swiped.
994								this._translateTimeline(timelineComponents, distance + translateValue, wrapperWidth - timelineTotalWidth);
995							}, this),
996							// Swipe left to scroll the timeline right
997							swipeLeft:$.proxy(function(event, direction, distance, duration, fingerCount) {
998									// Get the current translate value
999								var translateValue = this._getTranslateValue(timelineComponents['eventsWrapper']),
1000									// Get the width of the timeline wrapper.
1001									wrapperWidth = Number(timelineComponents['timelineWrapper'].width());
1002
1003								// Translate the timeline to the right (also know as scroll right)
1004								// according to the amount of distance swiped.
1005								this._translateTimeline(timelineComponents, -distance + translateValue, wrapperWidth - timelineTotalWidth);
1006							}, this),
1007							// Swipe distance... 0 = any distance in px
1008							threshold:30,
1009							preventDefaultEvents: false
1010							}
1011						); // End TouchSwipe Event
1012					}, this), 1000); // End setTimeout function
1013				}, this)); // End addFile function
1014
1015				// Add a touch-enabled class to the necessary elements.
1016				timelineComponents['timelineWrapper'].addClass('touch-enabled')
1017					.parent().siblings('.events-content').addClass('touch-enabled');
1018
1019				/* Swipe function */
1020				function swipe(self, direction) {
1021					self._timelineComponents(timelineComponents);
1022					timelineTotalWidth = self._setTimelineWidth(timelineComponents);
1023					self._showNewContent(timelineComponents, timelineTotalWidth, direction);
1024				}
1025
1026			} // End useTouchSwipe this.settings
1027
1028			// Keyboard navigation
1029			if(this.settings.useKeyboardKeys == true) {
1030
1031				// Set a global variable to equal the function.
1032				this._setup.keyboardKeys = keyboardKeys;
1033
1034				var id = this.$element.attr('id');
1035				// On keyup
1036				$(document).on('keyup.'+this._name+'_'+id, $.proxy(this._setup.keyboardKeys, this));
1037
1038				/* Keyboardkeys function */
1039				function keyboardKeys(event) {
1040					this._timelineComponents(timelineComponents);
1041					timelineTotalWidth = this._setTimelineWidth(timelineComponents);
1042
1043					// If Left arrow (keyCode 37) AND the timeline is in the viewport, show prev content
1044					if(event.which=='37' && this._elementInViewport(this.element))
1045						this._showNewContent(timelineComponents, timelineTotalWidth, 'prev');
1046					// If Right arrow (keyCode 39) AND the timeline is in the viewport, show next content
1047					else if(event.which=='39' && this._elementInViewport(this.element))
1048						this._showNewContent(timelineComponents, timelineTotalWidth, 'next');
1049				}
1050			} // End useKeyboardKeys this.settings
1051		}, // End _setup() function.
1052
1053		/* Autoplay function */
1054		_autoplay: function (timelineComponents) {
1055			// NOTE: if autoplay cycle is paused, clicking any timeline button
1056			// will not reset the autoplay cycle to play.
1057
1058			var isPaused,
1059				tick,
1060				percentTime,
1061				current,
1062				autoplayTimelineTotalWidth,
1063				dataSpeed,
1064				speed;
1065
1066			this._timelineComponents(timelineComponents);
1067
1068			// Set a global variable to equal the function.
1069			this._autoplay.countEvents = countEvents;
1070			this._autoplay.start = start;
1071			this._autoplay.pause = pause;
1072			this._autoplay.resume = resume;
1073			this._autoplay.moved = moved;
1074			this._autoplay.changeButtons = changeButtons;
1075			this._autoplay.refresh = refresh;
1076			this._autoplay.destroy = destroy;
1077
1078			// Call the start function
1079			this._autoplay.start(this, timelineComponents);
1080
1081			// Count events function
1082			function countEvents() {
1083				// Get the total number of events to check against
1084				return timelineComponents['timelineEvents'].length;
1085			}
1086
1087			// Start function
1088			function start(self, timelineComponents) {
1089				// Reset timer
1090				percentTime = 0;
1091
1092				self._timelineComponents(timelineComponents);
1093				// Get the timeline width
1094				autoplayTimelineTotalWidth = self._setTimelineWidth(timelineComponents);
1095				// Run interval every 0.01 second
1096				tick = setInterval($.proxy(interval, self), 10);
1097			};
1098			// Interval function.
1099			function interval() {
1100				isPaused = this.$element.data('plugin_'+ this._name)['autoplay']['isPaused'];
1101				this._timelineComponents(timelineComponents);
1102				
1103				// Speed
1104
1105				// Get the speed from the data attribute of the current events content.
1106				dataSpeed = this._timelineData(timelineComponents['eventsContent'].find('.selected'),"speed");
1107				// If the variable doesn't return an undefined value, then set the speed
1108				// from the data attribute.
1109				if (typeof dataSpeed !== 'undefined') speed = Number(dataSpeed);
1110				// Otherwise, set the speed from the settings.
1111				else speed = Number(this.settings.autoplaySpeed);
1112
1113				// If isPaused = false AND is in the viewport, start the autoplay cycle, otherwise pause the cycle.
1114				if(isPaused === false && this._elementInViewport(this.element)){
1115					// Set percentTime using the speed from the settings.
1116					// Check media queries...
1117					var checkMQ = this._checkMQ();
1118					// We need to adjust the calculations for percentTime because how slow it seems to be on mobile.
1119					// If mobile, set the correct speed
1120					if(checkMQ == 'mobile') percentTime += 3 / speed;
1121					// Everything else set the correct speed.
1122					else percentTime += 1 / speed;
1123					// Set the progress bar width
1124					this.$element.find('.progressBar').css({
1125						width: percentTime+"%"
1126					});
1127					// Recalculate the index of the current event, each time.
1128					// This is to make sure that if the user navigates to another event while playing or paused,
1129					// the current index will always reflect the current event,
1130					// otherwise autoplay may get out of sync.
1131					current = timelineComponents['eventsWrapper'].find('.selected').index();
1132
1133					//if percentTime is equal or greater than 100
1134					if(percentTime >= 100){
1135						// If dateOrder is normal AND the current index is equal to the total number of events
1136						// OR dateOrder is reverse AND current index is equal to 1 ...
1137						if((this.settings.dateOrder == "normal" && current == this._autoplay.countEvents()) || (this.settings.dateOrder == "reverse" && current == 1)) {
1138							// Go back to the start of the cycle.
1139							this._showNewContent(timelineComponents, autoplayTimelineTotalWidth, 'start');
1140							// Recalculate the current index to make sure it's reset back to 1 (the start).
1141							current = timelineComponents['eventsWrapper'].find('.selected').index();
1142						}
1143						else {
1144							// If dateOrder is normal.
1145							if (this.settings.dateOrder == "normal") {
1146								// Go to next event content.
1147								this._showNewContent(timelineComponents, autoplayTimelineTotalWidth, 'next');
1148							}
1149							// Else if dateOrder is reverse.
1150							else if (this.settings.dateOrder == "reverse") {
1151								// Go to next event content.
1152								this._showNewContent(timelineComponents, autoplayTimelineTotalWidth, 'prev');
1153							}
1154						}
1155					}
1156				} // End isPaused if statement
1157			} // End Interval function
1158
1159			// Pause function
1160			function pause(self) {
1161				self.$element.data('plugin_'+ self._name)['autoplay']['isPaused'] = true;
1162			}
1163			// Resume function
1164			function resume(self) {
1165				self.$element.data('plugin_'+ self._name)['autoplay']['isPaused'] = false;
1166			}
1167			// Moved function, when an event content has changed via autoplay or by manual navigation.
1168			function moved(self) {
1169					// Clear interval
1170					self._autoplay.destroy();
1171					// Restart the cycle.
1172					self._autoplay.start(self, timelineComponents);
1173			}
1174			// Change Buttons function
1175			function changeButtons(event) {
1176				event.preventDefault();
1177				// Get the event data
1178				var data = event.data,
1179					// Set variables using the corresponding data array selectors.
1180					pausebtnClicked = data[0],
1181					$pauseButton = data[1],
1182					state = data[2],
1183					// Find the pause play button wrapper.
1184					$pausePlay = this.$element.find('#pausePlay'),
1185
1186					// Check if the new object options are defined in the user options, if they are use them,
1187					// otherwise use the deprecated single options.
1188					iconClass = (this._options.iconClass != undefined) ? this.settings.iconClass : this.settings,
1189					iconBase = (this._options.iconClass != undefined) ? iconClass.base : iconClass.iconBaseClass,
1190					iconPlay = (this._options.iconClass != undefined) ? iconClass.play : iconClass.play_iconClass,
1191
1192					// Define the play button html
1193					$playButton = '<a href="" class="'+ iconBase +' '+ iconPlay +' play"></a>';
1194
1195				// If the event type is click and pausebtnClicked is true (so the pause button was clicked)...
1196				if (event.type == "click" && pausebtnClicked == true) {
1197					// Add class to parent to check against it later to stop on hover from reactivating the play cycle.
1198					$pausePlay.addClass('clicked');
1199					// Set a mouseEvent data to click on the element to check against later.
1200					this.$element.data('plugin_'+ this._name)['autoplay']['mouseEvent'] = 'click';
1201					// Change the button to the play button.
1202					$pausePlay.html($playButton);
1203					// Call the pause function to pause autoplay
1204					this._autoplay.pause(this);
1205					console.log('Autoplay is '+state+'.');
1206				}
1207				// Else if the event type is click and pausebtnClicked is false (so the play button was clicked)...
1208				else if (event.type == "click" && !pausebtnClicked) {
1209					// Remove class from the parent
1210					$pausePlay.removeClass('clicked');
1211					// Set the mouseEvent data to false on the element.
1212					this.$element.data('plugin_'+ this._name)['autoplay']['mouseEvent'] = false;
1213					// Change the button to the pause button.
1214					$pausePlay.html($pauseButton);
1215					// Call the resume function to resume the autoplay cycle.
1216					this._autoplay.resume(this);
1217					console.log('Autoplay is '+state+'.');
1218				}
1219				// If the event type is mouseenter (so it's paused) and the pause play button wrapper doesn't have the clicked class (paused via the pause button)...
1220				if(event.type == "mouseenter" && !$pausePlay.hasClass('clicked')) {
1221					// Set a mouseEvent data to hover on the element to check against later.
1222					this.$element.data('plugin_'+ this._name)['autoplay']['mouseEvent'] = 'hover';
1223					// Change the button to the play button.
1224					$pausePlay.html($playButton);
1225					// Call the pause function to pause autoplay
1226					this._autoplay.pause(this);
1227					console.log('Autoplay is '+state+'.');
1228				}
1229				// Else if the event type is mouseleave (so it's playing) and the pause play button wrapper doesn't have the clicked class (paused via the pause button)...
1230				// To stop autoplay resuming the cycle on mouseleave if it's already paused via the pause button.
1231				else if(event.type == "mouseleave" && !$pausePlay.hasClass('clicked')) {
1232					// Set the mouseEvent data to false on the element.
1233					this.$element.data('plugin_'+ this._name)['autoplay']['mouseEvent'] = false;
1234					// Change the button to the pause button.
1235					$pausePlay.html($pauseButton);
1236					// Call the resume function to resume the autoplay cycle.
1237					this._autoplay.resume(this);
1238					console.log('Autoplay is '+state+'.');
1239				}
1240			} // End changeButtons function
1241			// Refresh function
1242			function refresh(self) {
1243				self._timelineComponents(timelineComponents);
1244				autoplayTimelineTotalWidth = self._setTimelineWidth(timelineComponents);
1245			}
1246			// Destroy function, to destroy the autoplay interval.
1247			function destroy() {
1248				clearInterval(tick);
1249			}
1250		}, // End autoplay function
1251
1252		/* Get data from the data-attribute object */
1253		_timelineData: function (element, type) {
1254			if (element.data('horizontal-timeline')) {
1255				var data = element.data('horizontal-timeline');
1256
1257				if(type == "date") return data.date;
1258				else if(type == "customDisplay") return data.customDisplay;
1259				else if (type == "speed") return data.speed;
1260			}
1261			// data-date and data-custom-display deprecated as of v2.0.5.alpha.3
1262			// and will be removed in a later major version.
1263			else {
1264				var dataDate = element.data('date'),
1265					dataCustomDisplay = element.data('custom-display');
1266
1267				if(type == "date") return dataDate;
1268				else if(type == "customDisplay") return dataCustomDisplay;
1269			}
1270		},
1271		_eventContentListData: function () {
1272			// Check if the data-horizontal-timeline attribute exists on the events-content li,
1273			// If not then return the deprecated data-date.
1274			if (this.$element.find('li').data('horizontal-timeline')) {
1275				return "data-horizontal-timeline";
1276			}
1277			// data-date deprecated as of v2.0.5.alpha.3
1278			// and will be removed in a later major version.
1279			else {
1280				return "data-date";
1281			}
1282		},
1283
1284		/* Refresh public method
1285		*  - refreshes the timeline externally after initialisation.
1286		*  Use it like: $('#example').horizontalTimeline('refresh');
1287		*/
1288		refresh: function () {
1289			var timelineComponents = {};
1290
1291			this._timelineComponents(timelineComponents);
1292
1293			// Removes first and last id attributes of the event-content list.
1294			timelineComponents['eventsContent']
1295				.find('#first').removeAttr('id').end()
1296				.find('#last').removeAttr('id').end();
1297
1298			// Removes first and last classes from the timeline event date
1299			timelineComponents['eventsWrapper']
1300				.find('.first').removeClass('first').end()
1301				.find('.last').removeClass('last').end();
1302
1303			// Adds classes and IDs.
1304			this._addIdsAndClasses(timelineComponents);	 // changed
1305
1306			this._setDatePosition(timelineComponents);
1307			timelineTotalWidth = this._setTimelineWidth(timelineComponents);
1308			this._updateFilling(timelineComponents['eventsWrapper']
1309				.find('a.selected'), timelineComponents['fillingLine'], timelineTotalWidth);
1310
1311			if(this.settings.autoplay == true) this._autoplay.refresh(this);
1312			console.log('refreshed #'+this.element.id);
1313		},
1314
1315		/* Destroy public method
1316		*  - destroys the timeline externally after initialisation.
1317		* Removes all timeline created html and event handlers and resets the elements to the original state.
1318		*  Use it like: $('#example').horizontalTimeline('destroy');
1319		*/
1320		destroy: function () {
1321			var $this = this.$element,
1322				id = $this.attr('id'),
1323				originalEventsContent = $this.data('plugin_'+ this._name)['originalEventsContent'];
1324
1325			if($('.horizontal-timeline .timeline').length == 1) {
1326				$('.goto-horizontal-timeline').off('.'+this._name);
1327				$('body').removeData('plugin_'+ this._name +'_loadedFile');
1328			}
1329			$this.removeClass('loaded')
1330				.off('.'+this._name, '**')
1331				.find('.timeline').remove().end()
1332				.find('.events-content').replaceWith(originalEventsContent).swipe("destroy");
1333
1334			$this.find('.events-wrapper').removeClass('touch-enabled').swipe("destroy");
1335			$(document).off('.'+this._name+'_'+id);
1336			$(window).off('.'+this._name+'_'+id);
1337
1338			if(this.settings.autoplay == true) {
1339				this._autoplay.destroy();
1340			}
1341
1342			$this.removeData('plugin_' + pluginName);
1343
1344			console.log('destroyed #'+this.element.id);
1345		},
1346
1347		/* AddEvent public method
1348		* - adds a new event to the timeline externally after initialisation.
1349		* Adds a new event content to the timeline at a specified location.
1350		* Use it like: $('#example').horizontalTimeline('addEvent', [event content html], 'after', '01/01/2001');
1351		* (new event content html, insertion method (before or after), an existing unique date to position the new content around.)
1352		*/
1353		addEvent: function (html, insertMethod, arrangementDate) {
1354			var timelineComponents = {};
1355
1356			this._timelineComponents(timelineComponents);
1357
1358				// Get the new date from the HTML.
1359			var	newDate = html.split("date")[1].split('"')[2],
1360				// Select the specified event content
1361				$eventContent = timelineComponents['eventsContentList'].filter($.proxy(function(index, element) {
1362					var data = this._timelineData($(element), "date");
1363					if (data == arrangementDate) return $(element);
1364				}, this)),
1365				// Find the selected event.
1366				$selectedEvent = timelineComponents['eventsWrapper'].find('a.selected'),
1367				// Get the existing dates array.
1368				existingDates = this.$element.data('plugin_'+ this._name)['existingDates'];
1369
1370			if(jQuery.inArray(newDate, existingDates) == -1) {
1371				existingDates.push(newDate);
1372				// If the insertMethod = before, then insert the new content before the specified date.
1373				if (insertMethod == 'before') $eventContent.before(html);
1374				// Else the insertMethod = after, insert the new content after the specified date.
1375				else if (insertMethod == 'after') $eventContent.after(html);
1376
1377				// Call the create.date function passing the insertMethod and arrangementDate arguments.
1378				// This creates the new timeline events before or after [insertMethod] specified date [arrangementDate].
1379				this._createDate(this, insertMethod, arrangementDate);
1380				// Update the olderEvents.
1381				this._updateOlderEvents($selectedEvent);
1382				// Call the refresh function to fresh the timeline accordingly.
1383				this.refresh();
1384
1385				/* Custom namespaced event: eventAdded with the data passed to the event as the newEventDate and newEventContent. */
1386				this.$element.trigger({
1387					type: "eventAdded."+this._name,
1388					newEventDate: newDate,
1389					newEventContent: html
1390				});
1391			}
1392			else return console.warn('The date '+ newDate +' is already in Timeline.');
1393		},
1394
1395		/* RemoveEvent public method
1396		* - removes the specified event from the timeline externally after initialisation.
1397		* Removes the event and the event content from the timeline using the unique date used in data-date.
1398		* Use it like: $('#example').horizontalTimeline('removeEvent', '01/01/2001');
1399		*/
1400		removeEvent: function (date) {
1401			var timelineComponents = {};
1402
1403			this._timelineComponents(timelineComponents);
1404
1405				// Select the specified timeline event
1406			var $event = timelineComponents['eventsWrapper'].find("a").filter($.proxy(function(index, element) {
1407					var data = this._timelineData($(element), "date");
1408					if (data == date) return $(element);
1409				}, this)),
1410				// Select the specified event content
1411				$eventContent = timelineComponents['eventsContentList'].filter($.proxy(function(index, element) {
1412					var data = this._timelineData($(element), "date");
1413					if (data == date) return $(element);
1414				}, this)),
1415				$newEvent,
1416				// Get the existing dates array.
1417				existingDates = this.$element.data('plugin_'+ this._name)['existingDates'],
1418				// Find the index of the date in the array.
1419				index = existingDates.indexOf(date);
1420
1421			// If there's more than 1 timeline events (We can't remove the very last event)...
1422			if (timelineComponents['timelineEvents'].length > 1) {
1423				// If the specified event is selected...
1424				if($event.is('.selected')) {
1425					// Remove the selected class from the specified event content
1426					$eventContent.removeClass('.selected');
1427					// If a next event exists, select it...
1428					if ($event.next().length) {
1429						// Add a selected class to the next timeline event and reference it.
1430						$newEvent = $event.next().addClass('selected');
1431						// Add a selected class to the next event content
1432						$eventContent.next().addClass('selected');
1433					}
1434					// If not, then select the previous event...
1435					else {
1436						// Add a selected class to the previous timeline event and reference it.
1437						$newEvent = $event.prev().addClass('selected');
1438						// Add a selected class to the previous event content.
1439						$eventContent.prev().addClass('selected');
1440					}
1441				}
1442				// If the specified event isn't selected, then just reference it to pass it to the functions
1443				// (we don't need to do anything special since it doesn't concern it).
1444				else $newEvent = timelineComponents['eventsWrapper'].find('a.selected');
1445				// Update the olderEvents using the newEvent as reference.
1446				this._updateOlderEvents($newEvent);
1447				// Remove the timeline event.
1448				$event.remove();
1449				// Remove the event content.
1450				$eventContent.remove();
1451
1452				// If the existing date exists...
1453				if (index > -1) {
1454					// Remove the existing date from the array.
1455					existingDates.splice(index, 1);
1456				}
1457
1458				// Call the refresh function to fresh the timeline accordingly.
1459				this.refresh();
1460
1461				/* Custom namespaced event: eventRemoved with the data passed to the event as the removedDate. */
1462				this.$element.trigger({
1463					type: "eventRemoved."+this._name,
1464					removedDate: date,
1465					removedContent: $eventContent[0].outerHTML
1466				});
1467			}
1468			// If the specified event is the only event, do nothing, since there should always be at least 1 event.
1469			else {
1470				console.warn('Timeline must always have at least 1 event after initialisation, therefore it can\'t be removed. Please use the Destroy method instead.');
1471			}
1472		}, // End removeEvent() function
1473
1474		/* goTo public method
1475		* - go to an event in the timeline externally after initialisation.
1476		* Changes and goes to the specified event in the timeline.
1477		* Use it like: $('#example').horizontalTimeline('goTo', '01/01/2001', {"smoothScroll": true, "speed": 500, "offset": 0, "easing": "linear"});
1478		* ([an existing unique date to go to], {[enable smoothScroll], [scrollSpeed], [scrollOffset], [scrollEasing]})
1479		* The go-to-timeline links uses this method.
1480		*/
1481
1482		// The object that equals itself as the function arguments, sets the defaults for the smoothScroll function. 0+ options can be overridden.
1483		goTo: function (date, {smoothScroll = false, speed = 500, offset = 0, easing = "linear"} = {}, instanceRef) {
1484			var timelineComponents = {};
1485			this._timelineComponents(timelineComponents);
1486			// If the variable instanceRef is undefined, set it to this instance.
1487			// Only used if the public method is used. (the go-to links passes the instanceRef as an argument.)
1488			if (typeof instanceRef == 'undefined') instanceRef = this;
1489
1490			// Get the existing dates array.
1491			var existingDates = this.$element.data('plugin_'+ this._name)['existingDates']
1492				speed = Number(speed),
1493				offset = Number(offset);
1494
1495			// If date exists in the timeline, we can then go to it.
1496			if(jQuery.inArray(date, existingDates) > -1) {
1497
1498				/* Custom namespaced event: goToTimeline with the data passed to the event as the goToDate and the timelineSelector (jQuery object).
1499				* (Has to be triggered on the body because of the go-to-timeline links in the DOM.)
1500				*/
1501				$('body').trigger({
1502					type: "goToTimeline."+this._name,
1503					goToDate: date,
1504					timelineSelector: instanceRef.$element
1505				});
1506
1507				// Find all event dates.
1508				var	prevDates = timelineComponents['eventsWrapper'].find('a'),
1509					// Find the targeted event date using the date
1510					selectedDate = timelineComponents['eventsWrapper'].find("a").filter($.proxy(function(index, element) {
1511						var data = this._timelineData($(element), "date");
1512						if (data == date) return $(element);
1513					}, this)),
1514					// Get the width value of the events (previously set)
1515					timelineTotalWidth = this._setTimelineWidth(timelineComponents);
1516
1517					//** SmoothScroll functions **//
1518					if (smoothScroll == true) {
1519						// Smoothly scroll the document to the target
1520						$('html, body').stop().animate(
1521							{
1522								'scrollTop': instanceRef.$element.offset().top - offset
1523							},
1524							speed,
1525							easing,
1526							function() {
1527								// Once scrolling/animating the document is complete, update the target timeline.
1528								goto(instanceRef);
1529							}
1530						); // End .animate function
1531					}
1532					else goto(instanceRef);
1533				}
1534				// The date is not in the timeline, so we can not go to it.
1535				else return console.warn('The date '+ date +' is not in the Timeline, so we can not go to it.');
1536
1537				function goto(instanceRef) {
1538					// Check if the targeted event hasn't already been selected, if not continue the code.
1539					if (!selectedDate.hasClass('selected')) {
1540						// Remove all selected classes from dates
1541						prevDates.removeClass('selected');
1542						// Add a selected class to the date we are targeting
1543						selectedDate.addClass('selected');
1544						// Update other dates as an older event for styling
1545						instanceRef._updateOlderEvents(selectedDate);
1546						// Update the filling line up to the selected date
1547						instanceRef._updateFilling(selectedDate, timelineComponents['fillingLine'], timelineTotalWidth);
1548						// Update the visible content of the selected event
1549						instanceRef._updateVisibleContent(selectedDate, timelineComponents['eventsContent']);
1550					}
1551					// Translate (scroll) the timeline left or right according to the position of the targeted event date
1552					instanceRef._updateTimelinePosition(selectedDate, timelineComponents, timelineTotalWidth);
1553				} // End goto() function
1554		}, // End goTo() public method function
1555
1556		_updateSlide: function (timelineComponents, timelineTotalWidth, string) {
1557			// Retrieve translateX value of timelineComponents['eventsWrapper']
1558			var translateValue = this._getTranslateValue(timelineComponents['eventsWrapper']),
1559				wrapperWidth = Number(timelineComponents['timelineWrapper'].width());
1560			// Translate the timeline to the left/right (also know as scroll left/scroll right)
1561			if (string == 'right') this._translateTimeline(timelineComponents, translateValue - wrapperWidth, wrapperWidth - timelineTotalWidth);
1562			else this._translateTimeline(timelineComponents, translateValue + wrapperWidth);
1563		},
1564
1565		_showNewContent: function (timelineComponents, timelineTotalWidth, string) {
1566			// Show prev/next content
1567				// Find the .selected content
1568			var visibleContent =  timelineComponents['eventsContent'].find('.selected');
1569
1570			// If dateOrder is normal...
1571			if (this.settings.dateOrder == "normal")
1572				// Find the prev/next content for detection later.
1573				var newContent = (string == 'next') ?  visibleContent.next() : visibleContent.prev();
1574
1575			// If dateOrder is reverse
1576			else if (this.settings.dateOrder == "reverse")
1577				// Find the prev/next content in reverse fore detection later.
1578				var newContent = (string == 'next') ?  visibleContent.prev() : visibleContent.next();
1579
1580			// If a prev/next content exists
1581			// OR dateOrder is reverse AND string is start (for Autoplay)...
1582			// This determines whether we can navigate prev or next.
1583			if (newContent.length > 0 || (this.settings.dateOrder == "reverse" && string == 'start')) {
1584				// Find the .selected event
1585				var selectedDate = timelineComponents['eventsWrapper'].find('.selected'),
1586					newEvent;
1587
1588				// If start... (For Autoplay), find the first event
1589				if(string == 'start') {
1590
1591					// If the dateOrder is normal (starting from the left)...
1592					if (this.settings.dateOrder == "normal") {
1593						// Find the first event.
1594						newEvent = timelineComponents['eventsWrapper'].find('.first');
1595					}
1596					// Else if the dateOrder is reverse (starting from the right)...
1597					else if (this.settings.dateOrder == "reverse") {
1598						// Find the last event.
1599						newEvent = timelineComponents['eventsWrapper'].find('.last');
1600					}
1601				}
1602				// If next, find the next event from the current selected event
1603				else if (string == 'next') newEvent = selectedDate.next('a');
1604
1605				// If prev, find the prev event from the current selected event
1606				else if (string == 'prev') newEvent = selectedDate.prev('a');
1607
1608				this._updateVisibleContent(newEvent, timelineComponents['eventsContent']);
1609
1610				newEvent.addClass('selected');
1611				selectedDate.removeClass('selected');
1612
1613				this._updateFilling(newEvent, timelineComponents['fillingLine'], timelineTotalWidth);
1614				this._updateOlderEvents(newEvent);
1615				this._updateTimelinePosition(newEvent, timelineComponents, timelineTotalWidth);
1616			}
1617		},
1618
1619		_updateTimelinePosition: function (event, timelineComponents, timelineTotalWidth) {
1620				// Get the css left value of the targeted event date
1621			var eventLeft = Number(event.css('left').replace('px', '')),
1622				// Get the width value of the .events-wrapper
1623				timelineWidth = timelineComponents['timelineWrapper'].width();
1624
1625			this._translateTimeline(timelineComponents, - eventLeft + timelineWidth/2, timelineWidth - timelineTotalWidth);
1626		},
1627
1628		_translateTimeline: function (timelineComponents, value, totalTranslateValue) {
1629			// Only negative translate value
1630			var value = (value > 0) ? 0 : value;
1631			// Do not translate more than timeline width
1632			value = (!(typeof totalTranslateValue === 'undefined') &&  value < totalTranslateValue ) ? totalTranslateValue : value;
1633			this._setTransformValue(timelineComponents['eventsWrapper'], 'translateX', value+'px');
1634
1635			// Disable the buttons if necessary
1636			this._buttonStates(timelineComponents, value, totalTranslateValue);
1637		},
1638
1639		_updateFilling: function (selectedEvent, filling, totalTranslateValue) {
1640			// Change .filling-line length according to the selected event
1641				// Get the css left value of the selected event and remove the px unit
1642			var eventLeft = selectedEvent.css('left').replace('px', ''),
1643				// Get the css width value of the selected event and remove the px unit
1644				eventWidth = selectedEvent.css('width').replace('px', '');
1645			// Add the left and width together and divide by 2
1646			eventLeft = Number(eventLeft) + Number(eventWidth)/2;
1647			// Divide the eventLeft and the totalTranslateValue to get the filling line value
1648			var scaleValue = eventLeft/totalTranslateValue;
1649			// Set the filling line value
1650			this._setTransformValue(filling, 'scaleX', scaleValue);
1651		},
1652
1653		// Fixed intervals between dates specified in the options.
1654		_setDatePosition: function (timelineComponents) {
1655			var	distnew = 0,
1656				distprev = 0,
1657				startingNum = 0;
1658
1659			this._setDateIntervals(timelineComponents);
1660
1661			var checkMQ = this._checkMQ(),
1662				// Check if the new object options are defined in the user options, if they are use them,
1663				// otherwise use the deprecated single options.
1664				minimal = (this._options.dateIntervals != undefined) ? this.settings.dateIntervals.minimal : this.settings.minimalFirstDateInterval;
1665
1666			if (minimal == true || checkMQ == 'mobile') {
1667				// Set the 1st date to 0px on the timeline but with a padding left of 10px.
1668				timelineComponents['timelineEvents'].first().css({'left': '0px','padding-left': '10px'});
1669				startingNum = 1;
1670			}
1671			// When i starts at 1, it means starts at 2nd date.
1672			for (i = startingNum; i < timelineComponents['timelineEvents'].length; i++) {
1673				distnew = distprev + dateIntervals;
1674				timelineComponents['timelineEvents'].eq(i).css('left', distnew + 'px');
1675				distprev = distnew;
1676			}
1677
1678		},
1679
1680		_setTimelineWidth: function (timelineComponents) {
1681			var	totalWidth = 0,
1682				// Get wrapper width
1683				wrapperWidth = timelineComponents['timelineWrapper'].width(),
1684				// Get the css left value of the last event date, remove the px unit and add 100 to it.
1685				lastEventLeft = Number(timelineComponents['timelineEvents'].last().css('left').replace('px', '')) + 100;
1686
1687			// Set a fail-safe, if lastEventLeft is less than the wrapperWidth then use the wrapperWidth as totalWidth.
1688			// Stops the timeline width from being too small.
1689			if (lastEventLeft < wrapperWidth) {
1690				totalWidth = wrapperWidth;
1691			}
1692			else {
1693				totalWidth = lastEventLeft;
1694			}
1695			timelineComponents['eventsWrapper'].css('width', totalWidth+'px');
1696			this._updateTimelinePosition(timelineComponents['eventsWrapper'].find('a.selected'), timelineComponents, totalWidth);
1697
1698			return totalWidth;
1699		},
1700
1701		_updateVisibleContent: function (event, eventsContent) {
1702			var eventDate = this._timelineData(event, "date");
1703				visibleContent = eventsContent.find('.selected'),
1704				dataAttribute = this._eventContentListData(),
1705				// Function to find the new content...
1706				newContent = eventsContent.find('li['+ dataAttribute +']').filter($.proxy(function(index, element) {
1707					var data = this._timelineData($(element), "date");
1708					if (data == eventDate) return $(element);
1709				}, this)),
1710
1711				newContentHeight = newContent.outerHeight(),
1712
1713				// Check if the deprecated single options are defined in the user options, if they are use them,
1714				// otherwise use the new object options.
1715
1716				// Set the single options into an array to check against.
1717				optionArray = [this._options.enter_animationClass, this._options.exit_animationClass],
1718
1719				// A variable to include in an if statement that queries if the single options are undefined.
1720				singleUndefined = (optionArray[0] == undefined && optionArray[1] == undefined),
1721
1722				// A variable to include in an if statement that queries if the single option is defined 
1723				// AND the object option is also defined.
1724				bothDefined = (optionArray[0] != undefined && this._options.animationClass != undefined)
1725					|| (optionArray[1] != undefined && this._options.animationClass != undefined),
1726
1727				// If single option are undefined OR both single and object options are defined
1728				// then default to the object options, otherwise use the deprecated single option.
1729				animationObj = (singleUndefined || bothDefined) ? this.settings.animationClass : this.settings,
1730
1731				// If animationObj equals the object options...
1732
1733				enterObj = (animationObj == this.settings.animationClass) ? animationObj.enter : animationObj.enter_animationClass,
1734				exitObj = (animationObj == this.settings.animationClass) ? animationObj.exit : animationObj.exit_animationClass,
1735
1736				allClasses = exitObj.right + ' ' + exitObj.left + ' ' + enterObj.left + ' ' + enterObj.right;
1737
1738			// If newContent index is more than the visibleContent index,
1739			// then we have selected an event to the right.
1740			if (newContent.index() > visibleContent.index()) {
1741					// Set the selected and the enter right classes.
1742				var classEntering = 'selected ' + enterObj.right,
1743					// Set the exit left class.
1744					classExiting = exitObj.left;
1745			}
1746			// Else, we have selecting an event to the left.
1747			else {
1748					// Set the selected and the enter left classes.
1749				var classEntering = 'selected ' + enterObj.left,
1750					// Set the exit right class.
1751					classExiting = exitObj.right;
1752			}
1753
1754			/* Add/remove classes to animate them in and out using CSS3. */
1755
1756			function whichAnimationEvent(){
1757				var t,
1758					el = document.createElement("fakeelement"),
1759					animations = {
1760						"animation": "animationend",
1761						"OAnimation": "oAnimationEnd",
1762						"MozAnimation": "animationend",
1763						"WebkitAnimation": "webkitAnimationEnd"
1764					};
1765
1766				for (t in animations){
1767					if (el.style[t] !== undefined) return animations[t];
1768				}
1769			}
1770
1771			var animationEvent = whichAnimationEvent(),
1772			    dataAttribute = this._eventContentListData();
1773
1774			// Add the enter class to the newContent.
1775			newContent.addClass(classEntering);
1776			// Add the exit class to the visibleContent and on animation end...
1777			visibleContent
1778				.addClass(classExiting)
1779				.one(animationEvent, function(e){
1780					// Remove all enter and exit classes from all the event content.
1781					eventsContent.find('li['+ dataAttribute +']').removeClass(allClasses);
1782				})
1783				// And then remove the selected class.
1784				.removeClass('selected');
1785
1786			// Update the height.
1787			eventsContent.height(newContentHeight+'px');
1788
1789			// For use with autoplay...
1790			if (this.settings.autoplay == true) this._autoplay.moved(this);
1791
1792			/* Custom namespaced event: eventChanged with the data passed to the event as the current selected eventDate. */
1793			this.$element.trigger({
1794				type: "eventChanged."+this._name,
1795				currentEventDate: eventDate
1796			});
1797
1798		}, // End _updateVisibleContent function
1799
1800		_updateOlderEvents: function (event) {
1801			event.prevAll('a').addClass('older-event').end()
1802				.nextAll('a').removeClass('older-event');
1803			if (event.is('.selected')) event.removeClass('older-event');
1804		},
1805
1806		_getTranslateValue: function (timeline) {
1807			var timelineStyle = window.getComputedStyle(timeline.get(0), null),
1808				timelineTranslate = timelineStyle.getPropertyValue("-webkit-transform") ||
1809					timelineStyle.getPropertyValue("-moz-transform") ||
1810					timelineStyle.getPropertyValue("-ms-transform") ||
1811					timelineStyle.getPropertyValue("-o-transform") ||
1812					timelineStyle.getPropertyValue("transform");
1813
1814			if(timelineTranslate.indexOf('(') >=0) {
1815				var timelineTranslate = timelineTranslate.split('(')[1];
1816				timelineTranslate = timelineTranslate.split(')')[0];
1817				timelineTranslate = timelineTranslate.split(',');
1818				var translateValue = timelineTranslate[4];
1819			} else {
1820				var translateValue = 0;
1821			}
1822			return Number(translateValue);
1823		},
1824
1825		_setTransformValue: function (element, property, value) {
1826			element.css({
1827				"-webkit-transform": property + "("+value+")",
1828				"-moz-transform": property + "("+value+")",
1829				"-ms-transform": property + "("+value+")",
1830				"-o-transform": property + "("+value+")",
1831				"transform": property + "("+value+")"
1832			});
1833		},
1834
1835		/* How to tell if a DOM element is visible in the current viewport?
1836		http://stackoverflow.com/questions/123999/how-to-tell-if-a-dom-element-is-visible-in-the-current-viewport */
1837		_elementInViewport: function (el) {
1838			var top = el.offsetTop,
1839				left = el.offsetLeft,
1840				width = el.offsetWidth,
1841				height = el.offsetHeight;
1842
1843			while(el.offsetParent) {
1844				el = el.offsetParent;
1845				top += el.offsetTop;
1846				left += el.offsetLeft;
1847			}
1848
1849			return (
1850				top < (window.pageYOffset + window.innerHeight) &&
1851				left < (window.pageXOffset + window.innerWidth) &&
1852				(top + height) > window.pageYOffset &&
1853				(left + width) > window.pageXOffset
1854			);
1855		},
1856
1857		_setDateIntervals: function (timelineComponents) {
1858			var checkMQ = this._checkMQ(),
1859				// Set a minimum value for the intervals.
1860				minimumInterval = 120,
1861
1862				// Check if the deprecated single options are defined in the user options, if they are use them,
1863				// otherwise use the new object options.
1864
1865				// Set the single options into an array to check against.
1866				optionArray = [this._options.desktopDateIntervals, this._options.tabletDateIntervals, this._options.mobileDateIntervals],
1867
1868				// A variable to include in an if statement that queries if the single options are undefined.
1869				singleUndefined = (optionArray[0] == undefined && optionArray[1] == undefined && optionArray[2] == undefined),
1870
1871				// A variable to include in an if statement that queries if the single option is defined 
1872				// AND the object option is also defined.
1873				bothDefined = (optionArray[0] != undefined && this._options.dateIntervals != undefined) 
1874					|| (optionArray[1] != undefined && this._options.dateIntervals != undefined) 
1875					|| (optionArray[2] != undefined && this._options.dateIntervals != undefined),
1876
1877				// If single options are undefined OR both single and object options are defined
1878				// then default to the object options, otherwise use the deprecated single options.
1879				date_intervals = (singleUndefined || bothDefined) ? this.settings.dateIntervals : this.settings,
1880
1881				// If date_intervals equals the object options...
1882				desktop = (date_intervals == this.settings.dateIntervals) ? date_intervals.desktop : date_intervals.desktopDateIntervals,
1883
1884				tablet = (date_intervals == this.settings.dateIntervals) ? date_intervals.tablet : date_intervals.tabletDateIntervals,
1885
1886				mobile = (date_intervals == this.settings.dateIntervals) ? date_intervals.mobile : date_intervals.mobileDateIntervals;
1887				
1888			// If desktop is detected, set dateIntervals to desktop
1889			if (checkMQ == 'desktop') dateIntervals = desktop;
1890			// If tablet is detected, set dateIntervals to tablet
1891			else if (checkMQ == 'tablet') dateIntervals = tablet;
1892			// If mobile is detected, set dateIntervals to mobile
1893			else if (checkMQ == 'mobile') dateIntervals = mobile;
1894			
1895			// If dateIntervals options are set to below the minimum value, then change it.
1896			if (dateIntervals < minimumInterval) dateIntervals = minimumInterval;
1897		},
1898
1899		_checkMQ: function () {
1900			// Check for mobile, table or desktop device
1901			// https://stackoverflow.com/a/14913306/2358222
1902			return window.getComputedStyle(this.element,':before').content.replace(/'/g, "").replace(/"/g, "");
1903		},
1904
1905		//** Button States **//
1906		_buttonStates: function (timelineComponents, translateValue, totalTranslateValue){
1907			var nextButton = timelineComponents['timelineNavigation'].find('.next'),
1908				prevButton = timelineComponents['timelineNavigation'].find('.prev'),
1909
1910				leftButton = timelineComponents['timelineNavigation'].find('.scroll-left'),
1911				rightButton = timelineComponents['timelineNavigation'].find('.scroll-right'),
1912
1913				firstEvent = timelineComponents['timelineWrapper'].find('.first'),
1914				lastEvent = timelineComponents['timelineWrapper'].find('.last'),
1915				// Get the wrapper width
1916				wrapperWidth = timelineComponents['timelineWrapper'].width(),
1917				// Get the width value of the events (previously set)
1918				timelineTotalWidth = timelineComponents['eventsWrapper'].width();
1919
1920			// If wrapper width equals the timeline total width,
1921			// then disable both scroll left and right buttons
1922			if(wrapperWidth == timelineTotalWidth){
1923				leftButton.addClass('inactive');
1924				rightButton.addClass('inactive');
1925			}
1926
1927			/* Prev/Next buttons */
1928			if (timelineComponents['eventsContentList'].length == 1) {
1929				prevButton.addClass('inactive');
1930				nextButton.addClass('inactive');
1931			}
1932			else {
1933				// If first event is selected, then disable the prev button
1934				if(firstEvent.is('.selected')) prevButton.addClass('inactive');
1935				// If not, then enable the prev button
1936				else prevButton.removeClass('inactive');
1937
1938				// If last event is selected, then disable the next button
1939				if(lastEvent.is('.selected')) nextButton.addClass('inactive');
1940				// If not, then enable the next button
1941				else nextButton.removeClass('inactive');
1942			}
1943			/* Scroll left/right buttons */
1944
1945			// If translate value equals zero, it's the start of the timeline,
1946			// so disable the scroll left button
1947			if (translateValue == 0) leftButton.addClass('inactive');
1948			// If not, then enable the scroll left button
1949			else leftButton.removeClass('inactive');
1950
1951			// If translate value equals to the total translate value, it's the end of the timeline,
1952			// so disable the scroll right button
1953			if (translateValue == totalTranslateValue) rightButton.addClass('inactive');
1954			// If not, then enable the scroll right button
1955			else rightButton.removeClass('inactive');
1956		}, // End _buttonStates() function
1957
1958		// Function to add required js and css files dynamically
1959		// (CDN URL of the plugin, file type JS or CSS, callback function)
1960		_addFile: function (url, type, callback) {
1961			// If addRequiredFile is true...
1962			if (this.settings.addRequiredFile == true) {
1963					// Set loadedFile variable as body data of the loadedfile array, to check against later
1964				var loadedFile = $('body').data('plugin_'+ this._name +'_loadedFile'),
1965					// Declare an empty variable
1966					fileExists,
1967					// Variables for script and style
1968					js = type == 'js',
1969					css = type == 'css',
1970					// Get the name from the url
1971					strip = url.split('libs/'),
1972					strip = strip[1].split('/'),
1973					name = strip[0];
1974
1975				// If js, check if the name is in a src attribute in a <script> tag
1976				if(js) fileExists = $('script[src*="'+name+'"]');
1977
1978				// Else if css, check if the name is in a href attribute in a <link> tag
1979				else if (css) fileExists = $('link[href*="'+name+'"]');
1980
1981				// If loadedFile is undefined/not set, create a new array for the loaded files.
1982				if (typeof loadedFile === 'undefined' || loadedFile === null) loadedFile = new Array();
1983
1984				// If loadedFile array doesn't include the url AND
1985				// the file doesn't exist in the document...
1986
1987				// Using !loadedFile.includes(url) would be more ideal,
1988				// but due to no support in IE11, we can't use it.
1989				if (loadedFile.indexOf(url) == -1 && !fileExists.length) {
1990
1991					// File isn't loaded yet...
1992					// If adding js...
1993					if(js) {
1994						console.groupCollapsed(name + ' on ' + this.$element.attr('id') + " timeline");
1995						console.log('The plugin isn\'t loaded.');
1996
1997						// Load the plugin dynamically via Ajax.
1998						$.getScript(url)
1999							.done(function(script, textStatus) {
2000								// Then execute it via the callback option
2001								// Check if callback is a function, if it is then set a variable as the callback to be called.
2002								if (typeof callback === "function") callback(this);
2003							})
2004							.fail(function(jqxhr, settings, exception) {
2005								console.error("Failed to get " + url + "\n" + jqxhr + "\n" + this.settings + "\n" + exception);
2006							}); // End $.getScript function
2007
2008						console.log('It was loaded dynamically.');
2009					}
2010					// Else if adding CSS...
2011					else if (css) {
2012						console.groupCollapsed(name);
2013						console.log('The plugin isn\'t loaded.');
2014
2015						// Add a the CSS file in a new <link> after the last <link> in the head.
2016						$('<link>').attr({'href':url, 'rel':'stylesheet', 'type':"text/css"}).insertAfter(
2017							$('head').find('link').last()
2018						);
2019
2020						console.log('It was loaded dynamically.');
2021					}
2022					// Push/add the url to the loadedFile array to check against.
2023					loadedFile.push(url);
2024
2025				}
2026				// Else if the file exists in the document AND
2027				// the URL isn't in the loadedFile array...
2028
2029				// Using !loadedFile.includes(url) would be more ideal,
2030				// but due to no support in IE11, we can't use it.
2031				else if (fileExists.length && loadedFile.indexOf(url) == -1) {
2032					// The file is already loaded in the document via a <script> tag...
2033					if(js) {
2034						console.groupCollapsed(name + ' on ' + this.$element.attr('id') + " timeline");
2035						console.log('The plugin has already been loaded in the document via a <script> tag, no need to load it again.');
2036
2037						// Execute the plugin via the callback option.
2038						// Check if callback is a function, if it is then set a variable as the callback to be called.
2039						if (typeof callback === "function") callback(this);
2040					}
2041					// Push/add the url to the loadedFile array to check against.
2042					loadedFile.push(url);
2043				}
2044				// Else the plugin has already been loaded...
2045				else {
2046					if(js) {
2047						console.groupCollapsed(name + ' on ' + this.$element.attr('id') + " timeline");
2048						console.log('The plugin has already been loaded, no need to load it again.');
2049
2050						// Execute the plugin via the callback option.
2051						// Check if callback is a function, if it is then set a variable as the callback to be called.
2052						if (typeof callback === "function") callback(this);
2053					}
2054				}
2055
2056				if(js) {
2057					console.log('Executed on:', this.$element);
2058				}
2059				console.groupEnd();
2060
2061				// Save the loadedFile array as data to the body to be able to reload it next time it's accessed.
2062				$('body').data('plugin_'+ this._name +'_loadedFile', loadedFile);
2063			} // End if addRequiredFile statement.
2064			// If addRequiredFile is false we just need to execute the plugin via the callback option.
2065			else {
2066				// Check if callback is a function, if it is then set a variable as the callback to be called.
2067				if (typeof callback === "function") callback(this);
2068			}
2069		} // End addFile function
2070 	}; // End Timeline Prototype
2071
2072	// A really lightweight plugin wrapper around the constructor,
2073	// preventing against multiple instantiations and allowing any
2074	// public function (ie. a function whose name doesn't start
2075	// with an underscore) to be called via the jQuery plugin,
2076	// e.g. $(element).defaultPluginName('functionName', arg1, arg2)
2077	$.fn[pluginName] = function (options) {
2078		var args = arguments,
2079			windowWidth = $(window).width(),
2080		    	// data-date deprecated as of v2.0.5.alpha.3
2081			// and will be removed in a later major version.
2082			dataAttribute = ($(this).find('li').data('horizontal-timeline')) ? "data-horizontal-timeline": "data-date",
2083		    
2084			dateExists = $(this).find('.events-content').find('li['+ dataAttribute +']').map(function() {
2085				if ($(this).data('horizontal-timeline')) {
2086					var data = $(this).data('horizontal-timeline');
2087
2088					return data.date;
2089				}
2090				// data-date deprecated as of v2.0.5.alpha.3
2091				// and will be removed in a later major version.
2092				else {
2093					var dataDate = $(this).data('date');
2094
2095					return dataDate;
2096				}
2097			}).get();
2098
2099		// Is the first parameter an object (options), or was omitted,
2100		// instantiate a new instance of the plugin.
2101		if (options === undefined || typeof options === 'object') {
2102			return this.each(function () {
2103
2104				// Only allow the plugin to be instantiated once,
2105				// so we check that the element has no plugin instantiation yet
2106				if (!$.data(this, 'plugin_' + pluginName)) {
2107
2108					// if it has no instance, create a new one,
2109					// pass options to our plugin constructor,
2110					// and store the plugin instance
2111					// in the elements jQuery data object.
2112					$.data(this, 'plugin_' + pluginName, {
2113						'originalEventsContent': $(this).find('.events-content').clone()[0],
2114						'windowWidth': windowWidth,
2115						'existingDates': dateExists,
2116						'Timeline': new Timeline(this, options)
2117					});
2118					if (options !== undefined && options.autoplay == true) { // changed
2119						autoplayObj = {
2120							"isPaused": false,
2121							"mouseEvent": false
2122						};
2123
2124						$(this).data('plugin_'+ pluginName)['autoplay'] = autoplayObj;
2125					}
2126				}
2127			});
2128
2129		// If the first parameter is a string and it doesn't start
2130		// with an underscore or "contains" the `init`-function,
2131		// treat this as a call to a public method.
2132		} 
2133		else if (typeof options === 'string' && options[0] !== '_' && options !== 'init') {
2134			// Cache the method call
2135			// to make it possible
2136			// to return a value
2137			var returns;
2138
2139			this.each(function () {
2140				var instance = $.data(this, 'plugin_' + pluginName)['Timeline'];
2141
2142				// Tests that there's already a plugin-instance
2143				// and checks that the requested public method exists
2144				if (instance instanceof Timeline && typeof instance[options] === 'function') {
2145
2146					// Call the method of our plugin instance,
2147					// and pass it the supplied arguments.
2148					returns = instance[options].apply( instance, Array.prototype.slice.call( args, 1 ));
2149				}
2150			});
2151
2152			// If the earlier cached method
2153			// gives a value back return the value,
2154			// otherwise return this to preserve chainability.
2155			return returns !== undefined ? returns : this;
2156		}
2157	};
2158
2159})( jQuery, window, document );

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.