// Place all the behaviors and hooks related to the matching controller here. // All this logic will automatically be available in application.js. // TODO // - error handling of assigning user to project, check XHR data.errors // - error handling of removing user from project, check XHR data.errors // - refresh project users tab after manage user modal is closed // - refactor view handling using library, ex. backbone.js /* global Comments CounterBadge animateSpinner initFormSubmitLinks HelperModule I18n dropdownSelector */ (function(global) { var newProjectModal = null; var newProjectModalForm = null; var newProjectModalBody = null; var newProjectBtn = null; var editProjectModal = null; var editProjectModalTitle = null; var editProjectModalBody = null; var editProjectBtn = null; var projectActionsModal = null; var projectActionsModalHeader = null; var projectActionsModalBody = null; var projectActionsModalFooter = null; var exportProjectsModal = null; var exportProjectsModalHeader = null; var exportProjectsModalBody = null; var exportProjectsBtn = null; var exportProjectsSubmit = null; var projectsViewMode = 'cards'; var projectsViewFilter = $('.projects-view-filter.active').data('filter'); var projectsChanged = false; var projectsViewSort = $('#sortMenuDropdown a.disabled').data('sort'); var TABLE; // Arrays with selected project and folder IDs shared between both views var selectedProjects = []; var selectedProjectFolders = []; /** * Initialize the JS for new project modal to work. */ function initNewProjectModal() { newProjectModal.off().on('hidden.bs.modal', function() { var teamSelect = newProjectModalForm.find('select#project_team_id'); var teamHidden = newProjectModalForm.find('input#project_visibility_hidden'); var teamVisible = newProjectModalForm.find('input#project_visibility_visible'); // When closing the new project modal, clear its input vals // and potential errors newProjectModalForm.clearFormErrors(); // Clear input fields newProjectModalForm.clearFormFields(); teamSelect.val(0); teamSelect.selectpicker('refresh'); teamHidden.prop('checked', true); teamHidden.attr('checked', 'checked'); teamHidden.parent().addClass('active'); teamVisible.prop('checked', false); teamVisible.parent().removeClass('active'); }).on('show.bs.modal', function() { var teamSelect = newProjectModalForm.find('select#project_team_id'); teamSelect.selectpicker('refresh'); }); newProjectModalForm.off() .on('ajax:beforeSend', function() { animateSpinner(newProjectModalBody); }) .on('ajax:success', function(ev, data) { projectsChanged = true; refreshCurrentView(); newProjectModal.modal('hide'); HelperModule.flashAlertMsg(data.message, 'success'); }) .on('ajax:error', function(jqxhr, status) { $(this).renderFormErrors('project', status.responseJSON); }) .on('ajax:complete', function() { animateSpinner(newProjectModalBody, false); }); newProjectBtn.off().click(function() { // Show the modal newProjectModal.modal('show'); return false; }); } // init project archive/restore function function initArchiveRestoreButton(el) { el.find('form.edit_project').off() .on('ajax:beforeSend', function() { animateSpinner($('#projects-cards-view').closest('.tab-content')); }) .on('ajax:success', function(ev, data) { projectsChanged = true; HelperModule.flashAlertMsg(data.message, 'success'); // Project saved, reload view refreshCurrentView(); }) .on('ajax:error', function(ev, data) { HelperModule.flashAlertMsg(data.responseJSON.message, 'danger'); }) .on('ajax:complete', function() { animateSpinner($('#projects-cards-view').closest('.tab-content'), false); }); } function initEditProjectButton(el) { el.find(".dropdown-menu a[data-action='edit']").off() .on('ajax:success', function(ev, data) { // Update modal title editProjectModalTitle.html(data.title); // Set modal body editProjectModalBody.html(data.html); // Add modal body's submit handler editProjectModal.find('form').off() .on('ajax:beforeSend', function() { animateSpinner(this); }) .on('ajax:success', function(ev2, data2) { projectsChanged = true; // Hide modal editProjectModal.modal('hide'); HelperModule.flashAlertMsg(data2.message, 'success'); // Project saved, reload view refreshCurrentView(); }) .on('ajax:error', function(ev2, data2) { $(this).renderFormErrors('project', data2.responseJSON.errors); }) .on('ajax:complete', function() { animateSpinner(this, false); }); // Show the modal editProjectModal.modal('show'); }) .on('ajax:error', function() { // TODO }); } /** * Initialize the JS for edit project modal to work. */ function initEditProjectModal() { // Edit button click handler editProjectBtn.off().click(function() { // Submit the modal body's form editProjectModalBody.find('form').submit(); }); // On hide modal handler editProjectModal.off().on('hidden.bs.modal', function() { editProjectModalBody.html(''); }); } function initManageUsersModal() { // Reload users tab HTML element when modal is closed projectActionsModal.off('hide.bs.modal').on('hide.bs.modal', function() { var projectEl = $('#' + $(this).attr('data-project-id')); // Load HTML to refresh users list $.ajax({ url: projectEl.attr('data-project-users-tab-url'), type: 'GET', dataType: 'json', success: function(data) { projectEl.find('#users-' + projectEl.attr('id')).html(data.html); CounterBadge.updateCounterBadge( data.counter, data.project_id, 'users' ); initUsersEditLink(projectEl); projectsChanged = true; }, error: function() { // TODO } }); }); // Remove modal content when modal window is closed. projectActionsModal.off('hidden.bs.modal').on('hidden.bs.modal', function() { projectActionsModalHeader.html(''); projectActionsModalBody.html(''); projectActionsModalFooter.html(''); }); } // Initialize users editing modal remote loading. global.initUsersEditLink = function($el) { $el.find('.manage-users-link').off() .on('ajax:before', function() { var projectId = $(this).closest('.panel-default').attr('id'); projectActionsModal.attr('data-project-id', projectId); projectActionsModal.modal('show'); }) .on('ajax:success', function(e, data) { $('#manage-users-modal-project').text(data.project.name); initUsersModalBody(data); }); } /** * Initialize the JS for export projects modal to work. */ function initExportProjectsModal() { exportProjectsBtn.off('click').click(function() { // Load HTML to refresh users list $.ajax({ url: exportProjectsBtn.data('export-projects-modal-url'), type: 'GET', dataType: 'json', data: { project_ids: selectedProjects, project_folder_ids: selectedProjectFolders }, success: function(data) { // Update modal title exportProjectsModalHeader.html(data.title); // Set modal body exportProjectsModalBody.html(data.html); // Update modal footer (show/hide buttons) if (data.status === 'error') { $('#export-projects-modal-close').show(); $('#export-projects-modal-cancel').hide(); exportProjectsSubmit.hide(); } else { $('#export-projects-modal-close').hide(); $('#export-projects-modal-cancel').show(); exportProjectsSubmit.show(); } // Show the modal exportProjectsModal.modal('show'); }, error: function() { // TODO } }); }); // Remove modal content when modal window is closed. exportProjectsModal.off().on('hidden.bs.modal', function() { exportProjectsModalHeader.html(''); exportProjectsModalBody.html(''); }); } function initExportProjects() { // Submit the export projects exportProjectsSubmit.off('click').click(function() { $.ajax({ url: exportProjectsSubmit.data('export-projects-submit-url'), type: 'POST', dataType: 'json', data: { project_ids: selectedProjects, project_folder_ids: selectedProjectFolders }, success: function(data) { // Hide modal and show success flash exportProjectsModal.modal('hide'); HelperModule.flashAlertMsg(data.flash, 'success'); }, error: function() { // TODO } }); }); } // Initialize reloading manage user modal content after posting new // user. function initAddUserForm() { projectActionsModalBody.find('.add-user-form').off() .on('ajax:success', function(e, data) { var errorBlock; initUsersModalBody(data); if (data.status === 'error') { $(this).addClass('has-error'); errorBlock = $(this).find('span.help-block'); if (errorBlock.length && errorBlock.length > 0) { errorBlock.html(data.error); } else { $(this).append("" + data.error + ''); } } }); } // Initialize remove user from project links. function initRemoveUserLinks() { projectActionsModalBody.find('.remove-user-link').off() .on('ajax:success', function(e, data) { initUsersModalBody(data); }); } // function initUserRoleForms() { projectActionsModalBody.find('.update-user-form select').off() .on('change', function() { $(this).parents('form').submit(); }); projectActionsModalBody.find('.update-user-form').off() .on('ajax:success', function(e, data) { initUsersModalBody(data); }) .on('ajax:error', function() { // TODO }); } // Initialize ajax listeners and elements style on modal body. This // function must be called when modal body is changed. function initUsersModalBody(data) { projectActionsModalHeader.html(data.html_header); projectActionsModalBody.html(data.html_body); projectActionsModalFooter.html(data.html_footer); projectActionsModalBody.find('.selectpicker').selectpicker(); initAddUserForm(); initRemoveUserLinks(); initUserRoleForms(); } function updateSelectedCards() { $('.panel-project').removeClass('selected'); $('.project-card-selector').prop('checked', false); $.each(selectedProjects, function(index, value) { var selectedCard = $('.panel-project[id=' + value + ']'); selectedCard.addClass('selected'); selectedCard.find('.project-card-selector').prop('checked', true); }); } function refreshProjectsToolbar() { let projectsToolbar = $('#projectsToolbar'); if (selectedProjects.length === 0 && selectedProjectFolders.length === 0) { projectsToolbar.find('.single-project-action, .multiple-projects-action').addClass('hidden'); projectsToolbar.find('.new-project-actions').removeClass('hidden'); } else if (selectedProjects.length === 1 && selectedProjectFolders.length === 0) { projectsToolbar.find('.new-project-actions').addClass('hidden'); projectsToolbar.find('.single-project-action, .multiple-projects-action').removeClass('hidden'); } else { projectsToolbar.find('.new-project-actions').addClass('hidden'); projectsToolbar.find('.single-project-action').addClass('hidden'); projectsToolbar.find('.multiple-projects-action').removeClass('hidden'); } } /** * Initializes cards view */ function init() { newProjectModal = $('#new-project-modal'); newProjectModalForm = newProjectModal.find('form'); newProjectModalBody = newProjectModal.find('.modal-body'); newProjectBtn = $('.new-project-btn'); editProjectModal = $('#edit-project-modal'); editProjectModalTitle = editProjectModal.find('#edit-project-modal-label'); editProjectModalBody = editProjectModal.find('.modal-body'); editProjectBtn = editProjectModal.find(".btn[data-action='submit']"); projectActionsModal = $('#project-actions-modal'); projectActionsModalHeader = projectActionsModal.find('.modal-title'); projectActionsModalBody = projectActionsModal.find('.modal-body'); projectActionsModalFooter = projectActionsModal.find('.modal-footer'); exportProjectsModal = $('#export-projects-modal'); exportProjectsModalHeader = exportProjectsModal.find('.modal-title'); exportProjectsModalBody = exportProjectsModal.find('.modal-body'); exportProjectsBtn = $('.export-projects-btn'); exportProjectsSubmit = $('#export-projects-modal-submit'); updateSelectedCards(); initNewProjectModal(); initEditProjectModal(); initManageUsersModal(); initExportProjectsModal(); initExportProjects(); initEditProjectButton($('.panel-project')); initArchiveRestoreButton($('.panel-project')); $('#cards-wrapper').on('click', '.folder-card-selector', function() { let folderCard = $(this).closest('.folder-card'); let folderId = folderCard.data('id'); let index = $.inArray(folderId, selectedProjectFolders); // If checkbox is checked and row ID is not in list of selected folder IDs if (this.checked && index === -1) { selectedProjectFolders.push(folderId); exportProjectsBtn.removeAttr('disabled'); // Otherwise, if checkbox is not checked and ID is in list of selected IDs } else if (!this.checked && index !== -1) { selectedProjectFolders.splice(index, 1); } refreshProjectsToolbar(); }); $('#cards-wrapper').on('click', '.project-card-selector', function() { let projectsToolbar = $('#projectsToolbar'); let projectCard = $(this).closest('.project-card'); let projectId = projectCard.data('id'); // Determine whether ID is in the list of selected project IDs let index = $.inArray(projectId, selectedProjects); // If checkbox is checked and row ID is not in list of selected project IDs if (this.checked && index === -1) { $(this).closest('.panel-project').addClass('selected'); selectedProjects.push(projectId); exportProjectsBtn.removeAttr('disabled'); // Otherwise, if checkbox is not checked and ID is in list of selected IDs } else if (!this.checked && index !== -1) { $(this).closest('.panel-project').removeClass('selected'); selectedProjects.splice(index, 1); } refreshProjectsToolbar(); selectedProjects.forEach(function(id) { if ($('#projects-cards-view').find(`.panel-project[data-id="${id}"]`).hasClass('project-folder')) { projectsToolbar.find('.project-only-action').attr('disabled', true); } }); }); // initialize project tab remote loading $('.panel-project .active').removeClass('active'); $('.panel-project .panel-footer [role=tab]').off() .on('ajax:before', function() { var $this = $(this); var parentNode = $this.parents('li'); var targetId = $this.attr('aria-controls'); if (parentNode.hasClass('active')) { // TODO move to fn parentNode.removeClass('active'); $('#' + targetId).removeClass('active'); return false; } return true; }) .on('ajax:success', function(e, data) { var $this = $(this); var targetId = $this.attr('aria-controls'); var target = $('#' + targetId); var parentNode = $this.parents('ul').parent(); target.html(data.html); initUsersEditLink(parentNode); parentNode.find('.active').removeClass('active'); $this.parents('li').addClass('active'); target.addClass('active'); Comments.init('simple') }) .on('ajax:error', function() { // TODO }); } function refreshCurrentView() { if (projectsViewMode === 'cards') { loadCardsView(); } else { TABLE.draw(); } // Also refresh sidebar tree navigation $.ajax({ url: $('#projects-cards-view').data('projects-sidebar-url'), type: 'GET', dataType: 'json', success: function(data) { $('#slide-panel .tree').html(data.html); Sidebar.loadLastState(); } }); } function loadCardsView() { // Load HTML with projects list var viewContainer = $('#cards-wrapper'); // animateSpinner(viewContainer, true); $.ajax({ url: viewContainer.data('projects-cards-url'), type: 'GET', dataType: 'json', data: { filter: projectsViewFilter, sort: projectsViewSort }, success: function(data) { viewContainer.find('.card').remove(); viewContainer.append(data.html); init(); }, error: function() { viewContainer.html('Error loading project list'); } }); } function initProjectsViewFilter() { $('.projects-view-filter').click(function(event) { event.preventDefault(); event.stopPropagation(); if ($(this).data('filter') === projectsViewFilter) { return; } $('.projects-view-filter').removeClass('active'); $(this).addClass('active'); selectedProjects = []; projectsViewFilter = $(this).data('filter'); projectsViewFilterChanged = true; if ($('#projects-cards-view').hasClass('active')) { loadCardsView(); } else if (!$.isEmptyObject(TABLE)) { TABLE.draw(); } }); } function initProjectsViewModeSwitch() { $('input[name=projects-view-mode-selector]').off() .on('change', function() { if ($(this).val() === projectsViewMode) { return; } projectsViewMode = $(this).val(); if (projectsChanged) { refreshCurrentView(); } projectsChanged = false; }) .on('click', function() { $(this).next().click(); }); } function initSorting() { $('#sortMenuDropdown a').click(function(event) { event.preventDefault(); event.stopPropagation(); if (projectsViewSort !== $(this).data('sort')) { $('#sortMenuDropdown a').removeClass('disabled'); projectsViewSort = $(this).data('sort'); $('#sortMenu').html(I18n.t('general.sort.' + projectsViewSort + '_html')); loadCardsView(); $(this).addClass('disabled'); $('#sortMenu').dropdown('toggle'); } }); } function initProjectsFilter() { let $projectsFilter = $('#projectsToolbar .projects-filters'); let $membersFilter = $('.assignee-filter', $projectsFilter); let $foldersCB = $('#folder_search', $projectsFilter); let $createdOnFilter = $('#calendarStartDate', $projectsFilter); let $dueFilter = $('#calendarDueDate', $projectsFilter); dropdownSelector.init($membersFilter); // Clear filters $('.clear-button', $projectsFilter).click((e) => { e.stopPropagation(); e.preventDefault(); dropdownSelector.clearData($membersFilter); $createdOnFilter.data('DateTimePicker').clear(); $dueFilter.data('DateTimePicker').clear(); $foldersCB.prop('checked', false); }); // Prevent filter window close $($projectsFilter).click((e) => { if (!$(e.target).is('input')) { e.stopPropagation(); e.preventDefault(); dropdownSelector.closeDropdown($membersFilter); } }); } // Updates "Select all" control in a data table function updateDataTableSelectAllCtrl() { var $table = TABLE.table().node(); var $header = TABLE.table().header(); var $chkboxAll = $('.project-row-selector', $table); var $chkboxChecked = $('.project-row-selector:checked', $table); var chkboxSelectAll = $('input[name="select_all"]', $header).get(0); // If none of the checkboxes are checked if ($chkboxChecked.length === 0) { chkboxSelectAll.checked = false; if ('indeterminate' in chkboxSelectAll) { chkboxSelectAll.indeterminate = false; } // If all of the checkboxes are checked } else if ($chkboxChecked.length === $chkboxAll.length) { chkboxSelectAll.checked = true; if ('indeterminate' in chkboxSelectAll) { chkboxSelectAll.indeterminate = false; } // If some of the checkboxes are checked } else { chkboxSelectAll.checked = true; if ('indeterminate' in chkboxSelectAll) { chkboxSelectAll.indeterminate = true; } } } function initRowSelection() { // Handle clicks on checkbox $('.dt-body-center .project-row-selector').change(function(e) { // Get row ID var $row = $(this).closest('tr'); var data = TABLE.row($row).data(); var rowId = data.DT_RowId; // Determine whether row ID is in the list of selected project IDs var index = $.inArray(rowId, selectedProjects); // If checkbox is checked and row ID is not in list of selected project IDs if (this.checked && index === -1) { selectedProjects.push(rowId); exportProjectsBtn.removeAttr('disabled'); // Otherwise, if checkbox is not checked and ID is in list of selected IDs } else if (!this.checked && index !== -1) { selectedProjects.splice(index, 1); if (selectedProjects.length === 0) { exportProjectsBtn.attr('disabled', 'disabled'); } } updateDataTableSelectAllCtrl(); e.stopPropagation(); }); // Handle click on "Select all" control $('.dataTables_scrollHead input[name="select_all"]').change(function(e) { if (this.checked) { $('.project-row-selector:not(:checked)').trigger('click'); } else { $('.project-row-selector:checked').trigger('click'); } // Prevent click event from propagating to parent e.stopPropagation(); }); } function updateSelectedRows() { TABLE.rows().every(function() { var rowSelector = $(this.node()).find('input[type="checkbox"]'); var rowId = this.data().DT_RowId; if ($.inArray(rowId, selectedProjects) !== -1) { rowSelector.prop('checked', true); } else { rowSelector.prop('checked', false); } }); updateDataTableSelectAllCtrl(); } function dataTableInit() { var TABLE_ID = '#projects-overview-table'; TABLE = $(TABLE_ID).DataTable({ dom: "R<'row'<'col-sm-9-custom toolbar'l><'col-sm-3-custom'f>><'row'<'col-sm-12't>><'row'<'col-sm-7'i><'col-sm-5'p>>", stateSave: true, stateDuration: 0, processing: true, serverSide: true, scrollY: '64vh', scrollCollapse: true, destroy: true, ajax: { url: $(TABLE_ID).data('source'), global: false, type: 'POST', data: function(params) { params.filter = projectsViewFilter; // return { ...params, ...{ filter: projectsViewFilter } }; } }, colReorder: { fixedColumnsLeft: 9 }, columnDefs: [{ // Checkbox column needs special handling targets: 0, searchable: false, orderable: false, className: 'dt-body-center', sWidth: '1%', render: function() { return ""; } }, { targets: 8, searchable: false, orderable: false, className: 'dt-body-center', sWidth: '1%' }], oLanguage: { sSearch: I18n.t('general.filter') }, rowCallback: function(row, data) { // Get row ID var rowId = data.DT_RowId; var dropdown = $(row).find('.dropdown'); var dropdownCell = dropdown.closest('td'); // If row ID is in the list of selected row IDs if ($.inArray(rowId, selectedProjects) !== -1) { $(row).find('input[type="checkbox"]').prop('checked', true); } initEditProjectButton($(row)); initArchiveRestoreButton($(row)); dropdown.off().on('show.bs.dropdown', function() { $('body').append(dropdown.css({ left: dropdown.offset().left, position: 'absolute', top: dropdown.offset().top }).detach()); }); dropdown.off().on('hidden.bs.dropdown', function() { dropdownCell.append(dropdown.removeAttr('style').detach()); }); }, order: [[2, 'asc']], columns: [ { data: 'checkbox' }, { data: 'status' }, { data: 'name' }, { data: 'start' }, { data: 'visibility' }, { data: 'users' }, { data: 'experiments' }, { data: 'tasks' }, { data: 'actions' } ], fnDrawCallback: function() { animateSpinner(this, false); updateDataTableSelectAllCtrl(); initRowSelection(); initFormSubmitLinks($(this)); }, stateLoadCallback: function(settings, callback) { $.ajax({ url: $(TABLE_ID).data('state-load-source'), dataType: 'json', type: 'GET', success: function(json) { callback(json.state); } }); }, stateSaveCallback: function() { // Don't do anything, state will be updated at backend, based on params } }); // Handle click on table cells with checkboxes $(TABLE_ID).off().on('click', 'tbody td', function(e) { if ($(e.target).is( '.project-row-selector, .active-project-link, button, span' )) { // Skip if clicking on selector checkbox, links and buttons return; } $(this).parent().find('.project-row-selector').trigger('click'); }); return TABLE; } $('.projects-view-mode-switch a').off().on('shown.bs.tab', function(event) { if ($(event.target).data('mode') === 'table') { $('#cards-wrapper').addClass('list'); } else { $('#cards-wrapper').removeClass('list'); } }); initProjectsViewFilter(); initProjectsViewModeSwitch(); initSorting(); loadCardsView(); initProjectsFilter(); }(window));