mirror of
https://github.com/scinote-eln/scinote-web.git
synced 2025-10-08 21:06:24 +08:00
Add pagination to projects list [SCI-6655]
This commit is contained in:
parent
a85b78291d
commit
d96103d714
6 changed files with 249 additions and 19 deletions
|
@ -7,7 +7,7 @@
|
||||||
// - refresh project users tab after manage user modal is closed
|
// - refresh project users tab after manage user modal is closed
|
||||||
// - refactor view handling using library, ex. backbone.js
|
// - refactor view handling using library, ex. backbone.js
|
||||||
|
|
||||||
/* global HelperModule dropdownSelector Sidebar Turbolinks filterDropdown */
|
/* global HelperModule dropdownSelector Sidebar Turbolinks filterDropdown InfiniteScroll */
|
||||||
|
|
||||||
var ProjectsIndex = (function() {
|
var ProjectsIndex = (function() {
|
||||||
const PERMISSIONS = ['editable', 'archivable', 'restorable', 'moveable', 'deletable'];
|
const PERMISSIONS = ['editable', 'archivable', 'restorable', 'moveable', 'deletable'];
|
||||||
|
@ -16,6 +16,7 @@ var ProjectsIndex = (function() {
|
||||||
var cardsWrapper = '#cardsWrapper';
|
var cardsWrapper = '#cardsWrapper';
|
||||||
var editProjectModal = '#edit-modal';
|
var editProjectModal = '#edit-modal';
|
||||||
var moveToModal = '#move-to-modal';
|
var moveToModal = '#move-to-modal';
|
||||||
|
var pageSize = 20;
|
||||||
|
|
||||||
var exportProjectsModal = null;
|
var exportProjectsModal = null;
|
||||||
var exportProjectsModalHeader = null;
|
var exportProjectsModalHeader = null;
|
||||||
|
@ -443,13 +444,16 @@ var ProjectsIndex = (function() {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function loadPlaceHolder() {
|
||||||
|
let palceholder = '';
|
||||||
|
$.each(Array(pageSize), function() {
|
||||||
|
palceholder += $('#projectPlaceholder').html();
|
||||||
|
});
|
||||||
|
$(palceholder).insertAfter($(cardsWrapper).find('.table-header'));
|
||||||
|
}
|
||||||
|
|
||||||
function loadCardsView() {
|
function loadCardsView() {
|
||||||
var viewContainer = $(cardsWrapper);
|
var requestParams = {
|
||||||
$.ajax({
|
|
||||||
url: viewContainer.data('projects-cards-url'),
|
|
||||||
type: 'GET',
|
|
||||||
dataType: 'json',
|
|
||||||
data: {
|
|
||||||
view_mode: $('.projects-index').data('view-mode'),
|
view_mode: $('.projects-index').data('view-mode'),
|
||||||
sort: projectsCurrentSort,
|
sort: projectsCurrentSort,
|
||||||
search: projectsViewSearch,
|
search: projectsViewSearch,
|
||||||
|
@ -459,7 +463,16 @@ var ProjectsIndex = (function() {
|
||||||
folders_search: lookInsideFolders,
|
folders_search: lookInsideFolders,
|
||||||
archived_on_from: archivedOnFromFilter,
|
archived_on_from: archivedOnFromFilter,
|
||||||
archived_on_to: archivedOnToFilter
|
archived_on_to: archivedOnToFilter
|
||||||
},
|
};
|
||||||
|
var viewContainer = $(cardsWrapper);
|
||||||
|
var cardsUrl = viewContainer.data('projects-cards-url');
|
||||||
|
|
||||||
|
loadPlaceHolder();
|
||||||
|
$.ajax({
|
||||||
|
url: cardsUrl,
|
||||||
|
type: 'GET',
|
||||||
|
dataType: 'json',
|
||||||
|
data: { ...requestParams, ...{ page: 1 } },
|
||||||
success: function(data) {
|
success: function(data) {
|
||||||
$('#breadcrumbsWrapper').html(data.breadcrumbs_html);
|
$('#breadcrumbsWrapper').html(data.breadcrumbs_html);
|
||||||
$(projectsWrapper).find('.projects-title').html(data.title);
|
$(projectsWrapper).find('.projects-title').html(data.title);
|
||||||
|
@ -474,6 +487,23 @@ var ProjectsIndex = (function() {
|
||||||
selectedProjects.length = 0;
|
selectedProjects.length = 0;
|
||||||
selectedProjectFolders.length = 0;
|
selectedProjectFolders.length = 0;
|
||||||
updateProjectsToolbar();
|
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() {
|
error: function() {
|
||||||
viewContainer.html('Error loading project list');
|
viewContainer.html('Error loading project list');
|
||||||
|
@ -490,6 +520,7 @@ var ProjectsIndex = (function() {
|
||||||
$(projectsPageSelector).find('.cards-switch .button-to').removeClass('selected');
|
$(projectsPageSelector).find('.cards-switch .button-to').removeClass('selected');
|
||||||
$(ev.target).find('.button-to').addClass('selected');
|
$(ev.target).find('.button-to').addClass('selected');
|
||||||
$(ev.target).parents('.dropdown.view-switch').removeClass('open');
|
$(ev.target).parents('.dropdown.view-switch').removeClass('open');
|
||||||
|
InfiniteScroll.loadMore(cardsWrapper);
|
||||||
})
|
})
|
||||||
.on('ajax:error', '.change-projects-view-type-form', function(ev, data) {
|
.on('ajax:error', '.change-projects-view-type-form', function(ev, data) {
|
||||||
HelperModule.flashAlertMsg(data.responseJSON.flash, 'danger');
|
HelperModule.flashAlertMsg(data.responseJSON.flash, 'danger');
|
||||||
|
|
|
@ -6,7 +6,8 @@ var InfiniteScroll = (function() {
|
||||||
}
|
}
|
||||||
|
|
||||||
function scrollNotVisible($container) {
|
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) {
|
function loadData($container, page = 1) {
|
||||||
|
@ -15,8 +16,9 @@ var InfiniteScroll = (function() {
|
||||||
|
|
||||||
if ($container.hasClass('loading') || $container.hasClass('last-page')) return;
|
if ($container.hasClass('loading') || $container.hasClass('last-page')) return;
|
||||||
$container.addClass('loading');
|
$container.addClass('loading');
|
||||||
|
renderPlaceholder($container);
|
||||||
$.get($container.data('config').url, params, function(result) {
|
$.get($container.data('config').url, params, function(result) {
|
||||||
|
$container.find('.placeholder-block').remove();
|
||||||
if ($container.data('config').customResponse) {
|
if ($container.data('config').customResponse) {
|
||||||
$container.data('config').customResponse(result, $container);
|
$container.data('config').customResponse(result, $container);
|
||||||
} else {
|
} else {
|
||||||
|
@ -27,6 +29,9 @@ var InfiniteScroll = (function() {
|
||||||
$container.data('next-page', result.next_page);
|
$container.data('next-page', result.next_page);
|
||||||
} else {
|
} else {
|
||||||
$container.addClass('last-page');
|
$container.addClass('last-page');
|
||||||
|
if ($container.data('config').endOfListTemplate) {
|
||||||
|
$($($container.data('config').endOfListTemplate).html()).appendTo($container);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
$container.removeClass('loading');
|
$container.removeClass('loading');
|
||||||
|
|
||||||
|
@ -44,20 +49,73 @@ var InfiniteScroll = (function() {
|
||||||
var $container = $(object);
|
var $container = $(object);
|
||||||
$container.data('next-page', 2);
|
$container.data('next-page', 2);
|
||||||
$container.data('config', config);
|
$container.data('config', config);
|
||||||
|
|
||||||
if (config.loadFirstPage) {
|
if (config.loadFirstPage) {
|
||||||
loadData($container, 1);
|
loadData($container, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
$container.on('scroll', () => {
|
let eventTarget = $($container.data('config').eventTarget || $container);
|
||||||
if ($container.scrollTop() + $container.height() > getScrollHeight($container) - 150 && !$container.hasClass('last-page')) {
|
eventTarget.on('scroll', () => {
|
||||||
|
if (scrollHitBottom(eventTarget[0]) && !$container.hasClass('last-page')) {
|
||||||
loadData($container, $container.data('next-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 {
|
return {
|
||||||
init: (object, config) => {
|
init: (object, config) => {
|
||||||
|
removeScroll(object);
|
||||||
initScroll(object, config);
|
initScroll(object, config);
|
||||||
},
|
},
|
||||||
resetScroll: (object) => {
|
resetScroll: (object) => {
|
||||||
|
@ -65,6 +123,15 @@ var InfiniteScroll = (function() {
|
||||||
if (scrollNotVisible($(object))) {
|
if (scrollNotVisible($(object))) {
|
||||||
loadData($(object), $(object).data('next-page'));
|
loadData($(object), $(object).data('next-page'));
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
removeScroll: (object) => {
|
||||||
|
removeScroll(object);
|
||||||
|
},
|
||||||
|
loadMore: (object) => {
|
||||||
|
let $container = $(object);
|
||||||
|
if (scrollNotVisible($container)) {
|
||||||
|
loadData($container, $container.data('next-page'));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}());
|
}());
|
||||||
|
|
|
@ -623,6 +623,11 @@ li.module-hover {
|
||||||
--card-min-width: 291px;
|
--card-min-width: 291px;
|
||||||
--list-columns-number: 6;
|
--list-columns-number: 6;
|
||||||
|
|
||||||
|
&.last-page {
|
||||||
|
position: relative;
|
||||||
|
padding-bottom: 5em;
|
||||||
|
}
|
||||||
|
|
||||||
.projects-group {
|
.projects-group {
|
||||||
grid-column: 1/-1;
|
grid-column: 1/-1;
|
||||||
margin: 0;
|
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 {
|
&.list {
|
||||||
|
@ -884,7 +941,57 @@ li.module-hover {
|
||||||
position: initial;
|
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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -39,6 +39,7 @@ class ProjectsController < ApplicationController
|
||||||
if filters_included?
|
if filters_included?
|
||||||
render json: {
|
render json: {
|
||||||
toolbar_html: render_to_string(partial: 'projects/index/toolbar.html.erb'),
|
toolbar_html: render_to_string(partial: 'projects/index/toolbar.html.erb'),
|
||||||
|
filtered: true,
|
||||||
cards_html: render_to_string(
|
cards_html: render_to_string(
|
||||||
partial: 'projects/index/team_projects_grouped_by_folder.html.erb',
|
partial: 'projects/index/team_projects_grouped_by_folder.html.erb',
|
||||||
locals: { projects_by_folder: overview_service.grouped_by_folder_project_cards }
|
locals: { projects_by_folder: overview_service.grouped_by_folder_project_cards }
|
||||||
|
@ -63,14 +64,18 @@ class ProjectsController < ApplicationController
|
||||||
projects_cards_url = cards_projects_url
|
projects_cards_url = cards_projects_url
|
||||||
end
|
end
|
||||||
|
|
||||||
|
cards = Kaminari.paginate_array(overview_service.project_and_folder_cards)
|
||||||
|
.page(params[:page] || 1).per(20)
|
||||||
|
|
||||||
render json: {
|
render json: {
|
||||||
projects_cards_url: projects_cards_url,
|
projects_cards_url: projects_cards_url,
|
||||||
breadcrumbs_html: breadcrumbs_html,
|
breadcrumbs_html: breadcrumbs_html,
|
||||||
title: title,
|
title: title,
|
||||||
|
next_page: cards.next_page,
|
||||||
toolbar_html: render_to_string(partial: 'projects/index/toolbar.html.erb'),
|
toolbar_html: render_to_string(partial: 'projects/index/toolbar.html.erb'),
|
||||||
cards_html: render_to_string(
|
cards_html: render_to_string(
|
||||||
partial: 'projects/index/team_projects.html.erb',
|
partial: 'projects/index/team_projects.html.erb',
|
||||||
locals: { cards: overview_service.project_and_folder_cards }
|
locals: { cards: cards }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
|
@ -38,4 +38,23 @@
|
||||||
</div>
|
</div>
|
||||||
</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" %>
|
<%= javascript_include_tag "projects/index" %>
|
||||||
|
|
|
@ -419,6 +419,7 @@ en:
|
||||||
users: "Members"
|
users: "Members"
|
||||||
name: "Project name"
|
name: "Project name"
|
||||||
archived_date: "Archived"
|
archived_date: "Archived"
|
||||||
|
end_of_list_placeholder: 'You’ve reached the end of the list'
|
||||||
folder:
|
folder:
|
||||||
description: "%{projects_count} projects | %{folders_count} folders"
|
description: "%{projects_count} projects | %{folders_count} folders"
|
||||||
modal_new_project_folder:
|
modal_new_project_folder:
|
||||||
|
|
Loading…
Add table
Reference in a new issue