Skip to content

Commit 66820dd

Browse files
authored
Merge pull request DSpace#1936 from atmire/w2p-95335_created-ListableNotificationObjectComponent_contribute-7.2
Created `ListableNotificationObject`
2 parents d26dd04 + 6e54acd commit 66820dd

12 files changed

Lines changed: 182 additions & 33 deletions

src/app/navbar/navbar.module.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ const effects = [
2121
const ENTRY_COMPONENTS = [
2222
// put only entry components that use custom decorator
2323
NavbarSectionComponent,
24+
ExpandableNavbarSectionComponent,
2425
ThemedExpandableNavbarSectionComponent,
2526
];
2627

@@ -34,11 +35,9 @@ const ENTRY_COMPONENTS = [
3435
CoreModule.forRoot()
3536
],
3637
declarations: [
38+
...ENTRY_COMPONENTS,
3739
NavbarComponent,
3840
ThemedNavbarComponent,
39-
NavbarSectionComponent,
40-
ExpandableNavbarSectionComponent,
41-
ThemedExpandableNavbarSectionComponent,
4241
],
4342
providers: [],
4443
exports: [

src/app/shared/dso-selector/dso-selector/authorized-collection-selector/authorized-collection-selector.component.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,8 +53,9 @@ export class AuthorizedCollectionSelectorComponent extends DSOSelectorComponent
5353
* Perform a search for authorized collections with the current query and page
5454
* @param query Query to search objects for
5555
* @param page Page to retrieve
56+
* @param useCache Whether or not to use the cache
5657
*/
57-
search(query: string, page: number): Observable<RemoteData<PaginatedList<SearchResult<DSpaceObject>>>> {
58+
search(query: string, page: number, useCache: boolean = true): Observable<RemoteData<PaginatedList<SearchResult<DSpaceObject>>>> {
5859
let searchListService$: Observable<RemoteData<PaginatedList<Collection>>> = null;
5960
const findOptions: FindListOptions = {
6061
currentPage: page,
@@ -69,7 +70,7 @@ export class AuthorizedCollectionSelectorComponent extends DSOSelectorComponent
6970
findOptions);
7071
} else {
7172
searchListService$ = this.collectionDataService
72-
.getAuthorizedCollection(query, findOptions, true, false, followLink('parentCommunity'));
73+
.getAuthorizedCollection(query, findOptions, useCache, false, followLink('parentCommunity'));
7374
}
7475
return searchListService$.pipe(
7576
getFirstCompletedRemoteData(),

src/app/shared/dso-selector/dso-selector/dso-selector.component.html

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,12 +21,12 @@
2121
</button>
2222
<button *ngFor="let listEntry of (listEntries$ | async)"
2323
class="list-group-item list-group-item-action border-0 list-entry"
24-
[ngClass]="{'bg-primary': listEntry.indexableObject.id === currentDSOId}"
24+
[ngClass]="{'bg-primary': listEntry['id'] === currentDSOId}"
2525
title="{{ getName(listEntry) }}"
2626
dsHoverClass="ds-hover"
27-
(click)="onSelect.emit(listEntry.indexableObject)" #listEntryElement>
27+
(click)="onClick(listEntry)" #listEntryElement>
2828
<ds-listable-object-component-loader [object]="listEntry" [viewMode]="viewMode"
29-
[linkType]=linkTypes.None [context]="getContext(listEntry.indexableObject.id)"></ds-listable-object-component-loader>
29+
[linkType]=linkTypes.None [context]="getContext(listEntry['id'])"></ds-listable-object-component-loader>
3030
</button>
3131
</ng-container>
3232
<button *ngIf="loading"

src/app/shared/dso-selector/dso-selector/dso-selector.component.ts

Lines changed: 55 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,14 @@ import { RemoteData } from '../../../core/data/remote-data';
3535
import { NotificationsService } from '../../notifications/notifications.service';
3636
import { TranslateService } from '@ngx-translate/core';
3737
import { DSONameService } from '../../../core/breadcrumbs/dso-name.service';
38+
import {
39+
ListableNotificationObject
40+
} from '../../object-list/listable-notification-object/listable-notification-object.model';
41+
import { ListableObject } from '../../object-collection/shared/listable-object.model';
42+
import { NotificationType } from '../../notifications/models/notification-type';
43+
import {
44+
LISTABLE_NOTIFICATION_OBJECT
45+
} from '../../object-list/listable-notification-object/listable-notification-object.resource-type';
3846

3947
@Component({
4048
selector: 'ds-dso-selector',
@@ -82,7 +90,7 @@ export class DSOSelectorComponent implements OnInit, OnDestroy {
8290
/**
8391
* List with search results of DSpace objects for the current query
8492
*/
85-
listEntries$: BehaviorSubject<SearchResult<DSpaceObject>[]> = new BehaviorSubject(null);
93+
listEntries$: BehaviorSubject<ListableObject[]> = new BehaviorSubject(null);
8694

8795
/**
8896
* The current page to load
@@ -116,11 +124,6 @@ export class DSOSelectorComponent implements OnInit, OnDestroy {
116124
*/
117125
linkTypes = CollectionElementLinkType;
118126

119-
/**
120-
* Track whether the element has the mouse over it
121-
*/
122-
isMouseOver = false;
123-
124127
/**
125128
* Array to track all subscriptions and unsubscribe them onDestroy
126129
* @type {Array}
@@ -182,22 +185,26 @@ export class DSOSelectorComponent implements OnInit, OnDestroy {
182185
})
183186
);
184187
})
185-
).subscribe((rd) => {
186-
this.loading = false;
187-
if (rd.hasSucceeded) {
188-
const currentEntries = this.listEntries$.getValue();
189-
if (hasNoValue(currentEntries)) {
190-
this.listEntries$.next(rd.payload.page);
191-
} else {
192-
this.listEntries$.next([...currentEntries, ...rd.payload.page]);
193-
}
194-
// Check if there are more pages available after the current one
195-
this.hasNextPage = rd.payload.totalElements > this.listEntries$.getValue().length;
188+
).subscribe((rd: RemoteData<PaginatedList<SearchResult<DSpaceObject>>>) => {
189+
this.updateList(rd);
190+
}));
191+
}
192+
193+
updateList(rd: RemoteData<PaginatedList<SearchResult<DSpaceObject>>>) {
194+
this.loading = false;
195+
const currentEntries = this.listEntries$.getValue();
196+
if (rd.hasSucceeded) {
197+
if (hasNoValue(currentEntries)) {
198+
this.listEntries$.next(rd.payload.page);
196199
} else {
197-
this.listEntries$.next(null);
198-
this.hasNextPage = false;
200+
this.listEntries$.next([...currentEntries, ...rd.payload.page]);
199201
}
200-
}));
202+
// Check if there are more pages available after the current one
203+
this.hasNextPage = rd.payload.totalElements > this.listEntries$.getValue().length;
204+
} else {
205+
this.listEntries$.next([...(hasNoValue(currentEntries) ? [] : this.listEntries$.getValue()), new ListableNotificationObject(NotificationType.Error, 'dso-selector.results-could-not-be-retrieved', LISTABLE_NOTIFICATION_OBJECT.value)]);
206+
this.hasNextPage = false;
207+
}
201208
}
202209

203210
/**
@@ -211,16 +218,19 @@ export class DSOSelectorComponent implements OnInit, OnDestroy {
211218
* Perform a search for the current query and page
212219
* @param query Query to search objects for
213220
* @param page Page to retrieve
221+
* @param useCache Whether or not to use the cache
214222
*/
215-
search(query: string, page: number): Observable<RemoteData<PaginatedList<SearchResult<DSpaceObject>>>> {
223+
search(query: string, page: number, useCache: boolean = true): Observable<RemoteData<PaginatedList<SearchResult<DSpaceObject>>>> {
216224
return this.searchService.search(
217225
new PaginatedSearchOptions({
218226
query: query,
219227
dsoTypes: this.types,
220228
pagination: Object.assign({}, this.defaultPagination, {
221229
currentPage: page
222230
})
223-
})
231+
}),
232+
null,
233+
useCache,
224234
).pipe(
225235
getFirstCompletedRemoteData()
226236
);
@@ -262,7 +272,28 @@ export class DSOSelectorComponent implements OnInit, OnDestroy {
262272
this.subs.filter((sub) => hasValue(sub)).forEach((sub) => sub.unsubscribe());
263273
}
264274

265-
getName(searchResult: SearchResult<DSpaceObject>): string {
266-
return this.dsoNameService.getName(searchResult.indexableObject);
275+
/**
276+
* Handles the user clicks on the {@link ListableObject}s. When the {@link listableObject} is a
277+
* {@link ListableObject} it will retry the error when the user clicks it. Otherwise it will emit the {@link onSelect}.
278+
*
279+
* @param listableObject The {@link ListableObject} to evaluate
280+
*/
281+
onClick(listableObject: ListableObject): void {
282+
if (hasValue((listableObject as SearchResult<DSpaceObject>).indexableObject)) {
283+
this.onSelect.emit((listableObject as SearchResult<DSpaceObject>).indexableObject);
284+
} else {
285+
this.listEntries$.value.pop();
286+
this.hasNextPage = true;
287+
this.search(this.input.value ? this.input.value : '', this.currentPage$.value, false).pipe(
288+
getFirstCompletedRemoteData(),
289+
).subscribe((rd: RemoteData<PaginatedList<SearchResult<DSpaceObject>>>) => {
290+
this.updateList(rd);
291+
});
292+
}
293+
}
294+
295+
getName(listableObject: ListableObject): string {
296+
return hasValue((listableObject as SearchResult<DSpaceObject>).indexableObject) ?
297+
this.dsoNameService.getName((listableObject as SearchResult<DSpaceObject>).indexableObject) : null;
267298
}
268299
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
<div class="alert d-block {{ object?.notificationType }} m-0">{{ object?.message | translate }}</div>

src/app/shared/object-list/listable-notification-object/listable-notification-object.component.scss

Whitespace-only changes.
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import { ComponentFixture, TestBed } from '@angular/core/testing';
2+
import { ListableNotificationObjectComponent } from './listable-notification-object.component';
3+
import { NotificationType } from '../../notifications/models/notification-type';
4+
import { ListableNotificationObject } from './listable-notification-object.model';
5+
import { By } from '@angular/platform-browser';
6+
import { TranslateModule } from '@ngx-translate/core';
7+
8+
describe('ListableNotificationObjectComponent', () => {
9+
let component: ListableNotificationObjectComponent;
10+
let fixture: ComponentFixture<ListableNotificationObjectComponent>;
11+
12+
beforeEach(async () => {
13+
await TestBed.configureTestingModule({
14+
imports: [
15+
TranslateModule.forRoot(),
16+
],
17+
declarations: [
18+
ListableNotificationObjectComponent,
19+
],
20+
}).compileComponents();
21+
});
22+
23+
beforeEach(() => {
24+
fixture = TestBed.createComponent(ListableNotificationObjectComponent);
25+
component = fixture.componentInstance;
26+
fixture.detectChanges();
27+
});
28+
29+
describe('ui', () => {
30+
it('should display the given error message', () => {
31+
component.object = new ListableNotificationObject(NotificationType.Error, 'test error message');
32+
fixture.detectChanges();
33+
34+
const listableNotificationObject: Element = fixture.debugElement.query(By.css('.alert')).nativeElement;
35+
expect(listableNotificationObject.className).toContain(NotificationType.Error);
36+
expect(listableNotificationObject.innerHTML).toBe('test error message');
37+
});
38+
});
39+
40+
afterEach(() => {
41+
fixture.debugElement.nativeElement.remove();
42+
});
43+
});
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import { Component } from '@angular/core';
2+
import {
3+
AbstractListableElementComponent
4+
} from '../../object-collection/shared/object-collection-element/abstract-listable-element.component';
5+
import { ListableNotificationObject } from './listable-notification-object.model';
6+
import { listableObjectComponent } from '../../object-collection/shared/listable-object/listable-object.decorator';
7+
import { ViewMode } from '../../../core/shared/view-mode.model';
8+
import { LISTABLE_NOTIFICATION_OBJECT } from './listable-notification-object.resource-type';
9+
10+
/**
11+
* The component for displaying a notifications inside an object list
12+
*/
13+
@listableObjectComponent(ListableNotificationObject, ViewMode.ListElement)
14+
@listableObjectComponent(LISTABLE_NOTIFICATION_OBJECT.value, ViewMode.ListElement)
15+
@Component({
16+
selector: 'ds-listable-notification-object',
17+
templateUrl: './listable-notification-object.component.html',
18+
styleUrls: ['./listable-notification-object.component.scss'],
19+
})
20+
export class ListableNotificationObjectComponent extends AbstractListableElementComponent<ListableNotificationObject> {
21+
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import { ListableObject } from '../../object-collection/shared/listable-object.model';
2+
import { typedObject } from '../../../core/cache/builders/build-decorators';
3+
import { TypedObject } from '../../../core/cache/typed-object.model';
4+
import { LISTABLE_NOTIFICATION_OBJECT } from './listable-notification-object.resource-type';
5+
import { GenericConstructor } from '../../../core/shared/generic-constructor';
6+
import { NotificationType } from '../../notifications/models/notification-type';
7+
import { ResourceType } from '../../../core/shared/resource-type';
8+
9+
/**
10+
* Object representing a notification message inside a list of objects
11+
*/
12+
@typedObject
13+
export class ListableNotificationObject extends ListableObject implements TypedObject {
14+
15+
static type: ResourceType = LISTABLE_NOTIFICATION_OBJECT;
16+
type: ResourceType = LISTABLE_NOTIFICATION_OBJECT;
17+
18+
protected renderTypes: string[];
19+
20+
constructor(
21+
public notificationType: NotificationType = NotificationType.Error,
22+
public message: string = 'listable-notification-object.default-message',
23+
...renderTypes: string[]
24+
) {
25+
super();
26+
this.renderTypes = renderTypes;
27+
}
28+
29+
/**
30+
* Method that returns as which type of object this object should be rendered.
31+
*/
32+
getRenderTypes(): (string | GenericConstructor<ListableObject>)[] {
33+
return [...this.renderTypes, this.constructor as GenericConstructor<ListableObject>];
34+
}
35+
36+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import { ResourceType } from '../../../core/shared/resource-type';
2+
3+
/**
4+
* The resource type for {@link ListableNotificationObject}
5+
*
6+
* Needs to be in a separate file to prevent circular
7+
* dependencies in webpack.
8+
*/
9+
export const LISTABLE_NOTIFICATION_OBJECT = new ResourceType('listable-notification-object');

0 commit comments

Comments
 (0)