- jQuery.Gantt
- — Draw Gantt charts with the famous jQuery ease of development
-
-
-
Contributors
-
-
- Marek Bielańczuk wrote the original jQuery.Gantt plugin that this version is based off of.
-
-
- Tait Brown enforced stricter code guidelines by validating the code, updating it to support HTML5 and tweaking the design.
-
-
- Leo Pfeifenberger made major performance updates as well as adding requested features such as click events, state persisting via cookies and scrollToToday on load functionality.
-
-
- Grzegorz Russek helped fix the White Whale of a bug that prevented the hour view rendering correctly. Nice one.
-
- a JS Function that gets called when clicking on a Gantt-Item. The parameter passed to the function is the dataObj of the item
-
-
-
- onAddClick
-
- function (dt, rowId) { return; }
-
- a JS Function that gets called when clicking on a Gantt-Item. The parameter passed to the function is the DateTime in ms for the clicked Cell, and the ID if the source object (row)
-
-
-
- onRender
-
- function () { return; }
-
- a JS Function called whenever the chart is (re)rendered
-
-
-
- useCookie
-
- false
-
- indicates if cookies should be used to track the chart's state (scale, scrollposition) between postpacks
- jquery.cookie.js needs to be referenced for this to work
-
-
-
- scrollToToday
-
- true
-
- Boolean
-
-
-
-
-
-
-
-
- Source Configuration
-
-
-
-source: [{
- name: "Example",
- desc: "Lorem ipsum dolor sit amet.",
- values: [ ... ]
-}]
-
-
-
-
-
-
- Parameter
-
-
- Default
-
-
- Accepts Type
-
-
- Meaning
-
-
-
-
-
-
- name
-
-
- null
-
-
- String
-
-
- Bold value in the left-most column of the gantt row.
-
-
-
-
- desc
-
-
- null
-
-
- String
-
-
- Secondary value in the gantt row.
-
-
-
-
- values
-
-
- null
-
-
- Array
-
-
- Collection of date ranges for gantt items. See next table.
-
+ jQuery.Gantt
+ — Draw Gantt charts with the famous jQuery ease of development
+
+
+
Contributors
+
+
+ Marek Bielańczuk wrote the original jQuery.Gantt plugin that this version is based off of.
+
+
+ Tait Brown enforced stricter code guidelines by validating the code, updating it to support HTML5 and tweaking the design.
+
+
+ Leo Pfeifenberger made major performance updates as well as adding requested features such as click events, state persisting via cookies and scrollToToday
+ on load functionality.
+
+
+ Grzegorz Russek helped fix the White Whale of a bug that prevented the hour view rendering correctly. Nice one.
+
+ Array (12 strings representing the months of the year)
+
+
+
+
+ dow
+
+
+ ["S", "M", "T", "W", "T", "F", "S"]
+
+
+ Array (7 strings representing the days of the week)
+
+
+
+
+ holidays
+
+
+ undefined
+
+
+ Array of numbers (ms), date strings (see formats), or Date objects
+
+
+
+
+ navigate
+
+
+ "buttons"
+
+
+ string ("buttons", "scroll")
+
+
+
+
+ scale
+
+
+ "days"
+
+
+ string ("months", "weeks", "days", "hours")
+
+
+
+
+ maxScale
+
+
+ "months"
+
+
+ string ("months", "weeks", "days", "hours")
+
+
+
+
+ minScale
+
+
+ "hours"
+
+
+ string ("months", "weeks", "days", "hours")
+
+
+
+
+ waitText
+
+
+ "Please wait..."
+
+
+ string
+
+
+
+
+ onItemClick
+
+
+ function (data) { return; }
+
+ Function called when clicking on a Gantt item. The parameter passed to the function is the dataObj of the source item, if one was provided.
+
+
+
+ onAddClick
+
+ function (dt, rowId) { return; }
+
+ Function called when clicking on empty space inside the Gantt data panel. The parameter passed to the function is the date/time in milliseconds for the clicked cell, and the ID of the source object (row), if one was provided.
+ Function called whenever the chart is (re)rendered
+
+
+
+ useCookie
+
+ false
+
+ indicates whether or not cookies should be used to save and restore the chart's view state (scale, scroll position) between page loads;
+ jquery.cookie needs to be referenced for this to work
+
+
+
+ cookieKey
+
+ "jquery.fn.gantt"
+
+ The prefix used when storing cookies (depends on useCookie being set to true)
')
- .addClass(cls)
- .css({
- width: ((cellWidth * days) - barMarg) + 2
- })
- .data("dataObj", dataObj);
-
- if (desc) {
- bar
- .mouseover(function (e) {
- var hint = $('').html(desc);
- $("body").append(hint);
- hint.css("left", e.pageX);
- hint.css("top", e.pageY);
- hint.show();
- })
- .mouseout(function () {
- $(".fn-gantt-hint").remove();
- })
- .mousemove(function (e) {
- $(".fn-gantt-hint").css("left", e.pageX);
- $(".fn-gantt-hint").css("top", e.pageY + 15);
- });
- }
- bar.click(function (e) {
- e.stopPropagation();
- settings.onItemClick($(this).data("dataObj"));
- });
- return bar;
- },
-
- // Remove the `wd` (weekday) class and add `today` class to the
- // current day/week/month (depending on the current scale)
- markNow: function (element) {
- switch (settings.scale) {
- case "weeks":
- var cd = Date.parse(new Date());
- cd = (Math.floor(cd / 36400000) * 36400000);
- $(element).find(':findweek("' + cd + '")').removeClass('wd').addClass('today');
- break;
- case "months":
- $(element).find(':findmonth("' + new Date().getTime() + '")').removeClass('wd').addClass('today');
- break;
- default:
- var cd = Date.parse(new Date());
- cd = (Math.floor(cd / 36400000) * 36400000);
- $(element).find(':findday("' + cd + '")').removeClass('wd').addClass('today');
- break;
- }
- },
-
- // **Fill the Chart**
- // Parse the data and fill the data panel
- fillData: function (element, datapanel, leftpanel /* <- never used? */) {
- var invertColor = function (colStr) {
- try {
- colStr = colStr.replace("rgb(", "").replace(")", "");
- var rgbArr = colStr.split(",");
- var R = parseInt(rgbArr[0], 10);
- var G = parseInt(rgbArr[1], 10);
- var B = parseInt(rgbArr[2], 10);
- var gray = Math.round((255 - (0.299 * R + 0.587 * G + 0.114 * B)) * 0.9);
- return "rgb(" + gray + ", " + gray + ", " + gray + ")";
- } catch (err) {
- return "";
- }
- };
- var darkerColor = function(colStr) {
- try {
- colStr = colStr.replace('rgb(','').replace(')','');
- var rgbArr = colStr.split(',');
- var R = parseInt(rgbArr[0]);
- var G = parseInt(rgbArr[1]);
- var B = parseInt(rgbArr[2]);
- R = R-Math.round(parseInt(R)/7);
- G = G-Math.round(parseInt(G)/7);
- B = B-Math.round(parseInt(B)/7);
- return 'rgb('+R+', '+G+', '+B+')';
- } catch (err) {
- return '';
- }
- };
- $.each(element.data, function (i, entry) {
- if (i >= element.pageNum * settings.itemsPerPage && i < (element.pageNum * settings.itemsPerPage + settings.itemsPerPage)) {
-
- $.each(entry.values, function (j, day) {
- var _bar = null;
-
- switch (settings.scale) {
- // **Hourly data**
- case "hours":
- var dFrom = tools.genId(tools.dateDeserialize(day.from).getTime(), element.scaleStep);
- var from = $(element).find('#dh-' + dFrom);
-
- var dTo = tools.genId(tools.dateDeserialize(day.to).getTime(), element.scaleStep);
- var to = $(element).find('#dh-' + dTo);
-
- console.log(dFrom, dTo);
-
- var cFrom = from.attr("offset");
- var cTo = to.attr("offset");
- var dl = Math.floor((cTo - cFrom) / tools.getCellSize()) + 1;
-
- _bar = core.createProgressBar(
- dl,
- day.id ? day.id : "",
- day.customClass ? day.customClass : "",
- day.desc ? day.desc : "",
- day.label ? day.label : "",
- day.dataObj ? day.dataObj : null
- );
-
- // find row
- var topEl = $(element).find("#rowheader" + i);
-
- var top = tools.getCellSize() * 5 + 2 + parseInt(topEl.attr("offset"), 10);
- _bar.css({ 'top': top, 'left': Math.floor(cFrom) });
-
- datapanel.append(_bar);
- break;
-
- // **Weekly data**
- case "weeks":
- var dtFrom = tools.dateDeserialize(day.from);
- var dtTo = tools.dateDeserialize(day.to);
-
- if (dtFrom.getDate() <= 3 && dtFrom.getMonth() === 0) {
- dtFrom.setDate(dtFrom.getDate() + 4);
- }
-
- if (dtFrom.getDate() <= 3 && dtFrom.getMonth() === 0) {
- dtFrom.setDate(dtFrom.getDate() + 4);
- }
-
- if (dtTo.getDate() <= 3 && dtTo.getMonth() === 0) {
- dtTo.setDate(dtTo.getDate() + 4);
- }
-
- var from = $(element).find("#" + dtFrom.getWeekId());
-
- var cFrom = from.attr("offset");
-
- var to = $(element).find("#" + dtTo.getWeekId());
- var cTo = to.attr("offset");
-
- var dl = Math.round((cTo - cFrom) / tools.getCellSize()) + 1;
-
- _bar = core.createProgressBar(
- dl,
- day.id ? day.id : "",
- day.customClass ? day.customClass : "",
- day.desc ? day.desc : "",
- day.label ? day.label : "",
- day.dataObj ? day.dataObj : null
- );
-
- // find row
- var topEl = $(element).find("#rowheader" + i);
-
- var top = tools.getCellSize() * 3 + 2 + parseInt(topEl.attr("offset"), 10);
- _bar.css({ 'top': top, 'left': Math.floor(cFrom) });
-
- datapanel.append(_bar);
- break;
-
- // **Monthly data**
- case "months":
- var dtFrom = tools.dateDeserialize(day.from);
- var dtTo = tools.dateDeserialize(day.to);
-
- if (dtFrom.getDate() <= 3 && dtFrom.getMonth() === 0) {
- dtFrom.setDate(dtFrom.getDate() + 4);
- }
-
- if (dtFrom.getDate() <= 3 && dtFrom.getMonth() === 0) {
- dtFrom.setDate(dtFrom.getDate() + 4);
- }
-
- if (dtTo.getDate() <= 3 && dtTo.getMonth() === 0) {
- dtTo.setDate(dtTo.getDate() + 4);
- }
-
- var from = $(element).find("#dh-" + tools.genId(dtFrom.getTime()));
- var cFrom = from.attr("offset");
- var to = $(element).find("#dh-" + tools.genId(dtTo.getTime()));
- var cTo = to.attr("offset");
- var dl = Math.round((cTo - cFrom) / tools.getCellSize()) + 1;
-
- _bar = core.createProgressBar(
- dl,
- day.id ? day.id : "",
- day.customClass ? day.customClass : "",
- day.desc ? day.desc : "",
- day.label ? day.label : "",
- day.dataObj ? day.dataObj : null
- );
-
- // find row
- var topEl = $(element).find("#rowheader" + i);
-
- var top = tools.getCellSize() * 2 + 2 + parseInt(topEl.attr("offset"), 10);
- _bar.css({ 'top': top, 'left': Math.floor(cFrom) });
-
- datapanel.append(_bar);
- break;
-
- // **Days**
- default:
- var dFrom = tools.genId(tools.dateDeserialize(day.from).getTime());
- var dTo = tools.genId(tools.dateDeserialize(day.to).getTime());
-
- var from = $(element).find("#dh-" + dFrom);
- var cFrom = from.attr("offset");
-
- var dl = Math.floor(((dTo / 1000) - (dFrom / 1000)) / 86400) + 1;
- _bar = core.createProgressBar(
- dl,
- day.id ? day.id : "",
- day.customClass ? day.customClass : "",
- day.desc ? day.desc : "",
- day.label ? day.label : "",
- day.dataObj ? day.dataObj : null
- );
-
- // find row
- var topEl = $(element).find("#rowheader" + i);
-
- var top = tools.getCellSize() * 4 + 2 + parseInt(topEl.attr("offset"), 10);
- _bar.css({ 'top': top, 'left': Math.floor(cFrom) });
-
- datapanel.append(_bar);
-
- break;
- }
- var $l = _bar.find(".fn-label");
- if ($l && _bar.length) {
- var gray = invertColor(_bar[0].style.backgroundColor);
- $l.css("color", gray);
- } else if ($l) {
- $l.css("color", "");
- }
- });
-
- }
- });
- },
- // **Navigation**
- navigateTo: function (element, val) {
- var $rightPanel = $(element).find(".fn-gantt .rightPanel");
- var $dataPanel = $rightPanel.find(".dataPanel");
- $dataPanel.click = function () {
- alert(arguments.join(""));
- };
- var rightPanelWidth = $rightPanel.width();
- var dataPanelWidth = $dataPanel.width();
-
- switch (val) {
- case "begin":
- $dataPanel.animate({
- "margin-left": "0px"
- }, "fast", function () { core.repositionLabel(element); });
- element.scrollNavigation.panelMargin = 0;
- break;
- case "end":
- var mLeft = dataPanelWidth - rightPanelWidth;
- element.scrollNavigation.panelMargin = mLeft * -1;
- $dataPanel.animate({
- "margin-left": "-" + mLeft + "px"
- }, "fast", function () { core.repositionLabel(element); });
- break;
- case "now":
- if (!element.scrollNavigation.canScroll || !$dataPanel.find(".today").length) {
- return false;
- }
- var max_left = (dataPanelWidth - rightPanelWidth) * -1;
- var cur_marg = $dataPanel.css("margin-left").replace("px", "");
- var val = $dataPanel.find(".today").offset().left - $dataPanel.offset().left;
- val *= -1;
- if (val > 0) {
- val = 0;
- } else if (val < max_left) {
- val = max_left;
- }
- $dataPanel.animate({
- "margin-left": val + "px"
- }, "fast", core.repositionLabel(element));
- element.scrollNavigation.panelMargin = val;
- break;
- default:
- var max_left = (dataPanelWidth - rightPanelWidth) * -1;
- var cur_marg = $dataPanel.css("margin-left").replace("px", "");
- var val = parseInt(cur_marg, 10) + val;
- if (val <= 0 && val >= max_left) {
- $dataPanel.animate({
- "margin-left": val + "px"
- }, "fast", core.repositionLabel(element));
- }
- element.scrollNavigation.panelMargin = val;
- break;
- }
- core.synchronizeScroller(element);
- },
-
- // Navigate to a specific page
- navigatePage: function (element, val) {
- if ((element.pageNum + val) >= 0 && (element.pageNum + val) < Math.ceil(element.rowsNum / settings.itemsPerPage)) {
- core.waitToggle(element, true, function () {
- element.pageNum += val;
- element.hPosition = $(".fn-gantt .dataPanel").css("margin-left").replace("px", "");
- element.scaleOldWidth = false;
- core.init(element);
- });
- }
- },
-
- // Change zoom level
- zoomInOut: function (element, val) {
- core.waitToggle(element, true, function () {
-
- var zoomIn = (val < 0);
-
- var scaleSt = element.scaleStep + val * 3;
- scaleSt = scaleSt <= 1 ? 1 : scaleSt === 4 ? 3 : scaleSt;
- var scale = settings.scale;
- var headerRows = element.headerRows;
- if (settings.scale === "hours" && scaleSt >= 13) {
- scale = "days";
- headerRows = 4;
- scaleSt = 13;
- } else if (settings.scale === "days" && zoomIn) {
- scale = "hours";
- headerRows = 5;
- scaleSt = 12;
- } else if (settings.scale === "days" && !zoomIn) {
- scale = "weeks";
- headerRows = 3;
- scaleSt = 13;
- } else if (settings.scale === "weeks" && !zoomIn) {
- scale = "months";
- headerRows = 2;
- scaleSt = 14;
- } else if (settings.scale === "weeks" && zoomIn) {
- scale = "days";
- headerRows = 4;
- scaleSt = 13;
- } else if (settings.scale === "months" && zoomIn) {
- scale = "weeks";
- headerRows = 3;
- scaleSt = 13;
- }
-
- if ((zoomIn && $.inArray(scale, scales) < $.inArray(settings.minScale, scales))
- || (!zoomIn && $.inArray(scale, scales) > $.inArray(settings.maxScale, scales))) {
- core.init(element);
- return;
- }
- element.scaleStep = scaleSt;
- settings.scale = scale;
- element.headerRows = headerRows;
- var $rightPanel = $(element).find(".fn-gantt .rightPanel");
- var $dataPanel = $rightPanel.find(".dataPanel");
- element.hPosition = $dataPanel.css("margin-left").replace("px", "");
- element.scaleOldWidth = ($dataPanel.width() - $rightPanel.width());
-
- if (settings.useCookie) {
- $.cookie(this.cookieKey + "CurrentScale", settings.scale);
- // reset scrollPos
- $.cookie(this.cookieKey + "ScrollPos", null);
- }
- core.init(element);
- });
- },
-
- // Move chart via mouseclick
- mouseScroll: function (element, e) {
- var $dataPanel = $(element).find(".fn-gantt .dataPanel");
- $dataPanel.css("cursor", "move");
- var bPos = $dataPanel.offset();
- var mPos = element.scrollNavigation.mouseX === null ? e.pageX : element.scrollNavigation.mouseX;
- var delta = e.pageX - mPos;
- element.scrollNavigation.mouseX = e.pageX;
-
- core.scrollPanel(element, delta);
-
- clearTimeout(element.scrollNavigation.repositionDelay);
- element.scrollNavigation.repositionDelay = setTimeout(core.repositionLabel, 50, element);
- },
-
- // Move chart via mousewheel
- wheelScroll: function (element, e) {
- e.preventDefault(); // e is a jQuery Event
- var delta = e.detail ? e.detail * (-50) : e.wheelDelta / 120 * 50;
-
- core.scrollPanel(element, delta);
-
- clearTimeout(element.scrollNavigation.repositionDelay);
- element.scrollNavigation.repositionDelay = setTimeout(core.repositionLabel, 50, element);
- },
-
- // Move chart via slider control
- sliderScroll: function (element, e) {
- var $sliderBar = $(element).find(".nav-slider-bar");
- var $sliderBarBtn = $sliderBar.find(".nav-slider-button");
- var $rightPanel = $(element).find(".fn-gantt .rightPanel");
- var $dataPanel = $rightPanel.find(".dataPanel");
-
- var bPos = $sliderBar.offset();
- var bWidth = $sliderBar.width();
- var wButton = $sliderBarBtn.width();
-
- var pos, mLeft;
-
- if ((e.pageX >= bPos.left) && (e.pageX <= bPos.left + bWidth)) {
- pos = e.pageX - bPos.left;
- pos = pos - wButton / 2;
- $sliderBarBtn.css("left", pos);
-
- mLeft = $dataPanel.width() - $rightPanel.width();
-
- var pPos = pos * mLeft / bWidth * -1;
- if (pPos >= 0) {
- $dataPanel.css("margin-left", "0px");
- element.scrollNavigation.panelMargin = 0;
- } else if (pos >= bWidth - (wButton * 1)) {
- $dataPanel.css("margin-left", mLeft * -1 + "px");
- element.scrollNavigation.panelMargin = mLeft * -1;
- } else {
- $dataPanel.css("margin-left", pPos + "px");
- element.scrollNavigation.panelMargin = pPos;
- }
- clearTimeout(element.scrollNavigation.repositionDelay);
- element.scrollNavigation.repositionDelay = setTimeout(core.repositionLabel, 5, element);
- }
- },
-
- // Update scroll panel margins
- scrollPanel: function (element, delta) {
- if (!element.scrollNavigation.canScroll) {
- return false;
- }
- var _panelMargin = parseInt(element.scrollNavigation.panelMargin, 10) + delta;
- if (_panelMargin > 0) {
- element.scrollNavigation.panelMargin = 0;
- $(element).find(".fn-gantt .dataPanel").css("margin-left", element.scrollNavigation.panelMargin + "px");
- } else if (_panelMargin < element.scrollNavigation.panelMaxPos * -1) {
- element.scrollNavigation.panelMargin = element.scrollNavigation.panelMaxPos * -1;
- $(element).find(".fn-gantt .dataPanel").css("margin-left", element.scrollNavigation.panelMargin + "px");
- } else {
- element.scrollNavigation.panelMargin = _panelMargin;
- $(element).find(".fn-gantt .dataPanel").css("margin-left", element.scrollNavigation.panelMargin + "px");
- }
- core.synchronizeScroller(element);
- },
-
- // Synchronize scroller
- synchronizeScroller: function (element) {
- if (settings.navigate === "scroll") {
- var $rightPanel = $(element).find(".fn-gantt .rightPanel");
- var $dataPanel = $rightPanel.find(".dataPanel");
- var $sliderBar = $(element).find(".nav-slider-bar");
- var $sliderBtn = $sliderBar.find(".nav-slider-button");
-
- var bWidth = $sliderBar.width();
- var wButton = $sliderBtn.width();
-
- var mLeft = $dataPanel.width() - $rightPanel.width();
- var hPos = 0;
- if ($dataPanel.css("margin-left")) {
- hPos = $dataPanel.css("margin-left").replace("px", "");
- }
- var pos = hPos * bWidth / mLeft - $sliderBtn.width() * 0.25;
- pos = pos > 0 ? 0 : (pos * -1 >= bWidth - (wButton * 0.75)) ? (bWidth - (wButton * 1.25)) * -1 : pos;
- $sliderBtn.css("left", pos * -1);
- }
- },
-
- // Reposition data labels
- repositionLabel: function (element) {
- setTimeout(function () {
- var $dataPanel;
- if (!element) {
- $dataPanel = $(".fn-gantt .rightPanel .dataPanel");
- } else {
- var $rightPanel = $(element).find(".fn-gantt .rightPanel");
- $dataPanel = $rightPanel.find(".dataPanel");
- }
-
- if (settings.useCookie) {
- $.cookie(this.cookieKey + "ScrollPos", $dataPanel.css("margin-left").replace("px", ""));
- }
- }, 500);
- },
-
- // waitToggle
- waitToggle: function (element, show, fn) {
- if (show) {
- var eo = $(element).offset();
- var ew = $(element).outerWidth();
- var eh = $(element).outerHeight();
-
- if (!element.loader) {
- element.loader = $('
'
- + '
' + settings.waitText + '
');
- }
- $(element).append(element.loader);
- setTimeout(fn, 500);
-
- } else if (element.loader) {
- element.loader.detach();
- }
- }
- };
-
- // Utility functions
- // =================
- var tools = {
-
- // Return the maximum available date in data depending on the scale
- getMaxDate: function (element) {
- var maxDate = null;
- $.each(element.data, function (i, entry) {
- $.each(entry.values, function (i, date) {
- maxDate = maxDate < tools.dateDeserialize(date.to) ? tools.dateDeserialize(date.to) : maxDate;
- });
- });
- maxDate = maxDate || new Date();
- switch (settings.scale) {
- case "hours":
- maxDate.setHours(Math.ceil((maxDate.getHours()) / element.scaleStep) * element.scaleStep);
- maxDate.setHours(maxDate.getHours() + element.scaleStep * 3);
- break;
- case "weeks":
- var bd = new Date(maxDate.getTime());
- var bd = new Date(bd.setDate(bd.getDate() + 3 * 7));
- var md = Math.floor(bd.getDate() / 7) * 7;
- maxDate = new Date(bd.getFullYear(), bd.getMonth(), md === 0 ? 4 : md - 3);
- break;
- case "months":
- var bd = new Date(maxDate.getFullYear(), maxDate.getMonth(), 1);
- bd.setMonth(bd.getMonth() + 2);
- maxDate = new Date(bd.getFullYear(), bd.getMonth(), 1);
- break;
- default:
- maxDate.setHours(0);
- maxDate.setDate(maxDate.getDate() + 3);
- break;
- }
- return maxDate;
- },
-
- // Return the minimum available date in data depending on the scale
- getMinDate: function (element) {
- var minDate = null;
- $.each(element.data, function (i, entry) {
- $.each(entry.values, function (i, date) {
- minDate = minDate > tools.dateDeserialize(date.from) || minDate === null ? tools.dateDeserialize(date.from) : minDate;
- });
- });
- minDate = minDate || new Date();
- switch (settings.scale) {
- case "hours":
- minDate.setHours(Math.floor((minDate.getHours()) / element.scaleStep) * element.scaleStep);
- minDate.setHours(minDate.getHours() - element.scaleStep * 3);
- break;
- case "weeks":
- var bd = new Date(minDate.getTime());
- var bd = new Date(bd.setDate(bd.getDate() - 3 * 7));
- var md = Math.floor(bd.getDate() / 7) * 7;
- minDate = new Date(bd.getFullYear(), bd.getMonth(), md === 0 ? 4 : md - 3);
- break;
- case "months":
- var bd = new Date(minDate.getFullYear(), minDate.getMonth(), 1);
- bd.setMonth(bd.getMonth() - 3);
- minDate = new Date(bd.getFullYear(), bd.getMonth(), 1);
- break;
- default:
- minDate.setHours(0);
- minDate.setDate(minDate.getDate() - 3);
- break;
- }
- return minDate;
- },
-
- // Return an array of Date objects between `from` and `to`
- parseDateRange: function (from, to) {
- var current = new Date(from.getTime());
- var end = new Date(to.getTime()); // <- never used?
- var ret = [];
- var i = 0;
- do {
- ret[i++] = new Date(current.getTime());
- current.setDate(current.getDate() + 1);
- } while (current.getTime() <= to.getTime());
- return ret;
-
- },
-
- // Return an array of Date objects between `from` and `to`,
- // scaled hourly
- parseTimeRange: function (from, to, scaleStep) {
- var current = new Date(from);
- var end = new Date(to);
-
- // GR: Fix begin
- current.setMilliseconds(0);
- current.setSeconds(0);
- current.setMinutes(0);
- current.setHours(0);
-
- end.setMilliseconds(0);
- end.setSeconds(0);
- if (end.getMinutes() > 0 || end.getHours() > 0) {
- end.setMinutes(0);
- end.setHours(0);
- end.setTime(end.getTime() + (86400000)); // Add day
- }
- // GR: Fix end
-
- var ret = [];
- var i = 0;
- for(;;) {
- var dayStartTime = new Date(current);
- dayStartTime.setHours(Math.floor((current.getHours()) / scaleStep) * scaleStep);
-
- if (ret[i] && dayStartTime.getDay() !== ret[i].getDay()) {
- // If mark-cursor jumped to next day, make sure it starts at 0 hours
- dayStartTime.setHours(0);
- }
- ret[i] = dayStartTime;
-
- // Note that we use ">" because we want to include the end-time point.
- if (current.getTime() > to.getTime()) break;
-
- /* BUG-2: current is moved backwards producing a dead-lock! (crashes chrome/IE/firefox)
- * SEE: https://github.com/taitems/jQuery.Gantt/issues/62
- if (current.getDay() !== ret[i].getDay()) {
- current.setHours(0);
- }
- */
-
- // GR Fix Begin
- current = ktkGetNextDate(dayStartTime, scaleStep);
- // GR Fix End
-
- i++;
- }
-
- return ret;
- },
-
- // Return an array of Date objects between a range of weeks
- // between `from` and `to`
- parseWeeksRange: function (from, to) {
-
- var current = new Date(from);
- var end = new Date(to); // <- never used?
-
- var ret = [];
- var i = 0;
- do {
- if (current.getDay() === 0) {
- ret[i++] = current.getDayForWeek();
- }
- current.setDate(current.getDate() + 1);
- } while (current.getTime() <= to.getTime());
-
- return ret;
- },
-
-
- // Return an array of Date objects between a range of months
- // between `from` and `to`
- parseMonthsRange: function (from, to) {
-
- var current = new Date(from);
- var end = new Date(to); // <- never used?
-
- var ret = [];
- var i = 0;
- do {
- ret[i++] = new Date(current.getFullYear(), current.getMonth(), 1);
- current.setMonth(current.getMonth() + 1);
- } while (current.getTime() <= to.getTime());
-
- return ret;
- },
-
- // Deserialize a date from a string or integer
- dateDeserialize: function (date) {
- if (typeof date === "string") {
- date = date.replace(/\/Date\((.*)\)\//, "$1");
- date = $.isNumeric(date) ? parseInt(date, 10) : $.trim(date);
- }
- return new Date( date );
- },
-
- // Generate an id for a date
- genId: function (ticks) {
- var t = new Date(ticks);
- switch (settings.scale) {
- case "hours":
- var hour = t.getHours();
- if (arguments.length >= 2) {
- hour = (Math.floor((t.getHours()) / arguments[1]) * arguments[1]);
- }
- return (new Date(t.getFullYear(), t.getMonth(), t.getDate(), hour)).getTime();
- case "weeks":
- var y = t.getFullYear();
- var w = t.getDayForWeek().getWeekOfYear();
- var m = t.getMonth();
- if (m === 11 && w === 1) {
- y++;
- }
- return y + "-" + w;
- case "months":
- return t.getFullYear() + "-" + t.getMonth();
- default:
- return (new Date(t.getFullYear(), t.getMonth(), t.getDate())).getTime();
- }
- },
-
- // normalizes an array of dates into a map of start-of-day millisecond values
- _datesToDays: function ( dates ) {
- var dayMap = {};
- for (var i = 0, len = dates.length, day; i < len; i++) {
- day = tools.dateDeserialize( dates[i] );
- dayMap[ day.setHours(0, 0, 0, 0) ] = true;
- }
- return dayMap;
- },
- // Returns true when the given date appears in the array of holidays, if provided
- isHoliday: (function() { // IIFE
- // short-circuits the function if no holidays option was passed
- if (!settings.holidays) {
- return function () { return false; };
- }
- var holidays = false;
- // returns the function that will be used to check for holidayness of a given date
- return function(date) {
- if (!holidays) {
- holidays = tools._datesToDays( settings.holidays );
- }
- return !!holidays[
- // assumes numeric dates are already normalized to start-of-day
- $.isNumeric(date) ?
- date :
- ( new Date(date.getFullYear(), date.getMonth(), date.getDate()) ).getTime()
- ];
- };
- })(),
-
- // Get the current cell size
- _getCellSize: null,
- getCellSize: function () {
- if (!tools._getCellSize) {
- $("body").append(
- $('
')
- );
- tools._getCellSize = $("#measureCellWidth .row").height();
- $("#measureCellWidth").empty().remove();
- }
- return tools._getCellSize;
- },
-
- // Get the current size of the right panel
- getRightPanelSize: function () {
- $("body").append(
- $('
')
- );
- var ret = $("#measureCellWidth .rightPanel").height();
- $("#measureCellWidth").empty().remove();
- return ret;
- },
-
- // Get the current page height
- getPageHeight: function (element) {
- return element.pageNum + 1 === element.pageCount ? element.rowsOnLastPage * tools.getCellSize() : settings.itemsPerPage * tools.getCellSize();
- },
-
- // Get the current margin size of the progress bar
- _getProgressBarMargin: null,
- getProgressBarMargin: function () {
- if (!tools._getProgressBarMargin && tools._getProgressBarMargin !== 0) {
- $("body").append(
- $('
')
- );
- tools._getProgressBarMargin = parseInt($("#measureBarWidth .fn-gantt .rightPanel .day .bar").css("margin-left").replace("px", ""), 10);
- tools._getProgressBarMargin += parseInt($("#measureBarWidth .fn-gantt .rightPanel .day .bar").css("margin-right").replace("px", ""), 10);
- $("#measureBarWidth").empty().remove();
- }
- return tools._getProgressBarMargin;
- }
- };
-
-
- this.each(function () {
- this.data = null; // Received data
- this.pageNum = 0; // Current page number
- this.pageCount = 0; // Available pages count
- this.rowsOnLastPage = 0; // How many rows on last page
- this.rowsNum = 0; // Number of total rows
- this.hPosition = 0; // Current position on diagram (Horizontal)
- this.dateStart = null;
- this.dateEnd = null;
- this.scrollClicked = false;
- this.scaleOldWidth = null;
- this.headerRows = null;
-
- // Update cookie with current scale
- if (settings.useCookie) {
- var sc = $.cookie(this.cookieKey + "CurrentScale");
- if (sc) {
- settings.scale = $.cookie(this.cookieKey + "CurrentScale");
- } else {
- $.cookie(this.cookieKey + "CurrentScale", settings.scale);
- }
- }
-
- switch (settings.scale) {
- case "hours": this.headerRows = 5; this.scaleStep = 1; break;
- case "weeks": this.headerRows = 3; this.scaleStep = 13; break;
- case "months": this.headerRows = 2; this.scaleStep = 14; break;
- default: this.headerRows = 4; this.scaleStep = 13; break;
- }
-
- this.scrollNavigation = {
- panelMouseDown: false,
- scrollerMouseDown: false,
- mouseX: null,
- panelMargin: 0,
- repositionDelay: 0,
- panelMaxPos: 0,
- canScroll: true
- };
-
- this.gantt = null;
- this.loader = null;
-
- core.create(this);
-
- });
-
- };
-})(jQuery);
+/**
+ * jQuery Gantt Chart
+ *
+ * @see http://taitems.github.io/jQuery.Gantt/
+ * @license MIT
+ */
+/*jshint camelcase:true, freeze:true, jquery:true */
+(function($, undefined) {
+ "use strict";
+
+ var UTC_DAY_IN_MS = 24 * 60 * 60 * 1000;
+
+ // custom selector `:findday` used to match on specified day in ms.
+ //
+ // The selector is passed a date in ms and elements are added to the
+ // selection filter if the element date matches, as determined by the
+ // id attribute containing a parsable date in ms.
+ function findDay(elt, text) {
+ var cd = new Date(parseInt(text, 10));
+ cd.setHours(0, 0, 0, 0);
+ var id = $(elt).attr("id") || "";
+ var si = id.indexOf("-") + 1;
+ var ed = new Date(parseInt(id.substring(si, id.length), 10));
+ ed.setHours(0, 0, 0, 0);
+ return cd.getTime() === ed.getTime();
+ }
+ $.expr.pseudos.findday = $.expr.createPseudo ?
+ $.expr.createPseudo(function(text) {
+ return function(elt) {
+ return findDay(elt, text);
+ };
+ }) :
+ function(elt, i, match) {
+ return findDay(elt, match[3]);
+ };
+
+ // custom selector `:findweek` used to match on specified week in ms.
+ function findWeek(elt, text) {
+ var cd = new Date(parseInt(text, 10));
+ var y = cd.getFullYear();
+ var w = cd.getWeekOfYear();
+ var m = cd.getMonth();
+ if (m === 11 && w === 1) {
+ y++;
+ } else if (!m && w > 51) {
+ y--;
+ }
+ cd = y + "-" + w;
+ var id = $(elt).attr("id") || "";
+ var si = id.indexOf("-") + 1;
+ var ed = id.substring(si, id.length);
+ return cd === ed;
+ }
+ $.expr.pseudos.findweek = $.expr.createPseudo ?
+ $.expr.createPseudo(function(text) {
+ return function(elt) {
+ return findWeek(elt, text);
+ };
+ }) :
+ function(elt, i, match) {
+ return findWeek(elt, match[3]);
+ };
+
+ // custom selector `:findmonth` used to match on specified month in ms.
+ function findMonth(elt, text) {
+ var cd = new Date(parseInt(text, 10));
+ cd = cd.getFullYear() + "-" + cd.getMonth();
+ var id = $(elt).attr("id") || "";
+ var si = id.indexOf("-") + 1;
+ var ed = id.substring(si, id.length);
+ return cd === ed;
+ }
+ $.expr[':'].findmonth = $.expr.createPseudo ?
+ $.expr.createPseudo(function(text) {
+ return function(elt) {
+ return findMonth(elt, text);
+ };
+ }) :
+ function(elt, i, match) {
+ return findMonth(elt, match[3]);
+ };
+
+ // Date prototype helpers
+ // ======================
+
+ // `getWeekId` returns a string in the form of 'dh-YYYY-WW', where WW is
+ // the week # for the year.
+ // It is used to add an id to the week divs
+ Date.prototype.getWeekId = function() {
+ var y = this.getFullYear();
+ var w = this.getWeekOfYear();
+ var m = this.getMonth();
+ if (m === 11 && w === 1) {
+ y++;
+ } else if (!m && w > 51) {
+ y--;
+ }
+ return 'dh-' + y + "-" + w;
+ };
+
+ // `getRepDate` returns the milliseconds since the epoch for a given date
+ // depending on the active scale
+ Date.prototype.getRepDate = function(scale) {
+ switch (scale) {
+ case "hours":
+ return this.getTime();
+ case "weeks":
+ return this.getDayForWeek().getTime();
+ case "months":
+ return new Date(this.getFullYear(), this.getMonth(), 1).getTime();
+ case "days":
+ /* falls through */
+ default:
+ return this.getTime();
+ }
+ };
+
+ // `getDayOfYear` returns the day number for the year
+ Date.prototype.getDayOfYear = function() {
+ var year = this.getFullYear();
+ return (Date.UTC(year, this.getMonth(), this.getDate()) -
+ Date.UTC(year, 0, 0)) / UTC_DAY_IN_MS;
+ };
+
+ // Use ISO week by default
+ //TODO: make these options.
+ var firstDay = 1; // ISO week starts with Monday (1); use Sunday (0) for, e.g., North America
+ var weekOneDate = 4; // ISO week one always contains 4 Jan; use 1 Jan for, e.g., North America
+
+ // `getWeekOfYear` returns the week number for the year
+ //TODO: fix bug when firstDay=6/weekOneDate=1 : https://github.com/moment/moment/issues/2115
+ Date.prototype.getWeekOfYear = function() {
+ var year = this.getFullYear(),
+ month = this.getMonth(),
+ date = this.getDate(),
+ day = this.getDay();
+ //var diff = weekOneDate - day + 7 * (day < firstDay ? -1 : 1);
+ var diff = weekOneDate - day;
+ if (day < firstDay) {
+ diff -= 7;
+ }
+ if (diff + 7 < weekOneDate - firstDay) {
+ diff += 7;
+ }
+ return Math.ceil(new Date(year, month, date + diff).getDayOfYear() / 7);
+ };
+
+ // `getDayForWeek` returns the first day of this Date's week
+ Date.prototype.getDayForWeek = function() {
+ var day = this.getDay();
+ var diff = (day < firstDay ? -7 : 0) + firstDay - day;
+ return new Date(this.getFullYear(), this.getMonth(), this.getDate() + diff);
+ };
+
+ $.fn.gantt = function(options) {
+
+ var scales = ["hours", "days", "weeks", "months"];
+ //Default settings
+ var settings = {
+ source: [],
+ holidays: [],
+ // paging
+ itemsPerPage: 7,
+ // localisation
+ dow: ["S", "M", "T", "W", "T", "F", "S"],
+ months: ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"],
+ waitText: "Please wait...",
+ // navigation
+ navigate: "buttons",
+ scrollToToday: true,
+ // cookie options
+ useCookie: false,
+ cookieKey: "jquery.fn.gantt",
+ // scale parameters
+ scale: "days",
+ maxScale: "months",
+ minScale: "hours",
+ // callbacks
+ onItemClick: function(data) { return; },
+ onAddClick: function(dt, rowId) { return; },
+ onRender: $.noop
+ };
+
+ // read options
+ $.extend(settings, options);
+
+ // can't use cookie if don't have `$.cookie`
+ settings.useCookie = settings.useCookie && $.isFunction($.cookie);
+
+ // Grid management
+ // ===============
+
+ // Core object is responsible for navigation and rendering
+ var core = {
+ // Return the element whose topmost point lies under the given point
+ // Normalizes for old browsers (NOTE: doesn't work when element is outside viewport)
+ //TODO: https://github.com/taitems/jQuery.Gantt/issues/137
+ elementFromPoint: (function() { // IIFE
+ // version for normal browsers
+ if (document.compatMode === "CSS1Compat") {
+ return function(x, y) {
+ x -= window.pageXOffset;
+ y -= window.pageYOffset;
+ return document.elementFromPoint(x, y);
+ };
+ }
+ // version for older browsers
+ return function(x, y) {
+ x -= $(document).scrollLeft();
+ y -= $(document).scrollTop();
+ return document.elementFromPoint(x, y);
+ };
+ })(),
+
+ // **Create the chart**
+ create: function(element) {
+
+ // Initialize data with a json object or fetch via an xhr
+ // request depending on `settings.source`
+ if (typeof settings.source !== "string") {
+ element.data = settings.source;
+ core.init(element);
+ } else {
+ $.getJSON(settings.source, function(jsData) {
+ element.data = jsData;
+ core.init(element);
+ });
+ }
+ },
+
+ // **Setup the initial view**
+ // Here we calculate the number of rows, pages and visible start
+ // and end dates once the data are ready
+ init: function(element) {
+ element.rowsNum = element.data.length;
+ element.pageCount = Math.ceil(element.rowsNum / settings.itemsPerPage);
+ element.rowsOnLastPage = element.rowsNum - (Math.floor(element.rowsNum / settings.itemsPerPage) * settings.itemsPerPage);
+
+ element.dateStart = tools.getMinDate(element);
+ element.dateEnd = tools.getMaxDate(element);
+
+
+ /* core.render(element); */
+ core.waitToggle(element, function() { core.render(element); });
+ },
+
+ // **Render the grid**
+ render: function(element) {
+ var content = $('');
+ var $leftPanel = core.leftPanel(element);
+ content.append($leftPanel);
+ var $rightPanel = core.rightPanel(element, $leftPanel);
+ var pLeft, hPos;
+
+ content.append($rightPanel);
+ content.append(core.navigation(element));
+
+ var $dataPanel = $rightPanel.find(".dataPanel");
+
+ element.gantt = $('').append(content);
+
+ $(element).empty().append(element.gantt);
+
+ element.scrollNavigation.panelMargin = parseInt($dataPanel.css("left").replace("px", ""), 10);
+ element.scrollNavigation.panelMaxPos = ($dataPanel.width() - $rightPanel.width());
+
+ element.scrollNavigation.canScroll = ($dataPanel.width() > $rightPanel.width());
+
+ core.markNow(element);
+ core.fillData(element, $dataPanel, $leftPanel);
+
+ // Set a cookie to record current position in the view
+ if (settings.useCookie) {
+ var sc = $.cookie(settings.cookieKey + "ScrollPos");
+ if (sc) {
+ element.hPosition = sc;
+ }
+ }
+
+ // Scroll the grid to today's date
+ if (settings.scrollToToday) {
+ core.navigateTo(element, 'now');
+ core.scrollPanel(element, 0);
+ // or, scroll the grid to the left most date in the panel
+ } else {
+ if (element.hPosition !== 0) {
+ if (element.scaleOldWidth) {
+ pLeft = ($dataPanel.width() - $rightPanel.width());
+ hPos = pLeft * element.hPosition / element.scaleOldWidth;
+ element.hPosition = hPos > 0 ? 0 : hPos;
+ element.scaleOldWidth = null;
+ }
+ $dataPanel.css({ "left": element.hPosition });
+ element.scrollNavigation.panelMargin = element.hPosition;
+ }
+ core.repositionLabel(element);
+ }
+
+ $dataPanel.css({ height: $leftPanel.height() });
+ core.waitToggle(element);
+ settings.onRender();
+ },
+
+ // Create and return the left panel with labels
+ leftPanel: function(element) {
+ /* Left panel */
+ var ganttLeftPanel = $('')
+ .append($('')
+ .css("height", tools.getCellSize() * element.headerRows));
+
+ var entries = [];
+ $.each(element.data, function(i, entry) {
+ if (i >= element.pageNum * settings.itemsPerPage &&
+ i < (element.pageNum * settings.itemsPerPage + settings.itemsPerPage)) {
+ var dataId = ('id' in entry) ? '" data-id="' + entry.id : '';
+ entries.push(
+ '
' +
+ '' +
+ (entry.name || '') +
+ '' +
+ '
');
+
+ if (entry.desc) {
+ entries.push(
+ '
' +
+ '' +
+ entry.desc +
+ '' +
+ '
');
+ }
+
+ }
+ });
+ return ganttLeftPanel.append(entries.join(""));
+ },
+
+ // Create and return the data panel element
+ dataPanel: function(element, width) {
+ var dataPanel = $('');
+
+ // Handle mousewheel events for scrolling the data panel
+ var wheel = 'onwheel' in element ?
+ 'wheel' : document.onmousewheel !== undefined ?
+ 'mousewheel' : 'DOMMouseScroll';
+ $(element).on(wheel, function(e) { core.wheelScroll(element, e); });
+
+ // Handle click events and dispatch to registered `onAddClick` function
+ dataPanel.click(function(e) {
+
+ e.stopPropagation();
+ var corrX /* <- never used? */ , corrY;
+ var leftpanel = $(element).find(".fn-gantt .leftPanel");
+ var datapanel = $(element).find(".fn-gantt .dataPanel");
+ switch (settings.scale) {
+ case "months":
+ corrY = tools.getCellSize();
+ break;
+ case "hours":
+ corrY = tools.getCellSize() * 4;
+ break;
+ case "days":
+ corrY = tools.getCellSize() * 3;
+ break;
+ case "weeks":
+ /* falls through */
+ default:
+ corrY = tools.getCellSize() * 2;
+ }
+
+ /* Adjust, so get middle of elm
+ corrY -= Math.floor(tools.getCellSize() / 2);
+ */
+
+ // Find column where click occurred
+ var col = core.elementFromPoint(e.pageX, datapanel.offset().top + corrY);
+ // Was the label clicked directly?
+ if (col.className === "fn-label") {
+ col = $(col.parentNode);
+ } else {
+ col = $(col);
+ }
+
+ var dt = col.data("repdate");
+ // Find row where click occurred
+ var row = core.elementFromPoint(leftpanel.offset().left + leftpanel.width() - 10, e.pageY);
+ // Was the label clicked directly?
+ if (row.className.indexOf("fn-label") === 0) {
+ row = $(row.parentNode);
+ } else {
+ row = $(row);
+ }
+ var rowId = row.data('id');
+
+ // Dispatch user registered function with the DateTime in ms
+ // and the id if the clicked object is a row
+ settings.onAddClick(dt, rowId);
+ });
+ return dataPanel;
+ },
+
+ // Creates and return the right panel containing the year/week/day header
+ rightPanel: function(element, leftPanel /* <- never used? */ ) {
+ var range = null;
+ // Days of the week have a class of one of
+ // `sn` (Sunday), `sa` (Saturday), or `wd` (Weekday)
+ var dowClass = ["sn", "wd", "wd", "wd", "wd", "wd", "sa"];
+ //unused: was someone planning to allow styles to stretch to the bottom of the chart?
+ //var gridDowClass = [" sn", "", "", "", "", "", " sa"];
+
+ var yearArr = [];
+ var scaleUnitsThisYear = 0;
+
+ var monthArr = [];
+ var scaleUnitsThisMonth = 0;
+
+ var dayArr = [];
+ var hoursInDay = 0;
+
+ var dowArr = [];
+ var horArr = [];
+
+ var today = new Date();
+ today.setHours(0, 0, 0, 0);
+
+ // reused variables
+ var $row = $('');
+ var i, len;
+ var year, month, week, day;
+ var rday, dayClass;
+ var dataPanel, dataPanelWidth;
+
+ // Setup the headings based on the chosen `settings.scale`
+ switch (settings.scale) {
+ // **Hours**
+ case "hours":
+ range = tools.parseTimeRange(element.dateStart, element.dateEnd, element.scaleStep);
+ dataPanelWidth = range.length * tools.getCellSize();
+
+ year = range[0].getFullYear();
+ month = range[0].getMonth();
+ day = range[0];
+
+ for (i = 0, len = range.length; i < len; i++) {
+ rday = range[i];
+
+ // Fill years
+ var rfy = rday.getFullYear();
+ if (rfy !== year) {
+ yearArr.push(
+ '
' +
+ year +
+ '
');
+
+ year = rfy;
+ scaleUnitsThisYear = 0;
+ }
+ scaleUnitsThisYear++;
+
+
+ // Fill months
+ var rm = rday.getMonth();
+ if (rm !== month) {
+ monthArr.push(
+ '
' +
+ settings.months[month] +
+ '
');
+
+ month = rm;
+ scaleUnitsThisMonth = 0;
+ }
+ scaleUnitsThisMonth++;
+
+ // Fill days & hours
+ var rgetDay = rday.getDay();
+ var getDay = day.getDay();
+ if (rgetDay !== getDay) {
+ dayClass = (today - day === 0) ?
+ "today" : tools.isHoliday(day.getTime()) ?
+ "holiday" : dowClass[getDay];
+
+ dayArr.push(
+ '