44024: simple search UI with working search results

This commit is contained in:
Lotte Hofstede
2017-08-21 15:32:52 +02:00
parent 563cf6e820
commit bacb048bb6
26 changed files with 183 additions and 39 deletions

View File

@@ -1,4 +1,4 @@
import { Component } from '@angular/core'; import { Component, Inject } from '@angular/core';
import { Collection } from '../../core/shared/collection.model'; import { Collection } from '../../core/shared/collection.model';
import { ObjectListElementComponent } from '../object-list-element/object-list-element.component'; import { ObjectListElementComponent } from '../object-list-element/object-list-element.component';
@@ -11,4 +11,4 @@ import { listElementFor } from '../list-element-decorator';
}) })
@listElementFor(Collection) @listElementFor(Collection)
export class CollectionListElementComponent extends ObjectListElementComponent {} export class CollectionListElementComponent extends ObjectListElementComponent<Collection> {}

View File

@@ -1,4 +1,4 @@
import { Component, Input } from '@angular/core'; import { Component, Input, Inject } from '@angular/core';
import { Community } from '../../core/shared/community.model'; import { Community } from '../../core/shared/community.model';
import { ObjectListElementComponent } from '../object-list-element/object-list-element.component'; import { ObjectListElementComponent } from '../object-list-element/object-list-element.component';
@@ -11,4 +11,4 @@ import { listElementFor } from '../list-element-decorator';
}) })
@listElementFor(Community) @listElementFor(Community)
export class CommunityListElementComponent extends ObjectListElementComponent {} export class CommunityListElementComponent extends ObjectListElementComponent<Community> {}

View File

@@ -1,4 +1,4 @@
import { Component, Input } from '@angular/core'; import { Component, Input, Inject } from '@angular/core';
import { Item } from '../../core/shared/item.model'; import { Item } from '../../core/shared/item.model';
import { ObjectListElementComponent } from '../object-list-element/object-list-element.component'; import { ObjectListElementComponent } from '../object-list-element/object-list-element.component';
@@ -11,4 +11,4 @@ import { listElementFor } from '../list-element-decorator';
}) })
@listElementFor(Item) @listElementFor(Item)
export class ItemListElementComponent extends ObjectListElementComponent {} export class ItemListElementComponent extends ObjectListElementComponent<Item> {}

View File

@@ -1,12 +1,16 @@
import { ListableObject } from './listable-object/listable-object.model'; import { ListableObject } from './listable-object/listable-object.model';
import { GenericConstructor } from '../core/shared/generic-constructor'; import { GenericConstructor } from '../core/shared/generic-constructor';
const listElementForMetadataKey = Symbol('listElementFor'); const listElementMap = new Map();
export function listElementFor(listable: GenericConstructor<ListableObject>) {
export function listElementFor(value: GenericConstructor<ListableObject>) { return function decorator(objectElement: any) {
return Reflect.metadata(listElementForMetadataKey, value); if (!objectElement) {
return;
}
listElementMap.set(listable, objectElement);
};
} }
export function getListElementFor(target: any) { export function getListElementFor(listable: GenericConstructor<ListableObject>) {
return Reflect.getOwnMetadata(listElementForMetadataKey, target); return listElementMap.get(listable);
} }

View File

@@ -1,3 +1 @@
export interface ListableObject { export interface ListableObject {}
}

View File

@@ -6,8 +6,9 @@ import { ListableObject } from '../listable-object/listable-object.model';
styleUrls: ['./object-list-element.component.scss'], styleUrls: ['./object-list-element.component.scss'],
templateUrl: './object-list-element.component.html' templateUrl: './object-list-element.component.html'
}) })
export class ObjectListElementComponent { export class ObjectListElementComponent <T extends ListableObject> {
object: T;
// In the current version of Angular4, @Input is not supported by the NgComponentOutlet - instead we're using DI public constructor(@Inject('objectElementProvider') public listable: ListableObject) {
constructor(@Inject('objectElementProvider') public object: ListableObject) { } this.object = listable as T;
}
} }

View File

@@ -0,0 +1,6 @@
<a [routerLink]="['/collections/' + dso.id]" class="lead">
{{dso.name}}
</a>
<div *ngIf="dso.shortDescription" class="text-muted">
{{dso.shortDescription}}
</div>

View File

@@ -0,0 +1 @@
@import '../../../../styles/variables.scss';

View File

@@ -0,0 +1,15 @@
import { Component } from '@angular/core';
import { listElementFor } from '../../list-element-decorator';
import { CollectionSearchResult } from './collection-search-result.model';
import { SearchResultListElementComponent } from '../search-result-list-element.component';
import { Collection } from '../../../core/shared/collection.model';
@Component({
selector: 'ds-collection-search-result-list-element',
styleUrls: ['collection-search-result-list-element.component.scss'],
templateUrl: 'collection-search-result-list-element.component.html'
})
@listElementFor(CollectionSearchResult)
export class CollectionSearchResultListElementComponent extends SearchResultListElementComponent<CollectionSearchResult, Collection> {}

View File

@@ -0,0 +1,5 @@
import { SearchResult } from '../../../search/search-result.model';
import { Collection } from '../../../core/shared/collection.model';
export class CollectionSearchResult extends SearchResult<Collection> {
}

View File

@@ -0,0 +1,6 @@
<a [routerLink]="['/communities/' + dso.id]" class="lead">
{{dso.name}}
</a>
<div *ngIf="dso.shortDescription" class="text-muted">
{{dso.shortDescription}}
</div>

View File

@@ -0,0 +1 @@
@import '../../../../styles/variables.scss';

View File

@@ -0,0 +1,17 @@
import { Component } from '@angular/core';
import { listElementFor } from '../../list-element-decorator';
import { CommunitySearchResult } from './community-search-result.model';
import { SearchResultListElementComponent } from '../search-result-list-element.component';
import { Community } from '../../../core/shared/community.model';
@Component({
selector: 'ds-community-search-result-list-element',
styleUrls: ['community-search-result-list-element.component.scss'],
templateUrl: 'community-search-result-list-element.component.html'
})
@listElementFor(CommunitySearchResult)
export class CommunitySearchResultListElementComponent extends SearchResultListElementComponent<CommunitySearchResult, Community> {
}

View File

@@ -0,0 +1,5 @@
import { SearchResult } from '../../../search/search-result.model';
import { Community } from '../../../core/shared/community.model';
export class CommunitySearchResult extends SearchResult<Community> {
}

View File

@@ -0,0 +1,14 @@
<a [routerLink]="['/items/' + dso.id]" class="lead">
{{dso.findMetadata("dc.title")}}
</a>
<div>
<span class="text-muted">
<span *ngIf="dso.filterMetadata(['dc.contributor.author', 'dc.creator', 'dc.contributor.*']);" class="item-list-authors">
<span *ngFor="let authorMd of dso.filterMetadata(['dc.contributor.author', 'dc.creator', 'dc.contributor.*']); let last=last;">{{authorMd.value}}
<span *ngIf="!last">; </span>
</span>
</span>
(<span *ngIf="dso.findMetadata('dc.publisher')" class="item-list-publisher">{{dso.findMetadata("dc.publisher")}}, </span><span *ngIf="dso.findMetadata('dc.date.issued')" class="item-list-date">{{dso.findMetadata("dc.date.issued")}}</span>)
</span>
<div *ngIf="dso.findMetadata('dc.description.abstract')" class="item-list-abstract">{{dso.findMetadata("dc.description.abstract") | dsTruncate:[200] }}</div>
</div>

View File

@@ -0,0 +1 @@
@import '../../../../styles/variables.scss';

View File

@@ -0,0 +1,15 @@
import { Component } from '@angular/core';
import { listElementFor } from '../../list-element-decorator';
import { ItemSearchResult } from './item-search-result.model';
import { SearchResultListElementComponent } from '../search-result-list-element.component';
import { Item } from '../../../core/shared/item.model';
@Component({
selector: 'ds-item-search-result-list-element',
styleUrls: ['item-search-result-list-element.component.scss'],
templateUrl: 'item-search-result-list-element.component.html'
})
@listElementFor(ItemSearchResult)
export class ItemSearchResultListElementComponent extends SearchResultListElementComponent<ItemSearchResult, Item> {}

View File

@@ -0,0 +1,5 @@
import { SearchResult } from '../../../search/search-result.model';
import { Item } from '../../../core/shared/item.model';
export class ItemSearchResult extends SearchResult<Item> {
}

View File

@@ -0,0 +1,19 @@
import { Component, Inject } from '@angular/core';
import { ObjectListElementComponent } from '../object-list-element/object-list-element.component';
import { ListableObject } from '../listable-object/listable-object.model';
import { SearchResult } from '../../search/search-result.model';
import { DSpaceObject } from '../../core/shared/dspace-object.model';
@Component({
selector: 'ds-search-result-list-element',
template: ``
})
export class SearchResultListElementComponent<T extends SearchResult<K>, K extends DSpaceObject> extends ObjectListElementComponent<T> {
dso: K;
public constructor(@Inject('objectElementProvider') public listable: ListableObject) {
super(listable);
this.dso = this.object.dspaceObject;
}
}

View File

@@ -1,6 +1,7 @@
import { Component, Input, Injector, ReflectiveInjector, OnInit } from '@angular/core'; import { Component, Input, Injector, ReflectiveInjector, OnInit } from '@angular/core';
import { ListableObject } from '../listable-object/listable-object.model'; import { ListableObject } from '../listable-object/listable-object.model';
import { getListElementFor } from '../list-element-decorator' import { getListElementFor } from '../list-element-decorator'
import { GenericConstructor } from '../../core/shared/generic-constructor';
@Component({ @Component({
selector: 'ds-wrapper-list-element', selector: 'ds-wrapper-list-element',
@@ -15,11 +16,12 @@ export class WrapperListElementComponent implements OnInit {
ngOnInit(): void { ngOnInit(): void {
this.objectInjector = ReflectiveInjector.resolveAndCreate( this.objectInjector = ReflectiveInjector.resolveAndCreate(
[{provide: 'objectElementProvider', useFactory: () => ({ providedObject: this.object }) }], this.injector); [{provide: 'objectElementProvider', useFactory: () => (this.object) }], this.injector);
} }
private getListElement(): string { getListElement(): string {
return getListElementFor(this.object.constructor).constructor.name; const f: GenericConstructor<ListableObject> = this.object.constructor as GenericConstructor<ListableObject>;
return getListElementFor(f);
} }
} }

View File

@@ -19,7 +19,7 @@ import { DSpaceObject } from '../core/shared/dspace-object.model';
}) })
export class SearchPageComponent implements OnInit { export class SearchPageComponent implements OnInit {
private sub; private sub;
private results: RemoteData<Array<SearchResult<DSpaceObject>>>; results: RemoteData<Array<SearchResult<DSpaceObject>>>;
constructor( constructor(
private service: SearchService, private service: SearchService,

View File

@@ -9,6 +9,10 @@ import { SearchPageRoutingModule } from './search-page-routing.module';
import { SearchPageComponent } from './search-page.component'; import { SearchPageComponent } from './search-page.component';
import { SearchFormComponent } from '../shared/search-form/search-form.component'; import { SearchFormComponent } from '../shared/search-form/search-form.component';
import { SearchResultsComponent } from './search-results/search-results.compontent'; import { SearchResultsComponent } from './search-results/search-results.compontent';
import { SearchModule } from '../search/search.module';
import { ItemSearchResultListElementComponent } from '../object-list/search-result-list-element/item-search-result/item-search-result-list-element.component';
import { CollectionSearchResultListElementComponent } from '../object-list/search-result-list-element/collection-search-result/collection-search-result-list-element.component';
import { CommunitySearchResultListElementComponent } from '../object-list/search-result-list-element/community-search-result/community-search-result-list-element.component';
@NgModule({ @NgModule({
imports: [ imports: [
@@ -17,11 +21,20 @@ import { SearchResultsComponent } from './search-results/search-results.componte
TranslateModule, TranslateModule,
RouterModule, RouterModule,
SharedModule, SharedModule,
SearchModule
], ],
declarations: [ declarations: [
SearchPageComponent, SearchPageComponent,
SearchFormComponent, SearchFormComponent,
SearchResultsComponent SearchResultsComponent,
ItemSearchResultListElementComponent,
CollectionSearchResultListElementComponent,
CommunitySearchResultListElementComponent
],
entryComponents: [
ItemSearchResultListElementComponent,
CollectionSearchResultListElementComponent,
CommunitySearchResultListElementComponent
] ]
}) })
export class SearchPageModule { } export class SearchPageModule { }

View File

@@ -1,5 +1,6 @@
import { Component, OnInit, Input } from '@angular/core'; import { Component, OnInit, Input } from '@angular/core';
import { RemoteData } from '../../core/data/remote-data'; import { RemoteData } from '../../core/data/remote-data';
import { SearchResult } from '../../search/search-result.model';
import { DSpaceObject } from '../../core/shared/dspace-object.model'; import { DSpaceObject } from '../../core/shared/dspace-object.model';
/** /**
@@ -14,7 +15,7 @@ import { DSpaceObject } from '../../core/shared/dspace-object.model';
}) })
export class SearchResultsComponent implements OnInit { export class SearchResultsComponent implements OnInit {
@Input() searchResults: RemoteData<DSpaceObject[]>; @Input() searchResults: RemoteData<Array<SearchResult<DSpaceObject>>>;
ngOnInit(): void { ngOnInit(): void {
// onInit // onInit

View File

@@ -9,6 +9,7 @@ import { SearchOptions } from './search.models';
import { hasValue, isNotEmpty } from '../shared/empty.util'; import { hasValue, isNotEmpty } from '../shared/empty.util';
import { Metadatum } from '../core/shared/metadatum.model'; import { Metadatum } from '../core/shared/metadatum.model';
import { Item } from '../core/shared/item.model'; import { Item } from '../core/shared/item.model';
import { ItemSearchResult } from '../object-list/search-result-list-element/item-search-result/item-search-result.model';
@Injectable() @Injectable()
export class SearchService { export class SearchService {
@@ -62,12 +63,12 @@ export class SearchService {
elementsPerPage: returningPageInfo.elementsPerPage elementsPerPage: returningPageInfo.elementsPerPage
}); });
const payload = itemsRD.payload.map((items: Item[]) => { const payload = itemsRD.payload.map((items: Item[]) => {
return items.sort(()=>{ return items.sort(() => {
const values = [-1, 0, 1]; const values = [-1, 0, 1];
return values[Math.floor(Math.random() * values.length)]; return values[Math.floor(Math.random() * values.length)];
}) })
.map((item: Item, index: number) => { .map((item: Item, index: number) => {
const mockResult: SearchResult<DSpaceObject> = new SearchResult(); const mockResult: SearchResult<DSpaceObject> = new ItemSearchResult();
mockResult.dspaceObject = item; mockResult.dspaceObject = item;
const highlight = new Metadatum(); const highlight = new Metadatum();
highlight.key = 'dc.description.abstract'; highlight.key = 'dc.description.abstract';

View File

@@ -1,15 +1,19 @@
<form [formGroup]="searchFormGroup"> <form [formGroup]="searchFormGroup" action="/search">
<div class="input-group"> <div class="input-group">
<input type="text" class="form-control" aria-label="Search input"> <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> <div class="input-group-btn" ngbDropdown>
Go <button type="submit" class="btn btn-secondary">Search DSpace</button>
<button type="button" class="btn btn-secondary dropdown-toggle" data-toggle="dropdown"
aria-haspopup="true" aria-expanded="false" id="searchDropdown" ngbDropdownToggle>
<span class="sr-only">Toggle Dropdown</span>
</button> </button>
<div ngbDropdownMenu aria-labelledby="searchDropdown"> <div ngbDropdownMenu class="dropdown-menu dropdown-menu-right" aria-labelledby="searchDropdown">
<a class="dropdown-item" href="#">Search DSpace</a> <a class="dropdown-item" href="#">Search DSpace</a>
<a class="dropdown-item" href="#">Search this collection</a> <a class="dropdown-item" href="#">Search this Collection</a>
<a class="dropdown-item" href="#">Search this Community</a>
</div>
</div> </div>
</div> </div>
</div>
</form> </form>

View File

@@ -25,6 +25,7 @@ import { CommunityListElementComponent } from '../object-list/community-list-ele
import { CollectionListElementComponent } from '../object-list/collection-list-element/collection-list-element.component'; import { CollectionListElementComponent } from '../object-list/collection-list-element/collection-list-element.component';
import { TruncatePipe } from './utils/truncate.pipe'; import { TruncatePipe } from './utils/truncate.pipe';
import { WrapperListElementComponent } from '../object-list/wrapper-list-element/wrapper-list-element.component'; import { WrapperListElementComponent } from '../object-list/wrapper-list-element/wrapper-list-element.component';
import { SearchResultListElementComponent } from '../object-list/search-result-list-element/search-result-list-element.component';
const MODULES = [ const MODULES = [
// Do NOT include UniversalModule, HttpModule, or JsonpModule here // Do NOT include UniversalModule, HttpModule, or JsonpModule here
@@ -54,10 +55,15 @@ const COMPONENTS = [
ComcolPageLogoComponent, ComcolPageLogoComponent,
ObjectListComponent, ObjectListComponent,
ObjectListElementComponent, ObjectListElementComponent,
WrapperListElementComponent, WrapperListElementComponent
];
const ENTRY_COMPONENTS = [
// put shared entry components (components that are created dynamically) here
ItemListElementComponent, ItemListElementComponent,
CollectionListElementComponent, CollectionListElementComponent,
CommunityListElementComponent CommunityListElementComponent,
SearchResultListElementComponent
]; ];
const PROVIDERS = [ const PROVIDERS = [
@@ -72,7 +78,8 @@ const PROVIDERS = [
], ],
declarations: [ declarations: [
...PIPES, ...PIPES,
...COMPONENTS ...COMPONENTS,
...ENTRY_COMPONENTS
], ],
exports: [ exports: [
...MODULES, ...MODULES,
@@ -81,6 +88,9 @@ const PROVIDERS = [
], ],
providers: [ providers: [
...PROVIDERS ...PROVIDERS
],
entryComponents: [
...ENTRY_COMPONENTS
] ]
}) })
export class SharedModule { export class SharedModule {