summaryrefslogtreecommitdiff
path: root/assets/js/src
diff options
context:
space:
mode:
Diffstat (limited to 'assets/js/src')
-rw-r--r--assets/js/src/App.js196
-rw-r--r--assets/js/src/AvgTimeColumnChart.js39
-rw-r--r--assets/js/src/BudgetChart.js55
-rw-r--r--assets/js/src/BurndownChart.js48
-rw-r--r--assets/js/src/CumulativeFlowDiagram.js48
-rw-r--r--assets/js/src/LeadCycleTimeChart.js45
-rw-r--r--assets/js/src/Markdown.js50
-rw-r--r--assets/js/src/Popover.js50
-rw-r--r--assets/js/src/Router.js34
-rw-r--r--assets/js/src/Search.js61
-rw-r--r--assets/js/src/Sidebar.js25
-rw-r--r--assets/js/src/TaskRepartitionChart.js18
-rw-r--r--assets/js/src/TaskTimeColumnChart.js39
-rw-r--r--assets/js/src/Tooltip.js87
-rw-r--r--assets/js/src/UserRepartitionChart.js18
-rw-r--r--assets/js/src/analytic.js355
-rw-r--r--assets/js/src/base.js394
-rw-r--r--assets/js/src/board.js392
-rw-r--r--assets/js/src/calendar.js86
-rw-r--r--assets/js/src/screenshot.js204
-rw-r--r--assets/js/src/swimlane.js111
21 files changed, 1133 insertions, 1222 deletions
diff --git a/assets/js/src/App.js b/assets/js/src/App.js
new file mode 100644
index 00000000..2c3d10a7
--- /dev/null
+++ b/assets/js/src/App.js
@@ -0,0 +1,196 @@
+function App() {
+ this.popover = new Popover(this);
+ this.markdown = new Markdown();
+ this.sidebar = new Sidebar();
+ this.search = new Search();
+ this.tooltip = new Tooltip(this);
+ this.swimlane = new Swimlane();
+ this.keyboardShortcuts();
+ this.boardSelector();
+ this.listen();
+ this.poll();
+
+ // Alert box fadeout
+ $(".alert-fade-out").delay(4000).fadeOut(800, function() {
+ $(this).remove();
+ });
+
+ // Reload page when a destination project is changed
+ var reloading_project = false;
+ $("select.task-reload-project-destination").change(function() {
+ if (! reloading_project) {
+ $(".loading-icon").show();
+ reloading_project = true;
+ window.location = $(this).data("redirect").replace(/PROJECT_ID/g, $(this).val());
+ }
+ });
+}
+
+App.prototype.listen = function() {
+ $(document).off();
+
+ this.popover.listen();
+ this.markdown.listen();
+ this.sidebar.listen();
+ this.tooltip.listen();
+ this.search.listen();
+ this.search.focus();
+ this.taskAutoComplete();
+ this.datePicker();
+ this.focus();
+
+ // Dropdown
+ $(".dropit-submenu").hide();
+ $('.dropdown').not(".dropit").dropit({ triggerParentEl : "span" });
+};
+
+App.prototype.focus = function() {
+
+ // Autofocus fields (html5 autofocus works only with page onload)
+ $("[autofocus]").each(function(index, element) {
+ $(this).focus();
+ })
+
+ // Auto-select input fields
+ $(document).on('focus', '.auto-select', function() {
+ $(this).select();
+ });
+
+ // Workaround for chrome
+ $(document).on('mouseup', '.auto-select', function(e) {
+ e.preventDefault();
+ });
+};
+
+App.prototype.poll = function() {
+ window.setInterval(this.checkSession, 60000);
+};
+
+App.prototype.keyboardShortcuts = function() {
+ // Submit form
+ Mousetrap.bindGlobal("mod+enter", function() {
+ $("form").submit();
+ });
+
+ // Open board selector
+ Mousetrap.bind("b", function(e) {
+ e.preventDefault();
+ $('#board-selector').trigger('chosen:open');
+ });
+};
+
+App.prototype.checkSession = function() {
+ if (! $(".form-login").length) {
+ $.ajax({
+ cache: false,
+ url: $("body").data("status-url"),
+ statusCode: {
+ 401: function() {
+ window.location = $("body").data("login-url");
+ }
+ }
+ });
+ }
+};
+
+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
+ });
+};
+
+App.prototype.taskAutoComplete = function() {
+ // Task auto-completion
+ if ($(".task-autocomplete").length) {
+
+ if ($('.opposite_task_id').val() == '') {
+ $(".task-autocomplete").parent().find("input[type=submit]").attr('disabled','disabled');
+ }
+
+ $(".task-autocomplete").autocomplete({
+ source: $(".task-autocomplete").data("search-url"),
+ minLength: 1,
+ select: function(event, ui) {
+ var field = $(".task-autocomplete").data("dst-field");
+ $("input[name=" + field + "]").val(ui.item.id);
+
+ $(".task-autocomplete").parent().find("input[type=submit]").removeAttr('disabled');
+ }
+ });
+ }
+};
+
+App.prototype.boardSelector = function() {
+ $(".chosen-select").chosen({
+ width: "200px",
+ no_results_text: $(".chosen-select").data("notfound"),
+ disable_search_threshold: 10
+ });
+
+ $("#board-selector").chosen({
+ width: 180,
+ no_results_text: $("#board-selector").data("notfound")
+ });
+
+ $("#board-selector").change(function() {
+ window.location = $(this).attr("data-board-url").replace(/PROJECT_ID/g, $(this).val());
+ });
+};
+
+App.prototype.showLoadingIcon = function() {
+ $("body").append('<span id="app-loading-icon">&nbsp;<i class="fa fa-spinner fa-spin"></i></span>');
+};
+
+App.prototype.hideLoadingIcon = function() {
+ $("#app-loading-icon").remove();
+};
+
+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;
+};
+
+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";
+};
diff --git a/assets/js/src/AvgTimeColumnChart.js b/assets/js/src/AvgTimeColumnChart.js
new file mode 100644
index 00000000..f8b7d2ee
--- /dev/null
+++ b/assets/js/src/AvgTimeColumnChart.js
@@ -0,0 +1,39 @@
+function AvgTimeColumnChart() {
+}
+
+AvgTimeColumnChart.prototype.execute = function(app) {
+ 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: app.formatDuration
+ }
+ }
+ },
+ legend: {
+ show: false
+ }
+ });
+};
diff --git a/assets/js/src/BudgetChart.js b/assets/js/src/BudgetChart.js
new file mode 100644
index 00000000..9ab0d5a9
--- /dev/null
+++ b/assets/js/src/BudgetChart.js
@@ -0,0 +1,55 @@
+function BudgetChart() {
+}
+
+BudgetChart.prototype.execute = function() {
+ var categories = [];
+ var metrics = $("#chart").data("metrics");
+ var labels = $("#chart").data("labels");
+ var inputFormat = d3.time.format("%Y-%m-%d");
+ var outputFormat = d3.time.format($("#chart").data("date-format"));
+
+ var columns = [
+ [labels["in"]],
+ [labels["left"]],
+ [labels["out"]]
+ ];
+
+ var colors = {};
+ colors[labels["in"]] = '#5858FA';
+ colors[labels["left"]] = '#04B404';
+ colors[labels["out"]] = '#DF3A01';
+
+ for (var i = 0; i < metrics.length; i++) {
+ categories.push(outputFormat(inputFormat.parse(metrics[i]["date"])));
+ columns[0].push(metrics[i]["in"]);
+ columns[1].push(metrics[i]["left"]);
+ columns[2].push(metrics[i]["out"]);
+ }
+
+ c3.generate({
+ data: {
+ columns: columns,
+ colors: colors,
+ type : 'bar'
+ },
+ bar: {
+ width: {
+ ratio: 0.25
+ }
+ },
+ grid: {
+ x: {
+ show: true
+ },
+ y: {
+ show: true
+ }
+ },
+ axis: {
+ x: {
+ type: 'category',
+ categories: categories
+ }
+ }
+ });
+};
diff --git a/assets/js/src/BurndownChart.js b/assets/js/src/BurndownChart.js
new file mode 100644
index 00000000..79199b67
--- /dev/null
+++ b/assets/js/src/BurndownChart.js
@@ -0,0 +1,48 @@
+function BurndownChart() {
+}
+
+BurndownChart.prototype.execute = function() {
+ 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
+ }
+ }
+ });
+};
diff --git a/assets/js/src/CumulativeFlowDiagram.js b/assets/js/src/CumulativeFlowDiagram.js
new file mode 100644
index 00000000..61a0847b
--- /dev/null
+++ b/assets/js/src/CumulativeFlowDiagram.js
@@ -0,0 +1,48 @@
+function CumulativeFlowDiagram() {
+}
+
+CumulativeFlowDiagram.prototype.execute = function() {
+
+ 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
+ }
+ }
+ });
+};
diff --git a/assets/js/src/LeadCycleTimeChart.js b/assets/js/src/LeadCycleTimeChart.js
new file mode 100644
index 00000000..501ed892
--- /dev/null
+++ b/assets/js/src/LeadCycleTimeChart.js
@@ -0,0 +1,45 @@
+function LeadCycleTimeChart() {
+}
+
+LeadCycleTimeChart.prototype.execute = function(app) {
+ var metrics = $("#chart").data("metrics");
+ var cycle = [$("#chart").data("label-cycle")];
+ var lead = [$("#chart").data("label-lead")];
+ var categories = [];
+
+ var types = {};
+ types[$("#chart").data("label-cycle")] = 'area';
+ types[$("#chart").data("label-lead")] = 'area-spline';
+
+ var colors = {};
+ colors[$("#chart").data("label-lead")] = '#afb42b';
+ colors[$("#chart").data("label-cycle")] = '#4e342e';
+
+ for (var i = 0; i < metrics.length; i++) {
+ cycle.push(parseInt(metrics[i].avg_cycle_time));
+ lead.push(parseInt(metrics[i].avg_lead_time));
+ categories.push(metrics[i].day);
+ }
+
+ c3.generate({
+ data: {
+ columns: [
+ lead,
+ cycle
+ ],
+ types: types,
+ colors: colors
+ },
+ axis: {
+ x: {
+ type: 'category',
+ categories: categories
+ },
+ y: {
+ tick: {
+ format: app.formatDuration
+ }
+ }
+ }
+ });
+};
diff --git a/assets/js/src/Markdown.js b/assets/js/src/Markdown.js
new file mode 100644
index 00000000..3a51ffce
--- /dev/null
+++ b/assets/js/src/Markdown.js
@@ -0,0 +1,50 @@
+function Markdown() {
+}
+
+Markdown.prototype.showPreview = function(e) {
+ e.preventDefault();
+
+ var link = $(this);
+ var nav = $(this).closest("ul");
+ var write = $(".write-area");
+ var preview = $(".preview-area");
+ var textarea = $("textarea");
+
+ var request = $.ajax({
+ url: "?controller=app&action=preview", // TODO: remoe harcoded url
+ contentType: "application/json",
+ type: "POST",
+ processData: false,
+ dataType: "html",
+ data: JSON.stringify({
+ "text": textarea.val()
+ })
+ });
+
+ request.done(function(data) {
+ nav.find("li").removeClass("form-tab-selected");
+ link.parent().addClass("form-tab-selected");
+
+ preview.find(".markdown").html(data)
+ preview.css("height", textarea.css("height"));
+ preview.css("width", textarea.css("width"));
+
+ write.hide();
+ preview.show();
+ });
+};
+
+Markdown.prototype.showWriter = function(e) {
+ e.preventDefault();
+
+ $(this).closest("ul").find("li").removeClass("form-tab-selected")
+ $(this).parent().addClass("form-tab-selected");
+
+ $(".write-area").show();
+ $(".preview-area").hide();
+};
+
+Markdown.prototype.listen = function() {
+ $(document).on("click", "#markdown-preview", this.showPreview.bind(this));
+ $(document).on("click", "#markdown-write", this.showWriter.bind(this));
+};
diff --git a/assets/js/src/Popover.js b/assets/js/src/Popover.js
new file mode 100644
index 00000000..217ae55e
--- /dev/null
+++ b/assets/js/src/Popover.js
@@ -0,0 +1,50 @@
+function Popover(app) {
+ this.app = app;
+ this.router = new Router();
+ this.router.addRoute('screenshot-zone', Screenshot);
+ Mousetrap.bindGlobal("esc", this.close);
+}
+
+Popover.prototype.isOpen = function() {
+ return $('#popover-container').size() > 0;
+};
+
+Popover.prototype.open = function(link) {
+ var self = this;
+
+ $.get(link, function(content) {
+ $("body").append('<div id="popover-container"><div id="popover-content">' + content + '</div></div>');
+ self.router.dispatch();
+ self.app.listen();
+ });
+};
+
+Popover.prototype.close = function(e) {
+ if (e) {
+ e.preventDefault();
+ }
+
+ $('#popover-container').remove();
+};
+
+Popover.prototype.onClick = function(e) {
+ e.preventDefault();
+ e.stopPropagation();
+
+ var link = e.target.getAttribute("href");
+
+ if (! link) {
+ link = e.target.getAttribute("data-href");
+ }
+
+ if (link) {
+ this.open(link);
+ }
+};
+
+Popover.prototype.listen = function() {
+ $(document).on("click", ".popover", this.onClick.bind(this));
+ $(document).on("click", ".close-popover", this.close.bind(this));
+ $(document).on("click", "#popover-container", this.close.bind(this));
+ $(document).on("click", "#popover-content", function(e) { e.stopPropagation(); });
+};
diff --git a/assets/js/src/Router.js b/assets/js/src/Router.js
new file mode 100644
index 00000000..d1e5de2a
--- /dev/null
+++ b/assets/js/src/Router.js
@@ -0,0 +1,34 @@
+function Router() {
+ this.routes = {};
+}
+
+Router.prototype.addRoute = function(id, controller) {
+ this.routes[id] = controller;
+};
+
+Router.prototype.dispatch = function(app) {
+ for (var id in this.routes) {
+ if (document.getElementById(id)) {
+ var controller = Object.create(this.routes[id].prototype);
+ controller.execute(app);
+ break;
+ }
+ }
+};
+
+jQuery(document).ready(function() {
+ var app = new App();
+ var router = new Router();
+ router.addRoute('board', Board);
+ router.addRoute('calendar', Calendar);
+ router.addRoute('screenshot-zone', Screenshot);
+ router.addRoute('analytic-task-repartition', TaskRepartitionChart);
+ router.addRoute('analytic-user-repartition', UserRepartitionChart);
+ router.addRoute('analytic-cfd', CumulativeFlowDiagram);
+ router.addRoute('analytic-burndown', BurndownChart);
+ router.addRoute('budget-chart', BudgetChart);
+ router.addRoute('analytic-avg-time-column', AvgTimeColumnChart);
+ router.addRoute('analytic-task-time-column', TaskTimeColumnChart);
+ router.addRoute('analytic-lead-cycle-time', LeadCycleTimeChart);
+ router.dispatch(app);
+});
diff --git a/assets/js/src/Search.js b/assets/js/src/Search.js
new file mode 100644
index 00000000..d38ffd2b
--- /dev/null
+++ b/assets/js/src/Search.js
@@ -0,0 +1,61 @@
+function Search() {
+ this.keyboardShortcuts();
+}
+
+Search.prototype.focus = function() {
+ // Place cursor at the end when focusing on the search box
+ $(document).on("focus", "#form-search", function() {
+ if ($("#form-search")[0].setSelectionRange) {
+ $('#form-search')[0].setSelectionRange($('#form-search').val().length, $('#form-search').val().length);
+ }
+ });
+};
+
+Search.prototype.listen = function() {
+ // Filter helper for search
+ $(document).on("click", ".filter-helper", function (e) {
+ e.preventDefault();
+ $("#form-search").val($(this).data("filter"));
+ $("form.search").submit();
+ });
+};
+
+Search.prototype.keyboardShortcuts = function() {
+ // Switch view mode for projects: go to the board
+ Mousetrap.bind("v b", function(e) {
+ var link = $(".view-board");
+
+ if (link.length) {
+ window.location = link.attr('href');
+ }
+ });
+
+ // Switch view mode for projects: go to the calendar
+ Mousetrap.bind("v c", function(e) {
+ var link = $(".view-calendar");
+
+ if (link.length) {
+ window.location = link.attr('href');
+ }
+ });
+
+ // Switch view mode for projects: go to the listing
+ Mousetrap.bind("v l", function(e) {
+ var link = $(".view-listing");
+
+ if (link.length) {
+ window.location = link.attr('href');
+ }
+ });
+
+ // Focus to the search field
+ Mousetrap.bind("f", function(e) {
+ e.preventDefault();
+ var input = document.getElementById("form-search");
+
+ if (input) {
+ input.focus();
+ }
+ });
+};
+
diff --git a/assets/js/src/Sidebar.js b/assets/js/src/Sidebar.js
new file mode 100644
index 00000000..0794d6b3
--- /dev/null
+++ b/assets/js/src/Sidebar.js
@@ -0,0 +1,25 @@
+function Sidebar() {
+}
+
+Sidebar.prototype.expand = function(e) {
+ e.preventDefault();
+ $(".sidebar-container").removeClass("sidebar-collapsed");
+ $(".sidebar-collapse").show();
+ $(".sidebar h2").show();
+ $(".sidebar ul").show();
+ $(".sidebar-expand").hide();
+};
+
+Sidebar.prototype.collapse = function(e) {
+ e.preventDefault();
+ $(".sidebar-container").addClass("sidebar-collapsed");
+ $(".sidebar-expand").show();
+ $(".sidebar h2").hide();
+ $(".sidebar ul").hide();
+ $(".sidebar-collapse").hide();
+};
+
+Sidebar.prototype.listen = function() {
+ $(document).on("click", ".sidebar-collapse", this.collapse);
+ $(document).on("click", ".sidebar-expand", this.expand);
+};
diff --git a/assets/js/src/TaskRepartitionChart.js b/assets/js/src/TaskRepartitionChart.js
new file mode 100644
index 00000000..1b1368dc
--- /dev/null
+++ b/assets/js/src/TaskRepartitionChart.js
@@ -0,0 +1,18 @@
+function TaskRepartitionChart() {
+}
+
+TaskRepartitionChart.prototype.execute = function() {
+ var metrics = $("#chart").data("metrics");
+ var columns = [];
+
+ for (var i = 0; i < metrics.length; i++) {
+ columns.push([metrics[i].column_title, metrics[i].nb_tasks]);
+ }
+
+ c3.generate({
+ data: {
+ columns: columns,
+ type : 'donut'
+ }
+ });
+};
diff --git a/assets/js/src/TaskTimeColumnChart.js b/assets/js/src/TaskTimeColumnChart.js
new file mode 100644
index 00000000..a95424a0
--- /dev/null
+++ b/assets/js/src/TaskTimeColumnChart.js
@@ -0,0 +1,39 @@
+function TaskTimeColumnChart() {
+}
+
+TaskTimeColumnChart.prototype.execute = function(app) {
+ var metrics = $("#chart").data("metrics");
+ var plots = [$("#chart").data("label")];
+ var categories = [];
+
+ for (var i = 0; i < metrics.length; i++) {
+ plots.push(metrics[i].time_spent);
+ categories.push(metrics[i].title);
+ }
+
+ c3.generate({
+ data: {
+ columns: [plots],
+ type: 'bar'
+ },
+ bar: {
+ width: {
+ ratio: 0.5
+ }
+ },
+ axis: {
+ x: {
+ type: 'category',
+ categories: categories
+ },
+ y: {
+ tick: {
+ format: app.formatDuration
+ }
+ }
+ },
+ legend: {
+ show: false
+ }
+ });
+};
diff --git a/assets/js/src/Tooltip.js b/assets/js/src/Tooltip.js
new file mode 100644
index 00000000..48102da6
--- /dev/null
+++ b/assets/js/src/Tooltip.js
@@ -0,0 +1,87 @@
+function Tooltip(app) {
+ this.app = app;
+}
+
+Tooltip.prototype.listen = function() {
+ var self = this;
+
+ $(".tooltip").tooltip({
+ track: false,
+ show: false,
+ position: {
+ my: 'left-20 top',
+ at: 'center bottom+9',
+ using: function(position, feedback) {
+
+ $(this).css(position);
+
+ var arrow_pos = feedback.target.left + feedback.target.width / 2 - feedback.element.left - 20;
+
+ $("<div>")
+ .addClass("tooltip-arrow")
+ .addClass(feedback.vertical)
+ .addClass(arrow_pos < 1 ? "align-left" : "align-right")
+ .appendTo(this);
+ }
+ },
+ content: function() {
+ var _this = this;
+ var href = $(this).attr('data-href');
+
+ if (! href) {
+ return '<div class="markdown">' + $(this).attr("title") + '</div>';
+ }
+
+ $.get(href, function setTooltipContent(data) {
+ var tooltip = $('.ui-tooltip:visible');
+
+ $('.ui-tooltip-content:visible').html(data);
+
+ // Clear previous position, it interferes with the updated position computation
+ tooltip.css({ top: '', left: '' });
+
+ // Remove arrow, it will be added when repositionning
+ tooltip.children('.tooltip-arrow').remove();
+
+ // Reposition the tooltip
+ var position = $(_this).tooltip("option", "position");
+ position.of = $(_this);
+ tooltip.position(position);
+
+ // Toggle subtasks status
+ $('#tooltip-subtasks a').not(".popover").click(function(e) {
+
+ e.preventDefault();
+ e.stopPropagation();
+
+ if ($(this).hasClass("popover-subtask-restriction")) {
+ self.app.popover.open($(this).attr('href'));
+ $(_this).tooltip('close');
+ }
+ else {
+ $.get($(this).attr('href'), setTooltipContent);
+ }
+ });
+ });
+
+ return '<i class="fa fa-spinner fa-spin"></i>';
+ }
+ })
+ .on("mouseenter", function() {
+ var _this = this;
+ $(this).tooltip("open");
+
+ $(".ui-tooltip").on("mouseleave", function() {
+ $(_this).tooltip('close');
+ });
+ }).on("mouseleave focusout", function(e) {
+ e.stopImmediatePropagation();
+ var _this = this;
+
+ setTimeout(function() {
+ if (! $(".ui-tooltip:hover").length) {
+ $(_this).tooltip("close");
+ }
+ }, 100);
+ });
+}; \ No newline at end of file
diff --git a/assets/js/src/UserRepartitionChart.js b/assets/js/src/UserRepartitionChart.js
new file mode 100644
index 00000000..4255130a
--- /dev/null
+++ b/assets/js/src/UserRepartitionChart.js
@@ -0,0 +1,18 @@
+function UserRepartitionChart() {
+}
+
+UserRepartitionChart.prototype.execute = function() {
+ var metrics = $("#chart").data("metrics");
+ var columns = [];
+
+ for (var i = 0; i < metrics.length; i++) {
+ columns.push([metrics[i].user, metrics[i].nb_tasks]);
+ }
+
+ c3.generate({
+ data: {
+ columns: columns,
+ type : 'donut'
+ }
+ });
+};
diff --git a/assets/js/src/analytic.js b/assets/js/src/analytic.js
deleted file mode 100644
index a5df0ac6..00000000
--- a/assets/js/src/analytic.js
+++ /dev/null
@@ -1,355 +0,0 @@
-(function() {
-
- // CFD diagram
- function drawCfd()
- {
- 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
- }
- }
- });
- }
-
- // Burndown chart
- function drawBurndown()
- {
- 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
- }
- }
- });
- }
-
- // Draw task repartition chart
- function drawTaskRepartition()
- {
- var metrics = $("#chart").data("metrics");
- var columns = [];
-
- for (var i = 0; i < metrics.length; i++) {
- columns.push([metrics[i].column_title, metrics[i].nb_tasks]);
- }
-
- c3.generate({
- data: {
- columns: columns,
- type : 'donut'
- }
- });
- }
-
- // Draw user repartition chart
- function drawUserRepartition()
- {
- var metrics = $("#chart").data("metrics");
- var columns = [];
-
- for (var i = 0; i < metrics.length; i++) {
- columns.push([metrics[i].user, metrics[i].nb_tasks]);
- }
-
- c3.generate({
- data: {
- columns: columns,
- type : 'donut'
- }
- });
- }
-
- // Draw budget chart
- function drawBudget()
- {
- var categories = [];
- var metrics = $("#chart").data("metrics");
- var labels = $("#chart").data("labels");
- var inputFormat = d3.time.format("%Y-%m-%d");
- var outputFormat = d3.time.format($("#chart").data("date-format"));
-
- var columns = [
- [labels["in"]],
- [labels["left"]],
- [labels["out"]]
- ];
-
- var colors = {};
- colors[labels["in"]] = '#5858FA';
- colors[labels["left"]] = '#04B404';
- colors[labels["out"]] = '#DF3A01';
-
- for (var i = 0; i < metrics.length; i++) {
- categories.push(outputFormat(inputFormat.parse(metrics[i]["date"])));
- columns[0].push(metrics[i]["in"]);
- columns[1].push(metrics[i]["left"]);
- columns[2].push(metrics[i]["out"]);
- }
-
- c3.generate({
- data: {
- columns: columns,
- colors: colors,
- type : 'bar'
- },
- bar: {
- width: {
- ratio: 0.25
- }
- },
- grid: {
- x: {
- show: true
- },
- y: {
- show: true
- }
- },
- axis: {
- x: {
- type: 'category',
- categories: categories
- }
- }
- });
- }
-
- // Draw chart for average time spent into each column
- function drawAvgTimeColumn()
- {
- 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: formatDuration
- }
- }
- },
- legend: {
- show: false
- }
- });
- }
-
- // Draw chart for average time spent into each column
- function drawTaskTimeColumn()
- {
- var metrics = $("#chart").data("metrics");
- var plots = [$("#chart").data("label")];
- var categories = [];
-
- for (var i = 0; i < metrics.length; i++) {
- plots.push(metrics[i].time_spent);
- categories.push(metrics[i].title);
- }
-
- c3.generate({
- data: {
- columns: [plots],
- type: 'bar'
- },
- bar: {
- width: {
- ratio: 0.5
- }
- },
- axis: {
- x: {
- type: 'category',
- categories: categories
- },
- y: {
- tick: {
- format: formatDuration
- }
- }
- },
- legend: {
- show: false
- }
- });
- }
-
- // Draw lead and cycle time for the project
- function drawLeadAndCycleTime()
- {
- var metrics = $("#chart").data("metrics");
- var cycle = [$("#chart").data("label-cycle")];
- var lead = [$("#chart").data("label-lead")];
- var categories = [];
-
- var types = {};
- types[$("#chart").data("label-cycle")] = 'area';
- types[$("#chart").data("label-lead")] = 'area-spline';
-
- var colors = {};
- colors[$("#chart").data("label-lead")] = '#afb42b';
- colors[$("#chart").data("label-cycle")] = '#4e342e';
-
- for (var i = 0; i < metrics.length; i++) {
- cycle.push(parseInt(metrics[i].avg_cycle_time));
- lead.push(parseInt(metrics[i].avg_lead_time));
- categories.push(metrics[i].day);
- }
-
- c3.generate({
- data: {
- columns: [
- lead,
- cycle
- ],
- types: types,
- colors: colors
- },
- axis: {
- x: {
- type: 'category',
- categories: categories
- },
- y: {
- tick: {
- format: formatDuration
- }
- }
- }
- });
- }
-
- function formatDuration(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";
- }
-
- jQuery(document).ready(function() {
-
- if (Kanboard.Exists("analytic-task-repartition")) {
- drawTaskRepartition();
- }
- else if (Kanboard.Exists("analytic-user-repartition")) {
- drawUserRepartition();
- }
- else if (Kanboard.Exists("analytic-cfd")) {
- drawCfd();
- }
- else if (Kanboard.Exists("analytic-burndown")) {
- drawBurndown();
- }
- else if (Kanboard.Exists("budget-chart")) {
- drawBudget();
- }
- else if (Kanboard.Exists("analytic-avg-time-column")) {
- drawAvgTimeColumn();
- }
- else if (Kanboard.Exists("analytic-task-time-column")) {
- drawTaskTimeColumn();
- }
- else if (Kanboard.Exists("analytic-lead-cycle-time")) {
- drawLeadAndCycleTime();
- }
- });
-
-})();
diff --git a/assets/js/src/base.js b/assets/js/src/base.js
deleted file mode 100644
index ffad93b8..00000000
--- a/assets/js/src/base.js
+++ /dev/null
@@ -1,394 +0,0 @@
-var Kanboard = (function() {
-
- jQuery(document).ready(function() {
- Kanboard.Init();
- });
-
- return {
-
- ShowLoadingIcon: function() {
- $("body").append('<span id="app-loading-icon">&nbsp;<i class="fa fa-spinner fa-spin"></i></span>');
- },
-
- HideLoadingIcon: function() {
- $("#app-loading-icon").remove();
- },
-
- // Return true if the element#id exists
- Exists: function(id) {
- if (document.getElementById(id)) {
- return true;
- }
-
- return false;
- },
-
- // Open a popup on a link click
- Popover: function(e, callback) {
- e.preventDefault();
- e.stopPropagation();
-
- var link = e.target.getAttribute("href");
-
- if (! link) {
- link = e.target.getAttribute("data-href");
- }
-
- if (link) {
- Kanboard.OpenPopover(link, callback);
- }
- },
-
- // Display a popup
- OpenPopover: function(link, callback) {
-
- $.get(link, function(content) {
-
- $("body").append('<div id="popover-container"><div id="popover-content">' + content + '</div></div>');
-
- $("#popover-container").click(function() {
- Kanboard.ClosePopover();
- });
-
- $("#popover-content").click(function(e) {
- e.stopPropagation();
- });
-
- $(".close-popover").click(function(e) {
- e.preventDefault();
- Kanboard.ClosePopover();
- });
-
- Mousetrap.bindGlobal("esc", function() {
- Kanboard.ClosePopover();
- });
-
- if (callback) {
- callback();
- }
- });
- },
-
- ClosePopover: function() {
- $('#popover-container').remove();
- Kanboard.Screenshot.Destroy();
- },
-
- // Return true if the page is visible
- 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;
- },
-
- // Save preferences in local storage
- SetStorageItem: function(key, value) {
- if (typeof(Storage) !== "undefined") {
- localStorage.setItem(key, value);
- }
- },
-
- GetStorageItem: function(key) {
-
- if (typeof(Storage) !== "undefined") {
- return localStorage.getItem(key);
- }
-
- return '';
- },
-
- // Generate Markdown preview
- MarkdownPreview: function(e) {
-
- e.preventDefault();
-
- var link = $(this);
- var nav = $(this).closest("ul");
- var write = $(".write-area");
- var preview = $(".preview-area");
- var textarea = $("textarea");
-
- var request = $.ajax({
- url: "?controller=app&action=preview",
- contentType: "application/json",
- type: "POST",
- processData: false,
- dataType: "html",
- data: JSON.stringify({
- "text": textarea.val()
- })
- });
-
- request.done(function(data) {
-
- nav.find("li").removeClass("form-tab-selected");
- link.parent().addClass("form-tab-selected");
-
- preview.find(".markdown").html(data)
- preview.css("height", textarea.css("height"));
- preview.css("width", textarea.css("width"));
-
- write.hide();
- preview.show();
- });
- },
-
- // Show the Markdown textarea
- MarkdownWriter: function(e) {
-
- e.preventDefault();
-
- $(this).closest("ul").find("li").removeClass("form-tab-selected")
- $(this).parent().addClass("form-tab-selected");
-
- $(".write-area").show();
- $(".preview-area").hide();
- },
-
- // Check session and redirect to the login page if not logged
- CheckSession: function() {
-
- if (! $(".form-login").length) {
- $.ajax({
- cache: false,
- url: $("body").data("status-url"),
- statusCode: {
- 401: function() {
- window.location = $("body").data("login-url");
- }
- }
- });
- }
- },
-
- Init: function() {
-
- // Chosen select
- $(".chosen-select").chosen({
- width: "200px",
- no_results_text: $(".chosen-select").data("notfound"),
- disable_search_threshold: 10
- });
-
- // Project select box
- $("#board-selector").chosen({
- width: 180,
- no_results_text: $("#board-selector").data("notfound")
- });
-
- $("#board-selector").change(function() {
- window.location = $(this).attr("data-board-url").replace(/PROJECT_ID/g, $(this).val());
- });
-
- // Check the session every 60s
- window.setInterval(Kanboard.CheckSession, 60000);
-
- // Submit form
- Mousetrap.bindGlobal("mod+enter", function() {
- $("form").submit();
- });
-
- // Open board selector
- Mousetrap.bind("b", function(e) {
- e.preventDefault();
- $('#board-selector').trigger('chosen:open');
- });
-
- // Focus to the search box
- Mousetrap.bind("f", function(e) {
- e.preventDefault();
-
- var input = document.getElementById("form-search");
-
- if (input) {
- input.focus();
- }
- });
-
- // Switch view mode for projects: go to the board
- Mousetrap.bind("v b", function(e) {
- var link = $(".view-board");
-
- if (link.length) {
- window.location = link.attr('href');
- }
- });
-
- // Switch view mode for projects: go to the calendar
- Mousetrap.bind("v c", function(e) {
- var link = $(".view-calendar");
-
- if (link.length) {
- window.location = link.attr('href');
- }
- });
-
- // Switch view mode for projects: go to the listing
- Mousetrap.bind("v l", function(e) {
- var link = $(".view-listing");
-
- if (link.length) {
- window.location = link.attr('href');
- }
- });
-
- // Place cursor at the end when focusing on the search box
- $(document).on("focus", "#form-search", function() {
- if ($("#form-search")[0].setSelectionRange) {
- $('#form-search')[0].setSelectionRange($('#form-search').val().length, $('#form-search').val().length);
- }
- });
-
- // Filter helper for search
- $(document).on("click", ".filter-helper", function (e) {
- e.preventDefault();
- $("#form-search").val($(this).data("filter"));
- $("form.search").submit();
- });
-
- // Collapse sidebar
- $(document).on("click", ".sidebar-collapse", function (e) {
- e.preventDefault();
- $(".sidebar-container").addClass("sidebar-collapsed");
- $(".sidebar-expand").show();
- $(".sidebar h2").hide();
- $(".sidebar ul").hide();
- $(".sidebar-collapse").hide();
- });
-
- // Expand sidebar
- $(document).on("click", ".sidebar-expand", function (e) {
- e.preventDefault();
- $(".sidebar-container").removeClass("sidebar-collapsed");
- $(".sidebar-collapse").show();
- $(".sidebar h2").show();
- $(".sidebar ul").show();
- $(".sidebar-expand").hide();
- });
-
- // Reload page when a destination project is changed
- var reloading_project = false;
- $("select.task-reload-project-destination").change(function() {
- if (! reloading_project) {
- $(".loading-icon").show();
- reloading_project = true;
- window.location = $(this).data("redirect").replace(/PROJECT_ID/g, $(this).val());
- }
- });
-
- // Datepicker translation
- $.datepicker.setDefaults($.datepicker.regional[$("body").data("js-lang")]);
-
- // Alert box fadeout
- $(".alert-fade-out").delay(4000).fadeOut(800, function() {
- $(this).remove();
- });
-
- Kanboard.InitAfterAjax();
- },
-
- InitAfterAjax: function() {
-
- // Popover
- $(document).on("click", ".popover", Kanboard.Popover);
-
- // Autofocus fields (html5 autofocus works only with page onload)
- $("[autofocus]").each(function(index, element) {
- $(this).focus();
- })
-
- // 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
- });
-
- // Markdown Preview for textareas
- $("#markdown-preview").click(Kanboard.MarkdownPreview);
- $("#markdown-write").click(Kanboard.MarkdownWriter);
-
- // Auto-select input fields
- $(".auto-select").focus(function() {
- $(this).select();
- });
-
- // Dropdown
- $(".dropit-submenu").hide();
- $('.dropdown').not(".dropit").dropit({ triggerParentEl : "span" });
-
- // Task auto-completion
- if ($(".task-autocomplete").length) {
-
- if ($('.opposite_task_id').val() == '') {
- $(".task-autocomplete").parent().find("input[type=submit]").attr('disabled','disabled');
- }
-
- $(".task-autocomplete").autocomplete({
- source: $(".task-autocomplete").data("search-url"),
- minLength: 1,
- select: function(event, ui) {
- var field = $(".task-autocomplete").data("dst-field");
- $("input[name=" + field + "]").val(ui.item.id);
-
- $(".task-autocomplete").parent().find("input[type=submit]").removeAttr('disabled');
- }
- });
- }
-
- // Tooltip for column description
- $(".tooltip").tooltip({
- content: function() {
- return '<div class="markdown">' + $(this).attr("title") + '</div>';
- },
- position: {
- my: 'left-20 top',
- at: 'center bottom+9',
- using: function(position, feedback) {
-
- $(this).css(position);
-
- var arrow_pos = feedback.target.left + feedback.target.width / 2 - feedback.element.left - 20;
-
- $("<div>")
- .addClass("tooltip-arrow")
- .addClass(feedback.vertical)
- .addClass(arrow_pos < 1 ? "align-left" : "align-right")
- .appendTo(this);
- }
- }
- });
-
- // Screenshot
- if (Kanboard.Exists("screenshot-zone")) {
- Kanboard.Screenshot.Init();
- }
- }
- };
-
-})();
diff --git a/assets/js/src/board.js b/assets/js/src/board.js
index 40f140bf..7015f1c6 100644
--- a/assets/js/src/board.js
+++ b/assets/js/src/board.js
@@ -1,278 +1,164 @@
-(function() {
-
- var checkInterval = null;
-
- function on_popover(e)
- {
- e.preventDefault();
- e.stopPropagation();
-
- Kanboard.Popover(e, Kanboard.InitAfterAjax);
- }
-
- function keyboard_shortcuts()
- {
- Mousetrap.bind("n", function() {
- Kanboard.OpenPopover(
- $("#board").data("task-creation-url"),
- Kanboard.InitAfterAjax
- );
- });
-
- Mousetrap.bind("s", function() {
- Kanboard.ShowLoadingIcon();
-
- $.ajax({
- cache: false,
- url: $('.filter-display-mode:not([style="display: none;"]) a').attr('href'),
- success: function(data) {
- $("#board-container").remove();
- $("#main").append(data);
- Kanboard.InitAfterAjax();
- board_unload_events();
- board_load_events();
- compactview_reload();
- $('.filter-display-mode').toggle();
- Kanboard.HideLoadingIcon();
- }
- });
- });
-
- Mousetrap.bind("c", function() {
- compactview_toggle();
- });
+function Board() {
+ this.app = null;
+ this.checkInterval = null;
+}
+
+Board.prototype.execute = function(app) {
+ this.app = app;
+ this.app.swimlane.refresh();
+ this.app.swimlane.listen();
+ this.poll();
+ this.keyboardShortcuts();
+ this.resizeColumnHeight();
+ this.listen();
+ this.dragAndDrop();
+ this.compactView();
+
+ $(window).resize(this.resizeColumnHeight);
+};
+
+Board.prototype.poll = function() {
+ var interval = parseInt($("#board").attr("data-check-interval"));
+
+ if (interval > 0) {
+ this.checkInterval = window.setInterval(this.check.bind(this), interval * 1000);
}
+};
- function resize_column()
- {
- var position = $(".board-swimlane").position();
- $(".board-task-list").height($(window).height() - position.top);
- }
+Board.prototype.check = function() {
+ if (this.app.isVisible()) {
- // Setup the board
- function board_load_events()
- {
- // Resize column height
- resize_column();
+ var self = this;
+ this.app.showLoadingIcon();
- // Drag and drop
- $(".board-task-list").sortable({
- delay: 300,
- distance: 5,
- connectWith: ".column",
- placeholder: "draggable-placeholder",
- items: ".draggable-item",
- stop: function(event, ui) {
- board_save(
- ui.item.attr('data-task-id'),
- ui.item.parent().attr("data-column-id"),
- ui.item.index() + 1,
- ui.item.parent().attr('data-swimlane-id')
- );
+ $.ajax({
+ cache: false,
+ url: $("#board").attr("data-check-url"),
+ statusCode: {
+ 200: function(data) { self.refresh(data); },
+ 304: function () { self.app.hideLoadingIcon(); }
}
});
+ }
+};
+
+Board.prototype.save = function(taskId, columnId, position, swimlaneId) {
+ this.app.showLoadingIcon();
+
+ $.ajax({
+ cache: false,
+ url: $("#board").attr("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: this.refresh.bind(this),
+ error: this.app.hideLoadingIcon.bind(this)
+ });
+};
- // Task popover
- $("#board").on("click", ".task-board-popover", on_popover);
-
- // Redirect to the task details page
- $("#board").on("click", ".task-board", function() {
- window.location = $(this).data("task-url");
- });
-
- // Tooltips for tasks
- $(".task-board-tooltip").tooltip({
- track: false,
- position: {
- my: 'left-20 top',
- at: 'center bottom+9',
- using: function(position, feedback) {
-
- $(this).css(position);
-
- var arrow_pos = feedback.target.left + feedback.target.width / 2 - feedback.element.left - 20;
-
- $("<div>")
- .addClass("tooltip-arrow")
- .addClass(feedback.vertical)
- .addClass(arrow_pos < 1 ? "align-left" : "align-right")
- .appendTo(this);
- }
- },
- content: function(e) {
- var href = $(this).attr('data-href');
-
- if (! href) {
- return;
- }
-
- var _this = this;
- $.get(href, function setTooltipContent(data) {
-
- $('.ui-tooltip-content:visible').html(data);
-
- var tooltip = $('.ui-tooltip:visible');
-
- // Clear previous position, it interferes with the updated position computation
- tooltip.css({ top: '', left: '' });
-
- // Remove arrow, it will be added when repositionning
- tooltip.children('.tooltip-arrow').remove();
-
- // Reposition the tooltip
- var position = $(_this).tooltip("option", "position");
- position.of = $(_this);
- tooltip.position(position);
-
- // Toggle subtasks status
- $('#tooltip-subtasks a').not(".popover").click(function(e) {
-
- e.preventDefault();
- e.stopPropagation();
-
- if ($(this).hasClass("popover-subtask-restriction")) {
- Kanboard.OpenPopover($(this).attr('href'));
- $(_this).tooltip('close');
- }
- else {
- $.get($(this).attr('href'), setTooltipContent);
- }
- });
- });
-
- return '<i class="fa fa-spinner fa-spin"></i>';
- }
- }).on("mouseenter", function() {
-
- var _this = this;
- $(this).tooltip("open");
-
- $(".ui-tooltip").on("mouseleave", function () {
- $(_this).tooltip('close');
- });
-
- }).on("mouseleave focusout", function (e) {
-
- e.stopImmediatePropagation();
- var _this = this;
+Board.prototype.refresh = function(data) {
+ $("#board-container").replaceWith(data);
- setTimeout(function () {
- if (! $(".ui-tooltip:hover").length) {
- $(_this).tooltip("close");
- }
- }, 100);
- });
+ this.app.listen();
+ this.app.swimlane.refresh();
+ this.app.swimlane.listen();
+ this.resizeColumnHeight();
+ this.app.hideLoadingIcon();
+ this.listen();
+ this.dragAndDrop();
+ this.compactView();
+};
- // Automatic refresh
- var interval = parseInt($("#board").attr("data-check-interval"));
+Board.prototype.resizeColumnHeight = function() {
+ var position = $(".board-swimlane").position();
- if (interval > 0) {
- checkInterval = window.setInterval(board_check, interval * 1000);
- }
+ if (position) {
+ $(".board-task-list").height($(window).height() - position.top);
}
+};
+
+Board.prototype.dragAndDrop = function() {
+ var self = this;
+ $(".board-task-list").sortable({
+ delay: 300,
+ distance: 5,
+ connectWith: ".board-task-list",
+ placeholder: "draggable-placeholder",
+ items: ".draggable-item",
+ stop: function(event, ui) {
+ self.save(
+ ui.item.attr('data-task-id'),
+ ui.item.parent().attr("data-column-id"),
+ ui.item.index() + 1,
+ ui.item.parent().attr('data-swimlane-id')
+ );
+ }
+ });
+};
- // Stop events
- function board_unload_events()
- {
- clearInterval(checkInterval);
- }
+Board.prototype.listen = function() {
+ var self = this;
- // Save and refresh the board
- function board_save(taskId, columnId, position, swimlaneId)
- {
- board_unload_events();
- Kanboard.ShowLoadingIcon();
+ $(document).on("click", ".task-board", function() {
+ window.location = $(this).data("task-url");
+ });
- $.ajax({
- cache: false,
- url: $("#board").attr("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) {
- $("#board-container").remove();
- $("#main").append(data);
- Kanboard.InitAfterAjax();
- board_load_events();
- compactview_reload();
- Kanboard.HideLoadingIcon();
- }
- });
- }
+ $(document).on('click', ".filter-toggle-scrolling", function(e) {
+ e.preventDefault();
+ self.toggleCompactView();
+ });
+};
- // Check if the board have been changed by someone else
- function board_check()
- {
- if (Kanboard.IsVisible()) {
- $.ajax({
- cache: false,
- url: $("#board").attr("data-check-url"),
- statusCode: {
- 200: function(data) {
- $("#board-container").remove();
- $("#main").append(data);
- Kanboard.InitAfterAjax();
- board_unload_events();
- board_load_events();
- compactview_reload();
- }
- }
- });
- }
- }
+Board.prototype.toggleCompactView = function() {
+ var scrolling = localStorage.getItem("horizontal_scroll") || 1;
+ localStorage.setItem("horizontal_scroll", scrolling == 0 ? 1 : 0);
+ this.compactView();
+};
- function compactview_load_events()
- {
- jQuery(document).on('click', ".filter-toggle-scrolling", function(e) {
- e.preventDefault();
- compactview_toggle();
- });
+Board.prototype.compactView = function() {
+ if (localStorage.getItem("horizontal_scroll") == 0) {
+ $(".filter-wide").show();
+ $(".filter-compact").hide();
- compactview_reload();
+ $("#board-container").addClass("board-container-compact");
+ $("#board th").addClass("board-column-compact");
}
+ else {
+ $(".filter-wide").hide();
+ $(".filter-compact").show();
- function compactview_toggle()
- {
- var scrolling = Kanboard.GetStorageItem("horizontal_scroll") || 1;
- Kanboard.SetStorageItem("horizontal_scroll", scrolling == 0 ? 1 : 0);
- compactview_reload();
+ $("#board-container").removeClass("board-container-compact");
+ $("#board th").removeClass("board-column-compact");
}
-
- function compactview_reload()
- {
- if (Kanboard.GetStorageItem("horizontal_scroll") == 0) {
-
- $(".filter-wide").show();
- $(".filter-compact").hide();
-
- $("#board-container").addClass("board-container-compact");
- $("#board th").addClass("board-column-compact");
- }
- else {
-
- $(".filter-wide").hide();
- $(".filter-compact").show();
-
- $("#board-container").removeClass("board-container-compact");
- $("#board th").removeClass("board-column-compact");
+};
+
+Board.prototype.toggleCollapsedMode = 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.refresh(data);
}
- }
+ });
+};
- jQuery(document).ready(function() {
+Board.prototype.keyboardShortcuts = function() {
+ var self = this;
- if (Kanboard.Exists("board")) {
- board_load_events();
- compactview_load_events();
- keyboard_shortcuts();
+ Mousetrap.bind("c", function() { self.toggleCompactView(); });
+ Mousetrap.bind("s", function() { self.toggleCollapsedMode(); });
- $(window).resize(resize_column);
- }
+ Mousetrap.bind("n", function() {
+ self.app.popover.open($("#board").data("task-creation-url"));
});
-
-})();
+};
diff --git a/assets/js/src/calendar.js b/assets/js/src/calendar.js
index 68da57ee..ffb00dcd 100644
--- a/assets/js/src/calendar.js
+++ b/assets/js/src/calendar.js
@@ -1,51 +1,49 @@
-(function() {
+function Calendar() {
- jQuery(document).ready(function() {
- if (Kanboard.Exists("calendar")) {
- var calendar = $('#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()
- };
+Calendar.prototype.execute = function() {
+ var calendar = $('#calendar');
- for (var key in params) {
- url += "&" + key + "=" + params[key];
- }
+ 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');
- });
- }
+ $.getJSON(url, function(events) {
+ calendar.fullCalendar('removeEvents');
+ calendar.fullCalendar('addEventSource', events);
+ calendar.fullCalendar('rerenderEvents');
});
}
});
-
-})();
+};
diff --git a/assets/js/src/screenshot.js b/assets/js/src/screenshot.js
index dce49c77..fd50f8e7 100644
--- a/assets/js/src/screenshot.js
+++ b/assets/js/src/screenshot.js
@@ -1,145 +1,131 @@
-Kanboard.Screenshot = (function() {
+function Screenshot() {
+ this.pasteCatcher = null;
+}
- var pasteCatcher = null;
+Screenshot.prototype.execute = function() {
+ this.initialize();
+};
- // Setup event listener and workarounds
- function init()
- {
- destroy();
+// Setup event listener and workarounds
+Screenshot.prototype.initialize = function() {
+ this.destroy();
- if (! window.Clipboard) {
+ if (! window.Clipboard) {
- // Create a contenteditable element
- pasteCatcher = document.createElement("div");
- pasteCatcher.id = "screenshot-pastezone";
- pasteCatcher.contentEditable = "true";
+ // Create a contenteditable element
+ this.pasteCatcher = document.createElement("div");
+ this.pasteCatcher.id = "screenshot-pastezone";
+ this.pasteCatcher.contentEditable = "true";
- // Insert the content editable at the top to avoid scrolling down in the board view
- pasteCatcher.style.opacity = 0;
- pasteCatcher.style.position = "fixed";
- pasteCatcher.style.top = 0;
- pasteCatcher.style.right = 0;
- pasteCatcher.style.width = 0;
+ // Insert the content editable at the top to avoid scrolling down in the board view
+ this.pasteCatcher.style.opacity = 0;
+ this.pasteCatcher.style.position = "fixed";
+ this.pasteCatcher.style.top = 0;
+ this.pasteCatcher.style.right = 0;
+ this.pasteCatcher.style.width = 0;
- document.body.insertBefore(pasteCatcher, document.body.firstChild);
+ document.body.insertBefore(this.pasteCatcher, document.body.firstChild);
- // Set focus on the contenteditable element
- pasteCatcher.focus();
+ // Set focus on the contenteditable element
+ this.pasteCatcher.focus();
- // Set the focus when clicked anywhere in the document
- document.addEventListener("click", setFocus);
+ // Set the focus when clicked anywhere in the document
+ document.addEventListener("click", this.setFocus.bind(this));
- // Set the focus when clicked in screenshot dropzone (popover)
- document.getElementById("screenshot-zone").addEventListener("click", setFocus);
- }
-
- window.addEventListener("paste", pasteHandler);
+ // Set the focus when clicked in screenshot dropzone (popover)
+ document.getElementById("screenshot-zone").addEventListener("click", this.setFocus.bind(this));
}
- // Set focus on the contentEditable element
- function setFocus()
- {
- if (pasteCatcher !== null) {
- pasteCatcher.focus();
- }
+ window.addEventListener("paste", this.pasteHandler.bind(this));
+};
+
+// Destroy contentEditable element
+Screenshot.prototype.destroy = function() {
+ if (this.pasteCatcher != null) {
+ document.body.removeChild(this.pasteCatcher);
+ }
+ else if (document.getElementById("screenshot-pastezone")) {
+ document.body.removeChild(document.getElementById("screenshot-pastezone"));
}
- // Destroy contenteditable
- function destroy()
- {
- if (pasteCatcher != null) {
- document.body.removeChild(pasteCatcher);
- }
- else if (document.getElementById("screenshot-pastezone")) {
- document.body.removeChild(document.getElementById("screenshot-pastezone"));
- }
+ document.removeEventListener("click", this.setFocus.bind(this));
+ this.pasteCatcher = null;
+};
- document.removeEventListener("click", setFocus);
- pasteCatcher = null;
+// Set focus on contentEditable element
+Screenshot.prototype.setFocus = function() {
+ if (this.pasteCatcher !== null) {
+ this.pasteCatcher.focus();
}
+};
- // Paste event callback
- function pasteHandler(e)
- {
- // Firefox doesn't have the property e.clipboardData.items (only Chrome)
- if (e.clipboardData && e.clipboardData.items) {
+// Paste event callback
+Screenshot.prototype.pasteHandler = function(e) {
+ // Firefox doesn't have the property e.clipboardData.items (only Chrome)
+ if (e.clipboardData && e.clipboardData.items) {
- var items = e.clipboardData.items;
+ var items = e.clipboardData.items;
- if (items) {
+ if (items) {
- for (var i = 0; i < items.length; i++) {
+ for (var i = 0; i < items.length; i++) {
- // Find an image in pasted elements
- if (items[i].type.indexOf("image") !== -1) {
+ // Find an image in pasted elements
+ if (items[i].type.indexOf("image") !== -1) {
- var blob = items[i].getAsFile();
+ var blob = items[i].getAsFile();
- // Get the image as base64 data
- var reader = new FileReader();
- reader.onload = function(event) {
- createImage(event.target.result);
- };
+ // Get the image as base64 data
+ var reader = new FileReader();
+ var self = this;
+ reader.onload = function(event) {
+ self.createImage(event.target.result);
+ };
- reader.readAsDataURL(blob);
- }
+ reader.readAsDataURL(blob);
}
}
}
- else {
+ }
+ else {
- // Handle Firefox
- setTimeout(checkInput, 100);
- }
+ // Handle Firefox
+ setTimeout(this.checkInput.bind(this), 100);
}
+};
- // Parse the input in the paste catcher element
- function checkInput()
- {
- var child = pasteCatcher.childNodes[0];
+// Parse the input in the paste catcher element
+Screenshot.prototype.checkInput = function() {
+ var child = this.pasteCatcher.childNodes[0];
- if (child) {
- // If the user pastes an image, the src attribute
- // will represent the image as a base64 encoded string.
- if (child.tagName === "IMG") {
- createImage(child.src);
- }
+ if (child) {
+ // If the user pastes an image, the src attribute
+ // will represent the image as a base64 encoded string.
+ if (child.tagName === "IMG") {
+ this.createImage(child.src);
}
-
- pasteCatcher.innerHTML = "";
- }
-
- // Creates a new image from a given source
- function createImage(blob)
- {
- var pastedImage = new Image();
- pastedImage.src = blob;
-
- // Send the image content to the form variable
- pastedImage.onload = function() {
- var sourceSplit = blob.split("base64,");
- var sourceString = sourceSplit[1];
- $("input[name=screenshot]").val(sourceString);
- };
-
- var zone = document.getElementById("screenshot-zone");
- zone.innerHTML = "";
- zone.className = "screenshot-pasted";
- zone.appendChild(pastedImage);
-
- destroy();
- init();
}
- jQuery(document).ready(function() {
+ pasteCatcher.innerHTML = "";
+};
- if (Kanboard.Exists("screenshot-zone")) {
- init();
- }
- });
+// Creates a new image from a given source
+Screenshot.prototype.createImage = function(blob) {
+ var pastedImage = new Image();
+ pastedImage.src = blob;
- return {
- Init: init,
- Destroy: destroy
+ // Send the image content to the form variable
+ pastedImage.onload = function() {
+ var sourceSplit = blob.split("base64,");
+ var sourceString = sourceSplit[1];
+ $("input[name=screenshot]").val(sourceString);
};
-})(); \ No newline at end of file
+
+ var zone = document.getElementById("screenshot-zone");
+ zone.innerHTML = "";
+ zone.className = "screenshot-pasted";
+ zone.appendChild(pastedImage);
+
+ this.destroy();
+ this.initialize();
+};
diff --git a/assets/js/src/swimlane.js b/assets/js/src/swimlane.js
index 212d6d36..ce18dbfa 100644
--- a/assets/js/src/swimlane.js
+++ b/assets/js/src/swimlane.js
@@ -1,90 +1,67 @@
-(function() {
-
- // Expand a Swimlane via display attributes
- function expand(swimlaneId)
- {
- $('.swimlane-row-' + swimlaneId).css('display', 'table-row');
- $('.show-icon-swimlane-' + swimlaneId).css('display', 'none');
- $('.hide-icon-swimlane-' + swimlaneId).css('display', 'inline');
- }
-
- // Collapse a Swimlane via display attributes
- function collapse(swimlaneId)
- {
- $('.swimlane-row-' + swimlaneId).css('display', 'none');
- $('.show-icon-swimlane-' + swimlaneId).css('display', 'inline');
- $('.hide-icon-swimlane-' + swimlaneId).css('display', 'none');
- }
+function Swimlane() {
+}
- // Add swimlane Id to the hidden list and stores the list to localStorage
- function hide(id)
- {
- var storageKey = "hidden_swimlanes_" + $("#board").data("project-id");
- var hiddenSwimlaneIds = JSON.parse(Kanboard.GetStorageItem(storageKey)) || [];
+Swimlane.prototype.getStorageKey = function() {
+ return "hidden_swimlanes_" + $("#board").data("project-id");
+};
- hiddenSwimlaneIds.push(id);
+Swimlane.prototype.expand = function(swimlaneId) {
+ var swimlaneIds = this.getAllCollapsed();
+ var index = swimlaneIds.indexOf(swimlaneId);
- Kanboard.SetStorageItem(storageKey, JSON.stringify(hiddenSwimlaneIds));
+ if (index > -1) {
+ swimlaneIds.splice(index, 1);
}
- // Remove swimlane Id from the hidden list and stores the list to
- // localStorage
- function unhide(id)
- {
- var storageKey = "hidden_swimlanes_" + $("#board").data("project-id");
- var hiddenSwimlaneIds = JSON.parse(Kanboard.GetStorageItem(storageKey)) || [];
- var index = hiddenSwimlaneIds.indexOf(id);
+ localStorage.setItem(this.getStorageKey(), JSON.stringify(swimlaneIds));
- if (index > -1) {
- hiddenSwimlaneIds.splice(index, 1);
- }
+ $('.swimlane-row-' + swimlaneId).css('display', 'table-row');
+ $('.show-icon-swimlane-' + swimlaneId).css('display', 'none');
+ $('.hide-icon-swimlane-' + swimlaneId).css('display', 'inline');
+};
- Kanboard.SetStorageItem(storageKey, JSON.stringify(hiddenSwimlaneIds));
- }
+Swimlane.prototype.collapse = function(swimlaneId) {
+ var swimlaneIds = this.getAllCollapsed();
- // Check if swimlane Id is hidden. Anything > -1 means hidden.
- function isHidden(id)
- {
- return getAllHidden().indexOf(id) > -1;
+ if (swimlaneIds.indexOf(swimlaneId) < 0) {
+ swimlaneIds.push(swimlaneId);
+ localStorage.setItem(this.getStorageKey(), JSON.stringify(swimlaneIds));
}
- // Gets all swimlane Ids that are hidden
- function getAllHidden()
- {
- var storageKey = "hidden_swimlanes_" + $("#board").data("project-id");
- return JSON.parse(Kanboard.GetStorageItem(storageKey)) || [];
- }
+ $('.swimlane-row-' + swimlaneId).css('display', 'none');
+ $('.show-icon-swimlane-' + swimlaneId).css('display', 'inline');
+ $('.hide-icon-swimlane-' + swimlaneId).css('display', 'none');
+};
- // Reload the swimlane states (shown/hidden) after an ajax call
- jQuery(document).ajaxComplete(function() {
+Swimlane.prototype.isCollapsed = function(swimlaneId) {
+ return this.getAllCollapsed().indexOf(swimlaneId) > -1;
+};
- getAllHidden().map(function(swimlaneId) {
- collapse(swimlaneId);
- });
- });
+Swimlane.prototype.getAllCollapsed = function() {
+ return JSON.parse(localStorage.getItem(this.getStorageKey())) || [];
+};
- // Reload the swimlane states (shown/hidden) after page refresh
- jQuery(document).ready(function() {
+Swimlane.prototype.refresh = function() {
+ var swimlaneIds = this.getAllCollapsed();
- getAllHidden().map(function(swimlaneId) {
- collapse(swimlaneId);
- });
- });
+ for (var i = 0; i < swimlaneIds.length; i++) {
+ this.collapse(swimlaneIds[i]);
+ }
+};
- // Clicking on Show/Hide icon fires this.
- jQuery(document).on('click', ".board-swimlane-toggle", function(e) {
+Swimlane.prototype.listen = function() {
+ var self = this;
+
+ $(document).on('click', ".board-swimlane-toggle", function(e) {
e.preventDefault();
var swimlaneId = $(this).data('swimlane-id');
- if (isHidden(swimlaneId)) {
- unhide(swimlaneId);
- expand(swimlaneId);
+ if (self.isCollapsed(swimlaneId)) {
+ self.expand(swimlaneId);
}
else {
- hide(swimlaneId);
- collapse(swimlaneId);
+ self.collapse(swimlaneId);
}
});
-
-})();
+};