/** * Load more. * * @param string $type * @param int $regionID * @access public * @return void */ function loadMore(type, regionID) { var method = 'viewArchived' + type; var selector = '#archived' + type + 's'; var link = createLink('kanban', method, 'regionID=' + regionID); $(selector).load(link, function() { var windowHeight = $(window).height(); var affixedHeight = $('#regionTabs.affixed').height() + $('#kanbanContainer .kanban-affixed .kanban-cols').height(); $(selector + ' .panel-body').css('height', windowHeight - affixedHeight); $(selector).css('top', affixedHeight); $(selector).animate({right: 0}, 500); }); } /** * Hide kanban actions. * * @access public * @return void */ function hideAllAction() { $('.actions').hide(); $('.action').hide(); $('.kanban-group-header').hide(); $(".title").attr("disabled", true).css("pointer-events", "none"); $('.kanban-col.kanban-header-col').css('padding', '0px 0px 0px 0px'); window.sortableDisabled = true; } /** * Display the kanban in full screen. * * @access public * @return void */ function fullScreen() { var element = document.getElementById('kanbanContainer'); var requestMethod = element.requestFullScreen || element.webkitRequestFullScreen || element.mozRequestFullScreen || element.msRequestFullscreen; if(requestMethod) { var afterEnterFullscreen = function() { $('#kanbanContainer').addClass('fullscreen') .on('scroll', tryUpdateKanbanAffix); hideAllAction(); $.cookie('isFullScreen', 1); }; var whenFailEnterFullscreen = function(error) { exitFullScreen(); }; try { var result = requestMethod.call(element); if(result && (typeof result.then === 'function' || result instanceof window.Promise)) { result.then(afterEnterFullscreen).catch(whenFailEnterFullscreen); } else { afterEnterFullscreen(); } } catch (error) { whenFailEnterFullscreen(error); } } } /** * Exit full screen. * * @access public * @return void */ function exitFullScreen() { $('.region-actions > div > .action').show(); $(".title").attr("disabled", false).css("pointer-events", "auto"); if(!CRKanban && kanban.status == 'closed') return; $('#kanbanContainer').removeClass('fullscreen') .off('scroll', tryUpdateKanbanAffix); $('.actions').show(); $('.action').show(); $('.kanban-group-header').show(); $('.kanban-col.kanban-header-col').css('padding', '0px 30px'); window.sortableDisabled = false; $.cookie('isFullScreen', 0); } document.addEventListener('fullscreenchange', function (e) { if(!document.fullscreenElement) exitFullScreen(); }); document.addEventListener('webkitfullscreenchange', function (e) { if(!document.webkitFullscreenElement) exitFullScreen(); }); document.addEventListener('mozfullscreenchange', function (e) { if(!document.mozFullScreenElement) exitFullScreen(); }); document.addEventListener('msfullscreenChange', function (e) { if(!document.msfullscreenElement) exitFullScreen(); }); /** * Render header of a column. */ function renderHeaderCol($column, column, $header, kanbanData) { /* Render group header. */ var privs = kanbanData.actions; var columnPrivs = $column.data().col.actions; if(privs.includes('sortGroup')) { var groups = regions[column.region].groups; if($header.closest('.kanban').data('zui.kanban')) { groups = $header.closest('.kanban').data('zui.kanban').data; } if(groups.length > 1) { $column.closest('.kanban-board').addClass('sort'); $column.closest('.kanban-header').find('.kanban-group-header').remove(); $column.closest('.kanban-header').prepend('
'); } } var laneID = column.$kanbanData.lanes[0].id ? column.$kanbanData.lanes[0].id : 0; var printMoreBtn = (columnPrivs.includes('setColumn') || columnPrivs.includes('setWIP') || columnPrivs.includes('createColumn') || columnPrivs.includes('copyColumn') || columnPrivs.includes('archiveColumn') || columnPrivs.includes('deleteColumn') || columnPrivs.includes('splitColumn')); /* Render more menu. */ if(columnPrivs.includes('createCard') || printMoreBtn) { var addItemBtn = ''; var moreAction = ''; if(!$column.children('.actions').length) $column.append('
'); var $actions = $column.children('.actions'); if(column.parent != -1) { addItemBtn = [''].join(''); } var moreAction = ' '; if(CRKanban || kanban.status != 'closed') $actions.html(addItemBtn + moreAction); } if(columnPrivs.includes('sortColumn')) { if($column.hasClass('kanban-header-parent-col')) { $column.children('.kanban-header-col').addClass('sort'); } else { $column.addClass('sort'); } } if(alignment == 'left') { if($column.hasClass('kanban-header-parent-col')) { $column.children('.kanban-header-col').addClass('left'); } else { $column.addClass('left'); } } } /** * Render items count of a column. */ function renderCount($count, count, column) { /* Render WIP. */ var limit = !column.limit || column.limit == '-1' ? '' : column.limit; if($count.parent().find('.limit').length) { $count.parent().find('.limit').html(limit); } else { $count.parent().find('.count').before("("); $count.parent().find('.count').after("/" + limit + ")"); } if(column.limit != -1 && column.limit < count) { $count.parents('.title').parent('.kanban-header-col').css('background-color', 'transparent'); $count.parents('.title').find('.text').css('max-width', $count.parents('.title').width() - 200); $count.css({'color': '#FF5D5D', 'opacity': '1'}); if(!$count.parent().find('.error').length) $count.parent().find('.include-last').after(""); $count.parents('.title').find('.text-grey').css({'color': '#FF5D5D', 'opacity': '1'}); } else { $count.parents('.title').parent('.kanban-header-col').css('background-color', 'transparent'); $count.parents('.title').find('.text').css('max-width', $count.parents('.title').width() - 120); $count.css({'color': '#8b91a2', 'opacity': '0.5'}); $count.parent().find('.error').remove(); $count.parents('.title').find('.text-grey').css({'color': '#8b91a2', 'opacity': '0.5'}); } } /** * Render lane name. * * @param object $lane * @param object lane * @param object $kanban * @param object columns * @param object kanban * @access public * @return void */ function renderLaneName($lane, lane, $kanban, columns, kanban) { var canEditLaneColor = lane.actions.includes('editLaneColor'); var canEditLaneName = lane.actions.includes('editLaneName'); var canSort = lane.actions.includes('sortLane') && kanban.lanes.length > 1; var canDelete = lane.actions.includes('deleteLane'); $lane.parent().toggleClass('sort', canSort); if(!$lane.children('.actions').length && (canEditLaneColor || canEditLaneName || canDelete) && (CRKanban || kanbanInfo.status != 'closed')) { $([ '
', '', '', '', '
' ].join('')).appendTo($lane); } if($.cookie('isFullScreen') == 1) hideAllAction(); } /** * Render avatars of user. * @param {String|{account: string, avatar: string}} user User account or user object * @returns {string} */ function renderUsersAvatar(users, itemID, size) { var avatarSizeClass = 'avatar-' + (size || 'md'); if(users.length == 0 || (users.length == 1 && users[0] == '')) { return $('
'); } var assignees = []; for(var user of users) { var $noPrivAndNoAssigned = $('
'); if(!priv.canAssignCard && !user) { assignees.push($noPrivAndNoAssigned); continue; } if(!user) { assignees.push($('
')); continue; } if(typeof user === 'string') user = {account: user}; if(!user.avatar && window.userList && window.userList[user.account]) user = window.userList[user.account]; if(!user.name && window.users && window.users[user.account]) user.name = window.users[user.account]; assignees.push($('
').avatar({user: user})); } if(assignees.length > 3) assignees.splice(3, assignees.length - 3, '...'); return assignees; } /** * The function for rendering kanban item */ function renderKanbanItem(item, $item) { var whiteStyle = null; if(item.color == '#2a5f29' || item.color == '#b10b0b' || item.color == '#cfa227') whiteStyle = 'style="color:#FFFFFF"'; var privs = item.actions; var printMoreBtn = (privs.includes('editCard') || privs.includes('archiveCard') || privs.includes('copyCard') || privs.includes('deleteCard') || privs.includes('moveCard') || privs.includes('setCardColor')); var $actions = $item.children('.actions'); var $title = $item.children('.title'); if(printMoreBtn && !$actions.length && (CRKanban || kanban.status != 'closed')) { $( [ '
', '', '', '', '
' ].join('')).appendTo($item); } if(item.fromType == 'execution') { renderExecutionItem(item, $item); } else if(item.fromType == 'release') { renderReleaseItem(item, $item); } else if(item.fromType == 'build') { renderBuildItem(item, $item); } else if(item.fromType == 'productplan') { renderProductplanItem(item, $item); } else if(item.fromType == 'ticket') { renderTicketItem(item, $item); } else { if(!$title.length) { if(privs.includes('viewCard')) $title = $('').appendTo($item).attr('href', createLink('kanban', 'viewCard', 'cardID=' + item.id, '', true)); if(!privs.includes('viewCard')) $title = $('

').appendTo($item); } $title.text(item.name).attr('title', item.name); var $info = $item.children('.info'); if(!$info.length) $info = $( [ '
', '', '', '', '
', '
' ].join('')).appendTo($item); $item.data('card', item); $info.children('.estimate').text(item.estimate + kanbancardLang.lblHour); if(item.estimate == 0) $info.children('.estimate').hide(); $info.children('.pri') .attr('class', 'pri' + (item.pri ? ' label-pri label-pri-' + item.pri : '')) .text(item.pri); var $time = $info.children('.time'); if(item.end == '0000-00-00' && item.begin == '0000-00-00') { $time.hide(); } else { var today = new Date(); var begin = $.zui.createDate(item.begin); var end = $.zui.createDate(item.end); var needRemind = (begin.toLocaleDateString() == today.toLocaleDateString() || end.toLocaleDateString() == today.toLocaleDateString()); if(item.end == '0000-00-00' && item.begin != '0000-00-00') { $time.text($.zui.formatDate(begin, 'MM/dd') + ' ' + kanbancardLang.beginAB).attr('title', $.zui.formatDate(begin, 'yyyy/MM/dd') + ' ' +kanbancardLang.beginAB).show(); } else if(item.begin == '0000-00-00' && item.end != '0000-00-00') { $time.text($.zui.formatDate(end, 'MM/dd') + ' ' + kanbancardLang.deadlineAB).attr('title', $.zui.formatDate(end, 'yyyy/MM/dd') + ' ' + kanbancardLang.deadlineAB).show(); } else if(item.begin != '0000-00-00' && item.end != '0000-00-00') { $time.text($.zui.formatDate(begin, 'MM/dd') + ' ' + kanbancardLang.to + ' ' + $.zui.formatDate(end, 'MM/dd')).attr('title', $.zui.formatDate(begin, 'yyyy/MM/dd') + kanbancardLang.to + $.zui.formatDate(end, 'yyyy/MM/dd')).show(); } if(!$item.hasClass('has-color') && needRemind) $time.css('background-color', 'rgba(210, 50, 61, 0.3)'); if($item.hasClass('has-color') && needRemind) $time.css('background-color', 'rgba(255, 255, 255, 0.3)'); if(!needRemind) $time.css('background-color', 'rgba(0, 0, 0, 0.15)'); } /* Display avatars of assignedTo. */ var assignedTo = item.assignedTo.split(','); var $user = $info.children('.user'); var title = []; for(i = 0; i < assignedTo.length; i++) title.push(users[assignedTo[i]]); $user.html(renderUsersAvatar(assignedTo, item.id)).attr('title', title); } if(kanban.performable == 1 && (item.fromType == '' || item.fromType == 'execution')) { var $progress = $item.children('.progress-box'); if(!$progress.length) $progress = $('
' + item.progress + '%
').appendTo($item); $progress.find('.progress-bar').css('width', item.progress + '%'); $progress.find('.progress-number').html(item.progress + '%'); } $item.css('background-color', item.color); $item.toggleClass('has-color', item.color != '#fff' && item.color != ''); $item.find('.info > .label-light').css('background-color', item.color); $title = item.fromType == 'execution' ? $item.children('.header').children('.executionName').children('.title') : $item.children('.title'); if(!$title.children('.label-finish').length) $title.prepend('
' + kanbanLang.finished + '
'); var name = item.title ? item.title : item.name; if(kanban.performable == 1 && item.status == 'done' ) { var finishColor = '#2a5f29'; if(item.color == '#2a5f29') finishColor = '#FFFFFF'; $title.css('color', finishColor); $title.children('.label-finish').show(); if(item.color == '#2a5f29') { $item.children('.label-finish').css({'background-color':'#FFFFFF','color':'#2a5f29'}); } else { $item.children('.label-finish').css({'background-color':'','color':'#FFFFFF'}); } } else { $title.css('color', ''); $title.children('.label-finish').hide(); } if($.cookie('isFullScreen') == 1) hideAllAction(); } /** * Render execution item. * * @param object item * @param object $item * @access public * @return void */ function renderExecutionItem(item, $item) { /* Output header information. */ var privs = item.actions; if(privs.includes('sortCard')) $item.parent().addClass('sort'); var $header = $item.children('.header'); if(!$header.length) $header = $( [ '
', '
' ].join('')).appendTo($item); var $titleBox = $header.children('.executionName'); if(!$titleBox.length) $titleBox = $( [ '
', '
' ].join('')).appendTo($header); /* Print execution name. */ var $title = $titleBox.children('.title'); var name = item.name ? item.name : item.title; var title = item.title ? item.title : name; if(!$title.length) { var icon = mode == 'ALM' ? 'run' : 'project'; var viewMethod = item.execType == 'kanban' ? 'kanban' : 'view'; if(privs.includes('viewExecution') && item.deleted == '0' && item.children == '0') $title = $('' + name + '').appendTo($titleBox).attr('href', createLink('execution', viewMethod, 'executionID=' + item.fromID)); if(!privs.includes('viewExecution') || item.deleted == '1' || item.children != '0') $title = $('
' + name + '
').appendTo($titleBox); } if(!$title.children('i').length) { $title.append('' + item.title); } $title.attr('title', title); if(item.delay) { $delayed = $titleBox.children('.delayed'); if(!$delayed.length) { $('' + executionLang.delayed + '').appendTo($titleBox); } } $item.data('card', item); var $info = $item.children('.execInfo'); if(!$info.length) $info = $( [ '
', '
' ].join('')).appendTo($item); var $statusBox = $info.children('.execStatus'); if(!$statusBox.length) { if(item.deleted == '0') { $statusBox = $('' + executionLang.statusList[item.objectStatus] + '').appendTo($info); } else { $statusBox = $('' + executionLang.deleted + '').appendTo($info); } } /* Display deadline of execution. */ var $date = $info.children('.date'); var end = $.zui.createDate(item.end); var today = new Date(); var labelType = end.toLocaleDateString() == today.toLocaleDateString() ? 'danger' : 'wait'; if(!$date.length) $date = $('').appendTo($info); $date.text($.zui.formatDate(end, 'MM-dd') + ' ' + kanbancardLang.deadlineAB).attr('title', $.zui.formatDate(end, 'yyyy-MM-dd') + ' ' + kanbancardLang.deadlineAB).show(); /* Display avatars of PM. */ var $user = $info.children('.user'); var user = [item.PM]; if(users[item.PM]) { if(!$user.length) $user = $('
').appendTo($info); $user.html(renderUsersAvatar(user, item.id)).attr('title', users[item.PM]); } } /** * Render plan item. * * @param object item * @param object $item * @access public * @return void */ function renderReleaseItem(item, $item) { var privs = item.actions; if(privs.includes('sortCard')) $item.parent().addClass('sort'); /* Print name. */ var $title = $item.children('.releaseTitle'); var name = item.title ? item.title : item.name; if(!$title.length) { if(privs.includes('viewRelease') && item.deleted == '0') $title = $('' + name + '').appendTo($item).attr('href', createLink('release', 'view', 'releaseID=' + item.fromID)); if(!privs.includes('viewRelease') || item.deleted == '1') $title = $('
' + name + '
').appendTo($item); } if(!$title.children('i').length) { $title.prepend(''); } $title.attr('title', name); var $info = $item.children('.releaseInfo'); if(!$info.length) $info = $( [ '
', '
' ].join('')).appendTo($item); var $statusBox = $info.children('.releaseStatus'); if(!$statusBox.length) { if(item.deleted == '0') { $statusBox = $('' + releaseLang.statusList[item.objectStatus] + '').appendTo($info); } else { $statusBox = $('' + releaseLang.deleted + '').appendTo($info); } } /* Display release date. */ var $date = $info.children('.date'); var releaseDate = $.zui.createDate(item.date); if(!$date.length) $date = $('').appendTo($info); $date.text($.zui.formatDate(releaseDate, 'yyyy-MM-dd')).attr('title', $.zui.formatDate(releaseDate, 'yyyy-MM-dd')).show(); /* Display avatars of creator. */ var $user = $info.children('.user'); var user = [item.createdBy]; if(users[item.createdBy]) { if(!$user.length) $user = $('
').appendTo($info); $user.html(renderUsersAvatar(user, item.id)).attr('title', users[item.createdBy]); } } /** * Render product plan item. * * @param object item * @param object $item * @access public * @return void */ function renderProductplanItem(item, $item) { var privs = item.actions; if(privs.includes('sortCard')) $item.parent().addClass('sort'); /* Print name. */ var $title = $item.children('.productplanTitle'); var name = item.title ? item.title : item.name; if(!$title.length) { if(privs.includes('viewPlan') && item.deleted == '0') $title = $('' + name + '').appendTo($item).attr('href', createLink('productplan', 'view', 'productplanID=' + item.fromID)); if(!privs.includes('viewPlan') || item.deleted == '1') $title = $('
' + name + '
').appendTo($item); } if(!$title.children('i').length) { $title.append('' + name); } $title.attr('title', item.title); var $info = $item.children('.productplanInfo'); if(!$info.length) $info = $( [ '
', '
' ].join('')).appendTo($item); var $descBox = $info.children('.productplanDesc'); if(!$descBox.length) { $descBox = $('
' + item.desc + '
').appendTo($info); } var $statusBox = $info.children('.productplanStatus'); if(!$statusBox.length) { if(item.deleted == '0') { $statusBox = $('' + productplanLang.statusList[item.objectStatus] + '').appendTo($info); } else { $statusBox = $('' + productplanLang.deleted + '').appendTo($info); } } /* Display date of product plan. */ var $date = $info.children('.date'); var begin = $.zui.createDate(item.begin); var end = $.zui.createDate(item.end); var today = new Date(); var labelType = (item.begin <= $.zui.formatDate(today, 'yyyy-MM-dd') && item.end >= $.zui.formatDate(today, 'yyyy-MM-dd')) ? 'danger' : 'wait'; var labelTitle = $.zui.formatDate(begin, 'MM-dd') + ' ' + productplanLang.to + ' ' + $.zui.formatDate(end, 'MM-dd'); if((item.begin == '2030-01-01' || item.end == '2030-01-01')) { labelType = 'future'; labelTitle = productplanLang.future; } if(!$date.length) $date = $('').appendTo($info); $date.text(labelTitle).attr('title', labelTitle).show(); /* Display avatars of creator. */ var $user = $info.children('.user'); var user = [item.createdBy]; if(users[item.createdBy]) { if(!$user.length) $user = $('
').appendTo($info); $user.html(renderUsersAvatar(user, item.id)).attr('title', users[item.createdBy]); } } /** * Render build item. * * @param object item * @param object $item * @access public * @return void */ function renderBuildItem(item, $item) { var privs = item.actions; if(privs.includes('sortCard')) $item.parent().addClass('sort'); /* Print name. */ var $title = $item.children('.buildTitle'); var name = item.title ? item.title : item.name; if(!$title.length) { if(privs.includes('viewBuild') && item.deleted == '0') $title = $('' + name + '').appendTo($item).attr('href', createLink('build', 'view', 'buildID=' + item.fromID)); if(!privs.includes('viewBuild') || item.deleted == '1') $title = $('
' + name + '
').appendTo($item); } if(!$title.children('i').length) { $title.append('' + item.title); } $title.attr('title', name); var $descBox = $item.children('.buildDesc'); if(!$descBox.length) { $descBox = $('
' + item.desc + '
').appendTo($item); } var $info = $item.children('.info'); if(!$info.length) $info = $( [ '
', '
' ].join('')).appendTo($item); var $statusBox = $info.children('.buildStatus'); if(!$statusBox.length && item.deleted == '1') { $statusBox = $('' + productplanLang.deleted + '').appendTo($info); } /* Display build date. */ var $date = $info.children('.date'); var buildDate = $.zui.createDate(item.date); if(!$date.length) $date = $('').appendTo($info); $date.text($.zui.formatDate(buildDate, 'yyyy-MM-dd')).attr('title', $.zui.formatDate(buildDate, 'yyyy-MM-dd')).show(); /* Display avatars of creator. */ var $user = $info.children('.user'); var user = [item.builder]; if(users[item.builder]) { if(!$user.length) $user = $('
').appendTo($info); $user.html(renderUsersAvatar(user, item.id)).attr('title', users[item.builder]); } } /** * Render execution item. * * @param object item * @param object $item * @access public * @return void */ function renderTicketItem(item, $item) { /* Output header information. */ var privs = item.actions; if(privs.includes('sortCard')) $item.parent().addClass('sort'); var $header = $item.children('.header'); if(!$header.length) $header = $( [ '
', '
' ].join('')).appendTo($item); var $titleBox = $header.children('.ticketTitle'); if(!$titleBox.length) $titleBox = $( [ '
', '
' ].join('')).appendTo($header); /* Print ticket name. */ var $title = $titleBox.children('.title'); var name = item.title ? item.title : item.name; if(!$title.length) { var icon = 'ticket'; if(privs.includes('viewTicket') && item.deleted == '0') $title = $('' + name + '').appendTo($titleBox).attr('href', createLink('ticket', 'view', 'ticketID=' + item.fromID)); if(!privs.includes('viewTicket') || item.deleted == '1') $title = $('
' + name + '
').appendTo($titleBox); } $title.attr('title', name); $item.data('card', item); var $info = $item.children('.info'); if(!$info.length) $info = $( [ '
', '
' ].join('')).appendTo($item); var $statusBox = $info.children('.execStatus'); if(!$statusBox.length) { if(item.deleted == '0') { $statusBox = $('' + ticketLang.statusList[item.objectStatus] + '').appendTo($info); } else { $statusBox = $('' + ticketLang.deleted + '').appendTo($info); } } /* Display deadline of execution. */ if(item.deadline != '0000-00-00') { var $date = $info.children('.date'); var deadline = $.zui.createDate(item.deadline); var today = new Date(); var labelType = deadline.toLocaleDateString() == today.toLocaleDateString() ? 'danger' : 'wait'; if(!$date.length) $date = $('').appendTo($info); $date.text($.zui.formatDate(deadline, 'MM-dd') + ' ' + kanbancardLang.deadlineAB).attr('title', $.zui.formatDate(deadline, 'yyyy-MM-dd') + ' ' + kanbancardLang.deadlineAB).show(); } /* Display avatars of ticket assignedTo. */ var $user = $info.children('.user'); var user = [item.assignedTo]; if(users[item.assignedTo]) { if(!$user.length) $user = $('
').appendTo($info); $user.html(renderUsersAvatar(user, item.id)).attr('title', users[item.assignedTo]); } } /** * Show error message * @param {string|object} message Message */ function showErrorMessager(message) { var html = false; if(message instanceof Error) { message = message.message; } else if(typeof message === 'object') { html = []; $.each(message, function(key, msg) { html.push($.isArray(msg) ? msg.join('') : String(msg)); }); message = html.join('
'); } else { message = String(message); } if(typeof message === 'string' && message.length) { $.zui.messager.danger(message, {html: !!html}); } } /** * Move a card. * * @param int $cardID * @param int $fromColID * @param int $toColID * @param int $fromLaneID * @param int $toLaneID * @param int $kanbanID * @param int $regionID * @access public * @return string */ function moveCard(cardID, fromColID, toColID, fromLaneID, toLaneID, kanbanID, regionID) { if(!cardID) return false; var url = createLink('kanban', 'moveCard', 'cardID=' + cardID + '&fromColID='+ fromColID + '&toColID=' + toColID + '&fromLaneID='+ fromLaneID + '&toLaneID=' + toLaneID + '&kanbanID=' + kanbanID); return $.ajax( { method: 'post', dataType: 'json', url: url, async: false, success: function(data) { regions = data; updateRegion(regionID, data[regionID]); /* Disable related operations in full screen mode. */ if($.cookie('isFullScreen') == 1) { $('.actions').hide(); $('.action').hide(); $('.kanban-group-header').hide(); $(".title").attr("disabled", true).css("pointer-events", "none"); } setToolTip(); }, error: function(xhr, status, error) { showErrorMessager(error || lang.timeout); } }); } /** * Set a card's color. * * @param int $cardID * @param string $color * @param int $kanbanID * @param int $regionID * @access public * @return string */ function setCardColor(cardID, color, kanbanID, regionID) { if(!cardID) return false; color = color.replace('#', ''); var url = createLink('kanban', 'setCardColor', 'cardID=' + cardID + '&color=' + color + '&kanbanID=' + kanbanID); return $.ajax( { method: 'post', dataType: 'json', url: url, success: function(data) { updateRegion(regionID, data[regionID]); }, error: function(xhr, status, error) { showErrorMessager(error || lang.timeout); } }); } /** * Finish a card. * * @param int $cardID * @param int $kanbanID * @param int $regionID * @access public * @return void */ function finishCard(cardID, kanbanID, regionID) { if(!cardID) return false; var url = createLink('kanban', 'finishCard', 'cardID=' + cardID + '&kanbanID=' + kanbanID); return $.ajax( { method: 'post', dataType: 'json', url: url, success: function(data) { updateRegion(regionID, data[regionID]); } }); } /** * Update a region. * * @param int regionID * @param array regionData * @access public * @return boolean */ function updateRegion(regionID, regionData) { if(typeof(regionData) == 'undefined') regionData = []; if(!regionID) return false; var $region = $('#kanban'+ regionID).kanban(); if(!$region.length) return false; regions[regionID] = regionData ? regionData : regions[regionID]; $region.data('zui.kanban').render(regions[regionID].groups); resetRegionHeight('open'); return true; } /** * Update region name. * * @param int $regionID * @param string $name * @access public * @return void */ function updateRegionName(regionID, name) { $('.region[data-id="' + regionID + '"] > .region-header > strong:first').text(name); $('#regionNavTabs li[data-id="' + regionID + '"]').attr('title', name); $('#regionNavTabs li[data-id="' + regionID + '"]').find('a > span').text(name); initRegionTabs(); } /** * Update lane name. * * @param int $laneID * @param string $name * @access public * @return void */ function updateLaneName(laneID, name) { $('.kanban-lane[data-id="' + laneID + '"] > .kanban-lane-name > span').text(name).attr('title', name); } /** * Update lane color. * * @param int $laneID * @param string $color * @access public * @return void */ function updateLaneColor(laneID, color) { $('.kanban-lane[data-id="' + laneID + '"] > .kanban-lane-name').css('background-color', color); } /** * Update column name. * * @param int $columnID * @param string $name * @param string $color * @access public * @return void */ function updateColumnName(columnID, name, color) { $('.kanban-col[data-id="' + columnID + '"] > div.title > span:first').text(name).attr('title', name).css('color', color); } /** * Hide kanban action */ function hideKanbanAction() { $('.kanban').attr('data-action-enabled', null); $('.contextmenu').removeClass('contextmenu-show'); $('.contextmenu .contextmenu-menu').removeClass('open').removeClass('in'); $('#moreTasks, #moreColumns').animate({right: -400}, 500); } /** * Open form for adding task * @param {JQuery} $element Trigger element */ function openAddTaskForm($element) { var regionID = $element.closest('.kanban').data('id'); var groupID = $element.closest('.kanban-board').data('id'); var laneID = $element.closest('.kanban-lane').data('id'); var columnID = $element.closest('.kanban-col').data('id'); var status = $element.closest('.kanban-col').data('type'); var modalUrl = createLink('kanban', 'createCard', 'kanbanID=' + kanbanID + '®ionID=' + regionID + '&groupID=' + groupID + '&columnID=' + columnID); $.zui.modalTrigger.show( { url: modalUrl, width: '1000px' }); hideKanbanAction(); } /** * Reset lane height according to window height. */ function resetLaneHeight() { var maxHeight = '500px'; if(laneCount < 2) { var windowHeight = $(window).height(); var marginTop = $('#main').css('margin-top'); var headerHeight = $('.kanban > .kanban-board:first > .kanban-header').outerHeight(); var actionHeight = $('.kanban > .kanban-board:first > .kanban-lane > .kanban-col:first > .kanban-lane-actions').outerHeight(); maxHeight = windowHeight - parseInt(marginTop) - headerHeight - actionHeight; } $('.kanban-lane-items').css('max-height', maxHeight); } /** * Close modal and update kanban data. */ function closeModalAndUpdateKanban(regionID) { setTimeout(function() { $.zui.closeModal(); updateRegion(regionID); }, 1200); } /** * Status change map */ var statusChangeMap = { wait: ['doing', 'done', 'cancel'], doing: ['done', 'cancel'], done: ['doing', 'closed'], cancel: ['doing', 'closed'], closed: ['doing'] }; /** * Find drop columns * @param {JQuery} $element Drag element * @param {JQuery} $root Dnd root element */ function findDropColumns($element, $root) { var $task = $element; var task = $task.data('task'); //var status = task.status; var $col = $task.closest('.kanban-col'); var col = $col.data(); var lane = $col.closest('.kanban-lane').data('lane'); var allStatusCanChange = statusChangeMap[status]; hideKanbanAction(); return $root.find('.kanban-lane-col:not([data-type="EMPTY"],[data-type=""])').filter(function() { if($.cookie('isFullScreen') == 1 || (!CRKanban && kanbanInfo.status == 'closed')) return false; var $newCol = $(this); var newCol = $newCol.data(); var $newLane = $newCol.closest('.kanban-lane'); var newLane = $newLane.data('lane'); $newCol.addClass('can-drop-here'); return true; }); } /** * Handle drop card * @param {Object} event Drop event object */ function handleDropTask($element, event, kanban) { if(!event.target || !event.isNew) return; var $card = $element; var $oldCol = $card.closest('.kanban-col'); var $newCol = $(event.target).closest('.kanban-col'); var oldCol = $oldCol.data(); var newCol = $newCol.data(); var oldLane = $oldCol.closest('.kanban-lane').data('lane'); var newLane = $newCol.closest('.kanban-lane').data('lane'); var regionID = $card.closest('.region').data('id'); var kanbanID = $card.closest('#kanban').data('id'); if(!oldCol || !newCol || !newLane || !oldLane) return false; if(oldCol.id === newCol.id && newLane.id === oldLane.id) return false; var cardID = $card.data().id; moveCard(cardID, oldCol.id, newCol.id, oldLane.id, newLane.id, kanbanID, regionID); } /** * Handle finish drop task */ function handleFinishDrop() { $('.kanban').find('.can-drop-here').removeClass('can-drop-here'); } /** * Adjust add button postion in column */ function adjustAddBtnPosition($kanban) { if(!$kanban) { $('.kanban').children('.kanban-board').each(function() { adjustAddBtnPosition($(this)); }); return; } $kanban.find('.kanban-lane-col:not([data-type="EMPTY"])').each(function() { var $col = $(this); var items = $col.children('.kanban-lane-items')[0]; $col.toggleClass('has-scrollbar', items.scrollHeight > items.clientHeight); }); } /** * Kanban action handlers */ var kanbanActionHandlers = { addItem: openAddTaskForm, dropItem: handleDropTask }; /** * Handle kanban action */ function handleKanbanAction(action, $element, event, kanban) { $('.kanban').attr('data-action-enabled', action); var handler = kanbanActionHandlers[action]; if(handler) handler($element, event, kanban); } function processMinusBtn() { var $table = $('#splitTable'); var columnCount = $table.find('.child-column').length; if(columnCount > 2 && columnCount < 10) { $table.find('.btn-plus').show(); $table.find('.btn-close').show(); } else if(columnCount <= 2) { $table.find('.btn-close').hide(); } else if(columnCount >= 10) { $table.find('.btn-plus').hide(); } } /** * Create lane menu. * * @param object $options * @access public * @return void */ function createLaneMenu(options) { var lane = options.$trigger.closest('.kanban-lane').data('lane'); var privs = lane.actions; if(!privs.length) return []; var items = []; var regionID = lane.$kanbanData.region; var kanbanID = lane.$kanbanData.kanban; if(privs.includes('editLaneName')) items.push({label: kanbanLang.editLaneName, icon: 'edit', url: createLink('kanban', 'editLaneName', 'laneID=' + lane.id + '&executionID=0&from=kanban'), className: 'iframe', attrs: {'data-toggle': 'modal', 'data-width': '635px'}}); if(privs.includes('editLaneColor')) items.push({label: kanbanLang.editLaneColor, icon: 'color', url: createLink('kanban', 'editLaneColor', 'laneID=' + lane.id + '&executionID=0&from=kanban'), className: 'iframe', attrs: {'data-toggle': 'modal', 'data-width': '635px'}}); if(privs.includes('deleteLane')) items.push({label: kanbanLang.deleteLane, icon: 'trash', url: createLink('kanban', 'deleteLane', 'regionID=' + regionID + '&kanbanID=' + kanbanID + '&lane=' + lane.id), attrs: {'target': 'hiddenwin'}}); var bounds = options.$trigger[0].getBoundingClientRect(); items.$options = {x: bounds.right, y: bounds.top}; return items; } /** * Create card menu. * * @param object $options * @access public * @return array */ function createCardMenu(options) { var card = options.$trigger.closest('.kanban-item').data('item'); var privs = card.actions; if(!privs.length) return []; var items = []; if(privs.includes('editCard') && card.fromType == '') items.push({label: kanbanLang.editCard, icon: 'edit', url: createLink('kanban', 'editCard', 'cardID=' + card.id, '', 'true'), className: 'iframe', attrs: {'data-toggle': 'modal', 'data-width': '80%'}}); if(privs.includes('deleteCard')) items.push({label: card.fromType == '' ? kanbanLang.deleteCard : kanbanLang.removeCard, icon: card.fromType == '' ? 'trash' : 'unlink', url: createLink('kanban', 'deleteCard', 'cardID=' + card.id), attrs: {'target': 'hiddenwin'}}); if(kanban.performable == 1 && card.fromType == '') { if(card.status == 'done') { items.push({label: kanbanLang.activateCard, icon: 'magic', url: createLink('kanban', 'activateCard', 'cardID=' + card.id + '&kanbanID=' + card.kanban, '', 'true'), className: 'iframe', attrs: {'data-toggle': 'modal', 'data-width': '80%'}}); } else { items.push({label: kanbanLang.finishCard, icon: 'checked', onClick: function(){finishCard(card.id, card.kanban, card.region);}}); } } if(privs.includes('archiveCard') && kanban.archived == '1') items.push({label: kanbanLang.archiveCard, icon: 'card-archive', url: createLink('kanban', 'archiveCard', 'cardID=' + card.id), attrs: {'target': 'hiddenwin'}, onClick:function(){$('#archivedCards').replaceWith("
");}}); var editCardAction = (privs.includes('editCard') && card.fromType == '') ? true : false; var deleteCardAction = privs.includes('deleteCard'); var archiveCardAction = (privs.includes('archiveCard') && kanban.archived == '1') ? true : false; var performable = kanban.performable == 1 ? true : false; var moveCardAction = privs.includes('moveCard'); var setCardColorAction = privs.includes('setCardColor'); var basicActions = (editCardAction || deleteCardAction || archiveCardAction) ? true : false; var otherActions = (moveCardAction || setCardColorAction) ? true : false; if((performable || basicActions) && otherActions) { items.push({type: 'divider'}); } if(privs.includes('moveCard')) { var moveCardItems = []; var moveColumns = []; var parentColumns = []; var regionGroups = regions[options.$trigger.closest('.region').data('id')].groups; for(let i = 0; i < regionGroups.length ; i ++ ) { if(regionGroups[i].id == options.$trigger.closest('.kanban-board').data('id')) { moveColumns = regionGroups[i].columns; break; } } for(let i = moveColumns.length-1 ; i >= 0 ; i -- ) { if(moveColumns[i].parent > 0) parentColumns.push(moveColumns[i].parent); if(moveColumns[i].id == card.column || $.inArray(moveColumns[i].id, parentColumns) >= 0) continue; moveCardItems.push({label: moveColumns[i].name, onClick: function(){moveCard(card.id, card.column, moveColumns[i].id, card.lane, card.lane, card.kanban, card.region);}}); } moveCardItems = moveCardItems.reverse(); items.push({label: kanbanLang.moveCard, icon: 'move', items: moveCardItems}); } if(privs.includes('setCardColor')) { var cardColoritems = []; if(!card.color) card.color = "#fff"; for(let i = 0 ; i < colorList.length ; i ++ ) { var attr = card.color == colorList[i] ? '' : ''; var border = i == 0 ? 'border:1px solid #b0b0b0;' : ''; cardColoritems.push({label: "
" + colorListLang[colorList[i]] + attr , onClick: function(){setCardColor(card.id, colorList[i], card.kanban, card.region);}, html: true, attrs: {id: 'cardcolormenu'}, className: 'color' + i}); }; items.push({label: kanbanLang.cardColor, icon: 'color', items: cardColoritems}); } var bounds = options.$trigger[0].getBoundingClientRect(); items.$options = {x: bounds.right, y: bounds.top}; return items; } function createColumnMenu(options) { var column = options.$trigger.closest('.kanban-col').data('col'); var privs = column.actions; if(!privs.length) return []; var items = []; if(privs.includes('setColumn')) items.push({label: kanbanLang.editColumn, icon: 'edit', url: createLink('kanban', 'setColumn', 'columnID=' + column.id, '', 'true'), className: 'iframe', attrs: {'data-toggle': 'modal'}}); if(privs.includes('setWIP')) items.push({label: kanbanLang.setWIP, icon: 'alert', url: createLink('kanban', 'setWIP', 'columnID=' + column.id), className: 'iframe', attrs: {'data-toggle': 'modal', 'data-width' : '500px'}}); var basicActions = (privs.includes('setColumn') || privs.includes('setWIP')) ? true : false; var columnActions = (privs.includes('splitColumn') || privs.includes('createColumn') || privs.includes('copyColumn')) ? true : false; if(basicActions && columnActions) { items.push({type: 'divider'}); } if(privs.includes('splitColumn')) items.push({label: kanbanLang.splitColumn, icon: 'col-split', url: createLink('kanban', 'splitColumn', 'columnID=' + column.id, '', true), className: 'iframe', attrs: {'data-toggle': 'modal'}}); if(privs.includes('createColumn')) { items.push({label: kanbanLang.createColumnOnLeft, icon: 'col-add-left', url: createLink('kanban', 'createColumn', 'columnID=' + column.id + '&position=left'), className: 'iframe', attrs: {'data-toggle': 'modal'}}); items.push({label: kanbanLang.createColumnOnRight, icon: 'col-add-right', url: createLink('kanban', 'createColumn', 'columnID=' + column.id + '&position=right'), className: 'iframe', attrs: {'data-toggle': 'modal'}}); } if(privs.includes('copyColumn')) items.push({label: kanbanLang.copyColumn, icon: 'copy', url: createLink('kanban', 'copyColumn', 'columnID=' + column.id), className: 'iframe', attrs: {'data-toggle': 'modal'}}); var otherActions = ((privs.includes('archiveColumn') && kanban.archived == '1') || privs.includes('deleteColumn')) ? true : false; if(columnActions && otherActions) items.push({type: 'divider'}); if(privs.includes('archiveColumn') && kanban.archived == '1') items.push({label: kanbanLang.archiveColumn, icon: 'card-archive', url: createLink('kanban', 'archiveColumn', 'columnID=' + column.id), attrs: {'target': 'hiddenwin'}, onClick:function(){$('#archivedColumns').replaceWith("
");}}); if(privs.includes('deleteColumn')) items.push({label: kanbanLang.deleteColumn, icon: 'trash', url: createLink('kanban', 'deleteColumn', 'columnID=' + column.id), attrs: {'target': 'hiddenwin'}}); var bounds = options.$trigger[0].getBoundingClientRect(); items.$options = {x: bounds.right, y: bounds.top}; return items; } /** * Create create menu for column. * * @param object $options * @access public * @return object */ function createColumnCreateMenu(options) { var col = options.$trigger.closest('.kanban-col').data('col'); var privs = col.actions; var items = []; var regionID = col.region; var groupID = col.group; var laneID = col.$kanbanData.lanes[0].id ? col.$kanbanData.lanes[0].id : 0; var columnID = col.id; if(privs.includes('createCard')) items.push({label: kanbanLang.createCard, url: $.createLink('kanban', 'createCard', 'kanbanID=' + kanbanID + '®ionID=' + regionID + '&groupID=' + groupID + '&columnID=' + columnID, '', true), className: 'iframe', attrs: {'data-toggle': 'modal'}}); if(privs.includes('batchCreateCard')) items.push({label: kanbanLang.batchCreateCard, url: $.createLink('kanban', 'batchCreateCard', 'kanbanID=' + kanbanID + '®ionID=' + regionID + '&groupID=' + groupID + '&laneID=' + laneID + '&columnID=' + columnID), attrs: {'data-width': '80%'}}); if(kanban.object.indexOf('cards') != -1) items.push({label: kanbanLang.importCard, url: $.createLink('kanban', 'importCard', 'kanbanID=' + kanbanID + '®ionID=' + regionID + '&groupID=' + groupID + '&columnID=' + columnID), className: 'iframe', attrs: {'data-toggle': 'modal'}}); if(kanban.object && kanban.object != 'cards' && vision != 'lite') items.push({className: 'parentDivider'}); if(kanban.object && kanban.object != 'cards' && vision != 'lite') items.push({label: kanbanLang.importAB, className: 'import'}); return items; } /** Calculate column height */ function calcColHeight(col, lane, colCards, colHeight, kanban) { if(!isMultiLanes) return 0; var options = kanban.options; var fontSize = 13; var moreLabelHeight = 20; var laneNameHeight = lane.name.length * fontSize; if(!options.displayCards) return laneNameHeight > colHeight ? laneNameHeight + 2 * moreLabelHeight : colHeight; var displayCards = +(options.displayCards || 2); if (typeof displayCards !== 'number' || displayCards < 2) displayCards = 2; return (displayCards * (options.cardHeight + options.cardSpace) + options.cardSpace); } /** Handle sort cards */ function handleSortCards(event) { var newLaneID = event.element.closest('.kanban-lane').data('id'); var newColID = event.element.closest('.kanban-col').data('id'); var cards = event.element.closest('.kanban-lane-items').data('cards'); var orders = cards.map(function(card){return card.id}); var fromID = String(event.element.data('id')); var toID = String(event.target.data('id')); orders.splice(orders.indexOf(fromID), 1); orders.splice(orders.indexOf(toID) + (event.insert === 'before' ? 0 : 1), 0, fromID); var url = createLink('kanban', 'sortCard', 'kanbanID=' + kanbanID + '&laneID=' + newLaneID + '&columnID=' + newColID + '&cards=' + orders.join(',')); $.getJSON(url, function(response) { if(response.result === 'fail') { if(typeof response.message === 'string' && response.message.length) { bootbox.alert(response.message); } setTimeout(function(){return location.reload()}, 3000); } }); } /* Define menu creators */ window.menuCreators = { card: createCardMenu, lane: createLaneMenu, column: createColumnMenu, columnCreate: createColumnCreateMenu }; /** * init Kanban */ function initKanban($kanban) { var id = $kanban.data('id'); var region = regions[id]; var cardHeight = kanbanInfo.performable == 1 ? 87 : 60; $kanban.kanban( { data: region.groups, maxColHeight: 510, calcColHeight: calcColHeight, fluidBoardWidth: fluidBoard, minColWidth: typeof window.minColWidth === 'number' ? window.minColWidth : defaultMinColWidth, maxColWidth: typeof window.maxColWidth === 'number' ? window.maxColWidth : defaultMaxColWidth, cardHeight: cardHeight, displayCards: typeof window.displayCards === 'number' ? window.displayCards : 2, createColumnText: kanbanLang.createColumn, addItemText: '', itemRender: renderKanbanItem, onAction: handleKanbanAction, onRenderKanban: adjustAddBtnPosition, onRenderLaneName: renderLaneName, onRenderHeaderCol: renderHeaderCol, onRenderCount: renderCount, showCount: kanban.showWIP == '1' ? true : false, sortable: handleSortCards, virtualize: true, virtualRenderOptions: {container: $(window).add($('#kanbanContainer'))}, virtualCardList: true, droppable: { target: findDropColumns, finish: handleFinishDrop }, }); $kanban.on('click', '.action-cancel', hideKanbanAction); $kanban.on('scroll', function() { $.zui.ContextMenu.hide(); }); var kanbanMinColWidth = typeof window.minColWidth === 'number' ? window.minColWidth : defaultMinColWidth; if(kanbanMinColWidth < 190) { var miniColWidth = kanbanMinColWidth * 0.2; $('.kanban-header-col>.title>span:not(.text)').hide(); $('.kanban-header-col>.title > span.text').css('max-width', miniColWidth + 'px'); } } /** * Init when page ready */ $(function() { $(document).find('main#main').css('padding-top', '16px'); window.isMultiLanes = laneCount > 1; $.cookie('isFullScreen', 0); /* Init first kanban */ $('.kanban').each(function() { initKanban($(this)); }); if(navigator.userAgent.toLowerCase().indexOf("qqbrowser") > -1) $('.region .kanban-header-col > .actions').css('top', '30%'); $('.icon-angle-top,.icon-angle-down').on('click', function() { $(this).toggleClass('icon-angle-top icon-angle-down'); $(this).parents('.region').find('.kanban').toggle(); hideKanbanAction(); resetRegionHeight($(this).hasClass('icon-angle-top') ? 'open' : 'close'); }); $('#regionTabs, .region-header').on('click', '.action', hideKanbanAction); $('#TRAction').on('click', '.btn', hideKanbanAction); /* Hide action box when user click document */ $(document).on('click', function(e) { $('.kanban').each(function() { var currentAction = $(this).kanban().attr('data-action-enabled'); var canHideAction = (currentAction === 'headerMore' || currentAction === 'editLaneName') && !$(e.target).closest('.action,.action-box').length; if(canHideAction) hideKanbanAction(); }); }); /* Init contextmenu */ $('#kanban').on('click', '[data-contextmenu]', function(event) { var $trigger = $(this); var menuType = $trigger.data('contextmenu'); var menuCreator = window.menuCreators[menuType]; if(!menuCreator) return; var options = $.extend({event: event, $trigger: $trigger}, $trigger.data()); var items = menuCreator(options); if(!items || !items.length) return; $.zui.ContextMenu.show(items, items.$options || {event: event}); $('.parentDivider').parent().addClass('divider'); $('.import').parent().addClass('dropdown-submenu top'); var regionID = $(this).closest('.kanban').data('id'); var groupID = $(this).closest('.kanban-board').data('id'); var columnID = $(this).closest('.kanban-col').data('id'); /* The submenu of import. */ var importPlanLink = kanban.object.indexOf('plans') != -1 ? "
  • " + kanbanLang.importPlan + '
  • ' : ''; var importReleaseLink = kanban.object.indexOf('releases') != -1 ? "
  • " + kanbanLang.importRelease + '
  • ' : ''; var importExecutionLink = kanban.object.indexOf('executions') != -1 ? "
  • " + kanbanLang.importExecution + '
  • ' : ''; var importBuildLink = kanban.object.indexOf('builds') != -1 ? "
  • " + kanbanLang.importBuild + '
  • ' : ''; var importTicketLink = kanban.object.indexOf('tickets') != -1 ? "
  • " + kanbanLang.importTicket + '
  • ' : ''; var importSubmenu = ''; $('.import').parent().append(importSubmenu); }); /* Adjust the add button position on window resize */ $(window).on('resize', function(a) { adjustAddBtnPosition(); initRegionTabs(); }); resetLaneHeight(); /* Hide contextmenu when page scroll */ $(window).on('scroll', function() { $.zui.ContextMenu.hide(); if($('#regionTabs').length > 0) updateRegionTabAffixState(); }); $(document).on('click', '#splitTable .btn-plus', function() { var tr = $(this).closest('tr'); tr.after($('#childTpl').html().replace(/key/g, key)); tr.next().find('input[name^=color]').colorPicker(); key++; processMinusBtn(); return false; }); /* Remove a trade detail item. */ $(document).on('click', '#splitTable .btn-close', function() { $(this).closest('tr').remove(); processMinusBtn(); return false; }); /* Modify default color's border color. */ $(document).on('mouseout', '.color0', function() { $('.color0 .cardcolor').css('border', '1px solid #b0b0b0'); }); /* Modify default color's border color. */ $(document).on('mouseover', '.color0', function() { $('.color0 .cardcolor').css('border', '1px solid #fff'); }); /* Init sortable */ initSortable(); resetRegionHeight('open'); if(!CRKanban && kanbanInfo.status == 'closed') $('.kanban-col.kanban-header-col').css('padding', '0px 0px 0px 0px'); setToolTip(); distance = 0; radiusWidth = 10; initRegionTabs(); $('.leftBtn').click(function() { if($(this).hasClass('disabled')) return; swipeRegionNavTabs($('#regionNavTabs').find('ul'), 'left'); }); $('.rightBtn').click(function() { if($(this).hasClass('disabled')) return; swipeRegionNavTabs($('#regionNavTabs').find('ul'), 'right'); }); if(Object.values(regions).length <= 1) $('#kanbanBox').removeClass('hidden'); }); /** * Init sortable. * * @access public * @return void */ function initSortable() { var sortType = ''; var $cards = null; if(!CRKanban && kanbanInfo.status == 'closed') return; $('#kanban').sortable( { selector: '.region, .kanban-board, .kanban-lane, .kanban-col', trigger: '.region.sort > .region-header, .kanban-board.sort > .kanban-header > .kanban-group-header, .kanban-lane.sort > .kanban-lane-name, .kanban-header-col.sort', dropOnMouseleave: true, container: function($ele) { return $ele.parent(); }, targetSelector: function($ele) { var $parent = $ele.parent(); /* Sort regions. */ if($ele.hasClass('region')) { sortType = 'region'; return $parent.children('.region'); } /* Sort boards. */ if($ele.hasClass('kanban-board')) { sortType = 'board'; return $parent.children('.kanban-board'); } /* Sort lanes. */ if($ele.hasClass('kanban-lane')) { sortType = 'lane'; $cards = $ele.find('.kanban-item'); return $parent.children('.kanban-lane'); } /* Sort columns. */ if($ele.hasClass('kanban-col')) { sortType = 'column'; return $parent.children('.kanban-col'); } }, before: function() { return !window.sortableDisabled; }, start: function(e) { if(sortType == 'region') { showRegionIdList = ''; $('.icon-angle-top').each(function() { showRegionIdList += $(this).attr('data-id') + ','; $(this).attr('class', 'icon-angle-down'); }); $('.region').find('.kanban').hide(); hideKanbanAction(); } else if(sortType === 'column') { e.element.closest('.kanban-board').addClass('kanban-cols-sorting'); } $('#kanban').attr('data-sort-by', sortType); }, finish: function(e) { if(!e.changed) return; var url = ''; var orders = []; var regionID = ''; if(sortType == 'region') { e.list.each(function(index, data) { if(data.item.hasClass('region') && data.item.hasClass('sort')) orders.push(data.item.data('id')); }); $('.region').each(function() { if(showRegionIdList.includes($(this).attr('data-id'))) { $(this).find('.icon-angle-down').attr('class', 'icon-angle-top'); $(this).find('.kanban').show(); } }) url = createLink('kanban', 'sortRegion', 'regions=' + orders.join(',')); } else if(sortType == 'board') { regionID = e.element.closest('.region').data('id'); e.list.each(function(index, data) { if(data.item.hasClass('kanban-board') && data.item.hasClass('sort') && regionID == data.item.parent().data().id) orders.push(data.item.data('id')); }); url = createLink('kanban', 'sortGroup', 'region=' + regionID + '&groups=' + orders.join(',')); } else if(sortType == 'lane') { var groupID = e.element.closest('.kanban-board').data('id'); e.list.each(function(index, data) { if(data.item.hasClass('kanban-lane') && data.item.hasClass('sort') && groupID == data.item.data().lane.group) orders.push(data.item.data('id')); }); regionID = e.element.closest('.region').data('id'); url = createLink('kanban', 'sortLane', 'region=' + regionID + '&lanes=' + orders.join(',')); } else if(sortType == 'column') { var groupID = e.element.closest('.kanban-board').data('id'); e.list.each(function(index, data) { if(data.item.hasClass('kanban-col') && (data.item.hasClass('sort') || data.item.children().hasClass('sort')) && groupID == data.item.data().col.group) orders.push(data.item.data('id')); }); regionID = e.element.closest('.region').data('id'); url = createLink('kanban', 'sortColumn', 'region=' + regionID + '&kanbanID=' + kanban.id + '&columns=' + orders.join(',')); } if(!url) return true; $.getJSON(url, function(response) { if(response.result == 'fail') { if(typeof response.message === 'string' && response.message.length) { bootbox.alert(response.message); } setTimeout(function(){return location.reload()}, 3000); } else if (sortType == 'column') { updateRegion(regionID, response[regionID]); } }); }, always: function(e) { if(sortType == 'lane') $cards.show(); $('#kanban').find('.kanban-cols-sorting').removeClass('kanban-cols-sorting'); } }); } /** * Reset region height according to window height. * * @param string fold * @access public * @return void */ function resetRegionHeight(fold) { var regionCount = 0; if($.isEmptyObject(regions)) return false; for(var i in regions) { regionCount += 1; if(regionCount > 1) return false; } var regionID = Object.keys(regions)[0]; var region = regions[regionID].groups; var groupCount = 0; if($.isEmptyObject(region)) return false; for(var j in region) { groupCount += 1; if(groupCount > 1) return false; } var group = region[0]; var laneCount = 0; if($.isEmptyObject(group.lanes)) return false; for(var h in group.lanes) { laneCount += 1; if(laneCount > 1) return false; } var regionHeaderHeight = $('.region-header').outerHeight(); if(fold == 'open') { var windowHeight = $(window).height(); var headerHeight = $('#mainHeader').outerHeight(); var mainPadding = $('#main').css('padding-top'); var panelBorder = $('.panel').css('border-top-width'); var bodyPadding = $('.panel-body').css('padding-top'); var height = windowHeight - (parseInt(mainPadding) * 2) - (parseInt(bodyPadding) * 2) - headerHeight - (parseInt(panelBorder) * 2); var regionPadding = $('.kanban').css('padding-bottom'); var columnHeight = $('.kanban-header').outerHeight(); $('.region').css('min-height', height); $('.kanban-lane').css('height', height - regionHeaderHeight - parseInt(regionPadding) - columnHeight); } else { $('.region').css('height', regionHeaderHeight); } } $(document).on('click', '.dropdown-menu', function() { $.zui.ContextMenu.hide(); }); /** * Alerts for exceeding the limit. * * @access public * @return void */ function setToolTip() { $('[data-toggle="tooltip"]').tooltip({container: 'body'}); } /** * Update kanban affix state for all boards in page. * * @access public * @return void */ function updateRegionTabAffixState() { var $kanbanContainer = $('#kanbanContainer'); var kanbanContainer = $kanbanContainer[0].getBoundingClientRect(); var $regionTabs = $('#regionTabs'); var regionTabs = $regionTabs[0].getBoundingClientRect(); if(regionTabs.top <= 0 && !$regionTabs.hasClass('affixed')) { $regionTabs.addClass('affixed'); if( kanbanContainer.top > 0) $regionTabs.find('#region-tab-actions').addClass('hidden'); } else if($regionTabs.hasClass('affixed') && kanbanContainer.top > 44) { $regionTabs.removeClass('affixed'); $regionTabs.find('#region-tab-actions').removeClass('hidden'); } initRegionTabs(); } /** * Swipe region navigation tabs. * * @param object $object * @param string $direction * @access public * @return bool */ function swipeRegionNavTabs($object, direction) { var $regionNavTabs = $('#regionNavTabs'); var offsetWidth = $regionNavTabs[0].offsetWidth; var objectWidth = $object[0].offsetWidth; $object.find('li').each(function() { /* Get the offset of the item. */ var $item = $(this); var itemLeft = $item[0].offsetLeft; var itemWidth = $item[0].offsetWidth; var itemOffset = itemLeft + itemWidth; var radius = $item.hasClass('active') ? radiusWidth : 0; /* Calculate the offset after sliding. */ if(direction == 'left' && (itemOffset + distance + radius) >= -5) { /* If you swipe left, the distance is equal to the item's left. */ distance = - itemLeft + radius - ($item.prev().hasClass('active') ? radiusWidth : 0); /* tab overlap width */ distance += 20; if($item.prev().length == 0) { distance = 0; $('.leftBtn').addClass('disabled'); } $object[0].style.transform = 'translateX(' + distance + 'px)'; /* If the width of regionNavTabs plus offsetWidth is less than the width of object, change rightBtn to clickable. */ if(offsetWidth - distance < objectWidth) $('.rightBtn').removeClass('disabled'); return false; } var nextRadius = $item.next().hasClass('active') ? radiusWidth : 0; if(direction == 'right' && itemOffset > (offsetWidth - distance + nextRadius + radiusWidth * 2)) { /* If you swipe right, the distance is equal to the left distance of item plus the width of item minus the width of the regionNavTabs. */ distance = offsetWidth - itemOffset - radius + nextRadius - radiusWidth * 2; distance += 20; if($item.next().length == 0) { distance = - objectWidth + offsetWidth - radiusWidth * 2; $('.rightBtn').addClass('disabled'); } $object[0].style.transform = 'translateX(' + distance + 'px)'; /* If distance is less than 0, change leftBtn to clickable. */ if(distance < 0) $('.leftBtn').removeClass('disabled'); return false; } }); } /** * Init region tabs. * * @access public * @return void */ function initRegionTabs() { var $regionNavTabs = $('#regionNavTabs'); if($regionNavTabs.length == 0) return; /* Set the width of regionTab. */ $('#regionTabs').width($('#kanban').outerWidth()); var regionTabsWidth = $regionNavTabs[0].offsetWidth; var regionTabULWidth = $regionNavTabs.find('ul')[0].offsetWidth; var $acitiveItem = $('#regionNavTabs > ul > li.active'); var acitiveItemWidth = $acitiveItem[0].offsetWidth; var acitiveItemLeft = $acitiveItem[0].offsetLeft; /* Print left and right button. */ if(regionTabULWidth > regionTabsWidth) $('.leftBtn, .rightBtn').removeClass('hidden'); if(acitiveItemLeft + distance < 0) { distance = - acitiveItemLeft; distance += 20; if($acitiveItem.prev().length == 0) { distance = 0; $('.leftBtn').addClass('disabled'); } $('#regionNavTabs > ul')[0].style.transform = 'translateX(' + distance + 'px)'; } else if(acitiveItemWidth + acitiveItemLeft + distance + radiusWidth * 2 > regionTabsWidth && acitiveItemLeft != 0 && acitiveItemWidth != 0) { distance = regionTabsWidth - acitiveItemWidth - acitiveItemLeft + (distance != 0 ? - radiusWidth * 2 : radiusWidth); distance += 20; if($acitiveItem.next().length == 0) { distance = regionTabsWidth - acitiveItemWidth - acitiveItemLeft - radiusWidth * 2; $('.rightBtn').addClass('disabled'); } $('#regionNavTabs > ul')[0].style.transform = 'translateX(' + distance + 'px)'; } if(distance < 0) $('#regionTabs').find('.leftBtn').removeClass('disabled'); if($acitiveItem.next().length != 0 && regionTabULWidth > regionTabsWidth) $('#regionTabs').find('.rightBtn').removeClass('disabled'); $('#kanbanBox').removeClass('hidden'); } $('[data-tab]').on('shown.zui.tab', function(e) { /* Init vars. */ var $current = $(e.target); var $prev = $(e.relatedTarget); var $regionActions = $('#region-tab-actions'); var $regions = $('.region'); var contentID = $current.attr('href'); var regionID = $current.parent().attr('data-id'); var hasActions = $regionActions.hasClass('active'); /* Highlight the currently selected tab. */ $current.addClass('btn-active-text'); $current.parent().addClass('active'); $prev.removeClass('btn-active-text'); $prev.parent().removeClass('active'); /* Dynamic display and hidden of regions. */ if(contentID == 'all') { $regions.addClass('active').removeClass('notAll'); if(hasActions) $regionActions.removeClass('active'); $regions.each(function() { var isFold = $(this).find('.region-header i').hasClass('icon-angle-down'); if(isFold) $(this).find('.kanban').css('display', 'none'); }); } else { var $currentRegion = $(contentID); $regions.removeClass('active'); $currentRegion.addClass('active notAll'); $currentRegion.find('.kanban').css('display', 'block'); if(!hasActions) $regionActions.addClass('active'); /* Replace the link of the region actions button with the ID of the current region. */ $regionActions.find('li').each(function() { if($(this).hasClass('editRegion')) $(this).find('a').attr('href', createLink('kanban', 'editRegion', 'regionID=' + regionID, '', true)); if($(this).hasClass('createLane')) $(this).find('a').attr('href', createLink('kanban', 'createLane', 'kanbanID=' + kanbanID + '®ionID=' + regionID, '', true)); if($(this).hasClass('deleteRegion')) $(this).find('a').attr('href', createLink('kanban', 'deleteRegion', 'regionID=' + regionID)); if($(this).hasClass('archivedCard')) $(this).find('a').attr('href', "javascript:loadMore(\"Card\", " + regionID + ')'); if($(this).hasClass('archivedColumn')) $(this).find('a').attr('href', "javascript:loadMore(\"Column\", " + regionID + ')'); }); } window.scrollTo(0, 0); /* To manually refresh stay under the current tab, save the ID of the current region. */ var url = createLink('kanban', 'ajaxSaveRegionID', 'regionID=' + regionID); $.get(url); });