Use infinite-scrolling on lists

This allows to reduce the loading time of a big board.

Note that there is an infinite scroll implementation in the mixins,
but this doesn't fit well as the cards in the list can have arbitrary
height.

The idea to rely on the visibility of a spinner is based on
http://www.meteorpedia.com/read/Infinite_Scrolling
This commit is contained in:
Benjamin Tissoires 2019-01-30 16:25:23 +01:00
parent 1b11123797
commit 30d082e709
3 changed files with 95 additions and 1 deletions

View file

@ -211,6 +211,9 @@
max-height: 250px max-height: 250px
overflow: hidden overflow: hidden
.sk-spinner-list
margin-top: unset !important
list-header-color(background, color...) list-header-color(background, color...)
border-bottom: 6px solid background border-bottom: 6px solid background

View file

@ -4,7 +4,7 @@ template(name="listBody")
if cards.count if cards.count
+inlinedForm(autoclose=false position="top") +inlinedForm(autoclose=false position="top")
+addCardForm(listId=_id position="top") +addCardForm(listId=_id position="top")
each (cards (idOrNull ../../_id)) each (cardsWithLimit (idOrNull ../../_id))
a.minicard-wrapper.js-minicard(href=absoluteUrl a.minicard-wrapper.js-minicard(href=absoluteUrl
class="{{#if cardIsSelected}}is-selected{{/if}}" class="{{#if cardIsSelected}}is-selected{{/if}}"
class="{{#if MultiSelection.isSelected _id}}is-checked{{/if}}") class="{{#if MultiSelection.isSelected _id}}is-checked{{/if}}")
@ -12,6 +12,16 @@ template(name="listBody")
.materialCheckBox.multi-selection-checkbox.js-toggle-multi-selection( .materialCheckBox.multi-selection-checkbox.js-toggle-multi-selection(
class="{{#if MultiSelection.isSelected _id}}is-checked{{/if}}") class="{{#if MultiSelection.isSelected _id}}is-checked{{/if}}")
+minicard(this) +minicard(this)
if (showSpinner (idOrNull ../../_id))
.sk-spinner.sk-spinner-wave.sk-spinner-list(
class=currentBoard.colorClass
id="showMoreResults")
.sk-rect1
.sk-rect2
.sk-rect3
.sk-rect4
.sk-rect5
if canSeeAddCard if canSeeAddCard
+inlinedForm(autoclose=false position="bottom") +inlinedForm(autoclose=false position="bottom")
+addCardForm(listId=_id position="bottom") +addCardForm(listId=_id position="bottom")

View file

@ -1,6 +1,34 @@
const subManager = new SubsManager(); const subManager = new SubsManager();
const InfiniteScrollIter = 10;
BlazeComponent.extendComponent({ BlazeComponent.extendComponent({
onCreated() {
// for infinite scrolling
this.cardlimit = new ReactiveVar(InfiniteScrollIter);
},
onRendered() {
const domElement = this.find('.js-perfect-scrollbar');
this.$(domElement).on('scroll', () => this.updateList(domElement));
$(window).on(`resize.${this.data().listId}`, () => this.updateList(domElement));
// we add a Mutation Observer to allow propagations of cardlimit
// when the spinner stays in the current view (infinite scrolling)
this.mutationObserver = new MutationObserver(() => this.updateList(domElement));
this.mutationObserver.observe(domElement, {
childList: true,
});
this.updateList(domElement);
},
onDestroyed() {
$(window).off(`resize.${this.data().listId}`);
this.mutationObserver.disconnect();
},
mixins() { mixins() {
return [Mixins.PerfectScrollbar]; return [Mixins.PerfectScrollbar];
}, },
@ -60,6 +88,13 @@ BlazeComponent.extendComponent({
type: 'cardType-card', type: 'cardType-card',
}); });
// if the displayed card count is less than the total cards in the list,
// we need to increment the displayed card count to prevent the spinner
// to appear
const cardCount = this.data().cards(this.idOrNull(swimlaneId)).count();
if (pthis.cardlimit.get() < cardCount) {
this.cardlimit.set(this.cardlimit.get() + InfiniteScrollIter);
}
// In case the filter is active we need to add the newly inserted card in // In case the filter is active we need to add the newly inserted card in
// the list of exceptions -- cards that are not filtered. Otherwise the // the list of exceptions -- cards that are not filtered. Otherwise the
@ -119,6 +154,52 @@ BlazeComponent.extendComponent({
return undefined; return undefined;
}, },
cardsWithLimit(swimlaneId) {
const limit = this.cardlimit.get();
const selector = {
listId: this.currentData()._id,
archived: false,
};
if (swimlaneId)
selector.swimlaneId = swimlaneId;
return Cards.find(Filter.mongoSelector(selector), {
sort: ['sort'],
limit,
});
},
spinnerInView(container) {
const parentViewHeight = container.clientHeight;
const bottomViewPosition = container.scrollTop + parentViewHeight;
const spinner = this.find('.sk-spinner-list');
const threshold = spinner.offsetTop;
return bottomViewPosition > threshold;
},
showSpinner(swimlaneId) {
const list = Template.currentData();
return list.cards(swimlaneId).count() > this.cardlimit.get();
},
updateList(container) {
// first, if the spinner is not rendered, we have reached the end of
// the list of cards, so skip and disable firing the events
const target = this.find('.sk-spinner-list');
if (!target) {
this.$(container).off('scroll');
$(window).off(`resize.${this.data().listId}`);
return;
}
if (this.spinnerInView(container)) {
this.cardlimit.set(this.cardlimit.get() + InfiniteScrollIter);
Ps.update(container);
}
},
canSeeAddCard() { canSeeAddCard() {
return !this.reachedWipLimit() && Meteor.user() && Meteor.user().isBoardMember() && !Meteor.user().isCommentOnly(); return !this.reachedWipLimit() && Meteor.user() && Meteor.user().isBoardMember() && !Meteor.user().isCommentOnly();
}, },