mirror of
https://github.com/DSpace/dspace-angular.git
synced 2025-10-07 18:14:17 +00:00
44024: simple search UI with decorator bug
This commit is contained in:
@@ -21,6 +21,8 @@ import { ItemPageModule } from './item-page/item-page.module';
|
||||
import { CollectionPageModule } from './collection-page/collection-page.module';
|
||||
import { CommunityPageModule } from './community-page/community-page.module';
|
||||
|
||||
import { SearchPageModule } from './search-page/search-page.module';
|
||||
|
||||
import { AppComponent } from './app.component';
|
||||
import { HeaderComponent } from './header/header.component';
|
||||
import { PageNotFoundComponent } from './pagenotfound/pagenotfound.component';
|
||||
@@ -42,6 +44,7 @@ export function getConfig() {
|
||||
ItemPageModule,
|
||||
CollectionPageModule,
|
||||
CommunityPageModule,
|
||||
SearchPageModule,
|
||||
AppRoutingModule,
|
||||
StoreModule.provideStore(rootReducer),
|
||||
RouterStoreModule.connectRouter(),
|
||||
|
@@ -3,11 +3,12 @@ import { isEmpty, isNotEmpty } from '../../shared/empty.util';
|
||||
import { CacheableObject } from '../cache/object-cache.reducer';
|
||||
import { RemoteData } from '../data/remote-data';
|
||||
import { ResourceType } from './resource-type';
|
||||
import { ListableObject } from '../../object-list/listable-object/listable-object.model';
|
||||
|
||||
/**
|
||||
* An abstract model class for a DSpaceObject.
|
||||
*/
|
||||
export abstract class DSpaceObject implements CacheableObject {
|
||||
export abstract class DSpaceObject implements CacheableObject, ListableObject {
|
||||
|
||||
self: string;
|
||||
|
||||
|
@@ -1,6 +1,6 @@
|
||||
<a [routerLink]="['/collections/' + collection.id]" class="lead">
|
||||
{{collection.name}}
|
||||
<a [routerLink]="['/collections/' + object.id]" class="lead">
|
||||
{{object.name}}
|
||||
</a>
|
||||
<div *ngIf="collection.shortDescription" class="text-muted">
|
||||
{{collection.shortDescription}}
|
||||
<div *ngIf="object.shortDescription" class="text-muted">
|
||||
{{object.shortDescription}}
|
||||
</div>
|
||||
|
@@ -1,16 +1,14 @@
|
||||
import { Component, Input } from '@angular/core';
|
||||
import { Component } from '@angular/core';
|
||||
|
||||
import { Collection } from '../../core/shared/collection.model';
|
||||
import { ObjectListElementComponent } from '../object-list-element/object-list-element.component';
|
||||
import { listElementFor } from '../list-element-decorator';
|
||||
|
||||
@Component({
|
||||
selector: 'ds-collection-list-element',
|
||||
styleUrls: ['./collection-list-element.component.scss'],
|
||||
templateUrl: './collection-list-element.component.html'
|
||||
})
|
||||
export class CollectionListElementComponent {
|
||||
|
||||
@Input() collection: Collection;
|
||||
|
||||
data: any = {};
|
||||
|
||||
}
|
||||
@listElementFor(Collection)
|
||||
export class CollectionListElementComponent extends ObjectListElementComponent {}
|
||||
|
@@ -1,6 +1,6 @@
|
||||
<a [routerLink]="['/communities/' + community.id]" class="lead">
|
||||
{{community.name}}
|
||||
<a [routerLink]="['/communities/' + object.id]" class="lead">
|
||||
{{object.name}}
|
||||
</a>
|
||||
<div *ngIf="community.shortDescription" class="text-muted">
|
||||
{{community.shortDescription}}
|
||||
<div *ngIf="object.shortDescription" class="text-muted">
|
||||
{{object.shortDescription}}
|
||||
</div>
|
||||
|
@@ -1,16 +1,14 @@
|
||||
import { Component, Input } from '@angular/core';
|
||||
|
||||
import { Community } from '../../core/shared/community.model';
|
||||
import { ObjectListElementComponent } from '../object-list-element/object-list-element.component';
|
||||
import { listElementFor } from '../list-element-decorator';
|
||||
|
||||
@Component({
|
||||
selector: 'ds-community-list-element',
|
||||
styleUrls: ['./community-list-element.component.scss'],
|
||||
templateUrl: './community-list-element.component.html'
|
||||
})
|
||||
export class CommunityListElementComponent {
|
||||
|
||||
@Input() community: Community;
|
||||
|
||||
data: any = {};
|
||||
|
||||
}
|
||||
@listElementFor(Community)
|
||||
export class CommunityListElementComponent extends ObjectListElementComponent {}
|
||||
|
@@ -1,14 +1,14 @@
|
||||
<a [routerLink]="['/items/' + item.id]" class="lead">
|
||||
{{item.findMetadata("dc.title")}}
|
||||
<a [routerLink]="['/items/' + object.id]" class="lead">
|
||||
{{object.findMetadata("dc.title")}}
|
||||
</a>
|
||||
<div>
|
||||
<span class="text-muted">
|
||||
<span *ngIf="item.filterMetadata(['dc.contributor.author', 'dc.creator', 'dc.contributor.*']);" class="item-list-authors">
|
||||
<span *ngFor="let authorMd of item.filterMetadata(['dc.contributor.author', 'dc.creator', 'dc.contributor.*']); let last=last;">{{authorMd.value}}
|
||||
<span *ngIf="object.filterMetadata(['dc.contributor.author', 'dc.creator', 'dc.contributor.*']);" class="item-list-authors">
|
||||
<span *ngFor="let authorMd of object.filterMetadata(['dc.contributor.author', 'dc.creator', 'dc.contributor.*']); let last=last;">{{authorMd.value}}
|
||||
<span *ngIf="!last">; </span>
|
||||
</span>
|
||||
</span>
|
||||
(<span *ngIf="item.findMetadata('dc.publisher')" class="item-list-publisher">{{item.findMetadata("dc.publisher")}}, </span><span *ngIf="item.findMetadata('dc.date.issued')" class="item-list-date">{{item.findMetadata("dc.date.issued")}}</span>)
|
||||
(<span *ngIf="object.findMetadata('dc.publisher')" class="item-list-publisher">{{object.findMetadata("dc.publisher")}}, </span><span *ngIf="object.findMetadata('dc.date.issued')" class="item-list-date">{{object.findMetadata("dc.date.issued")}}</span>)
|
||||
</span>
|
||||
<div *ngIf="item.findMetadata('dc.description.abstract')" class="item-list-abstract">{{item.findMetadata("dc.description.abstract") | dsTruncate:[200] }}</div>
|
||||
<div *ngIf="object.findMetadata('dc.description.abstract')" class="item-list-abstract">{{object.findMetadata("dc.description.abstract") | dsTruncate:[200] }}</div>
|
||||
</div>
|
||||
|
@@ -1,15 +1,14 @@
|
||||
import { Component, Input } from '@angular/core';
|
||||
|
||||
import { Item } from '../../core/shared/item.model';
|
||||
import { ObjectListElementComponent } from '../object-list-element/object-list-element.component';
|
||||
import { listElementFor } from '../list-element-decorator';
|
||||
|
||||
@Component({
|
||||
selector: 'ds-item-list-element',
|
||||
styleUrls: ['./item-list-element.component.scss'],
|
||||
templateUrl: './item-list-element.component.html'
|
||||
})
|
||||
export class ItemListElementComponent {
|
||||
@Input() item: Item;
|
||||
|
||||
data: any = {};
|
||||
|
||||
}
|
||||
@listElementFor(Item)
|
||||
export class ItemListElementComponent extends ObjectListElementComponent {}
|
||||
|
12
src/app/object-list/list-element-decorator.ts
Normal file
12
src/app/object-list/list-element-decorator.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import { ListableObject } from './listable-object/listable-object.model';
|
||||
import { GenericConstructor } from '../core/shared/generic-constructor';
|
||||
|
||||
const listElementForMetadataKey = Symbol('listElementFor');
|
||||
|
||||
export function listElementFor(value: GenericConstructor<ListableObject>) {
|
||||
return Reflect.metadata(listElementForMetadataKey, value);
|
||||
}
|
||||
|
||||
export function getListElementFor(target: any) {
|
||||
return Reflect.getOwnMetadata(listElementForMetadataKey, target);
|
||||
}
|
@@ -0,0 +1,3 @@
|
||||
export interface ListableObject {
|
||||
|
||||
}
|
@@ -1,5 +0,0 @@
|
||||
<div [ngSwitch]="object.type">
|
||||
<ds-item-list-element *ngSwitchCase="type.Item" [item]="object"></ds-item-list-element>
|
||||
<ds-collection-list-element *ngSwitchCase="type.Collection" [collection]="object"></ds-collection-list-element>
|
||||
<ds-community-list-element *ngSwitchCase="type.Community" [community]="object"></ds-community-list-element>
|
||||
</div>
|
@@ -1,6 +1,5 @@
|
||||
import { Component, Input, OnInit } from '@angular/core';
|
||||
import { DSpaceObject } from '../../core/shared/dspace-object.model';
|
||||
import { ResourceType } from '../../core/shared/resource-type';
|
||||
import { Component, Inject } from '@angular/core';
|
||||
import { ListableObject } from '../listable-object/listable-object.model';
|
||||
|
||||
@Component({
|
||||
selector: 'ds-object-list-element',
|
||||
@@ -9,10 +8,6 @@ import { ResourceType } from '../../core/shared/resource-type';
|
||||
})
|
||||
export class ObjectListElementComponent {
|
||||
|
||||
public type = ResourceType;
|
||||
|
||||
@Input() object: DSpaceObject;
|
||||
|
||||
data: any = {};
|
||||
|
||||
// In the current version of Angular4, @Input is not supported by the NgComponentOutlet - instead we're using DI
|
||||
constructor(@Inject('objectElementProvider') public object: ListableObject) { }
|
||||
}
|
||||
|
@@ -10,7 +10,7 @@
|
||||
(sortFieldChange)="onSortDirectionChange($event)">
|
||||
<ul *ngIf="objects.hasSucceeded | async"> <!--class="list-unstyled"-->
|
||||
<li *ngFor="let object of (objects.payload | async) | paginate: { itemsPerPage: (pageInfo | async)?.elementsPerPage, currentPage: (pageInfo | async)?.currentPage, totalItems: (pageInfo | async)?.totalElements }">
|
||||
<ds-object-list-element [object]="object"></ds-object-list-element>
|
||||
<ds-wrapper-list-element [object]="object"></ds-wrapper-list-element>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
|
@@ -0,0 +1 @@
|
||||
<ng-container *ngComponentOutlet="getListElement(); injector: objectInjector;"></ng-container>
|
@@ -0,0 +1,3 @@
|
||||
@import '../../../styles/variables.scss';
|
||||
@import '../../../../node_modules/bootstrap/scss/variables';
|
||||
|
@@ -0,0 +1,25 @@
|
||||
import { Component, Input, Injector, ReflectiveInjector, OnInit } from '@angular/core';
|
||||
import { ListableObject } from '../listable-object/listable-object.model';
|
||||
import { getListElementFor } from '../list-element-decorator'
|
||||
|
||||
@Component({
|
||||
selector: 'ds-wrapper-list-element',
|
||||
styleUrls: ['./wrapper-list-element.component.scss'],
|
||||
templateUrl: './wrapper-list-element.component.html'
|
||||
})
|
||||
export class WrapperListElementComponent implements OnInit {
|
||||
@Input() object: ListableObject;
|
||||
objectInjector: Injector;
|
||||
|
||||
constructor(private injector: Injector) {}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.objectInjector = ReflectiveInjector.resolveAndCreate(
|
||||
[{provide: 'objectElementProvider', useFactory: () => ({ providedObject: this.object }) }], this.injector);
|
||||
|
||||
}
|
||||
|
||||
private getListElement(): string {
|
||||
return getListElementFor(this.object.constructor).constructor.name;
|
||||
}
|
||||
}
|
13
src/app/search-page/search-page-routing.module.ts
Normal file
13
src/app/search-page/search-page-routing.module.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { RouterModule } from '@angular/router';
|
||||
|
||||
import { SearchPageComponent } from './search-page.component';
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
RouterModule.forChild([
|
||||
{ path: 'search', component: SearchPageComponent }
|
||||
])
|
||||
]
|
||||
})
|
||||
export class SearchPageRoutingModule { }
|
4
src/app/search-page/search-page.component.html
Normal file
4
src/app/search-page/search-page.component.html
Normal file
@@ -0,0 +1,4 @@
|
||||
<div class="search-page">
|
||||
<ds-search-form></ds-search-form>
|
||||
<ds-search-results [searchResults]="results"></ds-search-results>
|
||||
</div>
|
1
src/app/search-page/search-page.component.scss
Normal file
1
src/app/search-page/search-page.component.scss
Normal file
@@ -0,0 +1 @@
|
||||
@import '../../styles/variables.scss';
|
42
src/app/search-page/search-page.component.ts
Normal file
42
src/app/search-page/search-page.component.ts
Normal file
@@ -0,0 +1,42 @@
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { SearchService } from '../search/search.service';
|
||||
import { ActivatedRoute } from '@angular/router';
|
||||
import { SortOptions } from '../core/cache/models/sort-options.model';
|
||||
import { RemoteData } from '../core/data/remote-data';
|
||||
import { SearchResult } from '../search/search-result.model';
|
||||
import { DSpaceObject } from '../core/shared/dspace-object.model';
|
||||
|
||||
/**
|
||||
* This component renders a simple item page.
|
||||
* The route parameter 'id' is used to request the item it represents.
|
||||
* All fields of the item that should be displayed, are defined in its template.
|
||||
*/
|
||||
|
||||
@Component({
|
||||
selector: 'ds-search-page',
|
||||
styleUrls: ['./search-page.component.scss'],
|
||||
templateUrl: './search-page.component.html',
|
||||
})
|
||||
export class SearchPageComponent implements OnInit {
|
||||
private sub;
|
||||
private results: RemoteData<Array<SearchResult<DSpaceObject>>>;
|
||||
|
||||
constructor(
|
||||
private service: SearchService,
|
||||
private route: ActivatedRoute,
|
||||
) { }
|
||||
|
||||
ngOnInit(): void {
|
||||
this.sub = this.route
|
||||
.queryParams
|
||||
.subscribe((params) => {
|
||||
const query: string = params.query || '';
|
||||
const scope: string = params.scope;
|
||||
const page: number = +params.page || 0;
|
||||
this.results = this.service.search(query, scope, {elementsPerPage: 10, currentPage: page, sort: new SortOptions()});
|
||||
});
|
||||
}
|
||||
ngOnDestroy() {
|
||||
this.sub.unsubscribe();
|
||||
}
|
||||
}
|
27
src/app/search-page/search-page.module.ts
Normal file
27
src/app/search-page/search-page.module.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { RouterModule } from '@angular/router';
|
||||
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
|
||||
import { SharedModule } from '../shared/shared.module';
|
||||
import { SearchPageRoutingModule } from './search-page-routing.module';
|
||||
import { SearchPageComponent } from './search-page.component';
|
||||
import { SearchFormComponent } from '../shared/search-form/search-form.component';
|
||||
import { SearchResultsComponent } from './search-results/search-results.compontent';
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
SearchPageRoutingModule,
|
||||
CommonModule,
|
||||
TranslateModule,
|
||||
RouterModule,
|
||||
SharedModule,
|
||||
],
|
||||
declarations: [
|
||||
SearchPageComponent,
|
||||
SearchFormComponent,
|
||||
SearchResultsComponent
|
||||
]
|
||||
})
|
||||
export class SearchPageModule { }
|
@@ -0,0 +1 @@
|
||||
<ds-object-list [objects]="searchResults"></ds-object-list>
|
@@ -0,0 +1,23 @@
|
||||
import { Component, OnInit, Input } from '@angular/core';
|
||||
import { RemoteData } from '../../core/data/remote-data';
|
||||
import { DSpaceObject } from '../../core/shared/dspace-object.model';
|
||||
|
||||
/**
|
||||
* This component renders a simple item page.
|
||||
* The route parameter 'id' is used to request the item it represents.
|
||||
* All fields of the item that should be displayed, are defined in its template.
|
||||
*/
|
||||
|
||||
@Component({
|
||||
selector: 'ds-search-results',
|
||||
templateUrl: './search-results.component.html',
|
||||
})
|
||||
|
||||
export class SearchResultsComponent implements OnInit {
|
||||
@Input() searchResults: RemoteData<DSpaceObject[]>;
|
||||
|
||||
ngOnInit(): void {
|
||||
// onInit
|
||||
|
||||
}
|
||||
}
|
@@ -1,7 +1,8 @@
|
||||
import { DSpaceObject } from '../core/shared/dspace-object.model';
|
||||
import { Metadatum } from '../core/shared/metadatum.model';
|
||||
import { ListableObject } from '../object-list/listable-object/listable-object.model';
|
||||
|
||||
export class SearchResult<T extends DSpaceObject>{
|
||||
export class SearchResult<T extends DSpaceObject> implements ListableObject {
|
||||
|
||||
dspaceObject: T;
|
||||
hitHighlights: Metadatum[];
|
||||
|
@@ -11,12 +11,12 @@ import {
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
|
||||
import { RemoteData } from '../../core/data/remote-data';
|
||||
import { DSpaceObject } from '../../core/shared/dspace-object.model';
|
||||
import { PageInfo } from '../../core/shared/page-info.model';
|
||||
|
||||
import { PaginationComponentOptions } from '../pagination/pagination-component-options.model';
|
||||
|
||||
import { SortOptions, SortDirection } from '../../core/cache/models/sort-options.model';
|
||||
import { ListableObject } from '../../object-list/listable-object/listable-object.model';
|
||||
|
||||
@Component({
|
||||
changeDetection: ChangeDetectionStrategy.Default,
|
||||
@@ -27,7 +27,7 @@ import { SortOptions, SortDirection } from '../../core/cache/models/sort-options
|
||||
})
|
||||
export class ObjectListComponent implements OnChanges, OnInit {
|
||||
|
||||
@Input() objects: RemoteData<DSpaceObject[]>;
|
||||
@Input() objects: RemoteData<ListableObject[]>;
|
||||
@Input() config: PaginationComponentOptions;
|
||||
@Input() sortConfig: SortOptions;
|
||||
@Input() hideGear = false;
|
||||
|
15
src/app/shared/search-form/search-form.component.html
Normal file
15
src/app/shared/search-form/search-form.component.html
Normal file
@@ -0,0 +1,15 @@
|
||||
<form [formGroup]="searchFormGroup">
|
||||
<div class="input-group">
|
||||
<input type="text" class="form-control" aria-label="Search input">
|
||||
<div ngbDropdown class="input-group-btn">
|
||||
<button type="submit" class="btn btn-secondary" id="searchDropdown" ngbDropdownToggle>
|
||||
Go
|
||||
</button>
|
||||
<div ngbDropdownMenu aria-labelledby="searchDropdown">
|
||||
<a class="dropdown-item" href="#">Search DSpace</a>
|
||||
<a class="dropdown-item" href="#">Search this collection</a>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
1
src/app/shared/search-form/search-form.component.scss
Normal file
1
src/app/shared/search-form/search-form.component.scss
Normal file
@@ -0,0 +1 @@
|
||||
@import '../../../styles/variables.scss';
|
28
src/app/shared/search-form/search-form.component.ts
Normal file
28
src/app/shared/search-form/search-form.component.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { FormGroup, FormControl } from '@angular/forms';
|
||||
|
||||
/**
|
||||
* This component renders a simple item page.
|
||||
* The route parameter 'id' is used to request the item it represents.
|
||||
* All fields of the item that should be displayed, are defined in its template.
|
||||
*/
|
||||
|
||||
@Component({
|
||||
selector: 'ds-search-form',
|
||||
styleUrls: ['./search-form.component.scss'],
|
||||
templateUrl: './search-form.component.html',
|
||||
})
|
||||
export class SearchFormComponent implements OnInit {
|
||||
searchFormGroup: FormGroup;
|
||||
//
|
||||
// constructor() {
|
||||
//
|
||||
// }
|
||||
//
|
||||
ngOnInit(): void {
|
||||
this.searchFormGroup = new FormGroup({
|
||||
firstName: new FormControl()
|
||||
});
|
||||
}
|
||||
|
||||
}
|
@@ -24,6 +24,7 @@ import { ItemListElementComponent } from '../object-list/item-list-element/item-
|
||||
import { CommunityListElementComponent } from '../object-list/community-list-element/community-list-element.component';
|
||||
import { CollectionListElementComponent } from '../object-list/collection-list-element/collection-list-element.component';
|
||||
import { TruncatePipe } from './utils/truncate.pipe';
|
||||
import { WrapperListElementComponent } from '../object-list/wrapper-list-element/wrapper-list-element.component';
|
||||
|
||||
const MODULES = [
|
||||
// Do NOT include UniversalModule, HttpModule, or JsonpModule here
|
||||
@@ -53,6 +54,7 @@ const COMPONENTS = [
|
||||
ComcolPageLogoComponent,
|
||||
ObjectListComponent,
|
||||
ObjectListElementComponent,
|
||||
WrapperListElementComponent,
|
||||
ItemListElementComponent,
|
||||
CollectionListElementComponent,
|
||||
CommunityListElementComponent
|
||||
|
@@ -3,5 +3,6 @@ export const ROUTES: string[] = [
|
||||
'items/:id',
|
||||
'collections/:id',
|
||||
'communities/:id',
|
||||
'search',
|
||||
'**'
|
||||
];
|
||||
|
Reference in New Issue
Block a user