PageSourceSearch

https://acwapower.com/scripts/horizontal_timeline.2.0_v2.0.5.3.js?v=639002447389292465

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