/* 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("
", { "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("
", { "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("
", { "class": "ganttview-vtheader" }); for (var i = 0; i < data.length; i++) { var itemDiv = jQuery("
", { "class": "ganttview-vtheader-item" }); if ($.trim(data[i].name).length > 0) itemDiv.append(jQuery("
", { "class": "ganttview-vtheader-item-name", "css": { "height": (data[i].series.length * cellHeight) + "px" } }).append(data[i].name)); var seriesDiv = jQuery("
", { "class": "ganttview-vtheader-series" }); for (var j = 0; j < data[i].series.length; j++) { seriesDiv.append(jQuery("
", { "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("
", { "class": "ganttview-hzheader" }); var monthsDiv = jQuery("
", { "class": "ganttview-hzheader-months" }); var daysDiv = jQuery("
", { "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("
", { "class": "ganttview-hzheader-month", "css": { "width": w + "px" } }).append(monthNames[m] + "/" + y)); for (var d in dates[y][m]) { daysDiv.append(jQuery("
", { "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("
", { "class": "ganttview-grid" }); var rowDiv = jQuery("
", { "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("
", { "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("
", { "class": "ganttview-blocks" }); for (var i = 0; i < data.length; i++) { for (var j = 0; j < data[i].series.length; j++) { blocksDiv.append(jQuery("
", { "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("
", { "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("
", { "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);