Great ! Thanks for reactivity
Done
[https://github.com/aspnetzero/aspnet-zero-core/issues/130])
Is there anyone who could help me on this ?
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 ;)
I just tried and it works like a charm. Thank you !
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 ?
Ok, tks for the hint ! I will do this and keep informed.
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 ?
Did you planned to fix this issue ?
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.