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