diff --git a/lib/content-services/i18n/en.json b/lib/content-services/i18n/en.json index 71cd50f25d..25624894c6 100644 --- a/lib/content-services/i18n/en.json +++ b/lib/content-services/i18n/en.json @@ -21,6 +21,9 @@ "NO_LABEL": "No" } }, + "ADF_DROPDOWN": { + "LOADING": "Loading..." + }, "ADF_CONFIRM_DIALOG": { "TITLE": "Confirm", "ACTION": "Do you want to proceed?", diff --git a/lib/content-services/site-dropdown/sites-dropdown.component.html b/lib/content-services/site-dropdown/sites-dropdown.component.html index 716a1ccfa5..43ed4f552a 100644 --- a/lib/content-services/site-dropdown/sites-dropdown.component.html +++ b/lib/content-services/site-dropdown/sites-dropdown.component.html @@ -1,17 +1,20 @@ diff --git a/lib/content-services/site-dropdown/sites-dropdown.component.spec.ts b/lib/content-services/site-dropdown/sites-dropdown.component.spec.ts index 4ff38cb72e..5103eb976f 100644 --- a/lib/content-services/site-dropdown/sites-dropdown.component.spec.ts +++ b/lib/content-services/site-dropdown/sites-dropdown.component.spec.ts @@ -46,6 +46,106 @@ describe('DropdownSitesComponent', () => { describe('Rendering tests', () => { + describe('Infinite Loading', () => { + + beforeEach(async(() => { + siteService = TestBed.get(SitesService); + fixture = TestBed.createComponent(DropdownSitesComponent); + debug = fixture.debugElement; + element = fixture.nativeElement; + component = fixture.componentInstance; + })); + + it('Should show loading item if there are more itemes', async(() => { + spyOn(siteService, 'getSites').and.returnValue(of({ + 'list': { + 'pagination': { + 'count': 2, + 'hasMoreItems': true, + 'totalItems': 2, + 'skipCount': 0, + 'maxItems': 100 + }, + 'entries': [ + { + 'entry': { + 'role': 'SiteManager', + 'visibility': 'PUBLIC', + 'guid': 'fake-1', + 'description': 'fake-test-site', + 'id': 'fake-test-site', + 'preset': 'site-dashboard', + 'title': 'fake-test-site' + } + }, + { + 'entry': { + 'role': 'SiteManager', + 'visibility': 'PUBLIC', + 'guid': 'fake-2', + 'description': 'This is a Sample Alfresco Team site.', + 'id': 'swsdp', + 'preset': 'site-dashboard', + 'title': 'fake-test-2' + } + } + ] + } + })); + + fixture.detectChanges(); + fixture.whenStable().then(() => { + fixture.detectChanges(); + expect(element.querySelector('[data-automation-id="lsite-loading"]')).toBeDefined(); + }); + })); + + it('Should not show loading item if there are more itemes', async(() => { + spyOn(siteService, 'getSites').and.returnValue(of({ + 'list': { + 'pagination': { + 'count': 2, + 'hasMoreItems': false, + 'totalItems': 2, + 'skipCount': 0, + 'maxItems': 100 + }, + 'entries': [ + { + 'entry': { + 'role': 'SiteManager', + 'visibility': 'PUBLIC', + 'guid': 'fake-1', + 'description': 'fake-test-site', + 'id': 'fake-test-site', + 'preset': 'site-dashboard', + 'title': 'fake-test-site' + } + }, + { + 'entry': { + 'role': 'SiteManager', + 'visibility': 'PUBLIC', + 'guid': 'fake-2', + 'description': 'This is a Sample Alfresco Team site.', + 'id': 'swsdp', + 'preset': 'site-dashboard', + 'title': 'fake-test-2' + } + } + ] + } + })); + + fixture.detectChanges(); + fixture.whenStable().then(() => { + fixture.detectChanges(); + expect(element.querySelector('[data-automation-id="lsite-loading"]')).toBeNull(); + }); + })); + + }); + describe('Sites', () => { beforeEach(async(() => { @@ -93,7 +193,7 @@ describe('DropdownSitesComponent', () => { })); function openSelectBox() { - const selectBox = debug.query(By.css(('[data-automation-id="site-my-files-select"] .mat-select-trigger'))); + const selectBox = debug.query(By.css(('[data-automation-id="site-my-files-option"] .mat-select-trigger'))); selectBox.triggerEventHandler('click', null); } diff --git a/lib/content-services/site-dropdown/sites-dropdown.component.ts b/lib/content-services/site-dropdown/sites-dropdown.component.ts index 31e445517b..d77d29dea4 100644 --- a/lib/content-services/site-dropdown/sites-dropdown.component.ts +++ b/lib/content-services/site-dropdown/sites-dropdown.component.ts @@ -15,9 +15,10 @@ * limitations under the License. */ -import { Component, EventEmitter, Input, OnInit, Output, ViewEncapsulation } from '@angular/core'; +import { Component, EventEmitter, Input, OnInit, Output, ViewChild, ViewEncapsulation } from '@angular/core'; import { SitesService, LogService } from '@alfresco/adf-core'; import { SitePaging, SiteEntry } from '@alfresco/js-api'; +import { MatSelect } from '@angular/material'; export enum Relations { Members = 'members', @@ -68,6 +69,15 @@ export class DropdownSitesComponent implements OnInit { @Output() change: EventEmitter = new EventEmitter(); + @ViewChild('siteSelect') + siteSelect: MatSelect; + + private loading = true; + private skipCount = 0; + private readonly MAX_ITEMS = 50; + private readonly ITEM_HEIGHT = 45; + private readonly ITEM_HEIGHT_TO_WAIT_BEFORE_LOAD_NEXT = (this.ITEM_HEIGHT * (this.MAX_ITEMS / 2)); + selected: SiteEntry = null; public MY_FILES_VALUE = '-my-'; @@ -77,47 +87,89 @@ export class DropdownSitesComponent implements OnInit { } ngOnInit() { + this.siteSelect.openedChange.subscribe(() => { + if (this.siteSelect.panelOpen) { + this.siteSelect.panel.nativeElement.addEventListener('scroll', (event) => this.loadAllOnScroll(event)); + } + }); + if (!this.siteList) { - this.setDefaultSiteList(); + this.loadSiteList(); } } + loadAllOnScroll(event) { + if (this.isInfiniteScrollingEnabled() && this.isScrollInNextFetchArea(event)) { + this.loading = true; + this.loadSiteList(); + } + } + + isScrollInNextFetchArea(event) { + return event.target.scrollTop >= (event.target.scrollHeight - event.target.offsetHeight - this.ITEM_HEIGHT_TO_WAIT_BEFORE_LOAD_NEXT); + } + selectedSite(event: any) { this.change.emit(event.value); } - private setDefaultSiteList() { - let extendedOptions = null; + private loadSiteList() { + let extendedOptions: any = { + skipCount: this.skipCount, + maxItems: this.MAX_ITEMS + }; + + this.skipCount += this.MAX_ITEMS; + if (this.relations) { - extendedOptions = { relations: [this.relations] }; + extendedOptions.relations = [this.relations]; } + this.sitesService.getSites(extendedOptions).subscribe((sitePaging: SitePaging) => { - this.siteList = this.relations === Relations.Members ? this.filteredResultsByMember(sitePaging) : sitePaging; + if (!this.siteList) { + this.siteList = this.relations === Relations.Members ? this.filteredResultsByMember(sitePaging) : sitePaging; - if (!this.hideMyFiles) { - let siteEntry = new SiteEntry({ - entry: { - id: '-my-', - guid: '-my-', - title: 'DROPDOWN.MY_FILES_OPTION' + if (!this.hideMyFiles) { + let siteEntry = new SiteEntry({ + entry: { + id: '-my-', + guid: '-my-', + title: 'DROPDOWN.MY_FILES_OPTION' + } + }); + + this.siteList.list.entries.unshift(siteEntry); + + if (!this.value) { + this.value = '-my-'; } - }); - - this.siteList.list.entries.unshift(siteEntry); - - if (!this.value) { - this.value = '-my-'; } + + } else { + let siteList: SitePaging = this.relations === Relations.Members ? this.filteredResultsByMember(sitePaging) : sitePaging; + + this.siteList.list.entries = this.siteList.list.entries.concat(siteList.list.entries); + this.siteList.list.pagination = sitePaging.list.pagination; } this.selected = this.siteList.list.entries.find((site: SiteEntry) => site.entry.id === this.value); + + this.loading = false; }, (error) => { this.logService.error(error); }); } + showLoading(): boolean { + return this.loading && (this.siteList && this.siteList.list.pagination && this.siteList.list.pagination.hasMoreItems); + } + + isInfiniteScrollingEnabled(): boolean { + return !this.loading && (this.siteList && this.siteList.list.pagination && this.siteList.list.pagination.hasMoreItems); + } + private filteredResultsByMember(sites: SitePaging): SitePaging { const loggedUserName = this.sitesService.getEcmCurrentLoggedUserName(); sites.list.entries = sites.list.entries.filter((site) => this.isCurrentUserMember(site, loggedUserName));