diff --git a/src/components/search-box/core-search-box.html b/src/components/search-box/core-search-box.html index 260d45215..84a8e4b74 100644 --- a/src/components/search-box/core-search-box.html +++ b/src/components/search-box/core-search-box.html @@ -2,10 +2,10 @@
- - diff --git a/src/core/user/components/participants/core-user-participants.html b/src/core/user/components/participants/core-user-participants.html index 269eee659..78b894138 100644 --- a/src/core/user/components/participants/core-user-participants.html +++ b/src/core/user/components/participants/core-user-participants.html @@ -1,21 +1,37 @@ + + + + - + + + + - + + + -

{{ participant.fullname }}

-

{{ 'core.lastaccess' | translate }}: {{ participant.lastcourseaccess | coreTimeAgo }}

-

{{ 'core.lastaccess' | translate }}: {{ participant.lastaccess | coreTimeAgo }}

+ +

{{ participant.fullname }}

+

{{ 'core.lastaccess' | translate }}: {{ participant.lastcourseaccess | coreTimeAgo }}

+

{{ 'core.lastaccess' | translate }}: {{ participant.lastaccess | coreTimeAgo }}

+
+ +

+
- +
\ No newline at end of file diff --git a/src/core/user/components/participants/participants.ts b/src/core/user/components/participants/participants.ts index ccc1b0b83..330daf791 100644 --- a/src/core/user/components/participants/participants.ts +++ b/src/core/user/components/participants/participants.ts @@ -17,6 +17,7 @@ import { Content } from 'ionic-angular'; import { CoreUserProvider } from '../../providers/user'; import { CoreDomUtilsProvider } from '@providers/utils/dom'; import { CoreSplitViewComponent } from '@components/split-view/split-view'; +import { CoreAppProvider } from '@providers/app'; /** * Component that displays the list of course participants. @@ -36,13 +37,24 @@ export class CoreUserParticipantsComponent implements OnInit { canLoadMore = false; loadMoreError = false; participantsLoaded = false; + canSearch = false; + showSearchBox = false; + disableSearch = false; + displaySearchResults = false; + searchQuery = ''; - constructor(private userProvider: CoreUserProvider, private domUtils: CoreDomUtilsProvider) { } + protected searchPage = 0; + + constructor(private userProvider: CoreUserProvider, + private domUtils: CoreDomUtilsProvider, + private appProvider: CoreAppProvider) { } /** * View loaded. */ ngOnInit(): void { + this.canSearch = this.userProvider.canSearchParticipantsInSite(); + // Get first participants. this.fetchData(true).then(() => { if (!this.participantId && this.splitviewCtrl.isOn() && this.participants.length > 0) { @@ -54,8 +66,6 @@ export class CoreUserParticipantsComponent implements OnInit { // Ignore errors. }); }).finally(() => { - this.participantsLoaded = true; - // Call resize to make infinite loading work, in some cases the content dimensions aren't read. this.content && this.content.resize(); }); @@ -81,6 +91,8 @@ export class CoreUserParticipantsComponent implements OnInit { }).catch((error) => { this.domUtils.showErrorModalDefault(error, 'Error loading participants'); this.loadMoreError = true; // Set to prevent infinite calls with infinite-loading. + }).finally(() => { + this.participantsLoaded = true; }); } @@ -91,9 +103,15 @@ export class CoreUserParticipantsComponent implements OnInit { * @return Resolved when done. */ loadMoreData(infiniteComplete?: any): Promise { - return this.fetchData().finally(() => { - infiniteComplete && infiniteComplete(); - }); + if (this.displaySearchResults) { + return this.search(this.searchQuery, true).finally(() => { + infiniteComplete && infiniteComplete(); + }); + } else { + return this.fetchData().finally(() => { + infiniteComplete && infiniteComplete(); + }); + } } /** @@ -111,10 +129,83 @@ export class CoreUserParticipantsComponent implements OnInit { /** * Navigate to a particular user profile. + * * @param userId User Id where to navigate. */ gotoParticipant(userId: number): void { this.participantId = userId; this.splitviewCtrl.push('CoreUserProfilePage', {userId: userId, courseId: this.courseId}); } + + /** + * Show or hide search box. + */ + toggleSearch(): void { + this.showSearchBox = !this.showSearchBox; + + if (!this.showSearchBox && this.displaySearchResults) { + this.clearSearch(); + } + } + + /** + * Clear search. + */ + clearSearch(): void { + if (!this.displaySearchResults) { + // Nothing to clear. + return; + } + + this.searchQuery = ''; + this.displaySearchResults = false; + this.participants = []; + this.searchPage = 0; + this.splitviewCtrl.emptyDetails(); + + // Remove search results and display all participants. + this.participantsLoaded = false; + this.fetchData(true); + } + + /** + * Start a new search or load more results. + * + * @param query Text to search for. + * @param loadMore Whether it's loading more or doing a new search. + * @return Resolved when done. + */ + search(query: string, loadMore?: boolean): Promise { + this.appProvider.closeKeyboard(); + + this.disableSearch = true; + this.participantsLoaded = loadMore; + this.loadMoreError = false; + + if (!loadMore) { + this.participantsLoaded = false; + this.searchQuery = query; + this.searchPage = 0; + this.participants = []; + } + + return this.userProvider.searchParticipants(this.courseId, query, true, this.searchPage).then((result) => { + + this.participants.push(...result.participants); + this.canLoadMore = result.canLoadMore; + this.searchPage++; + + if (!loadMore && this.participants.length) { + this.gotoParticipant(this.participants[0].id); + } + }).catch((error) => { + this.domUtils.showErrorModalDefault(error, 'Error searching users.'); + this.loadMoreError = true; + + }).finally(() => { + this.disableSearch = false; + this.participantsLoaded = true; + this.displaySearchResults = true; + }); + } } diff --git a/src/core/user/providers/user.ts b/src/core/user/providers/user.ts index e42a974be..fc685dadb 100644 --- a/src/core/user/providers/user.ts +++ b/src/core/user/providers/user.ts @@ -69,6 +69,32 @@ export class CoreUserProvider { this.sitesProvider.registerSiteSchema(this.siteSchema); } + /** + * Check if WS to search participants is available in site. + * + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with boolean: whether it's available. + * @since 3.8 + */ + canSearchParticipants(siteId?: string): Promise { + return this.sitesProvider.getSite(siteId).then((site) => { + return this.canSearchParticipantsInSite(site); + }); + } + + /** + * Check if WS to search participants is available in site. + * + * @param site Site. If not defined, current site. + * @return Whether it's available. + * @since 3.8 + */ + canSearchParticipantsInSite(site?: CoreSite): boolean { + site = site || this.sitesProvider.getCurrentSite(); + + return site.wsAvailable('core_enrol_search_users'); + } + /** * Change the given user profile picture. * @@ -505,6 +531,43 @@ export class CoreUserProvider { return Promise.all(promises); } + /** + * Search participants in a certain course. + * + * @param courseId ID of the course. + * @param search The string to search. + * @param searchAnywhere Whether to find a match anywhere or only at the beginning. + * @param page Page to get. + * @param limitNumber Number of participants to get. + * @param siteId Site Id. If not defined, use current site. + * @return Promise resolved when the participants are retrieved. + * @since 3.8 + */ + searchParticipants(courseId: number, search: string, searchAnywhere: boolean = true, page: number = 0, + perPage: number = CoreUserProvider.PARTICIPANTS_LIST_LIMIT, siteId?: string) + : Promise<{participants: any[], canLoadMore: boolean}> { + + return this.sitesProvider.getSite(siteId).then((site) => { + + const data = { + courseid: courseId, + search: search, + searchanywhere: searchAnywhere ? 1 : 0, + page: page, + perpage: perPage, + }, preSets: any = { + getFromCache: false // Always try to get updated data. If it fails, it will get it from cache. + }; + + return site.read('core_enrol_search_users', data, preSets).then((users) => { + const canLoadMore = users.length >= perPage; + this.storeUsers(users, siteId); + + return { participants: users, canLoadMore: canLoadMore }; + }); + }); + } + /** * Store user basic information in local DB to be retrieved if the WS call fails. *