Skip to content

Commit 756b78b

Browse files
authoredFeb 18, 2020
feat(ui5-list): add infinite-scroll capability (#1220)
- introduce infiniteScroll property, when set an event "loadMore" will be fired when user scrolls to the very bottom of the list, then the user might load new items - introduce "loadMore" event - introduce "busy" property to display a loading indicator
1 parent e79dcc8 commit 756b78b

File tree

8 files changed

+289
-15
lines changed

8 files changed

+289
-15
lines changed
 

‎packages/main/src/List.hbs

+8-1
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
class="ui5-list-root"
33
@focusin="{{_onfocusin}}"
44
@keydown="{{_onkeydown}}"
5+
@scroll="{{_onScroll}}"
56
>
67
<!-- header -->
78
{{#if header.length}}
@@ -33,5 +34,11 @@
3334
</footer>
3435
{{/if}}
3536

37+
{{#if showBusy}}
38+
<div class="ui5-list-busy-row">
39+
<ui5-busyindicator ?active="{{busy}}" size="Medium" class="ui5-list-busy-ind"></ui5-busyindicator>
40+
</div>
41+
{{/if}}
42+
3643
<div id="{{_id}}-after" tabindex="0" class="ui5-list-focusarea"></div>
37-
</div>
44+
</div>

‎packages/main/src/List.js

+85-4
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,17 @@ import { isTabNext } from "@ui5/webcomponents-base/dist/events/PseudoEvents.js";
66
import NavigationMode from "@ui5/webcomponents-base/dist/types/NavigationMode.js";
77
import ListMode from "./types/ListMode.js";
88
import ListSeparators from "./types/ListSeparators.js";
9+
import BusyIndicator from "./BusyIndicator.js";
910

1011
// Template
1112
import ListTemplate from "./generated/templates/ListTemplate.lit.js";
1213

1314
// Styles
1415
import listCss from "./generated/themes/List.css.js";
1516

17+
const BUSYINDICATOR_HEIGHT = 48; // px
18+
const INFINITE_SCROLL_DEBOUNCE_RATE = 250; // ms
19+
1620
/**
1721
* @public
1822
*/
@@ -128,6 +132,33 @@ const metadata = {
128132
type: ListSeparators,
129133
defaultValue: ListSeparators.All,
130134
},
135+
136+
/**
137+
* Defines if the component would fire the <code>loadMore</code> event,
138+
* when the user scrolls to the bottom of the list and help achieving an "infinite scroll" effect
139+
* by adding new items each time.
140+
*
141+
* @type {boolean}
142+
* @defaultvalue false
143+
* @public
144+
* @since 1.0.0-rc.6
145+
*/
146+
infiniteScroll: {
147+
type: Boolean,
148+
},
149+
150+
/**
151+
* Defines if the component would display a loading indicator at the bottom of the list.
152+
* It's especially useful, when combined with <code>infiniteScroll</code>.
153+
*
154+
* @type {boolean}
155+
* @defaultvalue false
156+
* @public
157+
* @since 1.0.0-rc.6
158+
*/
159+
busy: {
160+
type: Boolean,
161+
},
131162
},
132163
events: /** @lends sap.ui.webcomponents.main.List.prototype */ {
133164

@@ -176,6 +207,17 @@ const metadata = {
176207
selectionComponentPressed: { type: Boolean }, // protected, indicates if the user used the selection components to change the selection
177208
},
178209
},
210+
211+
/**
212+
* Fired when the user scrolls to the bottom of the list.
213+
* <br>
214+
* <b>Note:</b> The event is fired when the <code>infiniteScroll</code> property is enabled.
215+
*
216+
* @event
217+
* @public
218+
* @since 1.0.0-rc.6
219+
*/
220+
loadMore: {},
179221
},
180222
};
181223

@@ -254,6 +296,18 @@ class List extends UI5Element {
254296
this.addEventListener("ui5-_selectionRequested", this.onSelectionRequested.bind(this));
255297
}
256298

299+
get shouldRenderH1() {
300+
return !this.header.length && this.headerText;
301+
}
302+
303+
get showNoDataText() {
304+
return this.items.length === 0 && this.noDataText;
305+
}
306+
307+
get showBusy() {
308+
return this.busy || this.infiniteScroll;
309+
}
310+
257311
onBeforeRendering() {
258312
this.prepareListItems();
259313
}
@@ -391,6 +445,13 @@ class List extends UI5Element {
391445
}
392446
}
393447

448+
_onScroll(event) {
449+
if (!this.infiniteScroll) {
450+
return;
451+
}
452+
this.debounce(this.loadMore.bind(this, event.target), INFINITE_SCROLL_DEBOUNCE_RATE);
453+
}
454+
394455
_onfocusin(event) {
395456
// If the focusin event does not origin from one of the 'triggers' - ignore it.
396457
if (!this.isForwardElement(this.getNormalizedTarget(event.target))) {
@@ -555,12 +616,32 @@ class List extends UI5Element {
555616
return focused;
556617
}
557618

558-
get shouldRenderH1() {
559-
return !this.header.length && this.headerText;
619+
loadMore(el) {
620+
const scrollTop = el.scrollTop;
621+
const height = el.offsetHeight;
622+
const scrollHeight = el.scrollHeight;
623+
624+
if (this.previousScrollPosition > scrollTop) { // skip scrolling upwards
625+
this.previousScrollPosition = scrollTop;
626+
return;
627+
}
628+
this.previousScrollPosition = scrollTop;
629+
630+
if (scrollHeight - BUSYINDICATOR_HEIGHT <= height + scrollTop) {
631+
this.fireEvent("loadMore");
632+
}
560633
}
561634

562-
get showNoDataText() {
563-
return this.items.length === 0 && this.noDataText;
635+
debounce(fn, delay) {
636+
clearTimeout(this.debounceInterval);
637+
this.debounceInterval = setTimeout(() => {
638+
this.debounceInterval = null;
639+
fn();
640+
}, delay);
641+
}
642+
643+
static async onDefine() {
644+
await BusyIndicator.define();
564645
}
565646
}
566647

‎packages/main/src/themes/List.css

+15-8
Original file line numberDiff line numberDiff line change
@@ -16,23 +16,24 @@
1616
height: 100%;
1717
position: relative;
1818
box-sizing: border-box;
19+
overflow: auto;
1920
}
2021

21-
.ui5-list-root .ui5-list-ul {
22+
.ui5-list-ul {
2223
list-style-type: none;
2324
padding: 0;
2425
margin: 0;
2526
}
2627

27-
.ui5-list-root .ui5-list-ul:focus {
28+
.ui5-list-ul:focus {
2829
outline: none;
2930
}
3031

31-
.ui5-list-root .ui5-list-focusarea {
32+
.ui5-list-focusarea {
3233
position: fixed; /* keep it in the visible viewport, so that IE does not scroll on focus */
3334
}
3435

35-
.ui5-list-root .ui5-list-header {
36+
.ui5-list-header {
3637
overflow: hidden;
3738
white-space: nowrap;
3839
text-overflow: ellipsis;
@@ -47,7 +48,7 @@
4748
border-bottom: 1px solid var(--sapGroup_TitleBorderColor);
4849
}
4950

50-
.ui5-list-root .ui5-list-footer {
51+
.ui5-list-footer {
5152
height: 2rem;
5253
box-sizing: border-box;
5354
-webkit-text-size-adjust: none; /* To improve readability Mobile Safari automatically increases the size of small text so let's disable this */
@@ -62,7 +63,7 @@
6263
text-overflow: ellipsis;
6364
}
6465

65-
.ui5-list-root .ui5-list-nodata {
66+
.ui5-list-nodata {
6667
list-style-type: none;
6768
display: -webkit-box;
6869
display: flex;
@@ -78,9 +79,15 @@
7879
font-size: var(--sapFontMediumSize);
7980
}
8081

81-
82-
.ui5-list-root .ui5-list-nodata-text {
82+
.ui5-list-nodata-text {
8383
overflow: hidden;
8484
text-overflow: ellipsis;
8585
white-space: nowrap;
86+
}
87+
88+
.ui5-list-busy-row {
89+
display: flex;
90+
align-items: center;
91+
height: var(--_ui5_list_busy_row_height);
92+
justify-content: center;
8693
}

‎packages/main/src/themes/base/sizes-parameters.css

+2
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
--_ui5_list_item_img_size: 2rem;
2020
--_ui5_list_item_img_margin: 0.5rem 0.75rem 0.5rem 0rem;
2121
--_ui5_list_item_base_height: 3rem;
22+
--_ui5_list_busy_row_height: 3rem;
2223
--_ui5_month_picker_item_height: 3rem;
2324
--_ui5_year_picker_item_height: 3rem;
2425
--_ui5_tokenizer_root_padding: 0.1875rem;
@@ -107,6 +108,7 @@
107108
--_ui5_list_item_img_size: 1.75rem;
108109
--_ui5_list_item_img_margin: 0.55rem 0.75rem 0.5rem 0rem;
109110
--_ui5_list_item_base_height: 2rem;
111+
--_ui5_list_busy_row_height: 2rem;
110112

111113
--_ui5_month_picker_item_height: 2rem;
112114
--_ui5_panel_header_height: 2rem;

‎packages/main/test/pages/List.html

+50
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,27 @@
1313
</head>
1414

1515
<body style="background-color: var(--sapBackgroundColor);">
16+
17+
<ui5-title>List "infinite-scroll"</ui5-title>
18+
<label style="font-size:2rem;">new items loaded:</label><label id="result" style="font-size:2rem;"> 0 times: 0</label>
19+
<ui5-list id="infiniteScrollEx" style="height: 300px" infinite-scroll>
20+
<ui5-li-groupheader>New Items</ui5-li-groupheader>
21+
<ui5-li image="./img/HT-1000.jpg" icon="navigation-right-arrow" info="Available">Voluptate do eu cupidatat elit est culpa. Reprehenderit eiusmod voluptate ex est dolor nostrud Lorem Lorem do nisi laborum veniam. Sint do non culpa aute occaecat labore ipsum veniam minim tempor est. Duis pariatur aute culpa irure ad excepteur pariatur culpa culpa ea duis occaecat aute irure. Ipsum velit culpa non exercitation ex laboris deserunt in eu non officia in. Laborum sunt aliqua labore cupidatat sunt labore.</ui5-li>
22+
<ui5-li image="./img/HT-1010.jpg" icon="navigation-right-arrow" info="Re-stock" description="#12331232131" info-state="Error">Laptop Lenovo</ui5-li>
23+
<ui5-li image="./img/HT-1022.jpg" icon="navigation-right-arrow" info="Re-stock" description="#12331232131" info-state="Error">IPhone 3</ui5-li>
24+
25+
<ui5-li-groupheader>Discounted Items</ui5-li-groupheader>
26+
<ui5-li image="./img/HT-1030.jpg" icon-end icon="navigation-right-arrow" description="#12331232131" info="Reuqired" info-state="Error">HP Monitor 24</ui5-li>
27+
<ui5-li image="./img/HT-2026.jpg" icon-end icon="navigation-right-arrow" description="#12331232131" info="Available" info-state="Success">Audio cabel</ui5-li>
28+
<ui5-li image="./img/HT-2002.jpg" icon-end icon="navigation-right-arrow" info="Reuqired" info-state="Warning">DVD set</ui5-li>
29+
30+
<ui5-li-groupheader>Discounted Items</ui5-li-groupheader>
31+
<ui5-li image="./img/HT-1030.jpg" icon="navigation-right-arrow">HP Monitor 24</ui5-li>
32+
<ui5-li image="./img/HT-2026.jpg" icon="navigation-right-arrow">Audio cabel</ui5-li>
33+
<ui5-li image="./img/HT-2002.jpg" icon="navigation-right-arrow">DVD set</ui5-li>
34+
</ui5-list>
35+
36+
1637
<h2>ui5-list</h2>
1738

1839
<ui5-list header-text="API: GroupHeaderListItem" mode="MultiSelect">
@@ -294,6 +315,35 @@ <h3 id="infoLbl">Items 3/3</h3>
294315
var selectedItems = event.detail.selectedItems;
295316
lblSelectionChange2.innerHTML = "Event] selectionChange :: " + selectedItems.map(item => item.id).join("/");
296317
});
318+
319+
function template(i) {
320+
var li = document.createElement("ui5-li");
321+
li.textContent = "Title";
322+
li.description = "my description goes here " + i;
323+
li.info = "Available";
324+
return li;
325+
}
326+
327+
function insertItems(el, num) {
328+
for(var i= 0; i<num; i++) {
329+
el.appendChild(template(i));
330+
}
331+
}
332+
333+
var itemsLoadedTotal = 0;
334+
var itemsToLoad = 5;
335+
var loadedCount = 0;
336+
infiniteScrollEx.addEventListener("loadMore", (e) => {
337+
var el = infiniteScrollEx;
338+
el.busy = true;
339+
340+
setTimeout(() => {
341+
insertItems(el, 3);
342+
el.busy = false;
343+
result.textContent = (++loadedCount) + " times " + (itemsLoadedTotal += itemsToLoad);
344+
}, 200);
345+
});
346+
297347
</script>
298348
</body>
299349
</html>

‎packages/main/test/pages/List_test_page.html

+29
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,20 @@
1212
</head>
1313

1414
<body style="background-color: var(--sapBackgroundColor);">
15+
<ui5-title>List "infinite-scroll"</ui5-title>
16+
<ui5-button id="btnTrigger">scroll</ui5-button>
17+
<ui5-list id="infiniteScrollEx" style="height: 200px" infinite-scroll>
18+
<ui5-li>Laptop Lenovo</ui5-li>
19+
<ui5-li>IPhone 3</ui5-li>
20+
<ui5-li>HP Monitor 24</ui5-li>
21+
<ui5-li>Audio cabel</ui5-li>
22+
<ui5-li>DVD set</ui5-li>
23+
<ui5-li>HP Monitor 24</ui5-li>
24+
<ui5-li>Audio cabel</ui5-li>
25+
<ui5-li id="lastItem">Last Item</ui5-li>
26+
</ui5-list>
27+
<input id="loadMoreResult" value="0"/>
28+
<br/>
1529
<ui5-list id="listEvents" mode="SingleSelectEnd">
1630
<ui5-li id="country1" >Argentina</ui5-li>
1731
<ui5-li id="country2" selected >Bulgaria</ui5-li>
@@ -129,6 +143,21 @@
129143
listMultiSel.addEventListener("ui5-selectionChange", function(event) {
130144
fieldMultiSelResult.value = event.detail.selectionComponentPressed;
131145
})
146+
147+
btnTrigger.addEventListener("click", (e) => {
148+
const scrollableContiner = infiniteScrollEx.shadowRoot.querySelector(".ui5-list-root");
149+
scrollableContiner.scroll(0, scrollableContiner.scrollHeight);
150+
});
151+
152+
var loadMoreCounter = 0;
153+
infiniteScrollEx.addEventListener("ui5-loadMore", (e) => {
154+
for(var i = 0; i < 3; i++) {
155+
var li = document.createElement("ui5-li");
156+
li.textContent = "Title" + i;
157+
infiniteScrollEx.appendChild(li);
158+
}
159+
loadMoreResult.value= ++loadMoreCounter;
160+
});
132161
</script>
133162
</body>
134163
</html>

0 commit comments

Comments
 (0)
Please sign in to comment.