diff --git a/src/main/resources/assets/js/app/browse/UserItemsTreeGrid.ts b/src/main/resources/assets/js/app/browse/UserItemsTreeGrid.ts index 645c5bf4f..4d2df1582 100644 --- a/src/main/resources/assets/js/app/browse/UserItemsTreeGrid.ts +++ b/src/main/resources/assets/js/app/browse/UserItemsTreeGrid.ts @@ -25,6 +25,8 @@ import {i18n} from 'lib-admin-ui/util/Messages'; import {ListPrincipalsKeysResult, ListPrincipalsNamesRequest} from '../../graphql/principal/ListPrincipalsNamesRequest'; import {DefaultErrorHandler} from 'lib-admin-ui/DefaultErrorHandler'; import {GetPrincipalsTotalRequest} from '../../graphql/principal/GetPrincipalsTotalRequest'; +import {UserFilteredDataScrollEvent} from '../event/UserFilteredDataScrollEvent'; +import {AppHelper} from 'lib-admin-ui/util/AppHelper'; export class UserItemsTreeGrid extends TreeGrid { @@ -76,6 +78,17 @@ export class UserItemsTreeGrid } private initEventHandlers() { + const triggerFilteredDataScrollEvent = AppHelper.debounce(() => { + + const currentNumberOfItems = this.getRoot().getCurrentRoot().getChildren().length; + const numberOfItemsToAdd = 30; + const nextNumberOfItems = currentNumberOfItems + numberOfItemsToAdd; + + if(this.getGrid().getViewport().bottom === currentNumberOfItems){ + new UserFilteredDataScrollEvent(nextNumberOfItems).fire(); + } + }, 100); + BrowseFilterSearchEvent.on((event: BrowseFilterSearchEvent) => { const data = event.getData(); const items = data.getUserItems().map((userItem: UserItem) => { @@ -85,10 +98,14 @@ export class UserItemsTreeGrid this.searchTypes = data.getTypes(); this.filter(items); this.notifyLoaded(); + + this.getGrid().subscribeOnScroll(triggerFilteredDataScrollEvent); }); BrowseFilterResetEvent.on(() => { this.resetFilter(); + + this.getGrid().unsubscribeOnScroll(triggerFilteredDataScrollEvent); }); this.getGrid().subscribeOnDblClick((event, data) => { @@ -251,7 +268,7 @@ export class UserItemsTreeGrid } private fetchFilteredItems(): Q.Promise { - return new ListUserItemsRequest().setTypes(this.searchTypes).setQuery(this.searchString).sendAndParse() + return new ListUserItemsRequest().setCount(100).setTypes(this.searchTypes).setQuery(this.searchString).sendAndParse() .then((result) => { return result.userItems.map(item => new UserTreeGridItemBuilder().setAny(item).build()); }); diff --git a/src/main/resources/assets/js/app/browse/filter/PrincipalBrowseFilterPanel.ts b/src/main/resources/assets/js/app/browse/filter/PrincipalBrowseFilterPanel.ts index 397fa336f..7fd7e58cb 100644 --- a/src/main/resources/assets/js/app/browse/filter/PrincipalBrowseFilterPanel.ts +++ b/src/main/resources/assets/js/app/browse/filter/PrincipalBrowseFilterPanel.ts @@ -17,6 +17,7 @@ import {i18n} from 'lib-admin-ui/util/Messages'; import {UserItem} from 'lib-admin-ui/security/UserItem'; import {SearchInputValues} from 'lib-admin-ui/query/SearchInputValues'; import {Exception} from 'lib-admin-ui/Exception'; +import {UserFilteredDataScrollEvent} from '../../event/UserFilteredDataScrollEvent'; export class PrincipalBrowseFilterPanel extends BrowseFilterPanel { @@ -30,11 +31,20 @@ export class PrincipalBrowseFilterPanel this.fetchAndUpdateAggregations(); this.initHitsCounter(); + this.initEventHandlers(); + } + + private initEventHandlers() { + UserFilteredDataScrollEvent.on((event) => { + const newCounterAfterScroll = event.getCount(); + this.searchDataAndHandleResponse(newCounterAfterScroll); + }); } private initHitsCounter() { - new ListUserItemsRequest().sendAndParse().then((result: ListUserItemsRequestResult) => { - this.updateHitsCounter(result.userItems ? result.userItems.length : 0, true); + // setCount(1): Only total is used, so there's no need to traverse everything. + new ListUserItemsRequest().setCount(1).sendAndParse().then((result: ListUserItemsRequestResult) => { + this.updateHitsCounter(result.total, true); }).catch((reason: Error | Exception) => { DefaultErrorHandler.handle(reason); }); @@ -66,10 +76,10 @@ export class PrincipalBrowseFilterPanel } protected resetFacets(suppressEvent?: boolean, doResetAll?: boolean): Q.Promise { - return new ListUserItemsRequest().sendAndParse().then((result: ListUserItemsRequestResult) => { + // setCount(1): Only total and aggregations are used, so there's no need to traverse everything. + return new ListUserItemsRequest().setCount(1).sendAndParse().then((result: ListUserItemsRequestResult) => { return this.fetchAndUpdateAggregations().then(() => { - const userItems: UserItem[] = result.userItems; - this.updateHitsCounter(userItems ? userItems.length : 0, true); + this.updateHitsCounter(result.total, true); this.toggleAggregationsVisibility(result.aggregations); // then fire usual reset event with content grid reloading @@ -91,12 +101,18 @@ export class PrincipalBrowseFilterPanel .filter(type => type != null); } - private searchDataAndHandleResponse(): Q.Promise { + // setCount(100): Initially get only 100 users. The UserFilteredDataScrollEvent will request more if necessary. + private searchDataAndHandleResponse(count: number = 100): Q.Promise { const types: UserItemType[] = this.getCheckedTypes(); const searchString: string = this.getSearchInputValues().getTextSearchFieldValue(); const itemIds: string[] = this.getSelectedItemIds(); - return new ListUserItemsRequest().setTypes(types).setQuery(searchString).setItems(itemIds).sendAndParse() + return new ListUserItemsRequest() + .setCount(count) + .setTypes(types) + .setQuery(searchString) + .setItems(itemIds) + .sendAndParse() .then((result: ListUserItemsRequestResult) => { this.handleDataSearchResult(result, types, searchString); }).catch((reason: any) => { @@ -109,7 +125,8 @@ export class PrincipalBrowseFilterPanel const searchString: string = this.getSearchInputValues().getTextSearchFieldValue(); const itemIds: string[] = this.getSelectedItemIds(); - return new ListUserItemsRequest().setTypes(types).setQuery(searchString).setItems(itemIds).sendAndParse() + // setCount(100): After refreshing get only 100 users. + return new ListUserItemsRequest().setCount(100).setTypes(types).setQuery(searchString).setItems(itemIds).sendAndParse() .then((result: ListUserItemsRequestResult) => { if (result.userItems.length > 0) { this.handleDataSearchResult(result, types, searchString); @@ -135,7 +152,7 @@ export class PrincipalBrowseFilterPanel new BrowseFilterSearchEvent(new PrincipalBrowseSearchData(searchString, types, userItems)).fire(); - this.updateHitsCounter(userItems ? userItems.length : 0, StringHelper.isBlank(searchString)); + this.updateHitsCounter(result.total, StringHelper.isBlank(searchString)); this.toggleAggregationsVisibility(result.aggregations); }); } diff --git a/src/main/resources/assets/js/app/event/UserFilteredDataScrollEvent.ts b/src/main/resources/assets/js/app/event/UserFilteredDataScrollEvent.ts new file mode 100644 index 000000000..e151f1124 --- /dev/null +++ b/src/main/resources/assets/js/app/event/UserFilteredDataScrollEvent.ts @@ -0,0 +1,25 @@ +import {ClassHelper} from 'lib-admin-ui/ClassHelper'; +import {Event} from 'lib-admin-ui/event/Event'; + +export class UserFilteredDataScrollEvent + extends Event { + + private count: number; + + constructor(count: number) { + super(); + this.count = count; + } + + public getCount(): number { + return this.count; + } + + static on(handler: (event: UserFilteredDataScrollEvent) => void): void { + Event.bind(ClassHelper.getFullName(this), handler); + } + + static un(handler?: (event: UserFilteredDataScrollEvent) => void): void { + Event.unbind(ClassHelper.getFullName(this), handler); + } +} diff --git a/src/main/resources/assets/js/graphql/principal/ListUserItemsRequest.ts b/src/main/resources/assets/js/graphql/principal/ListUserItemsRequest.ts index 341766275..5b1465197 100644 --- a/src/main/resources/assets/js/graphql/principal/ListUserItemsRequest.ts +++ b/src/main/resources/assets/js/graphql/principal/ListUserItemsRequest.ts @@ -19,7 +19,7 @@ import {ListItemsProperties, ListItemsRequest} from './ListItemsRequest'; // UserItems does not map "name" property. Missing type? or wrong graph query? export type ListUserItemsRequestResult = { total: number, - userItems: UserItem[], + userItems: UserItem[], aggregations: BucketAggregation[] }; @@ -37,7 +37,7 @@ interface ListUserItemsGraph { description: string; displayName: string; } - }], + }], aggregations: UserItemBucketAggregationJson[]; } } @@ -47,6 +47,15 @@ export class ListUserItemsRequest private types: UserItemType[]; + constructor(){ + super(); + } + + setCount(value: number): ListUserItemsRequest { + this.count = value; + return this; + } + setTypes(types: UserItemType[]): ListUserItemsRequest { this.types = types; return this;