Add pagination to projects list [SCI-6655]

This commit is contained in:
Anton 2022-04-07 12:22:52 +02:00
parent a85b78291d
commit d96103d714
6 changed files with 249 additions and 19 deletions

View file

@ -7,7 +7,7 @@
// - refresh project users tab after manage user modal is closed
// - refactor view handling using library, ex. backbone.js
/* global HelperModule dropdownSelector Sidebar Turbolinks filterDropdown */
/* global HelperModule dropdownSelector Sidebar Turbolinks filterDropdown InfiniteScroll */
var ProjectsIndex = (function() {
const PERMISSIONS = ['editable', 'archivable', 'restorable', 'moveable', 'deletable'];
@ -16,6 +16,7 @@ var ProjectsIndex = (function() {
var cardsWrapper = '#cardsWrapper';
var editProjectModal = '#edit-modal';
var moveToModal = '#move-to-modal';
var pageSize = 20;
var exportProjectsModal = null;
var exportProjectsModalHeader = null;
@ -443,23 +444,35 @@ var ProjectsIndex = (function() {
});
}
function loadPlaceHolder() {
let palceholder = '';
$.each(Array(pageSize), function() {
palceholder += $('#projectPlaceholder').html();
});
$(palceholder).insertAfter($(cardsWrapper).find('.table-header'));
}
function loadCardsView() {
var requestParams = {
view_mode: $('.projects-index').data('view-mode'),
sort: projectsCurrentSort,
search: projectsViewSearch,
members: membersFilter,
created_on_from: createdOnFromFilter,
created_on_to: createdOnToFilter,
folders_search: lookInsideFolders,
archived_on_from: archivedOnFromFilter,
archived_on_to: archivedOnToFilter
};
var viewContainer = $(cardsWrapper);
var cardsUrl = viewContainer.data('projects-cards-url');
loadPlaceHolder();
$.ajax({
url: viewContainer.data('projects-cards-url'),
url: cardsUrl,
type: 'GET',
dataType: 'json',
data: {
view_mode: $('.projects-index').data('view-mode'),
sort: projectsCurrentSort,
search: projectsViewSearch,
members: membersFilter,
created_on_from: createdOnFromFilter,
created_on_to: createdOnToFilter,
folders_search: lookInsideFolders,
archived_on_from: archivedOnFromFilter,
archived_on_to: archivedOnToFilter
},
data: { ...requestParams, ...{ page: 1 } },
success: function(data) {
$('#breadcrumbsWrapper').html(data.breadcrumbs_html);
$(projectsWrapper).find('.projects-title').html(data.title);
@ -474,6 +487,23 @@ var ProjectsIndex = (function() {
selectedProjects.length = 0;
selectedProjectFolders.length = 0;
updateProjectsToolbar();
if (data.filtered) {
InfiniteScroll.removeScroll(cardsWrapper);
} else {
InfiniteScroll.init(cardsWrapper, {
url: cardsUrl,
eventTarget: window,
placeholderTemplate: '#projectPlaceholder',
endOfListTemplate: '#projectEndOfList',
pageSize: pageSize,
customResponse: (response) => {
$(response.cards_html).appendTo(cardsWrapper);
},
customParams: (params) => {
return { ...params, ...requestParams };
}
});
}
},
error: function() {
viewContainer.html('Error loading project list');
@ -490,6 +520,7 @@ var ProjectsIndex = (function() {
$(projectsPageSelector).find('.cards-switch .button-to').removeClass('selected');
$(ev.target).find('.button-to').addClass('selected');
$(ev.target).parents('.dropdown.view-switch').removeClass('open');
InfiniteScroll.loadMore(cardsWrapper);
})
.on('ajax:error', '.change-projects-view-type-form', function(ev, data) {
HelperModule.flashAlertMsg(data.responseJSON.flash, 'danger');

View file

@ -6,7 +6,8 @@ var InfiniteScroll = (function() {
}
function scrollNotVisible($container) {
return (getScrollHeight($container) - $container.height() - 150 <= 0);
let eventTarget = $($container.data('config').eventTarget || $container);
return scrollHitBottom(eventTarget[0]);
}
function loadData($container, page = 1) {
@ -15,8 +16,9 @@ var InfiniteScroll = (function() {
if ($container.hasClass('loading') || $container.hasClass('last-page')) return;
$container.addClass('loading');
renderPlaceholder($container);
$.get($container.data('config').url, params, function(result) {
$container.find('.placeholder-block').remove();
if ($container.data('config').customResponse) {
$container.data('config').customResponse(result, $container);
} else {
@ -27,6 +29,9 @@ var InfiniteScroll = (function() {
$container.data('next-page', result.next_page);
} else {
$container.addClass('last-page');
if ($container.data('config').endOfListTemplate) {
$($($container.data('config').endOfListTemplate).html()).appendTo($container);
}
}
$container.removeClass('loading');
@ -44,20 +49,73 @@ var InfiniteScroll = (function() {
var $container = $(object);
$container.data('next-page', 2);
$container.data('config', config);
if (config.loadFirstPage) {
loadData($container, 1);
}
$container.on('scroll', () => {
if ($container.scrollTop() + $container.height() > getScrollHeight($container) - 150 && !$container.hasClass('last-page')) {
let eventTarget = $($container.data('config').eventTarget || $container);
eventTarget.on('scroll', () => {
if (scrollHitBottom(eventTarget[0]) && !$container.hasClass('last-page')) {
loadData($container, $container.data('next-page'));
}
});
if (scrollNotVisible($container)) {
loadData($container, $container.data('next-page'));
}
}
// support functions
// Full scroll height
function scrollHeight(con) {
return con.scrollHeight || document.documentElement.scrollHeight;
}
// Top scroll position
function scrollTop(con) {
return con.scrollTop || con.scrollY || 0;
}
// Get container size
function containerSize(con) {
return con.innerHeight || con.offsetHeight;
}
// Container position
function containerPosition(con) {
return scrollTop(con) + containerSize(con);
}
// Check when load next page
function scrollHitBottom(con) {
return scrollHeight(con) - containerPosition(con) <= 0;
}
function removeScroll(con) {
let $container = $(con);
if (!$container.data('config')) {
return;
}
let eventTarget = $($container.data('config').eventTarget) || $container;
$container.data('config', null);
$container.data('next-page', null);
eventTarget.off('scroll');
}
function renderPlaceholder($container) {
let palceholder = '';
$.each(Array($container.data('config').pageSize || 10), function() {
palceholder += $($container.data('config').placeholderTemplate).html();
});
$(palceholder).addClass('placeholder-block').appendTo($container);
}
return {
init: (object, config) => {
removeScroll(object);
initScroll(object, config);
},
resetScroll: (object) => {
@ -65,6 +123,15 @@ var InfiniteScroll = (function() {
if (scrollNotVisible($(object))) {
loadData($(object), $(object).data('next-page'));
}
},
removeScroll: (object) => {
removeScroll(object);
},
loadMore: (object) => {
let $container = $(object);
if (scrollNotVisible($container)) {
loadData($container, $container.data('next-page'));
}
}
};
}());

View file

@ -623,6 +623,11 @@ li.module-hover {
--card-min-width: 291px;
--list-columns-number: 6;
&.last-page {
position: relative;
padding-bottom: 5em;
}
.projects-group {
grid-column: 1/-1;
margin: 0;
@ -784,6 +789,58 @@ li.module-hover {
}
}
}
&.project-placeholder {
align-items: center;
background-color: $color-white;
border-radius: $border-radius-default;
box-shadow: $flyout-shadow;
display: flex;
.placeholder-element {
animation-name: placeholder-pulsing;
animation-duration: 2s;
animation-iteration-count: infinite;
background-color: $color-alto;
border-radius: $border-radius-default;
height: 18px;
&.line-0 {
flex-basis: 80%;
}
&.line-1 {
flex-basis: 65%;
}
&.line-2 {
flex-basis: 85%;
}
&.line-3 {
flex-basis: 25%;
}
&.circle {
border-radius: 50%;
height: 27px;
margin-left: .5em;
width: 27px;
}
@keyframes placeholder-pulsing {
0% {
opacity: 1;
}
50% {
opacity: .5;
}
100% {
opacity: 1;
}
}
}
}
}
&.list {
@ -884,9 +941,59 @@ li.module-hover {
position: initial;
}
}
&.project-placeholder {
display: contents;
.placeholder-element {
display: none;
padding: .5em 0;
}
.line-0 {
display: block;
grid-column: 2;
width: 50%;
}
.line-1 {
display: block;
grid-column: 3;
width: 75%;
}
.line-2 {
display: block;
grid-column: 4;
width: 75%;
}
.circle-0 {
display: block;
grid-column: 5;
}
}
}
}
}
.project-list-end-placeholder {
align-items: center;
background-color: $color-concrete;
bottom: 1em;
display: flex;
height: 3em;
left: calc(50% - 150px);
margin: 0 auto;
padding: 1em;
position: absolute;
width: 300px;
> * {
flex-grow: 1;
text-align: center;
}
}
}
&.active {

View file

@ -39,6 +39,7 @@ class ProjectsController < ApplicationController
if filters_included?
render json: {
toolbar_html: render_to_string(partial: 'projects/index/toolbar.html.erb'),
filtered: true,
cards_html: render_to_string(
partial: 'projects/index/team_projects_grouped_by_folder.html.erb',
locals: { projects_by_folder: overview_service.grouped_by_folder_project_cards }
@ -63,14 +64,18 @@ class ProjectsController < ApplicationController
projects_cards_url = cards_projects_url
end
cards = Kaminari.paginate_array(overview_service.project_and_folder_cards)
.page(params[:page] || 1).per(20)
render json: {
projects_cards_url: projects_cards_url,
breadcrumbs_html: breadcrumbs_html,
title: title,
next_page: cards.next_page,
toolbar_html: render_to_string(partial: 'projects/index/toolbar.html.erb'),
cards_html: render_to_string(
partial: 'projects/index/team_projects.html.erb',
locals: { cards: overview_service.project_and_folder_cards }
locals: { cards: cards }
)
}
end

View file

@ -38,4 +38,23 @@
</div>
</div>
<template id="projectPlaceholder">
<div class="project-placeholder card">
<% 4.times do |i| %>
<div class="placeholder-element line-<%= i %>"></div>
<% end %>
<% 3.times do |i| %>
<div class="placeholder-element circle circle-<%= i %>"></div>
<% end %>
</div>
</template>
<template id="projectEndOfList">
<div class="project-list-end-placeholder">
<i class="fas fa-flag-checkered"></i>
<span><%= t('.end_of_list_placeholder') %></span>
<i class="fas fa-flag-checkered"></i>
</div>
</template>
<%= javascript_include_tag "projects/index" %>

View file

@ -419,6 +419,7 @@ en:
users: "Members"
name: "Project name"
archived_date: "Archived"
end_of_list_placeholder: 'Youve reached the end of the list'
folder:
description: "%{projects_count} projects | %{folders_count} folders"
modal_new_project_folder: