472 lines
16 KiB
JavaScript
472 lines
16 KiB
JavaScript
/**
|
|
* App Kanban
|
|
*/
|
|
|
|
'use strict';
|
|
|
|
(async function () {
|
|
let boards;
|
|
const kanbanSidebar = document.querySelector('.kanban-update-item-sidebar'),
|
|
kanbanWrapper = document.querySelector('.kanban-wrapper'),
|
|
commentEditor = document.querySelector('.comment-editor'),
|
|
kanbanAddNewBoard = document.querySelector('.kanban-add-new-board'),
|
|
kanbanAddNewInput = [].slice.call(document.querySelectorAll('.kanban-add-board-input')),
|
|
kanbanAddBoardBtn = document.querySelector('.kanban-add-board-btn'),
|
|
datePicker = document.querySelector('#due-date'),
|
|
select2 = $('.select2'), // ! Using jquery vars due to select2 jQuery dependency
|
|
assetsPath = document.querySelector('html').getAttribute('data-assets-path');
|
|
|
|
// Init kanban Offcanvas
|
|
const kanbanOffcanvas = new bootstrap.Offcanvas(kanbanSidebar);
|
|
|
|
// Get kanban data
|
|
const kanbanResponse = await fetch(assetsPath + 'json/kanban.json');
|
|
if (!kanbanResponse.ok) {
|
|
console.error('error', kanbanResponse);
|
|
}
|
|
boards = await kanbanResponse.json();
|
|
|
|
// datepicker init
|
|
if (datePicker) {
|
|
datePicker.flatpickr({
|
|
monthSelectorType: 'static',
|
|
altInput: true,
|
|
altFormat: 'j F, Y',
|
|
dateFormat: 'Y-m-d'
|
|
});
|
|
}
|
|
|
|
//! TODO: Update Event label and guest code to JS once select removes jQuery dependency
|
|
// select2
|
|
if (select2.length) {
|
|
function renderLabels(option) {
|
|
if (!option.id) {
|
|
return option.text;
|
|
}
|
|
var $badge = "<div class='badge " + $(option.element).data('color') + " rounded-pill'> " + option.text + '</div>';
|
|
return $badge;
|
|
}
|
|
|
|
select2.each(function () {
|
|
var $this = $(this);
|
|
$this.wrap("<div class='position-relative'></div>").select2({
|
|
placeholder: 'Select Label',
|
|
dropdownParent: $this.parent(),
|
|
templateResult: renderLabels,
|
|
templateSelection: renderLabels,
|
|
escapeMarkup: function (es) {
|
|
return es;
|
|
}
|
|
});
|
|
});
|
|
}
|
|
|
|
// Comment editor
|
|
if (commentEditor) {
|
|
new Quill(commentEditor, {
|
|
modules: {
|
|
toolbar: '.comment-toolbar'
|
|
},
|
|
placeholder: 'Write a Comment... ',
|
|
theme: 'snow'
|
|
});
|
|
}
|
|
|
|
// Render board dropdown
|
|
function renderBoardDropdown() {
|
|
return (
|
|
"<div class='dropdown'>" +
|
|
"<i class='dropdown-toggle ti ti-dots-vertical cursor-pointer' id='board-dropdown' data-bs-toggle='dropdown' aria-haspopup='true' aria-expanded='false'></i>" +
|
|
"<div class='dropdown-menu dropdown-menu-end' aria-labelledby='board-dropdown'>" +
|
|
"<a class='dropdown-item delete-board' href='javascript:void(0)'> <i class='ti ti-trash ti-xs' me-1></i> <span class='align-middle'>Delete</span></a>" +
|
|
"<a class='dropdown-item' href='javascript:void(0)'><i class='ti ti-edit ti-xs' me-1></i> <span class='align-middle'>Rename</span></a>" +
|
|
"<a class='dropdown-item' href='javascript:void(0)'><i class='ti ti-archive ti-xs' me-1></i> <span class='align-middle'>Archive</span></a>" +
|
|
'</div>' +
|
|
'</div>'
|
|
);
|
|
}
|
|
// Render item dropdown
|
|
function renderDropdown() {
|
|
return (
|
|
"<div class='dropdown kanban-tasks-item-dropdown'>" +
|
|
"<i class='dropdown-toggle ti ti-dots-vertical' id='kanban-tasks-item-dropdown' data-bs-toggle='dropdown' aria-haspopup='true' aria-expanded='false'></i>" +
|
|
"<div class='dropdown-menu dropdown-menu-end' aria-labelledby='kanban-tasks-item-dropdown'>" +
|
|
"<a class='dropdown-item' href='javascript:void(0)'>Copy task link</a>" +
|
|
"<a class='dropdown-item' href='javascript:void(0)'>Duplicate task</a>" +
|
|
"<a class='dropdown-item delete-task' href='javascript:void(0)'>Delete</a>" +
|
|
'</div>' +
|
|
'</div>'
|
|
);
|
|
}
|
|
// Render header
|
|
function renderHeader(color, text) {
|
|
return (
|
|
"<div class='d-flex justify-content-between flex-wrap align-items-center mb-2 pb-1'>" +
|
|
"<div class='item-badges'> " +
|
|
"<div class='badge rounded-pill bg-label-" +
|
|
color +
|
|
"'> " +
|
|
text +
|
|
'</div>' +
|
|
'</div>' +
|
|
renderDropdown() +
|
|
'</div>'
|
|
);
|
|
}
|
|
|
|
// Render avatar
|
|
function renderAvatar(images, pullUp, size, margin, members) {
|
|
var $transition = pullUp ? ' pull-up' : '',
|
|
$size = size ? 'avatar-' + size + '' : '',
|
|
member = members == undefined ? ' ' : members.split(',');
|
|
|
|
return images == undefined
|
|
? ' '
|
|
: images
|
|
.split(',')
|
|
.map(function (img, index, arr) {
|
|
var $margin = margin && index !== arr.length - 1 ? ' me-' + margin + '' : '';
|
|
|
|
return (
|
|
"<div class='avatar " +
|
|
$size +
|
|
$margin +
|
|
"'" +
|
|
"data-bs-toggle='tooltip' data-bs-placement='top'" +
|
|
"title='" +
|
|
member[index] +
|
|
"'" +
|
|
'>' +
|
|
"<img src='" +
|
|
assetsPath +
|
|
'img/avatars/' +
|
|
img +
|
|
"' alt='Avatar' class='rounded-circle " +
|
|
$transition +
|
|
"'>" +
|
|
'</div>'
|
|
);
|
|
})
|
|
.join(' ');
|
|
}
|
|
|
|
// Render footer
|
|
function renderFooter(attachments, comments, assigned, members) {
|
|
return (
|
|
"<div class='d-flex justify-content-between align-items-center flex-wrap mt-2 pt-1'>" +
|
|
"<div class='d-flex'> <span class='d-flex align-items-center me-2'><i class='ti ti-paperclip ti-xs me-1'></i>" +
|
|
"<span class='attachments'>" +
|
|
attachments +
|
|
'</span>' +
|
|
"</span> <span class='d-flex align-items-center ms-1'><i class='ti ti-message-dots ti-xs me-1'></i>" +
|
|
'<span> ' +
|
|
comments +
|
|
' </span>' +
|
|
'</span></div>' +
|
|
"<div class='avatar-group d-flex align-items-center assigned-avatar'>" +
|
|
renderAvatar(assigned, true, 'xs', null, members) +
|
|
'</div>' +
|
|
'</div>'
|
|
);
|
|
}
|
|
// Init kanban
|
|
const kanban = new jKanban({
|
|
element: '.kanban-wrapper',
|
|
gutter: '15px',
|
|
widthBoard: '250px',
|
|
dragItems: true,
|
|
boards: boards,
|
|
dragBoards: true,
|
|
addItemButton: true,
|
|
buttonContent: '+ Add Item',
|
|
itemAddOptions: {
|
|
enabled: true, // add a button to board for easy item creation
|
|
content: '+ Add New Item', // text or html content of the board button
|
|
class: 'kanban-title-button btn', // default class of the button
|
|
footer: false // position the button on footer
|
|
},
|
|
click: function (el) {
|
|
let element = el;
|
|
let title = element.getAttribute('data-eid')
|
|
? element.querySelector('.kanban-text').textContent
|
|
: element.textContent,
|
|
date = element.getAttribute('data-due-date'),
|
|
dateObj = new Date(),
|
|
year = dateObj.getFullYear(),
|
|
dateToUse = date
|
|
? date + ', ' + year
|
|
: dateObj.getDate() + ' ' + dateObj.toLocaleString('en', { month: 'long' }) + ', ' + year,
|
|
label = element.getAttribute('data-badge-text'),
|
|
avatars = element.getAttribute('data-assigned');
|
|
|
|
// Show kanban offcanvas
|
|
kanbanOffcanvas.show();
|
|
|
|
// To get data on sidebar
|
|
kanbanSidebar.querySelector('#title').value = title;
|
|
kanbanSidebar.querySelector('#due-date').nextSibling.value = dateToUse;
|
|
|
|
// ! Using jQuery method to get sidebar due to select2 dependency
|
|
$('.kanban-update-item-sidebar').find(select2).val(label).trigger('change');
|
|
|
|
// Remove & Update assigned
|
|
kanbanSidebar.querySelector('.assigned').innerHTML = '';
|
|
kanbanSidebar
|
|
.querySelector('.assigned')
|
|
.insertAdjacentHTML(
|
|
'afterbegin',
|
|
renderAvatar(avatars, false, 'xs', '1', el.getAttribute('data-members')) +
|
|
"<div class='avatar avatar-xs ms-1'>" +
|
|
"<span class='avatar-initial rounded-circle bg-label-secondary'><i class='ti ti-plus ti-xs text-heading'></i></span>" +
|
|
'</div>'
|
|
);
|
|
},
|
|
|
|
buttonClick: function (el, boardId) {
|
|
const addNew = document.createElement('form');
|
|
addNew.setAttribute('class', 'new-item-form');
|
|
addNew.innerHTML =
|
|
'<div class="mb-3">' +
|
|
'<textarea class="form-control add-new-item" rows="2" placeholder="Add Content" autofocus required></textarea>' +
|
|
'</div>' +
|
|
'<div class="mb-3">' +
|
|
'<button type="submit" class="btn btn-primary btn-sm me-2">Add</button>' +
|
|
'<button type="button" class="btn btn-label-secondary btn-sm cancel-add-item">Cancel</button>' +
|
|
'</div>';
|
|
kanban.addForm(boardId, addNew);
|
|
|
|
addNew.addEventListener('submit', function (e) {
|
|
e.preventDefault();
|
|
const currentBoard = [].slice.call(
|
|
document.querySelectorAll('.kanban-board[data-id=' + boardId + '] .kanban-item')
|
|
);
|
|
kanban.addElement(boardId, {
|
|
title: "<span class='kanban-text'>" + e.target[0].value + '</span>',
|
|
id: boardId + '-' + currentBoard.length + 1
|
|
});
|
|
|
|
// add dropdown in new boards
|
|
const kanbanText = [].slice.call(
|
|
document.querySelectorAll('.kanban-board[data-id=' + boardId + '] .kanban-text')
|
|
);
|
|
kanbanText.forEach(function (e) {
|
|
e.insertAdjacentHTML('beforebegin', renderDropdown());
|
|
});
|
|
|
|
// prevent sidebar to open onclick dropdown buttons of new tasks
|
|
const newTaskDropdown = [].slice.call(document.querySelectorAll('.kanban-item .kanban-tasks-item-dropdown'));
|
|
if (newTaskDropdown) {
|
|
newTaskDropdown.forEach(function (e) {
|
|
e.addEventListener('click', function (el) {
|
|
el.stopPropagation();
|
|
});
|
|
});
|
|
}
|
|
|
|
// delete tasks for new boards
|
|
const deleteTask = [].slice.call(
|
|
document.querySelectorAll('.kanban-board[data-id=' + boardId + '] .delete-task')
|
|
);
|
|
deleteTask.forEach(function (e) {
|
|
e.addEventListener('click', function () {
|
|
const id = this.closest('.kanban-item').getAttribute('data-eid');
|
|
kanban.removeElement(id);
|
|
});
|
|
});
|
|
addNew.remove();
|
|
});
|
|
|
|
// Remove form on clicking cancel button
|
|
addNew.querySelector('.cancel-add-item').addEventListener('click', function (e) {
|
|
addNew.remove();
|
|
});
|
|
}
|
|
});
|
|
|
|
// Kanban Wrapper scrollbar
|
|
if (kanbanWrapper) {
|
|
new PerfectScrollbar(kanbanWrapper);
|
|
}
|
|
|
|
const kanbanContainer = document.querySelector('.kanban-container'),
|
|
kanbanTitleBoard = [].slice.call(document.querySelectorAll('.kanban-title-board')),
|
|
kanbanItem = [].slice.call(document.querySelectorAll('.kanban-item'));
|
|
|
|
// Render custom items
|
|
if (kanbanItem) {
|
|
kanbanItem.forEach(function (el) {
|
|
const element = "<span class='kanban-text'>" + el.textContent + '</span>';
|
|
let img = '';
|
|
if (el.getAttribute('data-image') !== null) {
|
|
img =
|
|
"<img class='img-fluid rounded mb-2' src='" +
|
|
assetsPath +
|
|
'img/elements/' +
|
|
el.getAttribute('data-image') +
|
|
"'>";
|
|
}
|
|
el.textContent = '';
|
|
if (el.getAttribute('data-badge') !== undefined && el.getAttribute('data-badge-text') !== undefined) {
|
|
el.insertAdjacentHTML(
|
|
'afterbegin',
|
|
renderHeader(el.getAttribute('data-badge'), el.getAttribute('data-badge-text')) + img + element
|
|
);
|
|
}
|
|
if (
|
|
el.getAttribute('data-comments') !== undefined ||
|
|
el.getAttribute('data-due-date') !== undefined ||
|
|
el.getAttribute('data-assigned') !== undefined
|
|
) {
|
|
el.insertAdjacentHTML(
|
|
'beforeend',
|
|
renderFooter(
|
|
el.getAttribute('data-attachments'),
|
|
el.getAttribute('data-comments'),
|
|
el.getAttribute('data-assigned'),
|
|
el.getAttribute('data-members')
|
|
)
|
|
);
|
|
}
|
|
});
|
|
}
|
|
|
|
// To initialize tooltips for rendered items
|
|
const tooltipTriggerList = [].slice.call(document.querySelectorAll('[data-bs-toggle="tooltip"]'));
|
|
tooltipTriggerList.map(function (tooltipTriggerEl) {
|
|
return new bootstrap.Tooltip(tooltipTriggerEl);
|
|
});
|
|
|
|
// prevent sidebar to open onclick dropdown buttons of tasks
|
|
const tasksItemDropdown = [].slice.call(document.querySelectorAll('.kanban-tasks-item-dropdown'));
|
|
if (tasksItemDropdown) {
|
|
tasksItemDropdown.forEach(function (e) {
|
|
e.addEventListener('click', function (el) {
|
|
el.stopPropagation();
|
|
});
|
|
});
|
|
}
|
|
|
|
// Toggle add new input and actions add-new-btn
|
|
if (kanbanAddBoardBtn) {
|
|
kanbanAddBoardBtn.addEventListener('click', () => {
|
|
kanbanAddNewInput.forEach(el => {
|
|
el.value = '';
|
|
el.classList.toggle('d-none');
|
|
});
|
|
});
|
|
}
|
|
|
|
// Render add new inline with boards
|
|
if (kanbanContainer) {
|
|
kanbanContainer.appendChild(kanbanAddNewBoard);
|
|
}
|
|
|
|
// Makes kanban title editable for rendered boards
|
|
if (kanbanTitleBoard) {
|
|
kanbanTitleBoard.forEach(function (elem) {
|
|
elem.addEventListener('mouseenter', function () {
|
|
this.contentEditable = 'true';
|
|
});
|
|
|
|
// Appends delete icon with title
|
|
elem.insertAdjacentHTML('afterend', renderBoardDropdown());
|
|
});
|
|
}
|
|
|
|
// To delete Board for rendered boards
|
|
const deleteBoards = [].slice.call(document.querySelectorAll('.delete-board'));
|
|
if (deleteBoards) {
|
|
deleteBoards.forEach(function (elem) {
|
|
elem.addEventListener('click', function () {
|
|
const id = this.closest('.kanban-board').getAttribute('data-id');
|
|
kanban.removeBoard(id);
|
|
});
|
|
});
|
|
}
|
|
|
|
// Delete task for rendered boards
|
|
const deleteTask = [].slice.call(document.querySelectorAll('.delete-task'));
|
|
if (deleteTask) {
|
|
deleteTask.forEach(function (e) {
|
|
e.addEventListener('click', function () {
|
|
const id = this.closest('.kanban-item').getAttribute('data-eid');
|
|
kanban.removeElement(id);
|
|
});
|
|
});
|
|
}
|
|
|
|
// Cancel btn add new input
|
|
const cancelAddNew = document.querySelector('.kanban-add-board-cancel-btn');
|
|
if (cancelAddNew) {
|
|
cancelAddNew.addEventListener('click', function () {
|
|
kanbanAddNewInput.forEach(el => {
|
|
el.classList.toggle('d-none');
|
|
});
|
|
});
|
|
}
|
|
|
|
// Add new board
|
|
if (kanbanAddNewBoard) {
|
|
kanbanAddNewBoard.addEventListener('submit', function (e) {
|
|
e.preventDefault();
|
|
const thisEle = this,
|
|
value = thisEle.querySelector('.form-control').value,
|
|
id = value.replace(/\s+/g, '-').toLowerCase();
|
|
kanban.addBoards([
|
|
{
|
|
id: id,
|
|
title: value
|
|
}
|
|
]);
|
|
|
|
// Adds delete board option to new board, delete new boards & updates data-order
|
|
const kanbanBoardLastChild = document.querySelectorAll('.kanban-board:last-child')[0];
|
|
if (kanbanBoardLastChild) {
|
|
const header = kanbanBoardLastChild.querySelector('.kanban-title-board');
|
|
header.insertAdjacentHTML('afterend', renderBoardDropdown());
|
|
|
|
// To make newly added boards title editable
|
|
kanbanBoardLastChild.querySelector('.kanban-title-board').addEventListener('mouseenter', function () {
|
|
this.contentEditable = 'true';
|
|
});
|
|
}
|
|
|
|
// Add delete event to delete newly added boards
|
|
const deleteNewBoards = kanbanBoardLastChild.querySelector('.delete-board');
|
|
if (deleteNewBoards) {
|
|
deleteNewBoards.addEventListener('click', function () {
|
|
const id = this.closest('.kanban-board').getAttribute('data-id');
|
|
kanban.removeBoard(id);
|
|
});
|
|
}
|
|
|
|
// Remove current append new add new form
|
|
if (kanbanAddNewInput) {
|
|
kanbanAddNewInput.forEach(el => {
|
|
el.classList.add('d-none');
|
|
});
|
|
}
|
|
|
|
// To place inline add new btn after clicking add btn
|
|
if (kanbanContainer) {
|
|
kanbanContainer.appendChild(kanbanAddNewBoard);
|
|
}
|
|
});
|
|
}
|
|
|
|
// Clear comment editor on close
|
|
kanbanSidebar.addEventListener('hidden.bs.offcanvas', function () {
|
|
kanbanSidebar.querySelector('.ql-editor').firstElementChild.innerHTML = '';
|
|
});
|
|
|
|
// Re-init tooltip when offcanvas opens(Bootstrap bug)
|
|
if (kanbanSidebar) {
|
|
kanbanSidebar.addEventListener('shown.bs.offcanvas', function () {
|
|
const tooltipTriggerList = [].slice.call(kanbanSidebar.querySelectorAll('[data-bs-toggle="tooltip"]'));
|
|
tooltipTriggerList.map(function (tooltipTriggerEl) {
|
|
return new bootstrap.Tooltip(tooltipTriggerEl);
|
|
});
|
|
});
|
|
}
|
|
})();
|