', {'class': 'kanban-item new-item'});
var $input = getInput().val(name).appendTo($item);
kanban.addForm(boardId, $item[0]);
$input.focus();
+ setTimeout(function () {
+ $input[0].scrollIntoView();
+ });
var save = function () {
$item.remove();
kanban.inEditMode = false;
+ onCursorUpdate.fire({});
if (!$input.val()) { return; }
- kanban.addElement(boardId, {
+ var id = Util.createRandomInteger();
+ var item = {
+ "id": id,
"title": $input.val(),
- });
+ };
+ if (kanban.options.tags && kanban.options.tags.length) {
+ item.tags = kanban.options.tags;
+ }
+ kanban.addElement(boardId, item);
};
$input.blur(save);
$input.keydown(function (e) {
@@ -308,7 +714,7 @@ define([
e.stopPropagation();
save();
if (!$input.val()) { return; }
- $(el).closest('.kanban-board').find('.kanban-title-button.fa-plus').click();
+ $(el).closest('.kanban-board').find('footer .kanban-title-button').click();
return;
}
if (e.which === 27) {
@@ -316,35 +722,167 @@ define([
e.stopPropagation();
$item.remove();
kanban.inEditMode = false;
+ onCursorUpdate.fire({});
return;
}
});
},
+ renderMd: function (md) {
+ return DiffMd.render(md, true, false);
+ },
addItemButton: true,
+ getTextColor: getTextColor,
+ getAvatar: getAvatar,
+ cursors: remoteCursors,
boards: boards
});
+ if (migrated) { framework.localChange(); }
+
var addBoardDefault = document.getElementById('kanban-addboard');
$(addBoardDefault).attr('title', Messages.kanban_addBoard);
addBoardDefault.addEventListener('click', function () {
if (framework.isReadOnly()) { return; }
- var counter = 1;
+ /*var counter = 1;
// Get the new board id
var boardExists = function (b) { return b.id === "board" + counter; };
while (kanban.options.boards.some(boardExists)) { counter++; }
+ */
+ var id = Util.createRandomInteger();
- kanban.addBoards([{
- "id": "board" + counter,
+ kanban.addBoard({
+ "id": id,
"title": Messages.kanban_newBoard,
- "color": COLORS[Math.floor(Math.random()*COLORS.length)], // random color
- "item": [{
- "title": Messages._getKey('kanban_item', [1]),
- }]
- }]);
+ "item": []
+ });
kanban.onChange();
});
+ var $container = $('#cp-app-kanban-content');
+ var $cContainer = $('#cp-app-kanban-container');
+ var addControls = function () {
+ // Quick or normal mode
+ var small = h('span.cp-kanban-view-small.fa.fa-minus');
+ var big = h('span.cp-kanban-view.fa.fa-bars');
+ $(small).click(function () {
+ if ($cContainer.hasClass('cp-kanban-quick')) { return; }
+ $cContainer.addClass('cp-kanban-quick');
+ framework._.sfCommon.setPadAttribute('quickMode', true);
+ });
+ $(big).click(function () {
+ if (!$cContainer.hasClass('cp-kanban-quick')) { return; }
+ $cContainer.removeClass('cp-kanban-quick');
+ framework._.sfCommon.setPadAttribute('quickMode', false);
+ });
+
+ // Tags filter
+ var existing = getExistingTags(kanban.options.boards);
+ var list = h('div.cp-kanban-filterTags-list');
+ var reset = h('i.cp-kanban-filterTags-reset.fa.fa-times');
+ var tags = h('div.cp-kanban-filterTags', [
+ h('span.cp-kanban-filterTags-name', Messages.kanban_tags),
+ reset,
+ list
+ ]);
+ var $reset = $(reset);
+ var $list = $(list);
+
+ var getTags = function () {
+ return $list.find('span.active').map(function () {
+ return $(this).data('tag');
+ }).get();
+ };
+ var commitTags = function () {
+ var t = getTags();
+ if (t.length) {
+ $reset.css('visibility', '');
+ } else {
+ $reset.css('visibility', 'hidden');
+ }
+ framework._.sfCommon.setPadAttribute('tagsFilter', t);
+ kanban.options.tags = t;
+ kanban.setBoards(kanban.options.boards);
+ addEditItemButton(framework, kanban);
+ };
+
+ var redrawList = function (allTags) {
+ if (!Array.isArray(allTags)) { return; }
+ $list.empty();
+ if (!allTags.length) {
+ $list.append(h('em', Messages.kanban_noTags));
+ return;
+ }
+ allTags.forEach(function (t) {
+ var tag;
+ $list.append(tag = h('span', {
+ 'data-tag': t
+ }, t));
+ var $tag = $(tag).click(function () {
+ if ($tag.hasClass('active')) {
+ $tag.removeClass('active');
+ } else {
+ $tag.addClass('active');
+ }
+ commitTags();
+ });
+ });
+ };
+ redrawList(existing);
+
+ var setTags = function (tags) {
+ $list.find('span').removeClass('active');
+ if (!Array.isArray(tags)) { return; }
+ tags.forEach(function (t, i) {
+ if (existing.indexOf(t) === -1) {
+ // This tag doesn't exist anymore
+ tags.splice(i, 1);
+ return;
+ }
+ $list.find('span').filter(function () {
+ return $(this).data('tag') === t;
+ }).addClass('active');
+ });
+ framework._.sfCommon.setPadAttribute('tagsFilter', tags);
+ };
+ $reset.css('visibility', 'hidden').click(function () {
+ setTags([]);
+ commitTags();
+ });
+
+ var container = h('div#cp-kanban-controls', [
+ tags,
+ h('div.cp-kanban-changeView', [
+ small,
+ big
+ ])
+ ]);
+ $container.before(container);
+
+ onRedraw.reg(function () {
+ // Redraw if new tags have been added to items
+ var old = Sortify(existing);
+ var t = getTags();
+ existing = getExistingTags(kanban.options.boards);
+ if (old === Sortify(existing)) { return; } // No change
+ // New tags:
+ redrawList(existing);
+ setTags(t);
+ });
+ framework._.sfCommon.getPadAttribute('tagsFilter', function (err, res) {
+ if (!err && Array.isArray(res)) {
+ setTags(res);
+ commitTags();
+ }
+ });
+ framework._.sfCommon.getPadAttribute('quickMode', function (err, res) {
+ if (!err && res) {
+ $cContainer.addClass('cp-kanban-quick');
+ }
+ });
+ };
+ addControls();
+
return kanban;
};
@@ -387,7 +925,7 @@ define([
if (framework.isReadOnly()) { return; }
if (!kanban) { return; }
if (unlocked) {
- addRemoveItemButton(framework, kanban);
+ addEditItemButton(framework, kanban);
kanban.options.readOnly = false;
return void $container.removeClass('cp-app-readonly');
}
@@ -395,93 +933,86 @@ define([
$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 id = kanban.inEditMode;
+ var newBoard;
+ var $el = $container.find('[data-id="'+id+'"]');
+ if (id === "new") {
+ $el = $container.find('.kanban-item.new-item');
+ newBoard = $el.closest('.kanban-board').attr('data-id');
+ } else if (!$el.length) {
+ $el = $container.find('[data-eid="'+id+'"]');
}
- var board = $input.closest('.kanban-board').attr('data-id');
+ if (!$el.length) { return; }
+ var $input = $el.find('input');
+ if (!$input.length) { return; }
+ var input = $input[0];
+
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;
+ var json = kanban.getBoardJSON(id) || kanban.getItemJSON(id);
+ var oldVal = json && json.title;
return {
- board: board,
- pos: pos,
+ id: id,
+ newBoard: newBoard,
value: val,
start: start,
end: end,
oldValue: oldVal
};
} catch (e) {
+ console.error(e);
return {};
}
};
var restoreCursor = function (data) {
+ if (!data) { return; }
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;
- }
+ var id = data.id;
- // 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');
+ // An item was being added: add a new item
+ if (id === "new" && !data.oldValue) {
+ var $newBoard = $('.kanban-board[data-id="'+data.newBoard+'"]');
+ $newBoard.find('.kanban-title-button.fa-plus').click();
+ var $newInput = $newBoard.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;
+ // Edit a board title or a card title
+ var $el = $container.find('.kanban-board[data-id="'+id+'"]');
+ if (!$el.length) {
+ $el = $container.find('.kanban-item[data-eid="'+id+'"]');
+ }
+ if (!$el.length) { return; }
+
+ var isBoard = true;
+ var json = kanban.getBoardJSON(id);
+ if (!json) {
+ isBoard = false;
+ json = kanban.getItemJSON(id);
}
+
+ // Editing a board or card title...
+ $el.find(isBoard ? '.kanban-title-board' : '.kanban-item-text').click();
+ var $input = $el.find('input');
+ if (!$input.length) { return; }
+
+ // if the value was changed by a remote user, abort
+ setValueAndCursor($input[0], json.title, {
+ value: data.value,
+ selectionStart: data.start,
+ selectionEnd: data.end
+ });
} catch (e) {
+ console.error(e);
return;
}
};
@@ -490,7 +1021,7 @@ define([
// Init if needed
if (!kanban) {
kanban = initKanban(framework, (newContent || {}).content);
- addRemoveItemButton(framework, kanban);
+ addEditItemButton(framework, kanban);
return;
}
@@ -504,16 +1035,15 @@ define([
verbose("Content is different.. Applying content");
kanban.setBoards(remoteContent);
kanban.inEditMode = false;
- addRemoveItemButton(framework, kanban);
+ addEditItemButton(framework, kanban);
restoreCursor(cursor);
+ onRemoteChange.fire();
}
});
framework.setContentGetter(function () {
if (!kanban) {
- return {
- content: []
- };
+ throw new Error("NOT INITIALIZED");
}
var content = kanban.getBoardsJSON();
verbose("Content current value is " + content);
@@ -522,14 +1052,74 @@ define([
};
});
+ var cleanData = function (boards) {
+ if (typeof(boards) !== "object") { return; }
+ var items = boards.items || {};
+ var data = boards.data || {};
+ var list = boards.list || [];
+ Object.keys(data).forEach(function (id) {
+ if (list.indexOf(Number(id)) === -1) { delete data[id]; }
+ });
+ Object.keys(items).forEach(function (eid) {
+ var exists = Object.keys(data).some(function (id) {
+ return (data[id].item || []).indexOf(Number(eid)) !== -1;
+ });
+ if (!exists) { delete items[eid]; }
+ });
+ framework.localChange();
+ };
+
framework.onReady(function () {
$("#cp-app-kanban-content").focus();
+ var content = kanban.getBoardsJSON();
+ cleanData(content);
});
framework.onDefaultContentNeeded(function () {
kanban = initKanban(framework);
});
+ var myCursor = {};
+ onCursorUpdate.reg(function (data) {
+ myCursor = data;
+ framework.updateCursor();
+ });
+ framework.onCursorUpdate(function (data) {
+ if (!data) { return; }
+ var id = data.id;
+
+ // Clear existing cursor
+ if (remoteCursors[id] && remoteCursors[id].clear) {
+ remoteCursors[id].clear();
+ }
+ delete remoteCursors[id];
+
+ var cursor = data.cursor;
+ if (data.leave || !cursor) { return; }
+ if (!cursor.item && !cursor.board) { return; }
+
+ // Add new cursor
+ var avatar = getAvatar(cursor);
+ var $item = $('.kanban-item[data-eid="'+cursor.item+'"]');
+ var $board = $('.kanban-board[data-id="'+cursor.board+'"]');
+ if ($item.length) {
+ remoteCursors[id] = cursor;
+ $item.find('.cp-kanban-cursors').append(avatar);
+ return;
+ }
+ if ($board.length) {
+ remoteCursors[id] = cursor;
+ $board.find('header .cp-kanban-cursors').append(avatar);
+ }
+ });
+ framework.onCursorUpdate(function () {
+ if (!editModal || !editModal.conflict) { return; }
+ editModal.conflict.setValue();
+ });
+ framework.setCursorGetter(function () {
+ return myCursor;
+ });
+
framework.start();
};
diff --git a/www/kanban/jkanban.css b/www/kanban/jkanban.css
index ed5a8f5db..bb8d5eb6a 100644
--- a/www/kanban/jkanban.css
+++ b/www/kanban/jkanban.css
@@ -4,13 +4,13 @@
.kanban-board {
position: relative;
- float: left;
- background: #E2E4E6;
transition: all 0.3s cubic-bezier(0.23, 1, 0.32, 1);
margin: 10px;
vertical-align: top;
display: flex;
flex-flow: column;
+ width: 300px;
+ margin: 10px 5px;
}
.kanban-board.disabled-board {
@@ -19,6 +19,7 @@
.kanban-board.is-moving.gu-mirror {
transform: rotate(3deg);
+ opacity: 0.8;
}
.kanban-board.is-moving.gu-mirror .kanban-drag {
@@ -45,17 +46,11 @@
padding: .25rem .5rem;
}
-.kanban-board .kanban-drag {
- min-height: 200px;
- padding: 20px;
- flex: 1;
-}
.kanban-item {
background: #fff;
padding: 15px;
- margin-bottom: 20px;
- transition: all 0.3s cubic-bezier(0.23, 1, 0.32, 1);
+ margin-bottom: 10px;
}
.kanban-item:hover {
@@ -69,6 +64,7 @@
.kanban-item.is-moving.gu-mirror {
transform: rotate(3deg);
height: auto !important;
+ opacity: 0.8;
}
/* Dragula CSS */
diff --git a/www/kanban/jkanban.js b/www/kanban/jkanban.js
index f3f4bf6d4..a6606c787 100644
--- a/www/kanban/jkanban.js
+++ b/www/kanban/jkanban.js
@@ -45,18 +45,23 @@
this.drake = '';
this.drakeBoard = '';
this.addItemButton = false;
- this.buttonContent = '+';
defaults = {
element: '',
gutter: '15px',
widthBoard: '250px',
responsive: '700',
- colors: ["yellow", "green", "blue", "red", "orange"],
responsivePercentage: false,
- boards: [],
+ boards: {
+ data: {},
+ items: {},
+ list: []
+ },
+ getAvatar: function () {},
+ getTextColor: function () { return '#000'; },
+ cursors: {},
+ tags: [],
dragBoards: true,
addItemButton: false,
- buttonContent: '+',
readOnly: false,
dragEl: function (el, source) {},
dragendEl: function (el) {},
@@ -67,9 +72,9 @@
dropBoard: function (el, target, source, sibling) {},
click: function (el) {},
boardTitleclick: function (el, boardId) {},
- buttonClick: function (el, boardId) {},
- colorClick: function (el, type) {},
addItemClick: function (el, boardId) {},
+ renderMd: function (md) {},
+ refresh: function () {},
onChange: function () {}
};
@@ -80,19 +85,62 @@
this.init = function () {
// set initial boards
__setBoard();
+
+ // Scroll on drag
+ var $el = $(self.element)
+ var $inner = $el.find('.kanban-container');
+ var leftRegion = $el.position().left + 10;
+ var rightRegion = $(window).width() - 10;
+ var activeBoard;
+ var $aB;
+ var setActiveDrag = function (board) {
+ activeBoard = undefined;
+ if (!board) { return; }
+ if (!board.classList.contains('kanban-drag')) { return; }
+ activeBoard = board;
+ $aB = $(activeBoard);
+ };
+ var onMouseMove = function (isItem) {
+ return function (e) {
+ if (e.which !== 1) { return; } // left click
+ var distance = 20;
+ // If this is an item drag, check scroll
+ if (isItem && activeBoard) {
+ var rect = activeBoard.getBoundingClientRect();
+ if (e.pageX > rect.left && e.pageX < rect.right) {
+ if (e.pageY < (rect.top + 10)) {
+ distance *= -1;
+ $aB.scrollTop(distance + $aB.scrollTop()) ;
+ } else if (e.pageY > (rect.bottom - 10)) {
+ $aB.scrollTop(distance + $aB.scrollTop()) ;
+ }
+ }
+ }
+ // Itme or board: horizontal scroll if needed
+ if (e.pageX < leftRegion) {
+ distance *= -1;
+ $el.scrollLeft(distance + $el.scrollLeft()) ;
+ } else if (e.pageX >= rightRegion) {
+ $el.scrollLeft(distance + $el.scrollLeft()) ;
+ }
+ };
+ };
+
//set drag with dragula
if (window.innerWidth > self.options.responsive) {
//Init Drag Board
- self.drakeBoard = self.dragula([self.container], {
+ self.drakeBoard = self.dragula([self.container, self.trashContainer], {
moves: function (el, source, handle, sibling) {
if (self.options.readOnly) { return false; }
- if (!self.options.dragBoards) return false;
+ if (!self.options.dragBoards) { return false; }
return (handle.classList.contains('kanban-board-header') || handle.classList.contains('kanban-title-board'));
},
accepts: function (el, target, source, sibling) {
if (self.options.readOnly) { return false; }
- return target.classList.contains('kanban-container');
+ if (sibling && sibling.getAttribute('id') === "kanban-addboard") { return false; }
+ return target.classList.contains('kanban-container') ||
+ target.classList.contains('kanban-trash');
},
revertOnSpill: true,
direction: 'horizontal',
@@ -100,47 +148,66 @@
.on('drag', function (el, source) {
el.classList.add('is-moving');
self.options.dragBoard(el, source);
- if (typeof (el.dragfn) === 'function')
+ if (typeof (el.dragfn) === 'function') {
el.dragfn(el, source);
+ }
+ $(document).on('mousemove', onMouseMove());
})
.on('dragend', function (el) {
el.classList.remove('is-moving');
self.options.dragendBoard(el);
+ $(document).off('mousemove');
if (typeof (el.dragendfn) === 'function')
el.dragendfn(el);
})
+ .on('over', function (el, target, source) {
+ if (!target.classList.contains('kanban-trash')) { return false; }
+ $('.kanban-trash').addClass('kanban-trash-active');
+ })
+ .on('out', function (el, target) {
+ if (!target.classList.contains('kanban-trash')) { return false; }
+ $('.kanban-trash').removeClass('kanban-trash-active');
+ })
.on('drop', function (el, target, source, sibling) {
el.classList.remove('is-moving');
self.options.dropBoard(el, target, source, sibling);
- if (typeof (el.dropfn) === 'function')
+ if (typeof (el.dropfn) === 'function') {
el.dropfn(el, target, source, sibling);
+ }
- // TODO: update board object board order
- console.log("Drop " + $(el).attr("data-id") + " just before " + (sibling ? $(sibling).attr("data-id") : " end "));
- var index1, index2;
- self.options.boards.some(function (element, index) {
- if (element.id === $(el).attr("data-id")) {
- index1 = index;
- return true;
- }
- });
- if (sibling) {
- self.options.boards.some(function (element, index) {
- if (element.id === $(sibling).attr("data-id")) {
- index2 = index;
- return true;
- }
- })
- } else {
- index2 = self.options.boards.length;
+ var id = Number($(el).attr('data-id'));
+ var list = self.options.boards.list || [];
+
+ var index1 = list.indexOf(id);
+ if (index1 === -1) { return; }
+
+ // Move to trash?
+ if (target.classList.contains('kanban-trash')) {
+ list.splice(index1, 1);
+ delete self.options.boards.data[id];
+ self.onChange();
+ return;
+ }
+
+ var index2;
+ var id2 = Number($(sibling).attr("data-id"));
+ if (sibling && id2) {
+ index2 = list.indexOf(id2);
+ }
+ // If we can't find the drop position, drop at the end
+ if (typeof(index2) === "undefined" || index2 === -1) {
+ index2 = list.length;
}
+
console.log("Switch " + index1 + " and " + index2);
- if (index1 < index2)
+ if (index1 < index2) {
index2 = index2 - 1;
- self.options.boards.splice(index2, 0, self.options.boards.splice(index1, 1)[0]);
+ }
+ list.splice(index1, 1);
+ list.splice(index2, 0, id);
// send event that board has changed
self.onChange();
-
+ self.setBoards(self.options.boards);
});
//Init Drag Item
@@ -148,7 +215,7 @@
moves: function (el, source, handle, sibling) {
if (self.options.readOnly) { return false; }
if (el.classList.contains('new-item')) { return false; }
- return handle.classList.contains('kanban-item');
+ return el.classList.contains('kanban-item');
},
accepts: function (el, target, source, sibling) {
if (self.options.readOnly) { return false; }
@@ -163,93 +230,121 @@
// we need to calculate the position before starting to drag
self.dragItemPos = self.findElementPosition(el);
+ setActiveDrag();
el.classList.add('is-moving');
- var boardJSON = __findBoardJSON(source.parentNode.dataset.id);
- if (boardJSON.dragTo !== undefined) {
- self.options.boards.map(function (board) {
- if (boardJSON.dragTo.indexOf(board.id) === -1 && board.id !== source.parentNode.dataset.id) {
- self.findBoard(board.id).classList.add('disabled-board');
- }
- })
- }
+ $(document).on('mousemove', onMouseMove(el));
self.options.dragEl(el, source);
- if (el !== null && typeof (el.dragfn) === 'function')
+ if (el !== null && typeof (el.dragfn) === 'function') {
el.dragfn(el, source);
+ }
})
.on('dragend', function (el) {
console.log("In dragend");
el.classList.remove('is-moving');
self.options.dragendEl(el);
- if (el !== null && typeof (el.dragendfn) === 'function')
+ $(document).off('mousemove');
+ if (el !== null && typeof (el.dragendfn) === 'function') {
el.dragendfn(el);
+ }
})
.on('cancel', function (el, container, source) {
console.log("In cancel");
el.classList.remove('is-moving');
- // FIXME custom code
- var boardId = source.parentNode.dataset.id;
+ var boardId = $(source).closest('kanban-board').data('id');
self.options.dragcancelEl(el, boardId);
})
+ .on('over', function (el, target, source) {
+ setActiveDrag(target);
+ if (!target.classList.contains('kanban-trash')) { return false; }
+ target.classList.add('kanban-trash-active');
+ })
+ .on('out', function (el, target) {
+ setActiveDrag();
+ if (!target.classList.contains('kanban-trash')) { return false; }
+ target.classList.remove('kanban-trash-active');
+ })
.on('drop', function(el, target, source, sibling) {
self.enableAllBoards();
el.classList.remove('is-moving');
console.log("In drop");
- // TODO: update board object board order
- var board1;
- self.options.boards.some(function (element) {
- if (element.id === $(source.parentNode).attr("data-id")) {
- return board1 = element;
- }
- });
- var board2;
- self.options.boards.some(function (element) {
- if (element.id === $(target.parentNode).attr("data-id")) {
- return board2 = element;
- }
- });
- var pos1 = self.dragItemPos;
- var pos2 = (sibling) ? self.findElementPosition(sibling) : (board2.item.length + 1);
- console.log("Drop element " + pos1 + " before " + pos2);
-
- // TODO: update board object item order
+ var id1 = Number($(el).attr('data-eid'));
- var allB = document.querySelectorAll('.kanban-board');
- if (allB.length > 0 && allB !== undefined) {
- for (var i = 0; i < allB.length; i++) {
- allB[i].classList.remove('disabled-board');
- }
- }
- var boardJSON = __findBoardJSON(source.parentNode.dataset.id);
- if (boardJSON.dragTo !== undefined) {
- if (boardJSON.dragTo.indexOf(target.parentNode.dataset.id) === -1 && target.parentNode.dataset.id !== source.parentNode.dataset.id) {
- self.drake.cancel(true)
- }
+ // Move to trash?
+ if (target.classList.contains('kanban-trash')) {
+ self.moveItem(id1);
+ self.onChange();
+ return;
}
+
+ // Find the new board
+ var targetId = Number($(target).closest('.kanban-board').data('id'));
+ if (!targetId) { return; }
+ var board2 = __findBoardJSON(targetId);
+ var id2 = $(sibling).attr('data-eid');
+ if (id2) { id2 = Number(id2); }
+ var pos2 = id2 ? board2.item.indexOf(id2) : board2.item.length;
+ if (pos2 === -1) { pos2 = board2.item.length; }
+
+ // Remove the "move" effect
if (el !== null) {
- self.options.dropEl(el, target, source, sibling);
el.classList.remove('is-moving');
- if (typeof (el.dropfn) === 'function')
- el.dropfn(el, target, source, sibling);
}
- var item = board1.item[pos1];
- // if (board1==board2 && pos2
0 && allB !== undefined) {
@@ -259,31 +354,84 @@
}
};
- this.addElement = function (boardID, element) {
-
- // add Element to JSON
- var boardJSON = __findBoardJSON(boardID);
- boardJSON.item.push({
- title: element.title
- });
- var board = self.element.querySelector('[data-id="' + boardID + '"] .kanban-drag');
+ var getElementNode = function (element) {
var nodeItem = document.createElement('div');
nodeItem.classList.add('kanban-item');
- if (element.id) {
- nodeItem.setAttribute('data-eid', element.id)
+ nodeItem.dataset.eid = element.id;
+ if (element.color) {
+ if (/color/.test(element.color)) {
+ // Palette color
+ nodeItem.classList.add('cp-kanban-palette-'+element.color);
+ } else {
+ // Hex color code
+ var textColor = self.options.getTextColor(element.color);
+ nodeItem.setAttribute('style', 'background-color:#'+element.color+';color:'+textColor+';');
+ }
}
+ var nodeCursors = document.createElement('div');
+ nodeCursors.classList.add('cp-kanban-cursors');
+ Object.keys(self.options.cursors).forEach(function (id) {
+ var c = self.options.cursors[id];
+ if (Number(c.item) !== Number(element.id)) { return; }
+ var el = self.options.getAvatar(c);
+ nodeCursors.appendChild(el);
+ });
var nodeItemText = document.createElement('div');
nodeItemText.classList.add('kanban-item-text');
- nodeItemText.innerHTML = element.title;
+ nodeItemText.dataset.eid = element.id;
+ nodeItemText.innerText = element.title;
nodeItem.appendChild(nodeItemText);
+ // Check if this card is filtered out
+ if (Array.isArray(self.options.tags) && self.options.tags.length) {
+ var hide = !Array.isArray(element.tags) ||
+ !element.tags.some(function (tag) {
+ return self.options.tags.indexOf(tag) !== -1;
+ });
+ if (hide) {
+ nodeItem.classList.add('kanban-item-hidden');
+ }
+ }
+ if (element.body) {
+ var html = self.renderMd(element.body);
+ var nodeBody = document.createElement('div');
+ nodeBody.classList.add('kanban-item-body');
+ nodeBody.onclick = function (e) {
+ e.preventDefault();
+ };
+ nodeBody.innerHTML = html;
+ nodeItem.appendChild(nodeBody);
+ }
+ if (Array.isArray(element.tags)) {
+ var nodeTags = document.createElement('div');
+ nodeTags.classList.add('kanban-item-tags');
+ element.tags.forEach(function (_tag) {
+ var tag = document.createElement('span');
+ tag.innerText = _tag;
+ nodeTags.appendChild(tag);
+ });
+ nodeItem.appendChild(nodeTags);
+ }
+ nodeItem.appendChild(nodeCursors);
//add function
nodeItem.clickfn = element.click;
nodeItem.dragfn = element.drag;
nodeItem.dragendfn = element.dragend;
nodeItem.dropfn = element.drop;
__onclickHandler(nodeItemText);
- __onColorClickHandler(nodeItem, "item");
- board.appendChild(nodeItem);
+ return nodeItem;
+ };
+
+ this.addElement = function (boardID, element) {
+
+ // add Element to JSON
+ var boardJSON = __findBoardJSON(boardID);
+
+ boardJSON.item.push(element.id);
+ self.options.boards.items = self.options.boards.items || {};
+ self.options.boards.items[element.id] = element;
+
+ var board = self.element.querySelector('[data-id="' + boardID + '"] .kanban-drag');
+ board.appendChild(getElementNode(element));
// send event that board has changed
self.onChange();
return self;
@@ -295,116 +443,125 @@
return self;
};
-
- this.addBoards = function(boards) {
+ var getBoardNode = function (board) {
+ var boards = self.options.boards;
+ var boardWidth = self.options.widthBoard;
+ //create node
+ var boardNode = document.createElement('div');
+ boardNode.dataset.id = board.id;
+ boardNode.classList.add('kanban-board');
+ var boardNodeInner = document.createElement('div');
+ boardNodeInner.classList.add('kanban-board-inner');
+ //set style
if (self.options.responsivePercentage) {
- self.container.style.width = '100%';
- self.options.gutter = '1%';
- if (window.innerWidth > self.options.responsive) {
- var boardWidth = (100 - boards.length * 2) / boards.length;
+ boardNode.style.width = boardWidth + '%';
+ } else {
+ boardNode.style.width = boardWidth;
+ }
+ boardNode.style.marginLeft = self.options.gutter;
+ boardNode.style.marginRight = self.options.gutter;
+ // header board
+ var headerBoard = document.createElement('header');
+ if (board.class !== '' && board.class !== undefined) {
+ var allClasses = board.class.split(",");
+ } else {
+ allClasses = [];
+ }
+ headerBoard.classList.add('kanban-board-header');
+ allClasses.map(function (value) {
+ headerBoard.classList.add(value);
+ });
+ if (board.color !== '' && board.color !== undefined) {
+ if (/color/.test(board.color)) {
+ // Palette color
+ headerBoard.classList.add('cp-kanban-palette-'+board.color);
+ boardNodeInner.classList.add('cp-kanban-palette-'+board.color);
+ } else if (!/^[0-9a-f]{6}$/.test(board.color)) {
+ // "string" color (red, blue, etc.)
+ headerBoard.classList.add("kanban-header-" + board.color);
} else {
- var boardWidth = 100 - (boards.length * 2);
+ // Hex color code
+ var textColor = self.options.getTextColor(board.color);
+ headerBoard.setAttribute('style', 'background-color:#'+board.color+';color:'+textColor+';');
}
- } else {
- var boardWidth = self.options.widthBoard;
}
- var addButton = self.options.addItemButton;
- var buttonContent = self.options.buttonContent;
+ titleBoard = document.createElement('div');
+ titleBoard.classList.add('kanban-title-board');
+ titleBoard.innerText = board.title;
+
+ titleBoard.clickfn = board.boardTitleClick;
+ __onboardTitleClickHandler(titleBoard);
+ headerBoard.appendChild(titleBoard);
+
+ var nodeCursors = document.createElement('div');
+ nodeCursors.classList.add('cp-kanban-cursors');
+ Object.keys(self.options.cursors).forEach(function (id) {
+ var c = self.options.cursors[id];
+ if (Number(c.board) !== Number(board.id)) { return; }
+ var el = self.options.getAvatar(c);
+ nodeCursors.appendChild(el);
+ });
+ headerBoard.appendChild(nodeCursors);
+
+ //content board
+ var contentBoard = document.createElement('main');
+ contentBoard.classList.add('kanban-drag');
+ //add drag to array for dragula
+ self.boardContainer.push(contentBoard);
+ (board.item || []).forEach(function (itemkey) {
+ //create item
+ var itemKanban = boards.items[itemkey];
+ if (!itemKanban) { return; } // XXX clean invalid data
+ var nodeItem = getElementNode(itemKanban);
+ contentBoard.appendChild(nodeItem);
+ });
+ //footer board
+ var footerBoard = document.createElement('footer');
+ footerBoard.classList.add('kanban-board-footer');
+ //add button
+ var addBoardItem = document.createElement('span');
+ addBoardItem.classList.add('kanban-title-button');
+ addBoardItem.innerText = '+';
+ footerBoard.appendChild(addBoardItem);
+ __onAddItemClickHandler(addBoardItem);
+
+ //board assembly
+ boardNode.appendChild(boardNodeInner);
+ boardNodeInner.appendChild(headerBoard);
+ boardNodeInner.appendChild(contentBoard);
+ boardNodeInner.appendChild(footerBoard);
+
+ return boardNode;
+ };
+ this.addBoard = function (board) {
+ if (!board || !board.id) { return; }
+ var boards = self.options.boards;
+ boards.data = boards.data || {};
+ boards.list = boards.list || [];
+ // If it already there, abort
+ boards.data[board.id] = board;
+ if (boards.list.indexOf(board.id) !== -1) { return; }
+
+ boards.list.push(board.id);
+ var boardNode = getBoardNode(board);
+ self.container.appendChild(boardNode);
+ };
+
+ this.addBoards = function() {
//for on all the boards
- for (var boardkey in boards) {
+ var boards = self.options.boards;
+ boards.list = boards.list || [];
+ boards.data = boards.data || {};
+ for (var index in boards.list) {
// single board
- var board = boards[boardkey];
- if (self.options.boards !== boards)
- self.options.boards.push(board);
-
- //create node
- var boardNode = document.createElement('div');
- boardNode.dataset.id = board.id;
- boardNode.classList.add('kanban-board');
- //set style
- if (self.options.responsivePercentage) {
- boardNode.style.width = boardWidth + '%';
- } else {
- boardNode.style.width = boardWidth;
- }
- boardNode.style.marginLeft = self.options.gutter;
- boardNode.style.marginRight = self.options.gutter;
- // header board
- var headerBoard = document.createElement('header');
- if (board.class !== '' && board.class !== undefined)
- var allClasses = board.class.split(",");
- else allClasses = [];
- headerBoard.classList.add('kanban-board-header');
- allClasses.map(function (value) {
- headerBoard.classList.add(value);
- });
- if (board.color !== '' && board.color !== undefined) {
- headerBoard._jscLinkedInstance = undefined;
- jscolorL = new jscolor(headerBoard,{showOnClick: false, valueElement:undefined});
- jscolorL.fromString(board.color);
- headerBoard._jscLinkedInstance = undefined;
- headerBoard.classList.add("kanban-header-" + board.color);
- }
- titleBoard = document.createElement('div');
- titleBoard.classList.add('kanban-title-board');
- titleBoard.innerHTML = board.title;
- //titleBoard.setAttribute('title', board.title);
- titleBoard.clickfn = board.boardTitleClick;
- __onboardTitleClickHandler(titleBoard);
- headerBoard.appendChild(titleBoard);
- __onColorClickHandler(headerBoard, "board");
-
- // if add button is true, add button to the board
- if (addButton) {
- var btn = document.createElement("BUTTON");
- btn.setAttribute("class", "kanban-title-button btn btn-default btn-xs fa fa-times");
- //var buttonHtml = ''
- headerBoard.appendChild(btn);
- __onButtonClickHandler(btn, board.id);
- }
- //content board
- var contentBoard = document.createElement('main');
- contentBoard.classList.add('kanban-drag');
- //add drag to array for dragula
- self.boardContainer.push(contentBoard);
- for (var itemkey in board.item) {
- //create item
- var itemKanban = board.item[itemkey];
- var nodeItem = document.createElement('div');
- nodeItem.classList.add('kanban-item');
- nodeItem.dataset.eid = itemKanban.id;
- var nodeItemText = document.createElement('div');
- nodeItemText.classList.add('kanban-item-text');
- nodeItemText.dataset.eid = itemKanban.id;
- nodeItemText.innerHTML = itemKanban.title;
- nodeItem.appendChild(nodeItemText);
- //add function
- nodeItemText.clickfn = itemKanban.click;
- nodeItemText.dragfn = itemKanban.drag;
- nodeItemText.dragendfn = itemKanban.dragend;
- nodeItemText.dropfn = itemKanban.drop;
- //add click handler of item
- __onclickHandler(nodeItemText);
- if (itemKanban.color !== '' && itemKanban.color !== undefined) {
- jscolorL = new jscolor(nodeItem,{showOnClick: false, valueElement:undefined});
- jscolorL.fromString(itemKanban.color);
- }
- __onColorClickHandler(nodeItem, "item");
-
- contentBoard.appendChild(nodeItem);
- }
- //footer board
- //add button
- var addBoardItem = document.createElement('button');
- $(addBoardItem).addClass("kanban-title-button btn btn-default fa fa-plus");
- headerBoard.appendChild(addBoardItem);
- __onAddItemClickHandler(addBoardItem);
-
- //board assembly
- boardNode.appendChild(headerBoard);
- boardNode.appendChild(contentBoard);
+ var boardkey = boards.list[index];
+ var board = boards.data[boardkey];
+ if (!board) { continue; } // XXX clean invalid data
+
+ var boardNode = getBoardNode(board);
+
//board add
self.container.appendChild(boardNode);
}
@@ -416,13 +573,14 @@
}
this.setBoards = function (boards) {
- self.element
- for (var boardkey in this.options.boards) {
- var board = this.options.boards[boardkey];
- this.removeBoard(board.id);
+ //self.element
+ for (var i in this.options.boards.list) {
+ var boardkey = this.options.boards.list[i];
+ this.removeBoard(boardkey);
}
- this.options.boards = [];
- this.addBoards(boards);
+ this.options.boards = boards;
+ this.addBoards();
+ self.options.refresh();
}
this.findBoard = function (id) {
@@ -457,8 +615,13 @@
};
this.removeBoard = function (board) {
- if (typeof (board) === 'string')
+ var id;
+ if (typeof (board) === 'string' || typeof (board) === "number") {
+ id = board;
board = self.element.querySelector('[data-id="' + board + '"]');
+ } else if (board) {
+ id = board.id;
+ }
if (board) {
board.remove();
@@ -466,14 +629,15 @@
self.onChange();
}
+ // Remove duplicates
+ if (id) { $(self.element).find('.kanban-board[data-id="' + board + '"]').remove(); }
+
return self;
}
- // board button on click function
- this.onButtonClick = function (el) {
-
+ this.renderMd = function (md) {
+ return self.options.renderMd(md);
}
-
this.onChange = function () {
self.options.onChange();
}
@@ -485,6 +649,9 @@
this.getBoardJSON = function (id) {
return __findBoardJSON(id);
}
+ this.getItemJSON = function (id) {
+ return (self.options.boards.items || {})[id];
+ };
//PRIVATE FUNCTION
function __extendDefaults(source, properties) {
@@ -507,14 +674,24 @@
boardContainerOuter.appendChild(boardContainer);
var addBoard = document.createElement('div');
addBoard.id = 'kanban-addboard';
- addBoard.setAttribute('class', 'fa fa-plus');
+ addBoard.innerText = '+';
+ boardContainer.appendChild(addBoard);
+ var trash = self.trashContainer = document.createElement('div');
+ trash.setAttribute('id', 'kanban-trash');
+ trash.setAttribute('class', 'kanban-trash');
+ var trashBg = document.createElement('div');
+ var trashIcon = document.createElement('i');
+ trashIcon.setAttribute('class', 'fa fa-trash');
+ trash.appendChild(trashIcon);
+ trash.appendChild(trashBg);
+ self.boardContainer.push(trash);
self.container = boardContainer;
//add boards
- self.addBoards(self.options.boards);
+ self.addBoards();
//appends to container
self.element.appendChild(boardContainerOuter);
- self.element.appendChild(addBoard);
+ self.element.appendChild(trash);
// send event that board has changed
self.onChange();
@@ -539,17 +716,6 @@
});
}
- function __onColorClickHandler(nodeItem, type) {
- nodeItem.addEventListener('click', function (e) {
- if (Array.prototype.slice.call(nodeItem.classList).indexOf('is-moving') !== -1) {
- return;
- }
- e.preventDefault;
- e.stopPropagation();
- self.options.colorClick(this, type);
- });
- }
-
function __onAddItemClickHandler(nodeItem, clickfn) {
nodeItem.addEventListener('click', function (e) {
e.preventDefault;
@@ -560,24 +726,8 @@
});
}
- function __onButtonClickHandler(nodeItem, boardId) {
- nodeItem.addEventListener('click', function (e) {
- e.stopPropagation();
- e.preventDefault;
- self.options.buttonClick(this, boardId, e);
- // if(typeof(this.clickfn) === 'function')
- // this.clickfn(this);
- });
- }
-
function __findBoardJSON(id) {
- var el = []
- self.options.boards.map(function (board) {
- if (board.id === id) {
- return el.push(board)
- }
- })
- return el[0]
+ return (self.options.boards.data || {})[id];
}