I'm creating a prototype and want to use some of the Metronic placeholders on my dashboard.
<a class="postlink" href="http://keenthemes.com/preview/metronic/theme/admin_4/dashboard_3.html">http://keenthemes.com/preview/metronic/ ... ard_3.html</a>
After loading this page, I view source and copy the html tags I need. Most work, but the Timeline does not load correctly. It is the 4 row down, Activities and Events. When I inspect using Google Chrome, I don't get any error messages.
Any advice would be appreciated. Thanks.
15 Answer(s)
-
0
Disregard. I forgot the ../assets/global/plugins/horizontal-timeline/horizontal-timeline.js
Any advice on how to convert this to a .min.js?
-
0
Hi,
You can use bundler & minifier visual studio extension <a class="postlink" href="https://visualstudiogallery.msdn.microsoft.com/9ec27da7-e24b-4d56-8064-fd7e88ac1c40(">https://visualstudiogallery.msdn.micros ... e88ac1c40(</a>) to minify css files.
-
0
Hello, I'm facing the same problem... as I am very new with javascript, can you please precise where to add ../assets/global/plugins/horizontal-timeline/horizontal-timeline.js ?
Is it on angular-cli.json within "scripts" list ?
-
0
Hi @Ricavir,
Yes, you should put it in angular-cli.json's scripts list. Also be sure that this file is already in metronic folders. As I remember it is not included by default. You should copy it from metronic to your angular project's either "external_libs" folder or "/src/assets/metronic/" folder.
-
0
Hi,
I try and tried again. no way, I don't manage to display this horizontal timeline :? I added "../src/assets/metronic/global/plugins/horizontal-timeline/horizontal-timeline.js" on the scripts list of angular-cli.json and grabbed the javascript file on metronic site.
Do I have to add something else ? I really need to display this timeline for my customer :cry:
Any idea ?
-
0
Hi,
Do you have any javascript error or it just does not work ? Does your timeline html element has "cd-horizontal-timeline" css class ?
-
0
I just have one warning on tooltip.directive.js:65 but not related with timeline: "tooltipPlacement was deprecated, please use
placement
instead" I have the "cd-horizontal-timeline" css class in my html element (since I get HTML source code from metronic web site) And, of course, I added the line "../src/assets/metronic/global/plugins/horizontal-timeline/horizontal-timeline.js" to scripts list in angular-cli.json ; and put the javascript file "horizontal-timeline.js" in the right folder. But still nothing displayed : I just have the portlet and title displayed in the page, nothing inside. -
0
An other clue : I've tested something directly in css file > I changed initial opacity from 0 to 1. Timeline is displayed but items are not placed and buttons not working. I can see in the browser debugger that the javascript file "horizontal-timeline.js" is loaded.
But the javascript is still not "linked" to the page...I don't understand why.
-
0
Still working on this issue : the javascript initialisation is done before HTML file is loaded therefore, timeline component is not wired to javascript. This is why nothing is displayed because opacity is set to 0 by default.
Do you know a workarround/tip to make this component work ? I didn't test all metronic components, but it might the case with other ones.
<cite>JoshYates: </cite> I forgot the ../assets/global/plugins/horizontal-timeline/horizontal-timeline.js
@JoshYates : can you please precise if your timeline is working with an angular2 project ?
-
0
Hi,
I have checked the code for horozontal-timeline.js and it is not a jqueyr plugin. So it works before your html is ready.
1- To fix this problem, you can convert it to an angular directive 2- Or you can wrap it in a function of your component and call it in ngAfterViewInit.
-
0
Ok, tks for the hint ! I will do this and keep informed.
-
0
Hi,
I choose the first solution : convert timeline with angular directive. I started from here : <a class="postlink" href="https://github.com/BlackBoys/angular-horizontal-timeline">https://github.com/BlackBoys/angular-ho ... l-timeline</a>
I added the file timeline-directive.js (and added a reference to it on angular.cli) :
(function () { appModule .directive('timeLine', function ($timeout) { return { restrict: 'A', link: function (scope, element, attrs) { //angular.element(element).css('border', '5px solid red'); scope.$on('LastElem', function (event) { var timelines = element; eventsMinDistance = timelines.data('spacing'); var timeline = timelines, timelineComponents = {}; //cache timeline components timelineComponents['timelineWrapper'] = timeline.find('.events-wrapper'); timelineComponents['eventsWrapper'] = timelineComponents['timelineWrapper'].children('.events'); timelineComponents['fillingLine'] = timelineComponents['eventsWrapper'].children('.filling-line'); timelineComponents['timelineEvents'] = timelineComponents['eventsWrapper'].find('a'); //timelineComponents['timelineDates'] = parseDate(scope.nodes); timelineComponents['timelineDates'] = parseDate(timelineComponents['timelineEvents']); timelineComponents['eventsMinLapse'] = minLapse(timelineComponents['timelineDates']); timelineComponents['timelineNavigation'] = timeline.find('.cd-timeline-navigation'); timelineComponents['eventsContent'] = timeline.children('.events-content'); //assign a left postion to the single events along the timeline setDatePosition(timelineComponents, eventsMinDistance); //assign a width to the timeline var timelineTotWidth = setTimelineWidth(timelineComponents, eventsMinDistance); //the timeline has been initialize - show it timeline.addClass('loaded'); scope.$watch( // This function returns the value being watched. It is called for each turn of the $digest loop function () { return scope.nodes.length; }, // This is the change listener, called when the value returned from the above function changes function (newValue, oldValue) { if (newValue !== oldValue) { console.log('change'); $timeout(function () { timelineComponents['timelineWrapper'] = timeline.find('.events-wrapper'); timelineComponents['eventsWrapper'] = timelineComponents['timelineWrapper'].children('.events'); timelineComponents['fillingLine'] = timelineComponents['eventsWrapper'].children('.filling-line'); timelineComponents['timelineEvents'] = timelineComponents['eventsWrapper'].find('a'); timelineComponents['timelineDates'] = parseDate(timelineComponents['timelineEvents']); timelineComponents['eventsMinLapse'] = minLapse(timelineComponents['timelineDates']); timelineComponents['timelineNavigation'] = timeline.find('.cd-timeline-navigation'); timelineComponents['eventsContent'] = timeline.children('.events-content'); setDatePosition(timelineComponents, eventsMinDistance); timelineTotWidth = setTimelineWidth(timelineComponents, eventsMinDistance); }); // Only increment the counter if the value changed //scope.foodCounter = scope.foodCounter + 1; } } ); //detect click on the next arrow timelineComponents['timelineNavigation'].on('click', '.next', function (event) { event.preventDefault(); updateSlide(timelineComponents, timelineTotWidth, 'next'); }); //detect click on the prev arrow timelineComponents['timelineNavigation'].on('click', '.prev', function (event) { event.preventDefault(); updateSlide(timelineComponents, timelineTotWidth, 'prev'); }); //detect click on the a single event - show new event content timelineComponents['eventsWrapper'].on('click', 'a', function (event) { event.preventDefault(); timelineComponents['timelineEvents'].removeClass('selected'); $(this).addClass('selected'); updateOlderEvents($(this)); updateFilling($(this), timelineComponents['fillingLine'], timelineTotWidth); updateVisibleContent($(this), timelineComponents['eventsContent']); }); //on swipe, show next/prev event content timelineComponents['eventsContent'].on('swipeleft', function () { var mq = checkMQ(); (mq == 'mobile') && showNewContent(timelineComponents, timelineTotWidth, 'next'); }); timelineComponents['eventsContent'].on('swiperight', function () { var mq = checkMQ(); (mq == 'mobile') && showNewContent(timelineComponents, timelineTotWidth, 'prev'); }); //keyboard navigation $(document).keyup(function (event) { if (event.which == '37' && elementInViewport(timeline.get(0))) { showNewContent(timelineComponents, timelineTotWidth, 'prev'); } else if (event.which == '39' && elementInViewport(timeline.get(0))) { showNewContent(timelineComponents, timelineTotWidth, 'next'); } }); }); } } }) .directive('timeLineLi', function ($timeout) { return { restrict: 'A', link: function (scope, element, attrs) { console.dir(scope); if (scope.$last) { $timeout(function () { scope.$emit('LastElem'); }); } //scope.$watch('thing', function () { // var r = (Math.random() * 255).toFixed(0); // var g = (Math.random() * 255).toFixed(0); // var b = (Math.random() * 255).toFixed(0); // angular.element(element).css('color', 'rgb(' + r + ',' + g + ',' + b + ')'); //}); } } }); })();
I also changed horizontal-timeline.js like that :
function initTimeline(timelines) { timelines.each(function () { console.info("passage timeline"); eventsMinDistance = $(this).data('spacing'); var timeline = $(this), timelineComponents = {}; //cache timeline components timelineComponents['timelineWrapper'] = timeline.find('.events-wrapper'); timelineComponents['eventsWrapper'] = timelineComponents['timelineWrapper'].children('.events'); timelineComponents['fillingLine'] = timelineComponents['eventsWrapper'].children('.filling-line'); timelineComponents['timelineEvents'] = timelineComponents['eventsWrapper'].find('a'); timelineComponents['timelineDates'] = parseDate(timelineComponents['timelineEvents']); timelineComponents['eventsMinLapse'] = minLapse(timelineComponents['timelineDates']); timelineComponents['timelineNavigation'] = timeline.find('.cd-timeline-navigation'); timelineComponents['eventsContent'] = timeline.children('.events-content'); //assign a left postion to the single events along the timeline setDatePosition(timelineComponents, eventsMinDistance); //assign a width to the timeline var timelineTotWidth = setTimelineWidth(timelineComponents, eventsMinDistance); //the timeline has been initialize - show it timeline.addClass('loaded'); //detect click on the next arrow timelineComponents['timelineNavigation'].on('click', '.next', function(event){ event.preventDefault(); updateSlide(timelineComponents, timelineTotWidth, 'next'); }); //detect click on the prev arrow timelineComponents['timelineNavigation'].on('click', '.prev', function(event){ event.preventDefault(); updateSlide(timelineComponents, timelineTotWidth, 'prev'); }); //detect click on the a single event - show new event content timelineComponents['eventsWrapper'].on('click', 'a', function(event){ event.preventDefault(); timelineComponents['timelineEvents'].removeClass('selected'); $(this).addClass('selected'); updateOlderEvents($(this)); updateFilling($(this), timelineComponents['fillingLine'], timelineTotWidth); updateVisibleContent($(this), timelineComponents['eventsContent']); }); //on swipe, show next/prev event content timelineComponents['eventsContent'].on('swipeleft', function(){ var mq = checkMQ(); ( mq == 'mobile' ) && showNewContent(timelineComponents, timelineTotWidth, 'next'); }); timelineComponents['eventsContent'].on('swiperight', function(){ var mq = checkMQ(); ( mq == 'mobile' ) && showNewContent(timelineComponents, timelineTotWidth, 'prev'); }); //keyboard navigation $(document).keyup(function(event){ if(event.which=='37' && elementInViewport(timeline.get(0)) ) { showNewContent(timelineComponents, timelineTotWidth, 'prev'); } else if( event.which=='39' && elementInViewport(timeline.get(0))) { showNewContent(timelineComponents, timelineTotWidth, 'next'); } }); }); } function updateSlide(timelineComponents, timelineTotWidth, string) { //retrieve translateX value of timelineComponents['eventsWrapper'] var translateValue = getTranslateValue(timelineComponents['eventsWrapper']), wrapperWidth = Number(timelineComponents['timelineWrapper'].css('width').replace('px', '')); //translate the timeline to the left('next')/right('prev') (string == 'next') ? translateTimeline(timelineComponents, translateValue - wrapperWidth + eventsMinDistance, wrapperWidth - timelineTotWidth) : translateTimeline(timelineComponents, translateValue + wrapperWidth - eventsMinDistance); } function showNewContent(timelineComponents, timelineTotWidth, string) { //go from one event to the next/previous one var visibleContent = timelineComponents['eventsContent'].find('.selected'), newContent = ( string == 'next' ) ? visibleContent.next() : visibleContent.prev(); if ( newContent.length > 0 ) { //if there's a next/prev event - show it var selectedDate = timelineComponents['eventsWrapper'].find('.selected'), newEvent = ( string == 'next' ) ? selectedDate.parent('li').next('li').children('a') : selectedDate.parent('li').prev('li').children('a'); updateFilling(newEvent, timelineComponents['fillingLine'], timelineTotWidth); updateVisibleContent(newEvent, timelineComponents['eventsContent']); newEvent.addClass('selected'); selectedDate.removeClass('selected'); updateOlderEvents(newEvent); updateTimelinePosition(string, newEvent, timelineComponents); } } function updateTimelinePosition(string, event, timelineComponents) { //translate timeline to the left/right according to the position of the selected event var eventStyle = window.getComputedStyle(event.get(0), null), eventLeft = Number(eventStyle.getPropertyValue("left").replace('px', '')), timelineWidth = Number(timelineComponents['timelineWrapper'].css('width').replace('px', '')), timelineTotWidth = Number(timelineComponents['eventsWrapper'].css('width').replace('px', '')); var timelineTranslate = getTranslateValue(timelineComponents['eventsWrapper']); if( (string == 'next' && eventLeft > timelineWidth - timelineTranslate) || (string == 'prev' && eventLeft < - timelineTranslate) ) { translateTimeline(timelineComponents, - eventLeft + timelineWidth/2, timelineWidth - timelineTotWidth); } } function translateTimeline(timelineComponents, value, totWidth) { var eventsWrapper = timelineComponents['eventsWrapper'].get(0); value = (value > 0) ? 0 : value; //only negative translate value value = ( !(typeof totWidth === 'undefined') && value < totWidth ) ? totWidth : value; //do not translate more than timeline width setTransformValue(eventsWrapper, 'translateX', value+'px'); //update navigation arrows visibility (value == 0 ) ? timelineComponents['timelineNavigation'].find('.prev').addClass('inactive') : timelineComponents['timelineNavigation'].find('.prev').removeClass('inactive'); (value == totWidth ) ? timelineComponents['timelineNavigation'].find('.next').addClass('inactive') : timelineComponents['timelineNavigation'].find('.next').removeClass('inactive'); } function updateFilling(selectedEvent, filling, totWidth) { //change .filling-line length according to the selected event var eventStyle = window.getComputedStyle(selectedEvent.get(0), null), eventLeft = eventStyle.getPropertyValue("left"), eventWidth = eventStyle.getPropertyValue("width"); eventLeft = Number(eventLeft.replace('px', '')) + Number(eventWidth.replace('px', ''))/2; var scaleValue = eventLeft/totWidth; setTransformValue(filling.get(0), 'scaleX', scaleValue); } function setDatePosition(timelineComponents, min) { for (i = 0; i < timelineComponents['timelineDates'].length; i++) { var distance = daydiff(timelineComponents['timelineDates'][0], timelineComponents['timelineDates'][i]), distanceNorm = Math.round(distance/timelineComponents['eventsMinLapse']) + 2; timelineComponents['timelineEvents'].eq(i).css('left', distanceNorm*min+'px'); } } function setTimelineWidth(timelineComponents, width) { var timeSpan = daydiff(timelineComponents['timelineDates'][0], timelineComponents['timelineDates'][timelineComponents['timelineDates'].length-1]), timeSpanNorm = timeSpan/timelineComponents['eventsMinLapse'], timeSpanNorm = Math.round(timeSpanNorm) + 4, totalWidth = timeSpanNorm*width; timelineComponents['eventsWrapper'].css('width', totalWidth+'px'); updateFilling(timelineComponents['eventsWrapper'].find('a.selected'), timelineComponents['fillingLine'], totalWidth); updateTimelinePosition('next', timelineComponents['eventsWrapper'].find('a.selected'), timelineComponents); return totalWidth; } function updateVisibleContent(event, eventsContent) { var eventDate = event.data('date'), visibleContent = eventsContent.find('.selected'), selectedContent = eventsContent.find('[data-date="'+ eventDate +'"]'), selectedContentHeight = selectedContent.height(); if (selectedContent.index() > visibleContent.index()) { var classEnetering = 'selected enter-right', classLeaving = 'leave-left'; } else { var classEnetering = 'selected enter-left', classLeaving = 'leave-right'; } selectedContent.attr('class', classEnetering); visibleContent.attr('class', classLeaving).one('webkitAnimationEnd oanimationend msAnimationEnd animationend', function(){ visibleContent.removeClass('leave-right leave-left'); selectedContent.removeClass('enter-left enter-right'); }); eventsContent.css('height', selectedContentHeight+'px'); } function updateOlderEvents(event) { event.parent('li').prevAll('li').children('a').addClass('older-event').end().end().nextAll('li').children('a').removeClass('older-event'); } function getTranslateValue(timeline) { var timelineStyle = window.getComputedStyle(timeline.get(0), null), timelineTranslate = timelineStyle.getPropertyValue("-webkit-transform") || timelineStyle.getPropertyValue("-moz-transform") || timelineStyle.getPropertyValue("-ms-transform") || timelineStyle.getPropertyValue("-o-transform") || timelineStyle.getPropertyValue("transform"); if( timelineTranslate.indexOf('(') >=0 ) { var timelineTranslate = timelineTranslate.split('(')[1]; timelineTranslate = timelineTranslate.split(')')[0]; timelineTranslate = timelineTranslate.split(','); var translateValue = timelineTranslate[4]; } else { var translateValue = 0; } return Number(translateValue); } function setTransformValue(element, property, value) { element.style["-webkit-transform"] = property+"("+value+")"; element.style["-moz-transform"] = property+"("+value+")"; element.style["-ms-transform"] = property+"("+value+")"; element.style["-o-transform"] = property+"("+value+")"; element.style["transform"] = property+"("+value+")"; } //based on http://stackoverflow.com/questions/542938/how-do-i-get-the-number-of-days-between-two-dates-in-javascript function parseDate(events) { var dateArrays = []; events.each(function(){ var singleDate = $(this), dateComp = singleDate.data('date').split('T'); if( dateComp.length > 1 ) { //both DD/MM/YEAR and time are provided var dayComp = dateComp[0].split('/'), timeComp = dateComp[1].split(':'); } else if( dateComp[0].indexOf(':') >=0 ) { //only time is provide var dayComp = ["2000", "0", "0"], timeComp = dateComp[0].split(':'); } else { //only DD/MM/YEAR var dayComp = dateComp[0].split('/'), timeComp = ["0", "0"]; } var newDate = new Date(dayComp[2], dayComp[1]-1, dayComp[0], timeComp[0], timeComp[1]); dateArrays.push(newDate); }); return dateArrays; } function daydiff(first, second) { return Math.round((second-first)); } function minLapse(dates) { //determine the minimum distance among events var dateDistances = []; for (i = 1; i < dates.length; i++) { var distance = daydiff(dates[i-1], dates[i]); dateDistances.push(distance); } return Math.min.apply(null, dateDistances); } /* How to tell if a DOM element is visible in the current viewport? http://stackoverflow.com/questions/123999/how-to-tell-if-a-dom-element-is-visible-in-the-current-viewport */ function elementInViewport(el) { var top = el.offsetTop; var left = el.offsetLeft; var width = el.offsetWidth; var height = el.offsetHeight; while(el.offsetParent) { el = el.offsetParent; top += el.offsetTop; left += el.offsetLeft; } return ( top < (window.pageYOffset + window.innerHeight) && left < (window.pageXOffset + window.innerWidth) && (top + height) > window.pageYOffset && (left + width) > window.pageXOffset ); } function checkMQ() { //check if mobile or desktop device return window.getComputedStyle(document.querySelector('.cd-horizontal-timeline'), '::before').getPropertyValue('content').replace(/'/g, "").replace(/"/g, ""); }
But I'm having a javascript reference error coming from timeline-directive.js : "appModule is not defined"
Do you see something wrong in this approach ?
-
0
Hi,
It seems like you have created a directive for angular1. Sorry, it is my fault that I didn't direct you very well to solution.
You need to create a component because you are using angular2 version. You can create a similar one to <a class="postlink" href="https://github.com/aspnetzero/aspnet-zero-core/blob/dev/angular/src/app/shared/common/timing/date-range-picker.component.ts">https://github.com/aspnetzero/aspnet-ze ... mponent.ts</a>
Please let me know if you face any other problems.
-
0
Hello,
Just wanted to share the horizontal-timeline integration with Angular2. I added input/output decorators to the component to make it more generic and re-usable. Also created a specific object for timeline items.
Here is the HTML component :
<div class="cd-horizontal-timeline mt-timeline-horizontal" data-spacing="60"> <div class="timeline"> <div class="events-wrapper"> <div class="events"> <ol> <li *ngFor="let timelineItem of timelineItems"> <a href="#0" id="{{timelineItem.id}}" class="border-after-red bg-after-red " [ngClass]="{'selected' : timelineItem.id===selectedItemId}">{{timelineItem.title}}</a> </li> </ol> <span class="filling-line bg-red" aria-hidden="true"></span> </div> </div> <ul class="cd-timeline-navigation mt-ht-nav-icon"> <li> <a href="#0" class="prev inactive btn btn-outline red md-skip"> <i class="fa fa-chevron-left"></i> </a> </li> <li> <a href="#0" class="next btn btn-outline red md-skip"> <i class="fa fa-chevron-right"></i> </a> </li> </ul> </div> <div class="events-content"> <ol> <li *ngFor="let timelineItem of timelineItems" [ngClass]="{'selected' : timelineItem.id===selectedItemId}" id="{{timelineItem.id}}"> <div class="mt-title"> <h2 class="mt-content-title">{{timelineItem.title}}</h2> </div> <div class="mt-author"> <p class="btn btn-circle btn-icon-only blue"> <i class="{{timelineItem.icon}}"></i> </p> <div class="mt-author-name"> <a href="javascript:;" class="font-blue-madison">{{timelineItem.title}}</a> </div> <div class="mt-author-datetime font-grey-mint">{{timelineItem.date}}</div> </div> <div class="clearfix"></div> <div class="mt-content border-grey-steel"> <p> {{timelineItem.description}} </p> <a href="javascript:;" class="btn btn-circle btn-icon-only blue" (click)="timelineItemClick(timelineItem)"> <i class="fa fa-plus"></i> </a> </div> </li> </ol> </div> </div>
The typescript file for horizontal timeline :
import { Component, AfterViewInit, Injector, Input, Output, EventEmitter, ViewEncapsulation } from '@angular/core'; import { AppComponentBase } from '@shared/common/app-component-base'; import { TimelineItem } from './timeline-item'; import * as moment from 'moment'; @Component({ selector: 'timeline', templateUrl: './timeline.component.html', encapsulation: ViewEncapsulation.None }) export class TimelineComponent extends AppComponentBase implements AfterViewInit { @Input() timelineItems: TimelineItem[]; @Input() selectedItemId: number = 0; @Output() timelineItemClicked = new EventEmitter<TimelineItem>(); constructor( injector: Injector ) { super(injector); } ngAfterViewInit(): void { var timelines = $('.cd-horizontal-timeline'); (timelines.length > 0 && this.timelineItems.length > 0) && initTimeline(timelines, this.timelineItems, this.selectedItemId); } timelineItemClick(item: TimelineItem) { this.timelineItemClicked.emit(item); } } function initTimeline(timelines, items, selectedItemId) { timelines.each(function () { var eventsMinDistance = $(this).data('spacing'); var timeline = $(this), timelineComponents = {}; //cache timeline components timelineComponents['timelineWrapper'] = timeline.find('.events-wrapper'); timelineComponents['eventsWrapper'] = timelineComponents['timelineWrapper'].children('.events'); timelineComponents['fillingLine'] = timelineComponents['eventsWrapper'].children('.filling-line'); timelineComponents['timelineEvents'] = timelineComponents['eventsWrapper'].find('a'); timelineComponents['timelineDates'] = parseDate(items); timelineComponents['eventsMinLapse'] = minLapse(timelineComponents['timelineDates']); timelineComponents['timelineNavigation'] = timeline.find('.cd-timeline-navigation'); timelineComponents['eventsContent'] = timeline.children('.events-content'); //assign a left postion to the single events along the timeline setDatePosition(timelineComponents, eventsMinDistance); //assign a width to the timeline var timelineTotWidth = setTimelineWidth(timelineComponents, eventsMinDistance, selectedItemId); //the timeline has been initialize - show it timeline.addClass('loaded'); //detect click on the next arrow timelineComponents['timelineNavigation'].on('click', '.next', function (event) { event.preventDefault(); updateSlide(timelineComponents, timelineTotWidth, 'next', eventsMinDistance); }); //detect click on the prev arrow timelineComponents['timelineNavigation'].on('click', '.prev', function (event) { event.preventDefault(); updateSlide(timelineComponents, timelineTotWidth, 'prev', eventsMinDistance); }); //detect click on the a single event - show new event content timelineComponents['eventsWrapper'].on('click', 'a', function (event) { event.preventDefault(); timelineComponents['timelineEvents'].removeClass('selected'); $(this).addClass('selected'); updateOlderEvents($(this)); updateFilling($(this), timelineComponents['fillingLine'], timelineTotWidth); updateVisibleContent($(this), timelineComponents['eventsContent']); }); //on swipe, show next/prev event content timelineComponents['eventsContent'].on('swipeleft', function () { var mq = checkMQ(); (mq == 'mobile') && showNewContent(timelineComponents, timelineTotWidth, 'next'); }); timelineComponents['eventsContent'].on('swiperight', function () { var mq = checkMQ(); (mq == 'mobile') && showNewContent(timelineComponents, timelineTotWidth, 'prev'); }); //keyboard navigation $(document).keyup(function (event) { if (event.which == 37 && elementInViewport(timeline.get(0))) { showNewContent(timelineComponents, timelineTotWidth, 'prev'); } else if (event.which == 39 && elementInViewport(timeline.get(0))) { showNewContent(timelineComponents, timelineTotWidth, 'next'); } }); }); } function updateSlide(timelineComponents, timelineTotWidth, string, eventsMinDistance) { //retrieve translateX value of timelineComponents['eventsWrapper'] var translateValue = getTranslateValue(timelineComponents['eventsWrapper']), wrapperWidth = Number(timelineComponents['timelineWrapper'].css('width').replace('px', '')); //translate the timeline to the left('next')/right('prev') (string == 'next') ? translateTimeline(timelineComponents, translateValue - wrapperWidth + eventsMinDistance, wrapperWidth - timelineTotWidth) : translateTimeline(timelineComponents, translateValue + wrapperWidth - eventsMinDistance, undefined); } function showNewContent(timelineComponents, timelineTotWidth, string) { //go from one event to the next/previous one var visibleContent = timelineComponents['eventsContent'].find('.selected'), newContent = (string == 'next') ? visibleContent.next() : visibleContent.prev(); if (newContent.length > 0) { //if there's a next/prev event - show it var selectedDate = timelineComponents['eventsWrapper'].find('.selected'), newEvent = (string == 'next') ? selectedDate.parent('li').next('li').children('a') : selectedDate.parent('li').prev('li').children('a'); updateFilling(newEvent, timelineComponents['fillingLine'], timelineTotWidth); updateVisibleContent(newEvent, timelineComponents['eventsContent']); newEvent.addClass('selected'); selectedDate.removeClass('selected'); updateOlderEvents(newEvent); updateTimelinePosition(string, newEvent, timelineComponents); } } function updateTimelinePosition(string, event, timelineComponents) { //translate timeline to the left/right according to the position of the selected event var eventStyle = window.getComputedStyle(event.get(0), null), eventLeft = Number(eventStyle.getPropertyValue("left").replace('px', '')), timelineWidth = Number(timelineComponents['timelineWrapper'].css('width').replace('px', '')), timelineTotWidth = Number(timelineComponents['eventsWrapper'].css('width').replace('px', '')); var timelineTranslate = getTranslateValue(timelineComponents['eventsWrapper']); if ((string == 'next' && eventLeft > timelineWidth - timelineTranslate) || (string == 'prev' && eventLeft < - timelineTranslate)) { translateTimeline(timelineComponents, - eventLeft + timelineWidth / 2, timelineWidth - timelineTotWidth); } } function translateTimeline(timelineComponents, value, totWidth) { var eventsWrapper = timelineComponents['eventsWrapper'].get(0); value = (value > 0) ? 0 : value; //only negative translate value value = (!(typeof totWidth === 'undefined') && value < totWidth) ? totWidth : value; //do not translate more than timeline width setTransformValue(eventsWrapper, 'translateX', value + 'px'); //update navigation arrows visibility (value == 0) ? timelineComponents['timelineNavigation'].find('.prev').addClass('inactive') : timelineComponents['timelineNavigation'].find('.prev').removeClass('inactive'); (value == totWidth) ? timelineComponents['timelineNavigation'].find('.next').addClass('inactive') : timelineComponents['timelineNavigation'].find('.next').removeClass('inactive'); } function updateFilling(selectedEvent, filling, totWidth) { //change .filling-line length according to the selected event var eventStyle = window.getComputedStyle(selectedEvent.get(0), null), eventLeft = eventStyle.getPropertyValue("left"), eventWidth = eventStyle.getPropertyValue("width"); eventLeft = (Number(eventLeft.replace('px', '')) + Number(eventWidth.replace('px', '')) / 2).toString(); var scaleValue = Number(eventLeft) / totWidth; setTransformValue(filling.get(0), 'scaleX', scaleValue); } function setDatePosition(timelineComponents, min) { for (var i = 0; i < timelineComponents['timelineDates'].length; i++) { var distance = daydiff(timelineComponents['timelineDates'][0], timelineComponents['timelineDates'][i]), distanceNorm = Math.round(distance / timelineComponents['eventsMinLapse']) + 2; timelineComponents['timelineEvents'].eq(i).css('left', distanceNorm * min + 'px'); } } function setTimelineWidth(timelineComponents, width, selectedItemId) { var timeSpan = daydiff(timelineComponents['timelineDates'][0], timelineComponents['timelineDates'][timelineComponents['timelineDates'].length - 1]), timeSpanNorm = timeSpan / timelineComponents['eventsMinLapse'], timeSpanNorm = Math.round(timeSpanNorm) + 4, totalWidth = timeSpanNorm * width; timelineComponents['eventsWrapper'].css('width', totalWidth + 'px'); updateFilling(timelineComponents['eventsWrapper'].find('a#' + selectedItemId), timelineComponents['fillingLine'], totalWidth); updateTimelinePosition('next', timelineComponents['eventsWrapper'].find('a#' + selectedItemId), timelineComponents); return totalWidth; } function updateVisibleContent(event, eventsContent) { var eventId = event[0].id, visibleContent = eventsContent.find('.selected'), selectedContent = eventsContent.find('li#' + eventId), selectedContentHeight = selectedContent.height(); if (selectedContent.index() > visibleContent.index()) { var classEnetering = 'selected enter-right', classLeaving = 'leave-left'; } else { var classEnetering = 'selected enter-left', classLeaving = 'leave-right'; } selectedContent.attr('class', classEnetering); visibleContent.attr('class', classLeaving).one('webkitAnimationEnd oanimationend msAnimationEnd animationend', function () { visibleContent.removeClass('leave-right leave-left'); selectedContent.removeClass('enter-left enter-right'); }); eventsContent.css('height', selectedContentHeight + 'px'); } function updateOlderEvents(event) { event.parent('li').prevAll('li').children('a').addClass('older-event').end().end().nextAll('li').children('a').removeClass('older-event'); } function getTranslateValue(timeline) { var timelineStyle = window.getComputedStyle(timeline.get(0), null), timelineTranslate = timelineStyle.getPropertyValue("-webkit-transform") || timelineStyle.getPropertyValue("-moz-transform") || timelineStyle.getPropertyValue("-ms-transform") || timelineStyle.getPropertyValue("-o-transform") || timelineStyle.getPropertyValue("transform"); if (timelineTranslate.indexOf('(') >= 0) { var timelineTranslate = timelineTranslate.split('(')[1]; timelineTranslate = timelineTranslate.split(')')[0]; var translateValue = timelineTranslate.split(',')[4]; } else { var translateValue = "0"; } return Number(translateValue); } function setTransformValue(element, property, value) { element.style["-webkit-transform"] = property + "(" + value + ")"; element.style["-moz-transform"] = property + "(" + value + ")"; element.style["-ms-transform"] = property + "(" + value + ")"; element.style["-o-transform"] = property + "(" + value + ")"; element.style["transform"] = property + "(" + value + ")"; } //based on http://stackoverflow.com/questions/542938/how-do-i-get-the-number-of-days-between-two-dates-in-javascript function parseDate(events) { var dateArrays = []; for (var i = 0; i < events.length; i++) { var dateComp = events[i].date; var dayComp = dateComp.split('/'); var timeComp = [0, 0]; var newDate = new Date(dayComp[2], dayComp[1] - 1, dayComp[0], timeComp[0], timeComp[1]); dateArrays.push(newDate); } return dateArrays; } function daydiff(first, second) { return Math.round((second - first)); } function minLapse(dates) { //determine the minimum distance among events var dateDistances = []; for (var i = 1; i < dates.length; i++) { var distance = daydiff(dates[i - 1], dates[i]); dateDistances.push(distance); } return Math.min.apply(null, dateDistances); } /* How to tell if a DOM element is visible in the current viewport? http://stackoverflow.com/questions/123999/how-to-tell-if-a-dom-element-is-visible-in-the-current-viewport */ function elementInViewport(el) { var top = el.offsetTop; var left = el.offsetLeft; var width = el.offsetWidth; var height = el.offsetHeight; while (el.offsetParent) { el = el.offsetParent; top += el.offsetTop; left += el.offsetLeft; } return ( top < (window.pageYOffset + window.innerHeight) && left < (window.pageXOffset + window.innerWidth) && (top + height) > window.pageYOffset && (left + width) > window.pageXOffset ); } function checkMQ() { //check if mobile or desktop device return window.getComputedStyle(document.querySelector('.cd-horizontal-timeline'), '::before').getPropertyValue('content').replace(/'/g, "").replace(/"/g, ""); }
The timeline item object :
export class TimelineItem { id: number; date: string; title: string; description: string; icon: string; }
Then, just add it to your HTML template like this example :
<div class="portlet light margin-bottom-0"> <div class="portlet-body"> <timeline [timelineItems]="items" [selectedItemId]="selectedItemId" (timelineItemClicked)="timelineItemClicked($event)"></timeline> </div> </div>
Hope this can help others ;)
-
0
Hi,
Thank you for your great effort and share :)