$(document).on('click', '.task-toggle', function(e) { var $toggle = $(this); var id = $(this).data('id'); var isCollapsed = $toggle.toggleClass('collapsed').hasClass('collapsed'); $toggle.closest('[data-ride="table"]').find('tr.parent-' + id).toggle(!isCollapsed); e.stopPropagation(); e.preventDefault(); }); $(function() { if(viewType != 'kanban') toggleFold('#productplanForm', unfoldPlans, productID, 'productplan'); $('#productplanList tbody tr').each(function() { var $content = $(this).find('td.content'); var content = $content.find('div').html(); if(content.indexOf('= 0 || content.indexOf('= 0) { $content.append(""); } }); $('#createExecutionButton').on('click', function() { var projectID = $('#project').val(); var planID = $('#planID').val(); if(!projectID) { alert(projectNotEmpty); return false; } else { $.apps.open(createLink('execution', 'create', 'projectID=' + projectID + '&executionID=©ExecutionID=&planID=' + planID + '&confirm=&productID=' + productID), 'execution') } $('#projects').modal('hide'); }); $('.switchButton').click(function() { var viewType = $(this).attr('data-type'); var branchID = $(this).val(); $.cookie('viewType', viewType, {expires:config.cookieLife, path:config.webRoot}); var link = createLink('productplan', 'browse', "productID=" + productID + '&branch=' + branchID); location.href = link; }); $('#branch').change(function() { var branchID = $(this).val(); var link = createLink(rawModule, 'browse', "productID=" + productID + '&branch=' + branchID); location.href = link; }); if(viewType == 'kanban') { $('#branch_chosen .chosen-single span').prepend(''); $('#kanban').kanban( { data: kanbanData, minColWidth: typeof window.minColWidth === 'number' ? window.minColWidth: defaultMinColWidth, maxColWidth: typeof window.maxColWidth === 'number' ? window.maxColWidth: defaultMaxColWidth, maxColHeight: 460, minColHeight: 190, cardHeight: 80, itemRender: renderKanbanItem, virtualize: true, droppable: { target: findDropColumns, finish: handleFinishDrop } }); resetLaneHeight(); $('#kanban').on('scroll', function() { $.zui.ContextMenu.hide(); }); /* 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}); }); } /* Hide contextmenu when page scroll. */ $(window).on('scroll', function() { $.zui.ContextMenu.hide(); }); $('.execution-popover').on('click', function(e) { e.stopPropagation(); var showPopover = $(this).next().css('display') == 'block'; $('.popover.right').hide(); if(!showPopover) $(this).next().show(); }); $('.execution-link').on('click', function() { $('.popover.right').hide(); }); /* Hide popover tip. */ $(document).on('mousedown', function(e) { var $target = $(e.target); var $toggle = $target.closest('.popover, .execution-popover'); if(!$toggle.length) $('.popover.right').hide(); }); }); /* Define menu creators. */ window.menuCreators = {card: createCardMenu}; /** * 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('createExecution')) { var className = ''; var today = new Date(); var end = $.zui.createDate(card.end); if(end.getTime() < today.getTime()) { className = 'disabled'; } else if(card.status == 'done' || card.status == 'closed') { className = 'disabled'; } else if(product.type != 'normal') { var branchStatus = branchStatusList[card.branch]; if(branchStatus == 'closed') className = 'disabled'; } items.push({label: productplanLang.createExecution, icon: 'plus', url: '#projects', className: className, attrs: {'data-toggle': 'modal', 'onclick': 'getPlanID(this,' + card.branch + ')', 'data-id': card.id}}); } if(privs.includes('linkStory')) items.push({label: productplanLang.linkStory, icon: 'link', url: createLink(rawModule, 'view', "planID=" + card.id + "&type=story&orderBy=id_desc&link=true")}); if(privs.includes('linkBug')) items.push({label: productplanLang.linkBug, icon: 'bug', url: createLink(rawModule, 'view', "planID=" + card.id + "&type=bug&orderBy=id_desc&link=true")}); if(privs.includes('edit')) items.push({label: productplanLang.edit, icon: 'edit', url: createLink(rawModule, 'edit', "planID=" + card.id)}); if(privs.includes('start')) items.push({label: productplanLang.start, icon: 'start', url: createLink('productplan', 'start', "planID=" + card.id), attrs: {'target': 'hiddenwin'}}); if(privs.includes('finish')) items.push({label: productplanLang.finish, icon: 'checked', url: createLink('productplan', 'finish', "planID=" + card.id), attrs: {'target': 'hiddenwin'}}); if(privs.includes('close')) items.push({label: productplanLang.close, icon: 'off', url: createLink('productplan', 'close', "planID=" + card.id, '', true), className: 'iframe', attrs: {'data-toggle': 'modal'}}); if(privs.includes('activate')) items.push({label: productplanLang.activate, icon: 'magic', url: createLink('productplan', 'activate', "planID=" + card.id), attrs: {'target': 'hiddenwin'}}); if(privs.includes('delete')) items.push({label: productplanLang.delete, icon: 'trash', url: createLink('productplan', 'delete', "planID=" + card.id), attrs: {'target': 'hiddenwin'}}); var bounds = options.$trigger[0].getBoundingClientRect(); items.$options = {x: bounds.right, y: bounds.top}; return items; } $(document).on('click', 'td.content .more', function(e) { var $toggle = $(this); if($toggle.hasClass('open')) { $toggle.removeClass('open'); $toggle.closest('.content').find('div').css('height', '25px'); $toggle.css('padding-top', 0); $toggle.find('i').addClass('rotate-down'); } else { $toggle.addClass('open'); $toggle.closest('.content').find('div').css('height', 'auto'); $toggle.css('padding-top', ($toggle.closest('.content').find('div').height() - $toggle.height()) / 2); $toggle.find('i').removeClass('rotate-down'); } }); /** * Find drop columns. * * @param object $element Drag element * @param object $root Dnd root element * @access public * @return object|bool */ function findDropColumns($element, $root) { var $col = $element.closest('.kanban-col'); var col = $col.data(); var lane = $col.closest('.kanban-lane').data(); var kanbanRules = window.kanbanDropRules ? window.kanbanDropRules : null; if(!kanbanRules) return $root.find('.kanban-lane[data-id="' + lane.id + '"] .kanban-lane-col:not([data-type="project"],[data-type="' + col.type + '"])'); var colRules = kanbanRules[col.type]; var lane = $col.closest('.kanban-lane').data('lane'); return $root.find('.kanban-lane-col').filter(function() { if(!colRules) return false; if(colRules === true) return true; var $newCol = $(this); var newCol = $newCol.data(); if(newCol.id === col.id) return false; var $newLane = $newCol.closest('.kanban-lane'); var newLane = $newLane.data('lane'); return colRules.indexOf(newCol.type) > -1 && newLane.id === lane.id; }); } /** * Reset lane height according to window height. */ function resetLaneHeight() { if(product.type == 'normal') { var windowHeight = $(window).height(); var mainHeader = $('#mainHeader').outerHeight(); var marginMenu = $('#mainMenu').outerHeight(); var headerHeight = $('#kanban > .kanban-board > .kanban-header').outerHeight(); maxHeight = windowHeight - headerHeight - mainHeader - marginMenu - 80; $('.kanban-lane').css('height', maxHeight); } } /** * Handle finish drop card. * * @param object event * @access public * @return void */ function handleFinishDrop(event) { var $card = $(event.element); // The drag card. var $dragCol = $card.closest('.kanban-lane-col'); var $dropCol = $(event.target); /* Get d-n-d(drag and drop) infos. */ var card = $card.data('item'); var fromColType = $dragCol.data('type'); var toColType = $dropCol.data('type'); var kanbanID = $card.closest('.kanban').data('id'); changeCardColType(card, fromColType, toColType, kanbanID); } /** * Change column type for a card. * * @param object card * @param string fromColType The column type before change * @param string toColType The column type after change * @param int kanbanID * @access public * @return void */ function changeCardColType(card, fromColType, toColType, kanbanID) { if(typeof card == 'undefined') return false; var objectID = card.id; var privs = card.actions; var showIframe = false; var link = ''; if(toColType == 'doing') { if(fromColType == 'wait' && privs.includes('start')) { link = createLink('productplan', 'start', 'planID=' + objectID); showIframe = false; } else if((fromColType == 'done' || fromColType == 'closed') && privs.includes('activate')) { link = createLink('productplan', 'activate', 'planID=' + objectID); showIframe = false; } } else if(toColType == 'done') { if(fromColType == 'doing') { link = createLink('productplan', 'finish', 'planID=' + objectID); showIframe = false; } } else if(toColType == 'closed') { if(fromColType != 'closed') { link = createLink('productplan', 'close', 'planID=' + objectID, '', true); showIframe = true; } } if(showIframe) { var modalTrigger = new $.zui.ModalTrigger({type: 'iframe', width: '70%', url: link}); modalTrigger.show(); } else if(!showIframe && link) { hiddenwin.location.href = link; } } /** * The function for rendering kanban item. * * @param object item * @param object $item * @access public * @return void */ function renderKanbanItem(item, $item) { var privs = item.actions; var printMoreBtn = (privs.includes('createExecution') || privs.includes('linkStory') || privs.includes('linkBug') || privs.includes('edit') || privs.includes('start') || privs.includes('finish') || privs.includes('close') || privs.includes('activate') || privs.includes('delete')); /* Output header information. */ var $header = $item.children('.header'); if(!$header.length) $header = $( [ '
', '
' ].join('')).appendTo($item); var $titleBox = $item.children('.titleBox'); if(!$titleBox.length) $titleBox = $( [ '
', '
' ].join('')).appendTo($header); /* Print plan name. */ var $title = $titleBox.children('.title'); if(!$title.length) { if(privs.includes('view')) $title = $('').appendTo($titleBox).attr('href', createLink(rawModule, 'view', 'cardID=' + item.id)); if(!privs.includes('view')) $title = $('').appendTo($titleBox); } if(!$title.children('i').length) { $(function() {$title.prepend('');}) } $title.text(item.title).attr('title', item.title); var $info = $item.children('.productplanInfo'); if(!$info.length) $info = $( [ '
', '
' ].join('')).appendTo($item); /* Output plan desc information. */ var $descBox = $item.children('.productplanDesc'); if(!$descBox.length) { $descBox = $('
' + item.desc + '
').appendTo($info); } var $statusBox = $item.children('.productplanStatus'); if(!$statusBox.length) { if(item.deleted == '0') { $statusBox = $('' + productplanLang.statusList[item.status] + '').appendTo($info); } else { $statusBox = $('' + productplanLang.deleted + '').appendTo($info); } } /* Determine whether to print an expired label. */ var today = new Date(); var begin = $.zui.createDate(item.begin); var end = $.zui.createDate(item.end); if(item.delay && (item.status == 'wait' || item.status == 'doing')) { $expired = $titleBox.children('.expired'); if(!$expired.length) { $('' + productplanLang.expired + '').appendTo($titleBox); } } if(printMoreBtn) { $( [ '
', '', '', '', '
' ].join('')).appendTo($header); } /* 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 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; } /** * Get planID. * * @param object obj * @param int branch * @access public * @return void */ function getPlanID(obj, branch) { var planID = $(obj).attr("data-id"); $('#planID').val(planID); link = createLink('productplan', 'ajaxGetProjects', 'productID=' + productID + '&branch=' + branch); $.get(link, function(projects) { $('#project').replaceWith(projects); $("#project_chosen").remove(); $("#project").chosen(); var projectList = $("#project").val(); if(!projectList) { $("#project").attr('disabled', true); $("#project").trigger('chosen:updated'); $(".tips").removeClass('hidden'); var locateLink = createLink('product', 'project', 'status=all&productID=' + productID + '&branch=' + branch); var locateButton = "" + enterProjectList + ""; $("#projects .btn-primary").replaceWith(locateButton); } else { $(".tips").addClass('hidden'); } }); } if(!window.kanbanDropRules) { window.kanbanDropRules = { wait: ['doing', 'closed'], doing: ['done', 'closed'], done: ['doing', 'closed'], closed: ['doing'] } }