flectra/addons/web/static/lib/jquery.ganttView/jquery.ganttView.js

397 lines
14 KiB
JavaScript
Raw Normal View History

/*
jQuery.ganttView v.0.8.8
Copyright (c) 2010 JC Grubbs - jc.grubbs@devmynd.com
MIT License Applies
*/
/*
Options
-----------------
showWeekends: boolean
showToday: boolean
data: object
cellWidth: number
cellHeight: number
slideWidth: number
dataUrl: string
behavior: {
clickable: boolean,
draggable: boolean,
resizable: boolean,
onDblClick: function, // Modify by Flectra - Click becomes DblClick
onDrag: function,
onResize: function
}
*/
(function (jQuery) {
jQuery.fn.ganttView = function () {
var args = Array.prototype.slice.call(arguments);
if (args.length == 1 && typeof(args[0]) == "object") {
build.call(this, args[0]);
}
if (args.length == 2 && typeof(args[0]) == "string") {
handleMethod.call(this, args[0], args[1]);
}
};
function build(options) {
var els = this;
var defaults = {
showWeekends: true,
showToday: true,
cellWidth: 21,
cellHeight: 31,
slideWidth: 400,
vHeaderWidth: 100,
behavior: {
clickable: true,
draggable: true,
resizable: true
}
};
var opts = jQuery.extend(true, defaults, options);
if (opts.data) {
build();
} else if (opts.dataUrl) {
jQuery.getJSON(opts.dataUrl, function (data) { opts.data = data; build(); });
}
function build() {
var minDays = Math.floor((opts.slideWidth / opts.cellWidth) + 5);
var startEnd = DateUtils.getBoundaryDatesFromData(opts.data, minDays);
opts.start = startEnd[0];
opts.end = startEnd[1];
els.each(function () {
var container = jQuery(this);
var div = jQuery("<div>", { "class": "ganttview" });
new Chart(div, opts).render();
container.append(div);
var w = jQuery("div.ganttview-vtheader", container).outerWidth() +
jQuery("div.ganttview-slide-container", container).outerWidth();
container.css("width", (w + 2) + "px");
new Behavior(container, opts).apply();
});
}
}
function handleMethod(method, value) {
if (method == "setSlideWidth") {
var div = $("div.ganttview", this);
div.each(function () {
var vtWidth = $("div.ganttview-vtheader", div).outerWidth();
$(div).width(vtWidth + value + 1);
$("div.ganttview-slide-container", this).width(value);
});
}
}
var Chart = function(div, opts) {
function render() {
addVtHeader(div, opts.data, opts.cellHeight);
var slideDiv = jQuery("<div>", {
"class": "ganttview-slide-container",
"css": { "width": opts.slideWidth + "px" }
});
dates = getDates(opts.start, opts.end);
addHzHeader(slideDiv, dates, opts.cellWidth);
addGrid(slideDiv, opts.data, dates, opts.cellWidth, opts.showWeekends, opts.showToday);
addBlockContainers(slideDiv, opts.data);
addBlocks(slideDiv, opts.data, opts.cellWidth, opts.start);
div.append(slideDiv);
applyLastClass(div.parent());
}
var monthNames = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
// Creates a 3 dimensional array [year][month][day] of every day
// between the given start and end dates
function getDates(start, end) {
var end = end.clone().addDays(6);
var dates = [];
dates[start.getFullYear()] = [];
dates[start.getFullYear()][start.getMonth()] = [start]
var last = start;
while (last.compareTo(end) == -1) {
var next = last.clone().addDays(1);
if (!dates[next.getFullYear()]) { dates[next.getFullYear()] = []; }
if (!dates[next.getFullYear()][next.getMonth()]) {
dates[next.getFullYear()][next.getMonth()] = [];
}
dates[next.getFullYear()][next.getMonth()].push(next);
last = next;
}
return dates;
}
function addVtHeader(div, data, cellHeight) {
var headerDiv = jQuery("<div>", { "class": "ganttview-vtheader" });
for (var i = 0; i < data.length; i++) {
var itemDiv = jQuery("<div>", { "class": "ganttview-vtheader-item" });
if ($.trim(data[i].name).length > 0)
itemDiv.append(jQuery("<div>", {
"class": "ganttview-vtheader-item-name",
"css": { "height": (data[i].series.length * cellHeight) + "px" }
}).append(data[i].name));
var seriesDiv = jQuery("<div>", { "class": "ganttview-vtheader-series" });
for (var j = 0; j < data[i].series.length; j++) {
seriesDiv.append(jQuery("<div>", { "class": "ganttview-vtheader-series-name" })
.append(data[i].series[j].name));
}
itemDiv.append(seriesDiv);
headerDiv.append(itemDiv);
}
div.append(headerDiv);
}
function addHzHeader(div, dates, cellWidth) {
var headerDiv = jQuery("<div>", { "class": "ganttview-hzheader" });
var monthsDiv = jQuery("<div>", { "class": "ganttview-hzheader-months" });
var daysDiv = jQuery("<div>", { "class": "ganttview-hzheader-days" });
var totalW = 0;
for (var y in dates) {
for (var m in dates[y]) {
var w = dates[y][m].length * cellWidth;
totalW = totalW + w;
monthsDiv.append(jQuery("<div>", {
"class": "ganttview-hzheader-month",
"css": { "width": w + "px" }
}).append(monthNames[m] + "/" + y));
for (var d in dates[y][m]) {
daysDiv.append(jQuery("<div>", { "class": "ganttview-hzheader-day" })
.append(dates[y][m][d].getDate()));
}
}
}
monthsDiv.css("width", totalW + "px");
daysDiv.css("width", totalW + "px");
headerDiv.append(monthsDiv).append(daysDiv);
div.append(headerDiv);
}
function addGrid(div, data, dates, cellWidth, showWeekends, showToday) {
var gridDiv = jQuery("<div>", { "class": "ganttview-grid" });
var rowDiv = jQuery("<div>", { "class": "ganttview-grid-row" });
for (var y in dates) {
for (var m in dates[y]) {
for (var d in dates[y][m]) {
var cellDiv = jQuery("<div>", { "class": "ganttview-grid-row-cell" });
if (DateUtils.isWeekend(dates[y][m][d]) && showWeekends) {
cellDiv.addClass("ganttview-weekend");
}
if (DateUtils.isToday(dates[y][m][d]) && showToday) {
cellDiv.addClass("ganttview-today");
}
rowDiv.append(cellDiv);
}
}
}
var w = jQuery("div.ganttview-grid-row-cell", rowDiv).length * cellWidth;
rowDiv.css("width", w + "px");
gridDiv.css("width", w + "px");
for (var i = 0; i < data.length; i++) {
for (var j = 0; j < data[i].series.length; j++) {
gridDiv.append(rowDiv.clone());
}
}
div.append(gridDiv);
}
function addBlockContainers(div, data) {
var blocksDiv = jQuery("<div>", { "class": "ganttview-blocks" });
for (var i = 0; i < data.length; i++) {
for (var j = 0; j < data[i].series.length; j++) {
blocksDiv.append(jQuery("<div>", { "class": "ganttview-block-container" }));
}
}
div.append(blocksDiv);
}
function addBlocks(div, data, cellWidth, start) {
var rows = jQuery("div.ganttview-blocks div.ganttview-block-container", div);
var rowIdx = 0;
for (var i = 0; i < data.length; i++) {
for (var j = 0; j < data[i].series.length; j++) {
var series = data[i].series[j];
var size = DateUtils.daysBetween(series.start, series.end) + 1;
var offset = DateUtils.daysBetween(start, series.start);
var block = jQuery("<div>", {
"class": "ganttview-block",
"title": series.name + ", " + size + " days \n"+ series.start +" to "+ series.end,
"css": {
"width": ((size * cellWidth) - 7) + "px",
"margin-left": ((offset * cellWidth) + 3) + "px"
}
});
addBlockData(block, data[i], series);
if (data[i].series[j].color) {
block.css("background-color", data[i].series[j].color);
}
block.append(jQuery("<div>", { "class": "ganttview-block-text" }).text(size));
jQuery(rows[rowIdx]).append(block);
rowIdx = rowIdx + 1;
}
}
}
function addBlockData(block, data, series) {
// This allows custom attributes to be added to the series data objects
// and makes them available to the 'data' argument of click, resize, and drag handlers
var blockData = { id: data.id, name: data.name };
jQuery.extend(blockData, series);
block.data("block-data", blockData);
}
function applyLastClass(div) {
jQuery("div.ganttview-grid-row div.ganttview-grid-row-cell:last-child", div).addClass("last");
jQuery("div.ganttview-hzheader-days div.ganttview-hzheader-day:last-child", div).addClass("last");
jQuery("div.ganttview-hzheader-months div.ganttview-hzheader-month:last-child", div).addClass("last");
}
return {
render: render
};
}
var Behavior = function (div, opts) {
function apply() {
if (opts.behavior.clickable) {
bindBlockClick(div, opts.behavior.onDblClick);
}
if (opts.behavior.resizable) {
bindBlockResize(div, opts.cellWidth, opts.start, opts.behavior.onResize);
}
if (opts.behavior.draggable) {
bindBlockDrag(div, opts.cellWidth, opts.start, opts.behavior.onDrag);
}
}
function bindBlockClick(div, callback) {
jQuery("div.ganttview-block", div).on("dblclick", function () {
if (callback) { callback(jQuery(this).data("block-data")); }
});
}
function bindBlockResize(div, cellWidth, startDate, callback) {
jQuery("div.ganttview-block", div).resizable({
grid: cellWidth,
handles: "e,w",
stop: function () {
var block = jQuery(this);
updateDataAndPosition(div, block, cellWidth, startDate);
if (callback) { callback(block.data("block-data")); }
}
});
}
function bindBlockDrag(div, cellWidth, startDate, callback) {
jQuery("div.ganttview-block", div).draggable({
axis: "x",
grid: [cellWidth, cellWidth],
stop: function () {
var block = jQuery(this);
updateDataAndPosition(div, block, cellWidth, startDate);
if (callback) { callback(block.data("block-data")); }
}
});
}
function updateDataAndPosition(div, block, cellWidth, startDate) {
var container = jQuery("div.ganttview-slide-container", div);
var scroll = container.scrollLeft();
var offset = block.offset().left - container.offset().left - 1 + scroll;
// Set new start date
var daysFromStart = Math.round(offset / cellWidth);
var newStart = startDate.clone().addDays(daysFromStart);
block.data("block-data").start = newStart;
// Set new end date
var width = block.outerWidth();
var numberOfDays = Math.round(width / cellWidth) - 1;
block.data("block-data").end = newStart.clone().addDays(numberOfDays);
jQuery("div.ganttview-block-text", block).text(numberOfDays + 1);
// Remove top and left properties to avoid incorrect block positioning,
// set position to relative to keep blocks relative to scrollbar when scrolling
block.css("top", "").css("left", "")
.css("position", "relative").css("margin-left", offset + "px");
}
return {
apply: apply
};
}
var ArrayUtils = {
contains: function (arr, obj) {
var has = false;
for (var i = 0; i < arr.length; i++) { if (arr[i] == obj) { has = true; } }
return has;
}
};
var DateUtils = {
daysBetween: function (start, end) {
if (!start || !end) { return 0; }
start = Date.parse(start); end = Date.parse(end);
if (start.getYear() == 1901 || end.getYear() == 8099) { return 0; }
var count = 0, date = start.clone();
while (date.compareTo(end) == -1) { count = count + 1; date.addDays(1); }
return count;
},
isWeekend: function (date) {
return date.getDay() % 6 == 0;
},
isToday: function (date) {
return date.isToday();
},
getBoundaryDatesFromData: function (data, minDays) {
var minStart = new Date(); maxEnd = new Date();
for (var i = 0; i < data.length; i++) {
for (var j = 0; j < data[i].series.length; j++) {
var start = Date.parse(data[i].series[j].start);
var end = Date.parse(data[i].series[j].end)
if (i == 0 && j == 0) { minStart = start; maxEnd = end; }
if (minStart.compareTo(start) == 1) { minStart = start; }
if (maxEnd.compareTo(end) == -1) { maxEnd = end; }
}
}
// Insure that the width of the chart is at least the slide width to avoid empty
// whitespace to the right of the grid
if (DateUtils.daysBetween(minStart, maxEnd) < minDays) {
maxEnd = minStart.clone().addDays(minDays);
}
return [minStart, maxEnd];
}
};
})(jQuery);