jquery.gantt
Version:
A simple, lightweight jQuery gantt plugin.
590 lines (499 loc) • 20 kB
JavaScript
import $ from 'jquery';
import DEFAULTS from './Defaults';
import lazyload from './func/lazyload';
class Gantt {
constructor(element, options) {
const gridDefaults = {
gridtotalHeight: 0,
gridtotalWidth: 0,
eventsWidth: 0,
};
const lang = options.language || DEFAULTS.language;
// eslint-disable-next-line no-param-reassign
options.lang = Gantt.LANGUAGES[lang];
// extend defaults with the init options.
this.config = $.extend({}, DEFAULTS, options);
// grid defaults
this.gridDefaults = $.extend({}, gridDefaults);
// store main DOM element.
this.element = element;
// no data source
if (this.config.dataURL === '' && this.config.data.length <= 0) console.warn('jquery-gantt: must provide a data source');
// dataURL is required
else if (this.config.dataURL === '' && this.config.lazyLoad) console.warn('jquery-gantt: dataURL is required for lazyLoad');
// initialize
else {
this.init();
}
}
init() {
this.hook('onInit');
let html = '';
// erasing all the content from the container
$(this.element).html(html);
if (this.config.endDate.getTime() > this.config.startDate.getTime()) {
// lazy load
if (this.config.lazyLoad) {
this.initLazyLoad();
html = this.renderLazyLoadContainer();
} else if (this.config.data.length <= 0 && this.config.dataURL !== '') {
this.buildData().then((data) => {
this.config.data = data.Items;
html = this.renderContainer();
$(this.element).html(html);
this.attachEvents(this.element, this.config);
this.tooltipHover();
this.colHighlighter();
});
} else {
html = this.renderContainer();
}
if (this.config.data.length > 0 || this.config.lazyLoad) {
$(this.element).html(html);
this.attachEvents(this.element, this.config);
this.tooltipHover();
this.colHighlighter();
}
} else {
console.warn('jquery-gantt: start-date is not greater than end-date');
}
}
hook(hookName) {
if (this.config[hookName] !== undefined) {
this.config[hookName].call(this.el);
}
}
attachEvents(el, options) {
// sticky header
if (options.stickyHeader) {
const stickyH = function () {
const top = $('.gantt-wrapper', el).offset().top;
const height = $('.gantt-wrapper', el).height();
if (top >= $(this).scrollTop() || $(this).scrollTop() >= ((top + height) - 80)) {
$('.gantt-header', el).css('position', 'static');
$('.arrow', el).css('position', 'static');
} else {
$('.arrow', el).css({
position: 'relative',
top: `${$(this).scrollTop() - top}px`,
});
$('.gantt-header', el).css({
position: 'relative',
top: `${$(this).scrollTop() - top}px`,
left: `${$(this).scrollLeft}px`,
'z-index': 1,
});
}
};
$(window).scroll(stickyH);
}
// scroll page horizontally with mouse wheel
if (options.mouseScroll) {
$('.gantt-container', el).on('wheel mousewheel', function (e) {
if (e.originalEvent.wheelDelta > 0 || e.originalEvent.detail < 0) {
this.scrollLeft -= options.mouseScrollpx;
} else {
this.scrollLeft += options.mouseScrollpx;
}
});
}
if (options.lazyLoad) {
const self = this;
let loading = false;
$('.gantt-container', el).scroll(function () {
if (this.scrollWidth - this.clientWidth - this.scrollLeft < 900 && !loading) {
loading = true;
self.scrollHandler(() => {
loading = false;
});
}
});
}
// scroll drag
$('.gantt-container', el)
.on('mousedown', function (event) {
$(this).data('down', true)
.data('x', event.clientX)
.data('scrollLeft', this.scrollLeft)
.addClass('dragging');
return false;
})
.on('mouseup', function () {
$(this).data('down', false).removeClass('dragging');
})
.on('mousemove', function (event) {
if ($(this).data('down') === true) {
this.scrollLeft = ($(this).data('scrollLeft') + $(this).data('x')) - event.clientX;
}
if (options.autoHide) {
const sl = this.scrollLeft;
let daywidth = sl / options.cellWidth;
daywidth = Math.floor(daywidth);
const cWidth = daywidth * options.cellWidth;
const events = $('.gantt-events', el).find('.gantt-event');
$.each(events, (index, elem) => {
let eventLeft = $(elem).css('left');
eventLeft = parseInt(eventLeft, 10);
if (eventLeft <= cWidth) {
$(elem).closest('.gantt-event-row').hide();
} else {
$(elem).closest('.gantt-event-row').show();
}
});
const currentDay = new Date(options.startDate.getTime());
currentDay.setDate(currentDay.getDate() + daywidth);
}
})
.on('mouseleave', function () {
$(this).data('down', false).removeClass('dragging');
});
// arrow button click
$('.arrow', el).on('click', function () {
const direction = $(this).hasClass('arrow-right');
const scrollLeft = $('.gantt-container', el).scrollLeft();
if (direction) {
$('.gantt-container', el).scrollLeft(scrollLeft + 240);
} else {
$('.gantt-container', el).scrollLeft(scrollLeft - 240);
}
});
}
xhrRequest(startDate, endDate) {
const self = this;
const el = $(this.element);
const url = this.config.dataURL;
return $.ajax({
url,
dataType: 'jsonp',
jsonp: 'callback',
contentType: 'application/json',
crossDomain: true,
data: {
startDate: self.getFormattedDate(startDate),
endDate: self.getFormattedDate(endDate),
},
beforeSend() {
$('.gantt-loading', el).fadeIn();
},
});
}
scrollHandler(callback) {
const self = this;
const el = $(this.element);
const startDate = this.config.startDate;
let nextDate = new Date(this.config.startDate.getTime());
nextDate.setMonth(nextDate.getMonth() + 2);
const month = nextDate.getMonth() + 1;
const year = nextDate.getFullYear();
nextDate = this.lastDayMonth(year, month);
const monthEndDate = this.config.endDate.getMonth() + 1;
const yearEndDate = this.config.endDate.getFullYear();
const dayEndDate = this.config.endDate.getDate();
if (year === yearEndDate || year > yearEndDate) {
if (year > yearEndDate) {
nextDate.setMonth(monthEndDate - 1);
nextDate.setYear(yearEndDate);
nextDate.setDate(dayEndDate);
}
if (month === monthEndDate || month > monthEndDate) {
nextDate.setMonth(monthEndDate - 1);
nextDate.setDate(dayEndDate);
}
}
if (startDate.getTime() <= this.config.endDate.getTime()) {
this.xhrRequest(startDate, nextDate).done((data) => {
self.renderLazyLoad(data.Items, startDate, nextDate);
self.tooltipHover();
self.colHighlighter();
if (typeof callback === 'function') {
callback();
}
}).always(() => {
$('.gantt-loading', el).fadeOut();
});
}
}
renderHeader(num) {
const templateHeader = $('<div>', { class: 'gantt-header' });
const templateHeaderMonths = $('<div>', { class: 'gantt-header-months' });
const templateHeaderDays = $('<div>', { class: 'gantt-header-days' });
const templateHeaderDaysMin = $('<div>', { class: 'gantt-header-days-min' });
const totalWidth = this.getTotalWidth(num);
this.setHours();
const startDate = new Date(this.config.startDate.getTime());
for (let i = 0; i <= num; i += 1) {
const templateHeaderMonth = $('<div>', { class: 'gantt-header-month' });
const templateHeaderDay = $('<div>', { class: 'gantt-header-day' });
const templateHeaderDayMin = $('<div>', { class: 'gantt-header-day-min' });
const weekOfday = startDate.getDay();
const day = startDate.getDate();
const month = startDate.getMonth() + 1;
const year = startDate.getFullYear();
let monthWidth = this.daysInMonth(month, year) * this.config.cellWidth;
const dayTemplate = $(templateHeaderDay).text(day).css('width', this.config.cellWidth);
$(templateHeaderDays).append(dayTemplate);
const weekOfdayName = this.config.lang.dayNamesMin[weekOfday];
const dayMinTemplate = $(templateHeaderDayMin).text(weekOfdayName)
.css({ width: this.config.cellWidth });
$(templateHeaderDaysMin).append(dayMinTemplate);
const firstMonthIsNotFull = i === 0;
const firstDay = this.firstDayMonth(year, month);
const firstDayEndDateMonth = this.firstDayMonth(
this.config.endDate.getFullYear(),
this.config.endDate.getMonth() + 1);
startDate.setHours(0, 0, 0, 0);
const checkFirstDayMonth = this.isEqual(startDate, firstDay);
const checkFirstDayMonthEndDate = this.isEqual(startDate, firstDayEndDateMonth);
if (checkFirstDayMonth || firstMonthIsNotFull) {
if (firstMonthIsNotFull) {
monthWidth -= ((day - 1) * this.config.cellWidth);
}
if (checkFirstDayMonthEndDate) {
monthWidth = (this.config.endDate.getDate() * this.config.cellWidth);
}
const monthName = this.config.lang.monthNames[month - 1];
const monthTemplate = $(templateHeaderMonth).text(`${monthName} ${year}`).css({ width: monthWidth });
$(templateHeaderMonths).append(monthTemplate);
}
startDate.setDate(startDate.getDate() + 1);
}
templateHeader.append($(templateHeaderMonths).css('width', totalWidth));
templateHeader.append($(templateHeaderDays).css('width', totalWidth));
templateHeader.append($(templateHeaderDaysMin).css('width', totalWidth));
return templateHeader;
}
renderContainer() {
const template = $('<div>', { class: 'gantt-container' });
const templateWrapper = $('<div>', { class: 'gantt-wrapper' });
const arrowLeft = '<div class="arrow arrow-left"><span class="arrow-icon"></span></div>';
const arrowRight = '<div class="arrow arrow-right"><span class="arrow-icon"></span></div>';
// difference between start-date and end-date
const diffInDays = this.dateDiffInDays(this.config.startDate, this.config.endDate);
const templateHeader = this.renderHeader(diffInDays);
const templateGrid = this.renderGrid(diffInDays);
const templateEvents = this.renderEvents(diffInDays);
template.append(templateHeader).append(templateGrid).append(templateEvents);
templateWrapper.append(arrowLeft).append(template).append(arrowRight);
return templateWrapper;
}
renderGrid(num) {
const data = this.config.data;
const totalWidth = this.getTotalWidth(num);
const totalHeight = this.getTotalHeight(data.length);
const templateGrid = $('<div>', { class: 'gantt-grid', width: totalWidth });
const templateGridCols = $('<div>', { class: 'gantt-grid-cols' });
const templateGridRows = $('<div>', { class: 'gantt-grid-rows' });
const startDate = new Date(this.config.startDate.getTime());
for (let i = 0; i <= num; i += 1) {
const templateGridCol = $('<div>', { class: 'gantt-grid-col', width: this.config.cellWidth, height: totalHeight });
// firefox width problem fix
templateGridCol.css('width', this.config.cellWidth);
const month = startDate.getMonth() + 1;
const year = startDate.getFullYear();
const lastDay = this.lastDayMonth(year, month);
startDate.setHours(0, 0, 0, 0);
if (this.isEqual(startDate, lastDay)) {
templateGridCol.css('border-color', '#bec5cc');
}
templateGridCols.append(templateGridCol);
startDate.setDate(startDate.getDate() + 1);
}
data.forEach(function () {
const templateGridRow = $('<div>', { class: 'gantt-grid-row', width: totalWidth, height: this.config.cellHeight });
templateGridRows.append(templateGridRow);
}, this);
templateGrid.append(templateGridCols).append(templateGridRows);
return templateGrid;
}
renderEvents(num) {
const data = this.config.data;
const totalWidth = this.getTotalWidth(num);
// const totalHeight = this.getTotalHeight(data.length);
const templateEvents = $('<div>', { class: 'gantt-events', width: totalWidth });
data.forEach(function (el) {
const itemStartDate = new Date(el.startdate);
const templateEventRow = $('<div>', { class: 'gantt-event-row', width: this.totalWidth });
const templateEvent = $('<div>', { class: 'gantt-event' });
const tourWidth = (parseInt(el.minNight, 10) + 1) * this.config.cellWidth;
const remDay = this.dateDiffInDays(this.config.startDate, itemStartDate);
const tooltipData = $.extend(el.tooltipData, { price: el.price });
let tourType = '';
if (el.type === 'Tur') {
tourType = 'tourFly';
} else if (el.type === 'TurBus') {
tourType = 'tourBus';
} else {
tourType = 'cruise';
}
const title = `${el.minNight} Gece`;
const eventBlock = $('<a>', {
class: `gantt-event-block ${tourType}`,
width: `${tourWidth}px`,
href: `${el.url}`,
target: '_blank',
}).text(title).css('line-height', `${this.config.cellHeight - 28}px`).data('tooltip', this.tooltipView(tooltipData));
const eventIcon = $(`<div class="gantt-event-icon"><div class="${tourType}"></div></div>`);
const eventPrice = $('<div>', {
class: 'gantt-event-price',
}).text(`${el.price.original.price} ${el.price.original.priceType}`);
const eventDesc = $('<div>', {
class: 'gantt-event-desc',
}).text(el.title);
const left = ((remDay) * this.config.cellWidth);
templateEventRow
.append(templateEvent.css('left', left)
.append(eventBlock)
.append(eventIcon)
.append(eventPrice)
.append(eventDesc))
.css('height', this.config.cellHeight);
templateEvents.append(templateEventRow);
}, this);
return templateEvents;
}
getTotalWidth(day) {
return day === 0 ? 0 : (day + 1) * this.config.cellWidth;
}
getTotalHeight(len) {
return len * this.config.cellHeight;
}
setHours() {
this.config.startDate.setHours(0, 0, 0, 0);
this.config.endDate.setHours(0, 0, 0, 0);
}
firstDayMonth(year, month) {
return new Date(year, month - 1, 1);
}
lastDayMonth(year, month) {
return new Date(year, month, 0);
}
daysInMonth(month, year) {
return new Date(year, month, 0).getDate();
}
isEqual(a, b) {
return a.getTime() === b.getTime();
}
dateDiffInDays(a, b) {
const MS_PER_DAY = 1000 * 60 * 60 * 24;
const utc1 = Date.UTC(a.getFullYear(), a.getMonth(), a.getDate());
const utc2 = Date.UTC(b.getFullYear(), b.getMonth(), b.getDate());
return Math.floor((utc2 - utc1) / MS_PER_DAY);
}
getFormattedDate(date) {
const currDate = date.getDate();
const currMonth = date.getMonth() + 1;
const currYear = date.getFullYear();
return `${currYear}-${currMonth}-${currDate}`;
}
tooltipView(data) {
const template = '' +
'<div class="gantt-tooltip">' +
' <div class="tooltip-content">' +
' <img src="{0}" alt="tooltip-img">' +
' <span class="title">{1}</span>' +
' <div class="desc">' +
' {2} <br> {3} <br> {4}' +
' </div>' +
' </div>' +
' <div class="tooltip-action">' +
' <span>Gidiş: <span class="desc">{5}</span></span><br>' +
' <span>Dönüş: <span class="desc">{6}</span></span>' +
' <div class="price">' +
' <div class="tl">{7}</div>' +
' <div class="eur">{8}</div>' +
' </div>' +
' </div>' +
'</div>';
const html = this.format(template, data.image, data.title, data.desc[0], data.desc[1], data.desc[2], data.dates.begin, data.dates.end, this.format('{0} {1}', data.price.converted.price, data.price.converted.priceType), this.format('{0} {1}', data.price.original.price, data.price.original.priceType));
return html;
}
tooltipHover() {
const el = $(this.element);
const options = this.config;
// tooltip mouse enter & leave
$('.gantt-event-block', el)
.on('mouseenter', function () {
const elheight = $(el).height();
// const elwidth = $(el).width();
const eltop = $(el).offset().top;
let data = $(this).data('tooltip');
const left = $(this).offset().left;
const top = $(this).offset().top;
if (top >= ((elheight + eltop) - 168)) {
// position top
data = $(data).css({ left });
$('body').append(data);
const dheight = $('.gantt-tooltip', 'body').outerHeight();
$('.gantt-tooltip', 'body').css('top', top - dheight - 12);
} else {
// position bottom
data = $(data).css({
top: top + options.cellHeight,
left,
});
$('body').append(data);
}
$('.gantt-tooltip', 'body').hide().fadeIn(250);
})
.on('mouseleave', () => {
$('.gantt-tooltip', 'body').remove();
});
}
colHighlighter() {
const el = $(this.element);
// highlighter
$('.gantt-grid-col, .gantt-header-day, .gantt-header-day-min', el).on('mouseenter', function () {
// const grIndex = $(this).closest('.gantt-grid-col').index();
const gcIndex = $(this).index();
$('.gantt-container', el).find('.gantt-grid-col')
.removeClass('active')
.eq(gcIndex)
.addClass('active');
$('.gantt-header', el).find('.gantt-header-days').find('.gantt-header-day')
.removeClass('active')
.eq(gcIndex)
.addClass('active');
$('.gantt-container', el).find('.gantt-header').find('.gantt-header-days-min').find('.gantt-header-day-min')
.removeClass('active')
.eq(gcIndex)
.addClass('active');
});
// event hover
$('.gantt-event-block', el).on('mousemove', () => {
$('.gantt-container', el).find('.gantt-grid-col').removeClass('active');
$('.gantt-header', el).find('.gantt-header-days').find('.gantt-header-day').removeClass('active');
$('.gantt-container', el).find('.gantt-header').find('.gantt-header-days-min').find('.gantt-header-day-min')
.removeClass('active');
});
}
format() {
// eslint-disable-next-line prefer-rest-params
let theString = arguments[0];
for (let i = 1; i < arguments.length; i += 1) {
const regEx = new RegExp(`\\{${i - 1}\\}`, 'gm');
// eslint-disable-next-line prefer-rest-params
theString = theString.replace(regEx, arguments[i]);
}
return theString;
}
destroy() {
$(window).off('scroll');
this.element.empty();
this.element.removeData('plugin_Gantt');
this.hook('onDestroy');
}
static setDefaults(options) {
$.extend(DEFAULTS, options);
}
}
Gantt.LANGUAGES = {
en: {
monthNames: ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'],
monthNamesShort: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'],
dayNames: ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'],
dayNamesShort: ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'],
dayNamesMin: ['Su', 'Mo', 'Tu', 'We', 'Th', 'Fr', 'Sa'],
},
};
$.extend(Gantt.prototype, lazyload);
export default Gantt;