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