define([ 'jquery', 'json.sortify', '/bower_components/nthen/index.js', '/common/sframe-common.js', '/common/sframe-app-framework.js', '/common/common-util.js', '/common/common-hash.js', '/common/common-interface.js', '/common/modes.js', '/customize/messages.js', '/kanban/jkanban.js', '/common/jscolor.js', 'css!/kanban/jkanban.css', 'less!/kanban/app-kanban.less' ], function ( $, Sortify, nThen, SFCommon, Framework, Util, Hash, UI, Modes, Messages) { var verbose = function (x) { console.log(x); }; verbose = function () {}; // comment out to enable verbose logging var COLORS = ['yellow', 'green', 'orange', 'blue', 'red', 'purple', 'cyan', 'lightgreen', 'lightblue']; var addRemoveItemButton = function (framework, kanban) { if (!kanban) { return; } if (framework.isReadOnly() || framework.isLocked()) { return; } var $container = $(kanban.element); $container.find('.kanban-remove-item').remove(); $container.find('.kanban-board .kanban-item').each(function (i, el) { var pos = kanban.findElementPosition(el); var board = kanban.options.boards.find(function (b) { return b.id === $(el.parentNode.parentNode).attr('data-id'); }); $('<button>', { 'class': 'kanban-remove-item btn btn-default fa fa-times', title: Messages.kanban_removeItem }).click(function (e) { e.stopPropagation(); UI.confirm(Messages.kanban_removeItemConfirm, function (yes) { if (!yes) { return; } board.item.splice(pos, 1); $(el).remove(); kanban.onChange(); }); }).appendTo($(el)); }); }; // Kanban code var initKanban = function (framework, boards) { var defaultBoards = [{ "id": "todo", "title": Messages.kanban_todo, "color": "blue", "item": [{ "title": Messages._getKey('kanban_item', [1]) }, { "title": Messages._getKey('kanban_item', [2]) }] }, { "id": "working", "title": Messages.kanban_working, "color": "orange", "item": [{ "title": Messages._getKey('kanban_item', [3]) }, { "title": Messages._getKey('kanban_item', [4]) }] }, { "id": "done", "title": Messages.kanban_done, "color": "green", "item": [{ "title": Messages._getKey('kanban_item', [5]) }, { "title": Messages._getKey('kanban_item', [6]) }] }]; if (!boards) { verbose("Initializing with default boards content"); boards = defaultBoards; } else { verbose("Initializing with boards content " + boards); } // Remove any existing elements $(".kanban-container-outer").remove(); var getInput = function () { return $('<input>', { 'type': 'text', 'id': 'kanban-edit', 'size': '30' }).click(function (e) { e.stopPropagation(); }); }; var kanban = new window.jKanban({ element: '#cp-app-kanban-content', gutter: '5px', widthBoard: '300px', buttonContent: '❌', colors: COLORS, readOnly: framework.isReadOnly(), onChange: function () { verbose("Board object has changed"); framework.localChange(); if (kanban) { addRemoveItemButton(framework, kanban); } }, click: function (el) { if (framework.isReadOnly() || framework.isLocked()) { return; } if (kanban.inEditMode) { $(el).focus(); verbose("An edit is already active"); //return; } kanban.inEditMode = true; $(el).find('button').remove(); var name = $(el).text(); $(el).html(''); var $input = getInput().val(name).appendTo(el).focus(); $input[0].select(); var save = function () { // Store the value var name = $input.val(); // Remove the input $(el).text(name); // Save the value for the correct board var board = $(el).closest('.kanban-board').attr("data-id"); var pos = kanban.findElementPosition(el.parentNode); kanban.getBoardJSON(board).item[pos].title = name; kanban.onChange(); // Unlock edit mode kanban.inEditMode = false; }; $input.blur(save); $input.keydown(function (e) { if (e.which === 13) { e.preventDefault(); e.stopPropagation(); save(); if (!$input.val()) { return; } if (!$(el).closest('.kanban-item').is(':last-child')) { return; } $(el).closest('.kanban-board').find('.kanban-title-button.fa-plus').click(); return; } if (e.which === 27) { e.preventDefault(); e.stopPropagation(); $(el).text(name); kanban.inEditMode = false; addRemoveItemButton(framework, kanban); return; } }); }, boardTitleClick: function (el, e) { e.stopPropagation(); if (framework.isReadOnly() || framework.isLocked()) { return; } if (kanban.inEditMode) { $(el).focus(); verbose("An edit is already active"); //return; } kanban.inEditMode = true; var name = $(el).text(); $(el).html(''); var $input = getInput().val(name).appendTo(el).focus(); $input[0].select(); var save = function () { // Store the value var name = $input.val(); if (!name || !name.trim()) { return kanban.onChange(); } // Remove the input $(el).text(name); // Save the value for the correct board var board = $(el.parentNode.parentNode).attr("data-id"); kanban.getBoardJSON(board).title = name; kanban.onChange(); // Unlock edit mode kanban.inEditMode = false; }; $input.blur(save); $input.keydown(function (e) { if (e.which === 13) { e.preventDefault(); e.stopPropagation(); save(); return; } if (e.which === 27) { e.preventDefault(); e.stopPropagation(); $(el).text(name); kanban.inEditMode = false; return; } }); }, colorClick: function (el, type) { if (framework.isReadOnly() || framework.isLocked()) { return; } verbose("on color click"); var boardJSON; var board; if (type === "board") { verbose("board color click"); board = $(el.parentNode).attr("data-id"); boardJSON = kanban.getBoardJSON(board); } else { verbose("item color click"); board = $(el.parentNode.parentNode).attr("data-id"); var pos = kanban.findElementPosition(el); boardJSON = kanban.getBoardJSON(board).item[pos]; } var onchange = function (colorL) { var elL = el; var typeL = type; var boardJSONL; var boardL; if (typeL === "board") { verbose("board color change"); boardL = $(elL.parentNode).attr("data-id"); boardJSONL = kanban.getBoardJSON(boardL); } else { verbose("item color change"); boardL = $(elL.parentNode.parentNode).attr("data-id"); var pos = kanban.findElementPosition(elL); boardJSONL = kanban.getBoardJSON(boardL).item[pos]; } var currentColor = boardJSONL.color; verbose("Current color " + currentColor); if (currentColor !== colorL.toString()) { $(elL).removeClass("kanban-header-" + currentColor); boardJSONL.color = colorL.toString(); kanban.onChange(); } }; var jscolorL; el._jscLinkedInstance = undefined; jscolorL = new window.jscolor(el,{showOnClick: false, onFineChange: onchange, valueElement:undefined}); jscolorL.show(); var currentColor = boardJSON.color; if (currentColor === undefined) { currentColor = ''; } jscolorL.fromString(currentColor); }, buttonClick: function (el, boardId, e) { e.stopPropagation(); if (framework.isReadOnly() || framework.isLocked()) { return; } UI.confirm(Messages.kanban_deleteBoard, function (yes) { if (!yes) { return; } verbose("Delete board"); //var boardName = $(el.parentNode.parentNode).attr("data-id"); for (var index in kanban.options.boards) { if (kanban.options.boards[index].id === boardId) { break; } index++; } kanban.options.boards.splice(index, 1); kanban.removeBoard(boardId); kanban.onChange(); }); }, addItemClick: function (el) { if (framework.isReadOnly() || framework.isLocked()) { return; } if (kanban.inEditMode) { $(el).focus(); verbose("An edit is already active"); //return; } kanban.inEditMode = true; // create a form to enter element var boardId = $(el.parentNode.parentNode).attr("data-id"); var $item = $('<div>', {'class': 'kanban-item new-item'}); var $input = getInput().val(name).appendTo($item); kanban.addForm(boardId, $item[0]); $input.focus(); var save = function () { $item.remove(); kanban.inEditMode = false; if (!$input.val()) { return; } kanban.addElement(boardId, { "title": $input.val(), }); }; $input.blur(save); $input.keydown(function (e) { if (e.which === 13) { e.preventDefault(); e.stopPropagation(); save(); if (!$input.val()) { return; } $(el).closest('.kanban-board').find('.kanban-title-button.fa-plus').click(); return; } if (e.which === 27) { e.preventDefault(); e.stopPropagation(); $item.remove(); kanban.inEditMode = false; return; } }); }, addItemButton: true, boards: boards }); var addBoardDefault = document.getElementById('kanban-addboard'); $(addBoardDefault).attr('title', Messages.kanban_addBoard); addBoardDefault.addEventListener('click', function () { if (framework.isReadOnly()) { return; } var counter = 1; // Get the new board id var boardExists = function (b) { return b.id === "board" + counter; }; while (kanban.options.boards.some(boardExists)) { counter++; } kanban.addBoards([{ "id": "board" + counter, "title": Messages.kanban_newBoard, "color": COLORS[Math.floor(Math.random()*COLORS.length)], // random color "item": [{ "title": Messages._getKey('kanban_item', [1]), }] }]); kanban.onChange(); }); return kanban; }; var mkHelpMenu = function (framework) { var $toolbarContainer = $('#cp-app-kanban-container'); $toolbarContainer.prepend(framework._.sfCommon.getBurnAfterReadingWarning()); var helpMenu = framework._.sfCommon.createHelpMenu(['kanban']); $toolbarContainer.prepend(helpMenu.menu); framework._.toolbar.$drawer.append(helpMenu.button); }; // Start of the main loop var andThen2 = function (framework) { var kanban; var $container = $('#cp-app-kanban-content'); mkHelpMenu(framework); if (framework.isReadOnly()) { $container.addClass('cp-app-readonly'); } else { framework.setFileImporter({}, function (content /*, file */) { var parsed; try { parsed = JSON.parse(content); } catch (e) { return void console.error(e); } return { content: parsed }; }); } framework.setFileExporter('.json', function () { return new Blob([JSON.stringify(kanban.getBoardsJSON(), 0, 2)], { type: 'application/json', }); }); framework.onEditableChange(function (unlocked) { if (framework.isReadOnly()) { return; } if (!kanban) { return; } if (unlocked) { addRemoveItemButton(framework, kanban); kanban.options.readOnly = false; return void $container.removeClass('cp-app-readonly'); } kanban.options.readOnly = true; $container.addClass('cp-app-readonly'); }); var getSelectedElement = function () { var node = document.getSelection().anchorNode; return (node.nodeType === 3 ? node.parentNode : node); }; var getCursor = function () { if (!kanban || !kanban.inEditMode) { return; } try { var el = getSelectedElement(); var input = $(el).is('input') ? el : $(el).find('input')[0]; if (!input) { return; } var $input = $(input); var pos; var $item = $(el).closest('.kanban-item'); if ($item.length) { pos = kanban.findElementPosition($item[0]); } var board = $input.closest('.kanban-board').attr('data-id'); var val = ($input.val && $input.val()) || ''; var start = input.selectionStart; var end = input.selectionEnd; var boardEl = kanban.options.boards.find(function (b) { return b.id === board; }); var oldVal = ((pos ? boardEl.item[pos] : boardEl) || {}).title; return { board: board, pos: pos, value: val, start: start, end: end, oldValue: oldVal }; } catch (e) { return {}; } }; var restoreCursor = function (data) { try { var boardEl = kanban.options.boards.find(function (b) { return b.id === data.board; }); if (!boardEl) { return; } var $board = $('.kanban-board[data-id="'+data.board+'"'); // Editing a board title... if (!data.pos && $board.length) { if (boardEl.title !== data.oldValue) { return; } $board.find('.kanban-title-board').click(); var $boardInput = $board.find('.kanban-title-board input'); $boardInput.val(data.value); $boardInput[0].selectionStart = data.start; $boardInput[0].selectionEnd = data.end; return; } // Editing a deleted board title: abort if (!data.pos) { return; } // An item was added: add a new item if (!data.oldValue) { $board.find('.kanban-title-button.fa-plus').click(); var $newInput = $board.find('.kanban-item:last-child input'); $newInput.val(data.value); $newInput[0].selectionStart = data.start; $newInput[0].selectionEnd = data.end; return; } // An item was edited: click on the correct item var newVal = boardEl.item[data.pos]; if (!newVal || newVal.title !== data.oldValue) { return; } var $el = $('.kanban-board[data-id="' + data.board + '"]') .find('.kanban-item:nth-child('+(data.pos + 1)+')'); $el.find('.kanban-item-text').click(); var $input = $el.find('input'); if ($input.length) { $input.val(data.value); $input[0].selectionStart = data.start; $input[0].selectionEnd = data.end; } } catch (e) { return; } }; framework.onContentUpdate(function (newContent) { // Init if needed if (!kanban) { kanban = initKanban(framework, (newContent || {}).content); addRemoveItemButton(framework, kanban); return; } // Need to update the content verbose("Content should be updated to " + newContent); var currentContent = kanban.getBoardsJSON(); var remoteContent = newContent.content; if (Sortify(currentContent) !== Sortify(remoteContent)) { var cursor = getCursor(); verbose("Content is different.. Applying content"); kanban.setBoards(remoteContent); kanban.inEditMode = false; addRemoveItemButton(framework, kanban); restoreCursor(cursor); } }); framework.setContentGetter(function () { if (!kanban) { return { content: [] }; } var content = kanban.getBoardsJSON(); verbose("Content current value is " + content); return { content: content }; }); framework.onReady(function () { $("#cp-app-kanban-content").focus(); }); framework.onDefaultContentNeeded(function () { kanban = initKanban(framework); }); framework.start(); }; var main = function () { // var framework; nThen(function (waitFor) { // Framework initialization Framework.create({ toolbarContainer: '#cme_toolbox', contentContainer: '#cp-app-kanban-editor', }, waitFor(function (framework) { andThen2(framework); })); }); }; main(); });