")
+ .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 '
' + $(this).attr("title") + '
';
+ }
+
+ $.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 '
';
+ }
+ })
+ .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 000000000..4255130a4
--- /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 a5df0ac64..000000000
--- 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 ffad93b8e..000000000
--- 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('
');
- },
-
- 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('
');
-
- $("#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 '
' + $(this).attr("title") + '
';
- },
- 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;
-
- $("
")
- .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 40f140bfa..7015f1c62 100644
--- a/assets/js/src/board.js
+++ b/assets/js/src/board.js
@@ -1,278 +1,164 @@
-(function() {
+function Board() {
+ this.app = null;
+ this.checkInterval = null;
+}
- var 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();
- function on_popover(e)
- {
- e.preventDefault();
- e.stopPropagation();
+ $(window).resize(this.resizeColumnHeight);
+};
- Kanboard.Popover(e, Kanboard.InitAfterAjax);
+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 keyboard_shortcuts()
- {
- Mousetrap.bind("n", function() {
- Kanboard.OpenPopover(
- $("#board").data("task-creation-url"),
- Kanboard.InitAfterAjax
- );
- });
+Board.prototype.check = function() {
+ if (this.app.isVisible()) {
- 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 resize_column()
- {
- var position = $(".board-swimlane").position();
- $(".board-task-list").height($(window).height() - position.top);
- }
-
- // Setup the board
- function board_load_events()
- {
- // Resize column height
- resize_column();
-
- // 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')
- );
- }
- });
-
- // 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;
-
- $("
")
- .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 '';
- }
- }).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);
- });
-
- // Automatic refresh
- var interval = parseInt($("#board").attr("data-check-interval"));
-
- if (interval > 0) {
- checkInterval = window.setInterval(board_check, interval * 1000);
- }
- }
-
- // Stop events
- function board_unload_events()
- {
- clearInterval(checkInterval);
- }
-
- // Save and refresh the board
- function board_save(taskId, columnId, position, swimlaneId)
- {
- board_unload_events();
- Kanboard.ShowLoadingIcon();
+ var self = this;
+ 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: function(data) {
- $("#board-container").remove();
- $("#main").append(data);
- Kanboard.InitAfterAjax();
- board_load_events();
- compactview_reload();
- Kanboard.HideLoadingIcon();
+ url: $("#board").attr("data-check-url"),
+ statusCode: {
+ 200: function(data) { self.refresh(data); },
+ 304: function () { self.app.hideLoadingIcon(); }
}
});
}
+};
- // 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.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)
+ });
+};
+
+Board.prototype.refresh = function(data) {
+ $("#board-container").replaceWith(data);
+
+ this.app.listen();
+ this.app.swimlane.refresh();
+ this.app.swimlane.listen();
+ this.resizeColumnHeight();
+ this.app.hideLoadingIcon();
+ this.listen();
+ this.dragAndDrop();
+ this.compactView();
+};
+
+Board.prototype.resizeColumnHeight = function() {
+ var position = $(".board-swimlane").position();
+
+ if (position) {
+ $(".board-task-list").height($(window).height() - position.top);
}
+};
- function compactview_load_events()
- {
- jQuery(document).on('click', ".filter-toggle-scrolling", function(e) {
- e.preventDefault();
- compactview_toggle();
- });
-
- compactview_reload();
- }
-
- function compactview_toggle()
- {
- var scrolling = Kanboard.GetStorageItem("horizontal_scroll") || 1;
- Kanboard.SetStorageItem("horizontal_scroll", scrolling == 0 ? 1 : 0);
- compactview_reload();
- }
-
- 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");
- }
- }
-
- jQuery(document).ready(function() {
-
- if (Kanboard.Exists("board")) {
- board_load_events();
- compactview_load_events();
- keyboard_shortcuts();
-
- $(window).resize(resize_column);
+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')
+ );
}
});
+};
-})();
+Board.prototype.listen = function() {
+ var self = this;
+
+ $(document).on("click", ".task-board", function() {
+ window.location = $(this).data("task-url");
+ });
+
+ $(document).on('click', ".filter-toggle-scrolling", function(e) {
+ e.preventDefault();
+ self.toggleCompactView();
+ });
+};
+
+Board.prototype.toggleCompactView = function() {
+ var scrolling = localStorage.getItem("horizontal_scroll") || 1;
+ localStorage.setItem("horizontal_scroll", scrolling == 0 ? 1 : 0);
+ this.compactView();
+};
+
+Board.prototype.compactView = function() {
+ if (localStorage.getItem("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);
+ }
+ });
+};
+
+Board.prototype.keyboardShortcuts = function() {
+ var self = this;
+
+ Mousetrap.bind("c", function() { self.toggleCompactView(); });
+ Mousetrap.bind("s", function() { self.toggleCollapsedMode(); });
+
+ 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 68da57ee0..ffb00dcdd 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()
+ };
- $.getJSON(url, function(events) {
- calendar.fullCalendar('removeEvents');
- calendar.fullCalendar('addEventSource', events);
- calendar.fullCalendar('rerenderEvents');
- });
- }
+ for (var key in params) {
+ url += "&" + key + "=" + params[key];
+ }
+
+ $.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 dce49c77c..fd50f8e76 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
+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") {
+ this.createImage(child.src);
}
}
- // Parse the input in the paste catcher element
- function checkInput()
- {
- var child = pasteCatcher.childNodes[0];
+ pasteCatcher.innerHTML = "";
+};
- 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);
- }
- }
+// Creates a new image from a given source
+Screenshot.prototype.createImage = function(blob) {
+ var pastedImage = new Image();
+ pastedImage.src = blob;
- 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() {
-
- if (Kanboard.Exists("screenshot-zone")) {
- init();
- }
- });
-
- 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 212d6d369..ce18dbfa0 100644
--- a/assets/js/src/swimlane.js
+++ b/assets/js/src/swimlane.js
@@ -1,90 +1,67 @@
-(function() {
+function Swimlane() {
+}
- // 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');
+Swimlane.prototype.getStorageKey = function() {
+ return "hidden_swimlanes_" + $("#board").data("project-id");
+};
+
+Swimlane.prototype.expand = function(swimlaneId) {
+ var swimlaneIds = this.getAllCollapsed();
+ var index = swimlaneIds.indexOf(swimlaneId);
+
+ if (index > -1) {
+ swimlaneIds.splice(index, 1);
}
- // 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');
+ localStorage.setItem(this.getStorageKey(), JSON.stringify(swimlaneIds));
+
+ $('.swimlane-row-' + swimlaneId).css('display', 'table-row');
+ $('.show-icon-swimlane-' + swimlaneId).css('display', 'none');
+ $('.hide-icon-swimlane-' + swimlaneId).css('display', 'inline');
+};
+
+Swimlane.prototype.collapse = function(swimlaneId) {
+ var swimlaneIds = this.getAllCollapsed();
+
+ if (swimlaneIds.indexOf(swimlaneId) < 0) {
+ swimlaneIds.push(swimlaneId);
+ localStorage.setItem(this.getStorageKey(), JSON.stringify(swimlaneIds));
}
- // 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-row-' + swimlaneId).css('display', 'none');
+ $('.show-icon-swimlane-' + swimlaneId).css('display', 'inline');
+ $('.hide-icon-swimlane-' + swimlaneId).css('display', 'none');
+};
- hiddenSwimlaneIds.push(id);
+Swimlane.prototype.isCollapsed = function(swimlaneId) {
+ return this.getAllCollapsed().indexOf(swimlaneId) > -1;
+};
- Kanboard.SetStorageItem(storageKey, JSON.stringify(hiddenSwimlaneIds));
+Swimlane.prototype.getAllCollapsed = function() {
+ return JSON.parse(localStorage.getItem(this.getStorageKey())) || [];
+};
+
+Swimlane.prototype.refresh = function() {
+ var swimlaneIds = this.getAllCollapsed();
+
+ for (var i = 0; i < swimlaneIds.length; i++) {
+ this.collapse(swimlaneIds[i]);
}
+};
- // 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);
+Swimlane.prototype.listen = function() {
+ var self = this;
- if (index > -1) {
- hiddenSwimlaneIds.splice(index, 1);
- }
-
- Kanboard.SetStorageItem(storageKey, JSON.stringify(hiddenSwimlaneIds));
- }
-
- // Check if swimlane Id is hidden. Anything > -1 means hidden.
- function isHidden(id)
- {
- return getAllHidden().indexOf(id) > -1;
- }
-
- // Gets all swimlane Ids that are hidden
- function getAllHidden()
- {
- var storageKey = "hidden_swimlanes_" + $("#board").data("project-id");
- return JSON.parse(Kanboard.GetStorageItem(storageKey)) || [];
- }
-
- // Reload the swimlane states (shown/hidden) after an ajax call
- jQuery(document).ajaxComplete(function() {
-
- getAllHidden().map(function(swimlaneId) {
- collapse(swimlaneId);
- });
- });
-
- // Reload the swimlane states (shown/hidden) after page refresh
- jQuery(document).ready(function() {
-
- getAllHidden().map(function(swimlaneId) {
- collapse(swimlaneId);
- });
- });
-
- // Clicking on Show/Hide icon fires this.
- jQuery(document).on('click', ".board-swimlane-toggle", function(e) {
+ $(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);
}
});
-
-})();
+};
diff --git a/scripts/make-assets.sh b/scripts/make-assets.sh
index 0041afaa8..4d708f469 100755
--- a/scripts/make-assets.sh
+++ b/scripts/make-assets.sh
@@ -4,7 +4,7 @@ print_css="print links table board task comment subtask markdown"
app_css="base links title table form button alert tooltip header board task comment subtask markdown listing activity dashboard pagination popover confirm sidebar responsive dropdown screenshot filters"
vendor_css="jquery-ui.min jquery-ui-timepicker-addon.min chosen.min fullcalendar.min font-awesome.min c3.min"
-app_js="base board calendar analytic swimlane screenshot"
+app_js="Popover Tooltip Markdown Sidebar Search App Screenshot Calendar Board Swimlane TaskRepartitionChart UserRepartitionChart CumulativeFlowDiagram BurndownChart BudgetChart AvgTimeColumnChart TaskTimeColumnChart LeadCycleTimeChart Router"
vendor_js="jquery-1.11.1.min jquery-ui.min jquery-ui-timepicker-addon.min jquery.ui.touch-punch.min chosen.jquery.min dropit.min moment.min fullcalendar.min mousetrap.min mousetrap-global-bind.min app.min"
lang_js="da de es fi fr hu it ja nl pl pt-br ru sv sr th tr zh-cn"
@@ -34,7 +34,9 @@ function minify_js {
rm -f $dst_file $tmp_file 2>/dev/null
+ echo "(function() { 'use strict';" > $tmp_file;
for file in $app_js; do cat "assets/js/src/${file}.js" >> $tmp_file; done
+ echo "})();" >> $tmp_file;
curl -s \
-d compilation_level=SIMPLE_OPTIMIZATIONS \