'use strict'; var Kanboard = {}; Kanboard.Accordion = function(app) { this.app = app; }; Kanboard.Accordion.prototype.listen = function() { $(document).on("click", ".accordion-toggle", function(e) { var section = $(this).parents(".accordion-section"); e.preventDefault(); if (section.hasClass("accordion-collapsed")) { section.find(".accordion-content").show(); section.removeClass("accordion-collapsed"); } else { section.find(".accordion-content").hide(); section.addClass("accordion-collapsed"); } }); }; Kanboard.App = function() { this.controllers = {}; }; Kanboard.App.prototype.get = function(controller) { return this.controllers[controller]; }; Kanboard.App.prototype.execute = function() { for (var className in Kanboard) { if (className !== "App") { var controller = new Kanboard[className](this); this.controllers[className] = controller; if (typeof controller.execute === "function") { controller.execute(); } if (typeof controller.listen === "function") { controller.listen(); } if (typeof controller.focus === "function") { controller.focus(); } if (typeof controller.keyboardShortcuts === "function") { controller.keyboardShortcuts(); } } } this.focus(); this.chosen(); this.keyboardShortcuts(); this.datePicker(); this.autoComplete(); }; Kanboard.App.prototype.keyboardShortcuts = function() { var self = this; // Submit form Mousetrap.bindGlobal("mod+enter", function() { var forms = $("form"); if (forms.length == 1) { forms.submit(); } else if (forms.length > 1) { if (document.activeElement.tagName === 'INPUT' || document.activeElement.tagName === 'TEXTAREA') { $(document.activeElement).parents("form").submit(); } else if (self.get("Popover").isOpen()) { $("#popover-container form").submit(); } } }); // Open board selector Mousetrap.bind("b", function(e) { e.preventDefault(); $('#board-selector').trigger('chosen:open'); }); // Close popover and dropdown Mousetrap.bindGlobal("esc", function() { self.get("Popover").close(); self.get("Dropdown").close(); }); // Show keyboard shortcut Mousetrap.bind("?", function() { self.get("Popover").open($("body").data("keyboard-shortcut-url")); }); }; Kanboard.App.prototype.focus = function() { // Auto-select input fields $(document).on('focus', '.auto-select', function() { $(this).select(); }); // Workaround for chrome $(document).on('mouseup', '.auto-select', function(e) { e.preventDefault(); }); }; Kanboard.App.prototype.chosen = function() { $(".chosen-select").each(function() { var searchThreshold = $(this).data("search-threshold"); if (searchThreshold === undefined) { searchThreshold = 10; } $(this).chosen({ width: "180px", no_results_text: $(this).data("notfound"), disable_search_threshold: searchThreshold }); }); $(".select-auto-redirect").change(function() { var regex = new RegExp($(this).data('redirect-regex'), 'g'); window.location = $(this).data('redirect-url').replace(regex, $(this).val()); }); }; Kanboard.App.prototype.datePicker = function() { // Datepicker translation $.datepicker.setDefaults($.datepicker.regional[$("body").data("js-lang")]); // Datepicker $(".form-date").datepicker({ showOtherMonths: true, selectOtherMonths: true, dateFormat: 'yy-mm-dd', constrainInput: false }); // Datetime picker $(".form-datetime").datetimepicker({ controlType: 'select', oneLine: true, dateFormat: 'yy-mm-dd', // timeFormat: 'h:mm tt', constrainInput: false }); }; Kanboard.App.prototype.autoComplete = function() { $(".autocomplete").each(function() { var input = $(this); var field = input.data("dst-field"); var extraField = input.data("dst-extra-field"); if ($('#form-' + field).val() == '') { input.parent().find("button[type=submit]").attr('disabled','disabled'); } input.autocomplete({ source: input.data("search-url"), minLength: 1, select: function(event, ui) { $("input[name=" + field + "]").val(ui.item.id); if (extraField) { $("input[name=" + extraField + "]").val(ui.item[extraField]); } input.parent().find("button[type=submit]").removeAttr('disabled'); } }); }); }; Kanboard.App.prototype.hasId = function(id) { return !!document.getElementById(id); }; Kanboard.App.prototype.showLoadingIcon = function() { $("body").append(' '); }; Kanboard.App.prototype.hideLoadingIcon = function() { $("#app-loading-icon").remove(); }; Kanboard.App.prototype.formatDuration = function(d) { if (d >= 86400) { return Math.round(d/86400) + "d"; } else if (d >= 3600) { return Math.round(d/3600) + "h"; } else if (d >= 60) { return Math.round(d/60) + "m"; } return d + "s"; }; Kanboard.App.prototype.isVisible = function() { var property = ""; if (typeof document.hidden !== "undefined") { property = "visibilityState"; } else if (typeof document.mozHidden !== "undefined") { property = "mozVisibilityState"; } else if (typeof document.msHidden !== "undefined") { property = "msVisibilityState"; } else if (typeof document.webkitHidden !== "undefined") { property = "webkitVisibilityState"; } if (property != "") { return document[property] == "visible"; } return true; }; Kanboard.AvgTimeColumnChart = function(app) { this.app = app; }; Kanboard.AvgTimeColumnChart.prototype.execute = function() { if (this.app.hasId("analytic-avg-time-column")) { this.show(); } }; Kanboard.AvgTimeColumnChart.prototype.show = function() { var chart = $("#chart"); var metrics = chart.data("metrics"); var plots = [chart.data("label")]; var categories = []; for (var column_id in metrics) { plots.push(metrics[column_id].average); categories.push(metrics[column_id].title); } c3.generate({ data: { columns: [plots], type: 'bar' }, bar: { width: { ratio: 0.5 } }, axis: { x: { type: 'category', categories: categories }, y: { tick: { format: this.app.formatDuration } } }, legend: { show: false } }); }; Kanboard.BoardCollapsedMode = function(app) { this.app = app; }; Kanboard.BoardCollapsedMode.prototype.keyboardShortcuts = function() { var self = this; if (self.app.hasId("board")) { Mousetrap.bind("s", function() { self.toggle(); }); } }; Kanboard.BoardCollapsedMode.prototype.toggle = function() { var self = this; this.app.showLoadingIcon(); $.ajax({ cache: false, url: $('.filter-display-mode:not([style="display: none;"]) a').attr('href'), success: function(data) { $('.filter-display-mode').toggle(); self.app.get("BoardDragAndDrop").refresh(data); } }); }; Kanboard.BoardColumnScrolling = function(app) { this.app = app; }; Kanboard.BoardColumnScrolling.prototype.execute = function() { if (this.app.hasId("board")) { this.render(); $(window).on("load", this.render); $(window).resize(this.render); } }; Kanboard.BoardColumnScrolling.prototype.listen = function() { var self = this; $(document).on('click', ".filter-toggle-height", function(e) { e.preventDefault(); self.toggle(); }); }; Kanboard.BoardColumnScrolling.prototype.onBoardRendered = function() { this.render(); }; Kanboard.BoardColumnScrolling.prototype.toggle = function() { var scrolling = localStorage.getItem("column_scroll"); if (scrolling == undefined) { scrolling = 1; } localStorage.setItem("column_scroll", scrolling == 0 ? 1 : 0); this.render(); }; Kanboard.BoardColumnScrolling.prototype.render = function() { var taskList = $(".board-task-list"); var rotationWrapper = $(".board-rotation-wrapper"); var filterMax = $(".filter-max-height"); var filterMin = $(".filter-min-height"); if (localStorage.getItem("column_scroll") == 0) { var height = 80; filterMax.show(); filterMin.hide(); rotationWrapper.css("min-height", ''); taskList.each(function() { var columnHeight = $(this).height(); if (columnHeight > height) { height = columnHeight; } }); taskList.css("min-height", height); taskList.css("height", ''); } else { filterMax.hide(); filterMin.show(); if ($(".board-swimlane").length > 1) { taskList.each(function() { if ($(this).height() > 500) { $(this).css("height", 500); } else { $(this).css("min-height", 320); // Height of the dropdown menu rotationWrapper.css("min-height", 320); } }); } else { var height = $(window).height() - 170; taskList.css("height", height); rotationWrapper.css("min-height", height); } } }; Kanboard.BoardColumnView = function(app) { this.app = app; }; Kanboard.BoardColumnView.prototype.execute = function() { if (this.app.hasId("board")) { this.render(); } }; Kanboard.BoardColumnView.prototype.listen = function() { var self = this; $(document).on("click", ".board-toggle-column-view", function() { self.toggle($(this).data("column-id")); }); }; Kanboard.BoardColumnView.prototype.onBoardRendered = function() { this.render(); }; Kanboard.BoardColumnView.prototype.render = function() { var self = this; $(".board-column-header").each(function() { var columnId = $(this).data('column-id'); if (localStorage.getItem("hidden_column_" + columnId)) { self.hideColumn(columnId); } }); }; Kanboard.BoardColumnView.prototype.toggle = function(columnId) { if (localStorage.getItem("hidden_column_" + columnId)) { this.showColumn(columnId); } else { this.hideColumn(columnId); } }; Kanboard.BoardColumnView.prototype.hideColumn = function(columnId) { $(".board-column-" + columnId + " .board-column-expanded").hide(); $(".board-column-" + columnId + " .board-column-collapsed").show(); $(".board-column-header-" + columnId + " .board-column-expanded").hide(); $(".board-column-header-" + columnId + " .board-column-collapsed").show(); $(".board-column-header-" + columnId).each(function() { $(this).removeClass("board-column-compact"); $(this).addClass("board-column-header-collapsed"); }); $(".board-column-" + columnId).each(function() { $(this).addClass("board-column-task-collapsed"); }); $(".board-column-" + columnId + " .board-rotation").each(function() { $(this).css("width", $(".board-column-" + columnId + "").height()); }); localStorage.setItem("hidden_column_" + columnId, 1); }; Kanboard.BoardColumnView.prototype.showColumn = function(columnId) { $(".board-column-" + columnId + " .board-column-expanded").show(); $(".board-column-" + columnId + " .board-column-collapsed").hide(); $(".board-column-header-" + columnId + " .board-column-expanded").show(); $(".board-column-header-" + columnId + " .board-column-collapsed").hide(); $(".board-column-header-" + columnId).removeClass("board-column-header-collapsed"); $(".board-column-" + columnId).removeClass("board-column-task-collapsed"); if (localStorage.getItem("horizontal_scroll") == 0) { $(".board-column-header-" + columnId).addClass("board-column-compact"); } localStorage.removeItem("hidden_column_" + columnId); }; Kanboard.BoardDragAndDrop = function(app) { this.app = app; this.savingInProgress = false; }; Kanboard.BoardDragAndDrop.prototype.execute = function() { if (this.app.hasId("board")) { this.dragAndDrop(); this.executeListeners(); } }; Kanboard.BoardDragAndDrop.prototype.dragAndDrop = function() { var self = this; var params = { forcePlaceholderSize: true, tolerance: "pointer", connectWith: ".board-task-list", placeholder: "draggable-placeholder", items: ".draggable-item", stop: function(event, ui) { var task = ui.item; var taskId = task.attr('data-task-id'); var taskPosition = task.attr('data-position'); var taskColumnId = task.attr('data-column-id'); var taskSwimlaneId = task.attr('data-swimlane-id'); var newColumnId = task.parent().attr("data-column-id"); var newSwimlaneId = task.parent().attr('data-swimlane-id'); var newPosition = task.index() + 1; task.removeClass("draggable-item-selected"); if (newColumnId != taskColumnId || newSwimlaneId != taskSwimlaneId || newPosition != taskPosition) { self.changeTaskState(taskId); self.save(taskId, newColumnId, newPosition, newSwimlaneId); } }, start: function(event, ui) { ui.item.addClass("draggable-item-selected"); ui.placeholder.height(ui.item.height()); } }; if ($.support.touch) { $(".task-board-sort-handle").css("display", "inline"); params["handle"] = ".task-board-sort-handle"; } $(".board-task-list").sortable(params); }; Kanboard.BoardDragAndDrop.prototype.changeTaskState = function(taskId) { var task = $("div[data-task-id=" + taskId + "]"); task.addClass('task-board-saving-state'); task.find('.task-board-saving-icon').show(); }; Kanboard.BoardDragAndDrop.prototype.save = function(taskId, columnId, position, swimlaneId) { var self = this; self.app.showLoadingIcon(); self.savingInProgress = true; $.ajax({ cache: false, url: $("#board").data("save-url"), contentType: "application/json", type: "POST", processData: false, data: JSON.stringify({ "task_id": taskId, "column_id": columnId, "swimlane_id": swimlaneId, "position": position }), success: function(data) { self.refresh(data); self.savingInProgress = false; }, error: function() { self.app.hideLoadingIcon(); self.savingInProgress = false; } }); }; Kanboard.BoardDragAndDrop.prototype.refresh = function(data) { $("#board-container").replaceWith(data); this.app.hideLoadingIcon(); this.dragAndDrop(); this.executeListeners(); }; Kanboard.BoardDragAndDrop.prototype.executeListeners = function() { for (var className in this.app.controllers) { var controller = this.app.get(className); if (typeof controller.onBoardRendered === "function") { controller.onBoardRendered(); } } }; Kanboard.BoardHorizontalScrolling = function(app) { this.app = app; }; Kanboard.BoardHorizontalScrolling.prototype.execute = function() { if (this.app.hasId("board")) { this.render(); } }; Kanboard.BoardHorizontalScrolling.prototype.listen = function() { var self = this; $(document).on('click', ".filter-toggle-scrolling", function(e) { e.preventDefault(); self.toggle(); }); }; Kanboard.BoardHorizontalScrolling.prototype.keyboardShortcuts = function() { var self = this; if (self.app.hasId("board")) { Mousetrap.bind("c", function () { self.toggle(); }); } }; Kanboard.BoardHorizontalScrolling.prototype.onBoardRendered = function() { this.render(); }; Kanboard.BoardHorizontalScrolling.prototype.toggle = function() { var scrolling = localStorage.getItem("horizontal_scroll") || 1; localStorage.setItem("horizontal_scroll", scrolling == 0 ? 1 : 0); this.render(); }; Kanboard.BoardHorizontalScrolling.prototype.render = function() { if (localStorage.getItem("horizontal_scroll") == 0) { $(".filter-wide").show(); $(".filter-compact").hide(); $("#board-container").addClass("board-container-compact"); $("#board th:not(.board-column-header-collapsed)").addClass("board-column-compact"); } else { $(".filter-wide").hide(); $(".filter-compact").show(); $("#board-container").removeClass("board-container-compact"); $("#board th").removeClass("board-column-compact"); } }; Kanboard.BoardPolling = function(app) { this.app = app; }; Kanboard.BoardPolling.prototype.execute = function() { if (this.app.hasId("board")) { var interval = parseInt($("#board").attr("data-check-interval")); if (interval > 0) { window.setInterval(this.check.bind(this), interval * 1000); } } }; Kanboard.BoardPolling.prototype.check = function() { if (this.app.isVisible() && !this.app.get("BoardDragAndDrop").savingInProgress) { var self = this; this.app.showLoadingIcon(); $.ajax({ cache: false, url: $("#board").data("check-url"), statusCode: { 200: function(data) { self.app.get("BoardDragAndDrop").refresh(data); }, 304: function () { self.app.hideLoadingIcon(); } } }); } }; Kanboard.BoardTask = function(app) { this.app = app; }; Kanboard.BoardTask.prototype.listen = function() { var self = this; $(document).on("click", ".task-board-change-assignee", function(e) { e.preventDefault(); e.stopPropagation(); self.app.get("Popover").open($(this).data('url')); }); $(document).on("click", ".task-board", function(e) { if (e.target.tagName != "A" && e.target.tagName != "IMG") { window.location = $(this).data("task-url"); } }); }; Kanboard.BoardTask.prototype.keyboardShortcuts = function() { var self = this; if (self.app.hasId("board")) { Mousetrap.bind("n", function () { self.app.get("Popover").open($("#board").data("task-creation-url")); }); } }; Kanboard.BurndownChart = function(app) { this.app = app; }; Kanboard.BurndownChart.prototype.execute = function() { if (this.app.hasId("analytic-burndown")) { this.show(); } }; Kanboard.BurndownChart.prototype.show = function() { var chart = $("#chart"); var metrics = chart.data("metrics"); var columns = [[chart.data("label-total")]]; var categories = []; var inputFormat = d3.time.format("%Y-%m-%d"); var outputFormat = d3.time.format(chart.data("date-format")); for (var i = 0; i < metrics.length; i++) { for (var j = 0; j < metrics[i].length; j++) { if (i == 0) { columns.push([metrics[i][j]]); } else { columns[j + 1].push(metrics[i][j]); if (j > 0) { if (columns[0][i] == undefined) { columns[0].push(0); } columns[0][i] += metrics[i][j]; } if (j == 0) { categories.push(outputFormat(inputFormat.parse(metrics[i][j]))); } } } } c3.generate({ data: { columns: columns }, axis: { x: { type: 'category', categories: categories } } }); }; Kanboard.Calendar = function(app) { this.app = app; }; Kanboard.Calendar.prototype.execute = function() { var calendar = $('#calendar'); if (calendar.length == 1) { this.show(calendar); } }; Kanboard.Calendar.prototype.show = function(calendar) { calendar.fullCalendar({ lang: $("body").data("js-lang"), editable: true, eventLimit: true, defaultView: "month", header: { left: 'prev,next today', center: 'title', right: 'month,agendaWeek,agendaDay' }, eventDrop: function(event) { $.ajax({ cache: false, url: calendar.data("save-url"), contentType: "application/json", type: "POST", processData: false, data: JSON.stringify({ "task_id": event.id, "date_due": event.start.format() }) }); }, viewRender: function() { var url = calendar.data("check-url"); var params = { "start": calendar.fullCalendar('getView').start.format(), "end": calendar.fullCalendar('getView').end.format() }; for (var key in params) { url += "&" + key + "=" + params[key]; } $.getJSON(url, function(events) { calendar.fullCalendar('removeEvents'); calendar.fullCalendar('addEventSource', events); calendar.fullCalendar('rerenderEvents'); }); } }); }; Kanboard.Column = function(app) { this.app = app; }; Kanboard.Column.prototype.listen = function() { this.dragAndDrop(); }; Kanboard.Column.prototype.dragAndDrop = function() { var self = this; $(".draggable-row-handle").mouseenter(function() { $(this).parent().parent().addClass("draggable-item-hover"); }).mouseleave(function() { $(this).parent().parent().removeClass("draggable-item-hover"); }); $(".columns-table tbody").sortable({ forcePlaceholderSize: true, handle: "td:first i", helper: function(e, ui) { ui.children().each(function() { $(this).width($(this).width()); }); return ui; }, stop: function(event, ui) { var column = ui.item; column.removeClass("draggable-item-selected"); self.savePosition(column.data("column-id"), column.index() + 1); }, start: function(event, ui) { ui.item.addClass("draggable-item-selected"); } }).disableSelection(); }; Kanboard.Column.prototype.savePosition = function(columnId, position) { var url = $(".columns-table").data("save-position-url"); var self = this; this.app.showLoadingIcon(); $.ajax({ cache: false, url: url, contentType: "application/json", type: "POST", processData: false, data: JSON.stringify({ "column_id": columnId, "position": position }), complete: function() { self.app.hideLoadingIcon(); } }); }; Kanboard.CompareHoursColumnChart = function(app) { this.app = app; }; Kanboard.CompareHoursColumnChart.prototype.execute = function() { if (this.app.hasId("analytic-compare-hours")) { this.show(); } }; Kanboard.CompareHoursColumnChart.prototype.show = function() { var chart = $("#chart"); var metrics = chart.data("metrics"); var labelOpen = chart.data("label-open"); var labelClosed = chart.data("label-closed"); var spent = [chart.data("label-spent")]; var estimated = [chart.data("label-estimated")]; var categories = []; for (var status in metrics) { spent.push(parseFloat(metrics[status].time_spent)); estimated.push(parseFloat(metrics[status].time_estimated)); categories.push(status == 'open' ? labelOpen : labelClosed); } c3.generate({ data: { columns: [spent, estimated], type: 'bar' }, bar: { width: { ratio: 0.2 } }, axis: { x: { type: 'category', categories: categories } }, legend: { show: true } }); }; Kanboard.CumulativeFlowDiagram = function(app) { this.app = app; }; Kanboard.CumulativeFlowDiagram.prototype.execute = function() { if (this.app.hasId("analytic-cfd")) { this.show(); } }; Kanboard.CumulativeFlowDiagram.prototype.show = function() { var chart = $("#chart"); var metrics = chart.data("metrics"); var columns = []; var groups = []; var categories = []; var inputFormat = d3.time.format("%Y-%m-%d"); var outputFormat = d3.time.format(chart.data("date-format")); for (var i = 0; i < metrics.length; i++) { for (var j = 0; j < metrics[i].length; j++) { if (i == 0) { columns.push([metrics[i][j]]); if (j > 0) { groups.push(metrics[i][j]); } } else { columns[j].push(metrics[i][j]); if (j == 0) { categories.push(outputFormat(inputFormat.parse(metrics[i][j]))); } } } } c3.generate({ data: { columns: columns, type: 'area-spline', groups: [groups] }, axis: { x: { type: 'category', categories: categories } } }); }; Kanboard.Dropdown = function(app) { this.app = app; }; Kanboard.Dropdown.prototype.listen = function() { var self = this; $(document).on('click', function() { self.close(); }); $(document).on('click', '.dropdown-menu', function(e) { e.preventDefault(); e.stopImmediatePropagation(); self.close(); var submenu = $(this).next('ul'); var offset = $(this).offset(); // Clone the submenu outside of the column to avoid clipping issue with overflow $("body").append(jQuery("