Skip to content

Commit

Permalink
feat(ui5-list): add infinite-scroll capability (#1220)
Browse files Browse the repository at this point in the history
- 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
  • Loading branch information
ilhan007 committed Feb 18, 2020
1 parent e79dcc8 commit 756b78b
Show file tree
Hide file tree
Showing 8 changed files with 289 additions and 15 deletions.
9 changes: 8 additions & 1 deletion packages/main/src/List.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
class="ui5-list-root"
@focusin="{{_onfocusin}}"
@keydown="{{_onkeydown}}"
@scroll="{{_onScroll}}"
>
<!-- header -->
{{#if header.length}}
Expand Down Expand Up @@ -33,5 +34,11 @@
</footer>
{{/if}}

{{#if showBusy}}
<div class="ui5-list-busy-row">
<ui5-busyindicator ?active="{{busy}}" size="Medium" class="ui5-list-busy-ind"></ui5-busyindicator>
</div>
{{/if}}

<div id="{{_id}}-after" tabindex="0" class="ui5-list-focusarea"></div>
</div>
</div>
89 changes: 85 additions & 4 deletions packages/main/src/List.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,17 @@ import { isTabNext } from "@ui5/webcomponents-base/dist/events/PseudoEvents.js";
import NavigationMode from "@ui5/webcomponents-base/dist/types/NavigationMode.js";
import ListMode from "./types/ListMode.js";
import ListSeparators from "./types/ListSeparators.js";
import BusyIndicator from "./BusyIndicator.js";

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

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

const BUSYINDICATOR_HEIGHT = 48; // px
const INFINITE_SCROLL_DEBOUNCE_RATE = 250; // ms

/**
* @public
*/
Expand Down Expand Up @@ -128,6 +132,33 @@ const metadata = {
type: ListSeparators,
defaultValue: ListSeparators.All,
},

/**
* Defines if the component would fire the <code>loadMore</code> event,
* when the user scrolls to the bottom of the list and help achieving an "infinite scroll" effect
* by adding new items each time.
*
* @type {boolean}
* @defaultvalue false
* @public
* @since 1.0.0-rc.6
*/
infiniteScroll: {
type: Boolean,
},

/**
* Defines if the component would display a loading indicator at the bottom of the list.
* It's especially useful, when combined with <code>infiniteScroll</code>.
*
* @type {boolean}
* @defaultvalue false
* @public
* @since 1.0.0-rc.6
*/
busy: {
type: Boolean,
},
},
events: /** @lends sap.ui.webcomponents.main.List.prototype */ {

Expand Down Expand Up @@ -176,6 +207,17 @@ const metadata = {
selectionComponentPressed: { type: Boolean }, // protected, indicates if the user used the selection components to change the selection
},
},

/**
* Fired when the user scrolls to the bottom of the list.
* <br>
* <b>Note:</b> The event is fired when the <code>infiniteScroll</code> property is enabled.
*
* @event
* @public
* @since 1.0.0-rc.6
*/
loadMore: {},
},
};

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

get shouldRenderH1() {
return !this.header.length && this.headerText;
}

get showNoDataText() {
return this.items.length === 0 && this.noDataText;
}

get showBusy() {
return this.busy || this.infiniteScroll;
}

onBeforeRendering() {
this.prepareListItems();
}
Expand Down Expand Up @@ -391,6 +445,13 @@ class List extends UI5Element {
}
}

_onScroll(event) {
if (!this.infiniteScroll) {
return;
}
this.debounce(this.loadMore.bind(this, event.target), INFINITE_SCROLL_DEBOUNCE_RATE);
}

_onfocusin(event) {
// If the focusin event does not origin from one of the 'triggers' - ignore it.
if (!this.isForwardElement(this.getNormalizedTarget(event.target))) {
Expand Down Expand Up @@ -555,12 +616,32 @@ class List extends UI5Element {
return focused;
}

get shouldRenderH1() {
return !this.header.length && this.headerText;
loadMore(el) {
const scrollTop = el.scrollTop;
const height = el.offsetHeight;
const scrollHeight = el.scrollHeight;

if (this.previousScrollPosition > scrollTop) { // skip scrolling upwards
this.previousScrollPosition = scrollTop;
return;
}
this.previousScrollPosition = scrollTop;

if (scrollHeight - BUSYINDICATOR_HEIGHT <= height + scrollTop) {
this.fireEvent("loadMore");
}
}

get showNoDataText() {
return this.items.length === 0 && this.noDataText;
debounce(fn, delay) {
clearTimeout(this.debounceInterval);
this.debounceInterval = setTimeout(() => {
this.debounceInterval = null;
fn();
}, delay);
}

static async onDefine() {
await BusyIndicator.define();
}
}

Expand Down
23 changes: 15 additions & 8 deletions packages/main/src/themes/List.css
Original file line number Diff line number Diff line change
Expand Up @@ -16,23 +16,24 @@
height: 100%;
position: relative;
box-sizing: border-box;
overflow: auto;
}

.ui5-list-root .ui5-list-ul {
.ui5-list-ul {
list-style-type: none;
padding: 0;
margin: 0;
}

.ui5-list-root .ui5-list-ul:focus {
.ui5-list-ul:focus {
outline: none;
}

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

.ui5-list-root .ui5-list-header {
.ui5-list-header {
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
Expand All @@ -47,7 +48,7 @@
border-bottom: 1px solid var(--sapGroup_TitleBorderColor);
}

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

.ui5-list-root .ui5-list-nodata {
.ui5-list-nodata {
list-style-type: none;
display: -webkit-box;
display: flex;
Expand All @@ -78,9 +79,15 @@
font-size: var(--sapFontMediumSize);
}


.ui5-list-root .ui5-list-nodata-text {
.ui5-list-nodata-text {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}

.ui5-list-busy-row {
display: flex;
align-items: center;
height: var(--_ui5_list_busy_row_height);
justify-content: center;
}
2 changes: 2 additions & 0 deletions packages/main/src/themes/base/sizes-parameters.css
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
--_ui5_list_item_img_size: 2rem;
--_ui5_list_item_img_margin: 0.5rem 0.75rem 0.5rem 0rem;
--_ui5_list_item_base_height: 3rem;
--_ui5_list_busy_row_height: 3rem;
--_ui5_month_picker_item_height: 3rem;
--_ui5_year_picker_item_height: 3rem;
--_ui5_tokenizer_root_padding: 0.1875rem;
Expand Down Expand Up @@ -107,6 +108,7 @@
--_ui5_list_item_img_size: 1.75rem;
--_ui5_list_item_img_margin: 0.55rem 0.75rem 0.5rem 0rem;
--_ui5_list_item_base_height: 2rem;
--_ui5_list_busy_row_height: 2rem;

--_ui5_month_picker_item_height: 2rem;
--_ui5_panel_header_height: 2rem;
Expand Down
50 changes: 50 additions & 0 deletions packages/main/test/pages/List.html
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,27 @@
</head>

<body style="background-color: var(--sapBackgroundColor);">

<ui5-title>List "infinite-scroll"</ui5-title>
<label style="font-size:2rem;">new items loaded:</label><label id="result" style="font-size:2rem;"> 0 times: 0</label>
<ui5-list id="infiniteScrollEx" style="height: 300px" infinite-scroll>
<ui5-li-groupheader>New Items</ui5-li-groupheader>
<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>
<ui5-li image="./img/HT-1010.jpg" icon="navigation-right-arrow" info="Re-stock" description="#12331232131" info-state="Error">Laptop Lenovo</ui5-li>
<ui5-li image="./img/HT-1022.jpg" icon="navigation-right-arrow" info="Re-stock" description="#12331232131" info-state="Error">IPhone 3</ui5-li>

<ui5-li-groupheader>Discounted Items</ui5-li-groupheader>
<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>
<ui5-li image="./img/HT-2026.jpg" icon-end icon="navigation-right-arrow" description="#12331232131" info="Available" info-state="Success">Audio cabel</ui5-li>
<ui5-li image="./img/HT-2002.jpg" icon-end icon="navigation-right-arrow" info="Reuqired" info-state="Warning">DVD set</ui5-li>

<ui5-li-groupheader>Discounted Items</ui5-li-groupheader>
<ui5-li image="./img/HT-1030.jpg" icon="navigation-right-arrow">HP Monitor 24</ui5-li>
<ui5-li image="./img/HT-2026.jpg" icon="navigation-right-arrow">Audio cabel</ui5-li>
<ui5-li image="./img/HT-2002.jpg" icon="navigation-right-arrow">DVD set</ui5-li>
</ui5-list>


<h2>ui5-list</h2>

<ui5-list header-text="API: GroupHeaderListItem" mode="MultiSelect">
Expand Down Expand Up @@ -294,6 +315,35 @@ <h3 id="infoLbl">Items 3/3</h3>
var selectedItems = event.detail.selectedItems;
lblSelectionChange2.innerHTML = "Event] selectionChange :: " + selectedItems.map(item => item.id).join("/");
});

function template(i) {
var li = document.createElement("ui5-li");
li.textContent = "Title";
li.description = "my description goes here " + i;
li.info = "Available";
return li;
}

function insertItems(el, num) {
for(var i= 0; i<num; i++) {
el.appendChild(template(i));
}
}

var itemsLoadedTotal = 0;
var itemsToLoad = 5;
var loadedCount = 0;
infiniteScrollEx.addEventListener("loadMore", (e) => {
var el = infiniteScrollEx;
el.busy = true;

setTimeout(() => {
insertItems(el, 3);
el.busy = false;
result.textContent = (++loadedCount) + " times " + (itemsLoadedTotal += itemsToLoad);
}, 200);
});

</script>
</body>
</html>
29 changes: 29 additions & 0 deletions packages/main/test/pages/List_test_page.html
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,20 @@
</head>

<body style="background-color: var(--sapBackgroundColor);">
<ui5-title>List "infinite-scroll"</ui5-title>
<ui5-button id="btnTrigger">scroll</ui5-button>
<ui5-list id="infiniteScrollEx" style="height: 200px" infinite-scroll>
<ui5-li>Laptop Lenovo</ui5-li>
<ui5-li>IPhone 3</ui5-li>
<ui5-li>HP Monitor 24</ui5-li>
<ui5-li>Audio cabel</ui5-li>
<ui5-li>DVD set</ui5-li>
<ui5-li>HP Monitor 24</ui5-li>
<ui5-li>Audio cabel</ui5-li>
<ui5-li id="lastItem">Last Item</ui5-li>
</ui5-list>
<input id="loadMoreResult" value="0"/>
<br/>
<ui5-list id="listEvents" mode="SingleSelectEnd">
<ui5-li id="country1" >Argentina</ui5-li>
<ui5-li id="country2" selected >Bulgaria</ui5-li>
Expand Down Expand Up @@ -129,6 +143,21 @@
listMultiSel.addEventListener("ui5-selectionChange", function(event) {
fieldMultiSelResult.value = event.detail.selectionComponentPressed;
})

btnTrigger.addEventListener("click", (e) => {
const scrollableContiner = infiniteScrollEx.shadowRoot.querySelector(".ui5-list-root");
scrollableContiner.scroll(0, scrollableContiner.scrollHeight);
});

var loadMoreCounter = 0;
infiniteScrollEx.addEventListener("ui5-loadMore", (e) => {
for(var i = 0; i < 3; i++) {
var li = document.createElement("ui5-li");
li.textContent = "Title" + i;
infiniteScrollEx.appendChild(li);
}
loadMoreResult.value= ++loadMoreCounter;
});
</script>
</body>
</html>
Loading

0 comments on commit 756b78b

Please sign in to comment.