mirror of
https://github.com/DSpace/dspace-angular.git
synced 2025-10-07 10:04:11 +00:00
Added search scope lookup to replace dropdown
This commit is contained in:
@@ -8,52 +8,6 @@ describe('Search Page', () => {
|
|||||||
cy.get(SEARCHFORM_ID + ' input[name="query"]').should('have.value', queryString);
|
cy.get(SEARCHFORM_ID + ' input[name="query"]').should('have.value', queryString);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
it('should have right scope selected when navigating to page with scope parameter', () => {
|
|
||||||
// First, visit search with no params just to get the set of the scope options
|
|
||||||
cy.visit('/search');
|
|
||||||
cy.get(SEARCHFORM_ID + ' select[name="scope"] > option').as('options');
|
|
||||||
|
|
||||||
// Find length of scope options, select a random index
|
|
||||||
cy.get('@options').its('length')
|
|
||||||
.then(len => Math.floor(Math.random() * Math.floor(len)))
|
|
||||||
.then((index) => {
|
|
||||||
// return the option at that (randomly selected) index
|
|
||||||
return cy.get('@options').eq(index);
|
|
||||||
})
|
|
||||||
.then((option) => {
|
|
||||||
const randomScope: any = option.val();
|
|
||||||
// Visit the search page with the randomly selected option as a pararmeter
|
|
||||||
cy.visit('/search?scope=' + randomScope);
|
|
||||||
// Verify that scope is selected when the page reloads
|
|
||||||
cy.get(SEARCHFORM_ID + ' select[name="scope"]').find('option:selected').should('have.value', randomScope);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
it('should redirect to the correct url when scope was set and submit button was triggered', () => {
|
|
||||||
// First, visit search with no params just to get the set of scope options
|
|
||||||
cy.visit('/search');
|
|
||||||
cy.get(SEARCHFORM_ID + ' select[name="scope"] > option').as('options');
|
|
||||||
|
|
||||||
// Find length of scope options, select a random index (i.e. a random option in selectbox)
|
|
||||||
cy.get('@options').its('length')
|
|
||||||
.then(len => Math.floor(Math.random() * Math.floor(len)))
|
|
||||||
.then((index) => {
|
|
||||||
// return the option at that (randomly selected) index
|
|
||||||
return cy.get('@options').eq(index);
|
|
||||||
})
|
|
||||||
.then((option) => {
|
|
||||||
const randomScope: any = option.val();
|
|
||||||
// Select the option at our random index & click the search button
|
|
||||||
cy.get(SEARCHFORM_ID + ' select[name="scope"]').select(randomScope);
|
|
||||||
cy.get(SEARCHFORM_ID + ' button.search-button').click();
|
|
||||||
// Result should be the page URL should include that scope & page will reload with scope selected
|
|
||||||
cy.url().should('include', 'scope=' + randomScope);
|
|
||||||
cy.get(SEARCHFORM_ID + ' select[name="scope"]').find('option:selected').should('have.value', randomScope);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should redirect to the correct url when query was set and submit button was triggered', () => {
|
it('should redirect to the correct url when query was set and submit button was triggered', () => {
|
||||||
const queryString = 'Another interesting query string';
|
const queryString = 'Another interesting query string';
|
||||||
cy.visit('/search');
|
cy.visit('/search');
|
||||||
@@ -62,5 +16,4 @@ describe('Search Page', () => {
|
|||||||
cy.get(SEARCHFORM_ID + ' button.search-button').click();
|
cy.get(SEARCHFORM_ID + ' button.search-button').click();
|
||||||
cy.url().should('include', 'query=' + encodeURI(queryString));
|
cy.url().should('include', 'query=' + encodeURI(queryString));
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
@@ -395,48 +395,6 @@ export class SearchService implements OnDestroy {
|
|||||||
return this.rdb.buildFromHref(href);
|
return this.rdb.buildFromHref(href);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Request a list of DSpaceObjects that can be used as a scope, based on the current scope
|
|
||||||
* @param {string} scopeId UUID of the current scope, if the scope is empty, the repository wide scopes will be returned
|
|
||||||
* @returns {Observable<DSpaceObject[]>} Emits a list of DSpaceObjects which represent possible scopes
|
|
||||||
*/
|
|
||||||
getScopes(scopeId?: string): Observable<DSpaceObject[]> {
|
|
||||||
|
|
||||||
if (isEmpty(scopeId)) {
|
|
||||||
const top: Observable<Community[]> = this.communityService.findTop({ elementsPerPage: 9999 }).pipe(
|
|
||||||
getFirstSucceededRemoteData(),
|
|
||||||
map(
|
|
||||||
(communities: RemoteData<PaginatedList<Community>>) => communities.payload.page
|
|
||||||
)
|
|
||||||
);
|
|
||||||
return top;
|
|
||||||
}
|
|
||||||
|
|
||||||
const scopeObject: Observable<RemoteData<DSpaceObject>> = this.dspaceObjectService.findById(scopeId).pipe(getFirstSucceededRemoteData());
|
|
||||||
const scopeList: Observable<DSpaceObject[]> = scopeObject.pipe(
|
|
||||||
switchMap((dsoRD: RemoteData<DSpaceObject>) => {
|
|
||||||
if ((dsoRD.payload as any).type === Community.type.value) {
|
|
||||||
const community: Community = dsoRD.payload as Community;
|
|
||||||
this.linkService.resolveLinks(community, followLink('subcommunities'), followLink('collections'));
|
|
||||||
return observableCombineLatest([
|
|
||||||
community.subcommunities.pipe(getFirstCompletedRemoteData()),
|
|
||||||
community.collections.pipe(getFirstCompletedRemoteData())
|
|
||||||
]).pipe(
|
|
||||||
map(([subCommunities, collections]) => {
|
|
||||||
/*if this is a community, we also need to show the direct children*/
|
|
||||||
return [community, ...subCommunities.payload.page, ...collections.payload.page];
|
|
||||||
})
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
return observableOf([dsoRD.payload]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
));
|
|
||||||
|
|
||||||
return scopeList;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Requests the current view mode based on the current URL
|
* Requests the current view mode based on the current URL
|
||||||
* @returns {Observable<ViewMode>} The current view mode
|
* @returns {Observable<ViewMode>} The current view mode
|
||||||
|
@@ -15,7 +15,7 @@
|
|||||||
[query]="(searchOptions$ | async)?.query"
|
[query]="(searchOptions$ | async)?.query"
|
||||||
[scope]="(searchOptions$ | async)?.scope"
|
[scope]="(searchOptions$ | async)?.scope"
|
||||||
[currentUrl]="getSearchLink()"
|
[currentUrl]="getSearchLink()"
|
||||||
[scopes]="(scopeListRD$ | async)"
|
[showScopeSelector]="true"
|
||||||
[inPlaceSearch]="inPlaceSearch"
|
[inPlaceSearch]="inPlaceSearch"
|
||||||
[searchPlaceholder]="'mydspace.search-form.placeholder' | translate">
|
[searchPlaceholder]="'mydspace.search-form.placeholder' | translate">
|
||||||
</ds-search-form>
|
</ds-search-form>
|
||||||
|
@@ -78,11 +78,6 @@ export class MyDSpacePageComponent implements OnInit {
|
|||||||
*/
|
*/
|
||||||
sortOptions$: Observable<SortOptions[]>;
|
sortOptions$: Observable<SortOptions[]>;
|
||||||
|
|
||||||
/**
|
|
||||||
* The current relevant scopes
|
|
||||||
*/
|
|
||||||
scopeListRD$: Observable<DSpaceObject[]>;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Emits true if were on a small screen
|
* Emits true if were on a small screen
|
||||||
*/
|
*/
|
||||||
@@ -144,10 +139,6 @@ export class MyDSpacePageComponent implements OnInit {
|
|||||||
this.resultsRD$.next(results);
|
this.resultsRD$.next(results);
|
||||||
});
|
});
|
||||||
|
|
||||||
this.scopeListRD$ = this.searchConfigService.getCurrentScope('').pipe(
|
|
||||||
switchMap((scopeId) => this.service.getScopes(scopeId))
|
|
||||||
);
|
|
||||||
|
|
||||||
this.context$ = this.searchConfigService.getCurrentConfiguration('workspace')
|
this.context$ = this.searchConfigService.getCurrentConfiguration('workspace')
|
||||||
.pipe(
|
.pipe(
|
||||||
map((configuration: string) => {
|
map((configuration: string) => {
|
||||||
|
@@ -47,7 +47,7 @@
|
|||||||
[query]="(searchOptions$ | async)?.query"
|
[query]="(searchOptions$ | async)?.query"
|
||||||
[scope]="(searchOptions$ | async)?.scope"
|
[scope]="(searchOptions$ | async)?.scope"
|
||||||
[currentUrl]="searchLink"
|
[currentUrl]="searchLink"
|
||||||
[scopes]="(scopeListRD$ | async)"
|
[showScopeSelector]="true"
|
||||||
[inPlaceSearch]="inPlaceSearch"
|
[inPlaceSearch]="inPlaceSearch"
|
||||||
[searchPlaceholder]="'search.search-form.placeholder' | translate">
|
[searchPlaceholder]="'search.search-form.placeholder' | translate">
|
||||||
</ds-search-form>
|
</ds-search-form>
|
||||||
|
@@ -55,11 +55,6 @@ export class SearchComponent implements OnInit {
|
|||||||
*/
|
*/
|
||||||
sortOptions$: Observable<SortOptions[]>;
|
sortOptions$: Observable<SortOptions[]>;
|
||||||
|
|
||||||
/**
|
|
||||||
* The current relevant scopes
|
|
||||||
*/
|
|
||||||
scopeListRD$: Observable<DSpaceObject[]>;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Emits true if were on a small screen
|
* Emits true if were on a small screen
|
||||||
*/
|
*/
|
||||||
@@ -137,9 +132,7 @@ export class SearchComponent implements OnInit {
|
|||||||
).subscribe((results) => {
|
).subscribe((results) => {
|
||||||
this.resultsRD$.next(results);
|
this.resultsRD$.next(results);
|
||||||
});
|
});
|
||||||
this.scopeListRD$ = this.searchConfigService.getCurrentScope('').pipe(
|
|
||||||
switchMap((scopeId) => this.service.getScopes(scopeId))
|
|
||||||
);
|
|
||||||
if (isEmpty(this.configuration$)) {
|
if (isEmpty(this.configuration$)) {
|
||||||
this.configuration$ = this.searchConfigService.getCurrentConfiguration('default');
|
this.configuration$ = this.searchConfigService.getCurrentConfiguration('default');
|
||||||
}
|
}
|
||||||
|
@@ -9,7 +9,8 @@ import { hasValue, isNotEmpty } from '../../empty.util';
|
|||||||
export enum SelectorActionType {
|
export enum SelectorActionType {
|
||||||
CREATE = 'create',
|
CREATE = 'create',
|
||||||
EDIT = 'edit',
|
EDIT = 'edit',
|
||||||
EXPORT_METADATA = 'export-metadata'
|
EXPORT_METADATA = 'export-metadata',
|
||||||
|
SET_SCOPE = 'set-scope'
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -77,6 +78,7 @@ export abstract class DSOSelectorModalWrapperComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Method called when an object has been selected
|
* Method called when an object has been selected
|
||||||
* @param dso The selected DSpaceObject
|
* @param dso The selected DSpaceObject
|
||||||
|
@@ -0,0 +1,19 @@
|
|||||||
|
<div>
|
||||||
|
<div class="modal-header">{{'dso-selector.'+ action + '.' + objectType.toString().toLowerCase() + '.head' | translate}}
|
||||||
|
<button type="button" class="close" (click)="selectObject(undefined)" aria-label="Close">
|
||||||
|
<span aria-hidden="true">×</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<button class="btn btn-outline-primary btn-lg btn-block" (click)="selectObject(undefined)">{{'dso-selector.' + action + '.' + objectType.toString().toLowerCase() + '.button'| translate }}</button>
|
||||||
|
<h3 class="position-relative py-1 my-3 font-weight-normal">
|
||||||
|
<hr>
|
||||||
|
<div id="create-community-or-separator" class="text-center position-absolute w-100">
|
||||||
|
<span class="px-4 bg-white">or</span>
|
||||||
|
</div>
|
||||||
|
</h3>
|
||||||
|
|
||||||
|
<h5 class="px-2">{{'dso-selector.' + action + '.' + objectType.toString().toLowerCase() + '.input-header' | translate}}</h5>
|
||||||
|
<ds-dso-selector [currentDSOId]="dsoRD?.payload.uuid" [types]="selectorTypes" (onSelect)="selectObject($event)"></ds-dso-selector>
|
||||||
|
</div>
|
||||||
|
</div>
|
@@ -0,0 +1,3 @@
|
|||||||
|
#create-community-or-separator {
|
||||||
|
top: 0;
|
||||||
|
}
|
@@ -0,0 +1,73 @@
|
|||||||
|
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
|
||||||
|
import { TranslateModule } from '@ngx-translate/core';
|
||||||
|
import { DebugElement, NO_ERRORS_SCHEMA } from '@angular/core';
|
||||||
|
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap';
|
||||||
|
import { ActivatedRoute, Router } from '@angular/router';
|
||||||
|
import { ScopeSelectorModalComponent } from './scope-selector-modal.component';
|
||||||
|
import { Community } from '../../../core/shared/community.model';
|
||||||
|
import { MetadataValue } from '../../../core/shared/metadata.models';
|
||||||
|
import { createSuccessfulRemoteDataObject } from '../../remote-data.utils';
|
||||||
|
import { RouterStub } from '../../testing/router.stub';
|
||||||
|
|
||||||
|
describe('ScopeSelectorModalComponent', () => {
|
||||||
|
let component: ScopeSelectorModalComponent;
|
||||||
|
let fixture: ComponentFixture<ScopeSelectorModalComponent>;
|
||||||
|
let debugElement: DebugElement;
|
||||||
|
|
||||||
|
const community = new Community();
|
||||||
|
community.uuid = '1234-1234-1234-1234';
|
||||||
|
community.metadata = {
|
||||||
|
'dc.title': [Object.assign(new MetadataValue(), {
|
||||||
|
value: 'Community title',
|
||||||
|
language: undefined
|
||||||
|
})]
|
||||||
|
};
|
||||||
|
const router = new RouterStub();
|
||||||
|
const communityRD = createSuccessfulRemoteDataObject(community);
|
||||||
|
const modalStub = jasmine.createSpyObj('modalStub', ['close']);
|
||||||
|
|
||||||
|
beforeEach(waitForAsync(() => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
imports: [TranslateModule.forRoot()],
|
||||||
|
declarations: [ScopeSelectorModalComponent],
|
||||||
|
providers: [
|
||||||
|
{ provide: NgbActiveModal, useValue: modalStub },
|
||||||
|
{
|
||||||
|
provide: ActivatedRoute,
|
||||||
|
useValue: {
|
||||||
|
root: {
|
||||||
|
snapshot: {
|
||||||
|
data: {
|
||||||
|
dso: communityRD,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
provide: Router, useValue: router
|
||||||
|
}
|
||||||
|
],
|
||||||
|
schemas: [NO_ERRORS_SCHEMA]
|
||||||
|
}).compileComponents();
|
||||||
|
|
||||||
|
}));
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
fixture = TestBed.createComponent(ScopeSelectorModalComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
debugElement = fixture.debugElement;
|
||||||
|
fixture.detectChanges();
|
||||||
|
spyOn(component.scopeChange, 'emit');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should call navigate on the router with the correct edit path when navigate is called', () => {
|
||||||
|
component.navigate(community);
|
||||||
|
expect(component.scopeChange.emit).toHaveBeenCalledWith(community);
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
@@ -0,0 +1,34 @@
|
|||||||
|
import { Component, EventEmitter, OnInit } from '@angular/core';
|
||||||
|
import { ActivatedRoute } from '@angular/router';
|
||||||
|
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap';
|
||||||
|
import { DSpaceObjectType } from '../../../core/shared/dspace-object-type.model';
|
||||||
|
import { DSOSelectorModalWrapperComponent, SelectorActionType } from '../../dso-selector/modal-wrappers/dso-selector-modal-wrapper.component';
|
||||||
|
import { DSpaceObject } from '../../../core/shared/dspace-object.model';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Component to wrap a button - for top communities -
|
||||||
|
* and a list of parent communities - for sub communities
|
||||||
|
* inside a modal
|
||||||
|
* Used to create a new community
|
||||||
|
*/
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'ds-scope-selector-modal',
|
||||||
|
styleUrls: ['./scope-selector-modal.component.scss'],
|
||||||
|
templateUrl: './scope-selector-modal.component.html',
|
||||||
|
})
|
||||||
|
export class ScopeSelectorModalComponent extends DSOSelectorModalWrapperComponent implements OnInit {
|
||||||
|
objectType = DSpaceObjectType.COMMUNITY;
|
||||||
|
selectorTypes = [DSpaceObjectType.COMMUNITY, DSpaceObjectType.COLLECTION];
|
||||||
|
action = SelectorActionType.SET_SCOPE;
|
||||||
|
scopeChange = new EventEmitter<DSpaceObject>();
|
||||||
|
|
||||||
|
constructor(protected activeModal: NgbActiveModal, protected route: ActivatedRoute) {
|
||||||
|
super(activeModal, route);
|
||||||
|
}
|
||||||
|
|
||||||
|
navigate(dso: DSpaceObject) {
|
||||||
|
/* Handle search navigation in underlying component */
|
||||||
|
this.scopeChange.emit(dso);
|
||||||
|
}
|
||||||
|
}
|
@@ -1,12 +1,9 @@
|
|||||||
<form #form="ngForm" (ngSubmit)="onSubmit(form.value)" class="row" action="/search">
|
<form #form="ngForm" (ngSubmit)="onSubmit(form.value)" action="/search">
|
||||||
<div *ngIf="isNotEmpty(scopes)" class="col-12 col-sm-3">
|
<div>
|
||||||
<select [(ngModel)]="scope" name="scope" class="form-control" aria-label="Search scope" (change)="onScopeChange($event.target.value)" tabindex="0">
|
|
||||||
<option value>{{'search.form.search_dspace' | translate}}</option>
|
|
||||||
<option *ngFor="let scopeOption of scopes" [value]="scopeOption.id">{{scopeOption?.name ? scopeOption.name : 'search.form.search_dspace' | translate}}</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
<div [ngClass]="{'col-sm-9': isNotEmpty(scopes)}" class="col-12">
|
|
||||||
<div class="form-group input-group">
|
<div class="form-group input-group">
|
||||||
|
<div *ngIf="showScopeSelector === true" class="input-group-prepend">
|
||||||
|
<button class="scope-button btn btn-outline-secondary text-truncate" [ngbTooltip]="(selectedScope | async)?.name" type="button" (click)="openScopeModal()">{{(selectedScope | async)?.name || ('search.form.scope.all' | translate)}}</button>
|
||||||
|
</div>
|
||||||
<input type="text" [(ngModel)]="query" name="query" class="form-control" attr.aria-label="{{ searchPlaceholder }}"
|
<input type="text" [(ngModel)]="query" name="query" class="form-control" attr.aria-label="{{ searchPlaceholder }}"
|
||||||
[placeholder]="searchPlaceholder">
|
[placeholder]="searchPlaceholder">
|
||||||
<span class="input-group-append">
|
<span class="input-group-append">
|
||||||
|
@@ -3,3 +3,7 @@
|
|||||||
background-color: var(--bs-input-bg);
|
background-color: var(--bs-input-bg);
|
||||||
color: var(--bs-input-color);
|
color: var(--bs-input-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.scope-button {
|
||||||
|
max-width: $search-form-scope-max-width;
|
||||||
|
}
|
||||||
|
@@ -8,13 +8,11 @@ import { Community } from '../../core/shared/community.model';
|
|||||||
import { TranslateModule } from '@ngx-translate/core';
|
import { TranslateModule } from '@ngx-translate/core';
|
||||||
import { DSpaceObject } from '../../core/shared/dspace-object.model';
|
import { DSpaceObject } from '../../core/shared/dspace-object.model';
|
||||||
import { SearchService } from '../../core/shared/search/search.service';
|
import { SearchService } from '../../core/shared/search/search.service';
|
||||||
import { PaginationComponentOptions } from '../pagination/pagination-component-options.model';
|
|
||||||
import { SortDirection, SortOptions } from '../../core/cache/models/sort-options.model';
|
|
||||||
import { FindListOptions } from '../../core/data/request.models';
|
|
||||||
import { of as observableOf } from 'rxjs';
|
|
||||||
import { PaginationService } from '../../core/pagination/pagination.service';
|
import { PaginationService } from '../../core/pagination/pagination.service';
|
||||||
import { SearchConfigurationService } from '../../core/shared/search/search-configuration.service';
|
import { SearchConfigurationService } from '../../core/shared/search/search-configuration.service';
|
||||||
import { PaginationServiceStub } from '../testing/pagination-service.stub';
|
import { PaginationServiceStub } from '../testing/pagination-service.stub';
|
||||||
|
import { DSpaceObjectDataService } from '../../core/data/dspace-object-data.service';
|
||||||
|
import { createSuccessfulRemoteDataObject$ } from '../remote-data.utils';
|
||||||
|
|
||||||
describe('SearchFormComponent', () => {
|
describe('SearchFormComponent', () => {
|
||||||
let comp: SearchFormComponent;
|
let comp: SearchFormComponent;
|
||||||
@@ -35,7 +33,8 @@ describe('SearchFormComponent', () => {
|
|||||||
useValue: {}
|
useValue: {}
|
||||||
},
|
},
|
||||||
{ provide: PaginationService, useValue: paginationService },
|
{ provide: PaginationService, useValue: paginationService },
|
||||||
{ provide: SearchConfigurationService, useValue: searchConfigService }
|
{ provide: SearchConfigurationService, useValue: searchConfigService },
|
||||||
|
{ provide: DSpaceObjectDataService, useValue: { findById: () => createSuccessfulRemoteDataObject$(undefined)} }
|
||||||
],
|
],
|
||||||
declarations: [SearchFormComponent]
|
declarations: [SearchFormComponent]
|
||||||
}).compileComponents();
|
}).compileComponents();
|
||||||
@@ -48,24 +47,6 @@ describe('SearchFormComponent', () => {
|
|||||||
el = de.nativeElement;
|
el = de.nativeElement;
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should display scopes when available with default and all scopes', () => {
|
|
||||||
|
|
||||||
comp.scopes = objects;
|
|
||||||
fixture.detectChanges();
|
|
||||||
const select: HTMLElement = de.query(By.css('select')).nativeElement;
|
|
||||||
expect(select).toBeDefined();
|
|
||||||
const options: HTMLCollection = select.children;
|
|
||||||
const defOption: Element = options.item(0);
|
|
||||||
expect(defOption.getAttribute('value')).toBe('');
|
|
||||||
|
|
||||||
let index = 1;
|
|
||||||
objects.forEach((object) => {
|
|
||||||
expect(options.item(index).textContent).toBe(object.name);
|
|
||||||
expect(options.item(index).getAttribute('value')).toBe(object.uuid);
|
|
||||||
index++;
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should not display scopes when empty', () => {
|
it('should not display scopes when empty', () => {
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
const select = de.query(By.css('select'));
|
const select = de.query(By.css('select'));
|
||||||
@@ -84,17 +65,17 @@ describe('SearchFormComponent', () => {
|
|||||||
}));
|
}));
|
||||||
|
|
||||||
it('should select correct scope option in scope select', fakeAsync(() => {
|
it('should select correct scope option in scope select', fakeAsync(() => {
|
||||||
comp.scopes = objects;
|
|
||||||
fixture.detectChanges();
|
|
||||||
|
|
||||||
|
fixture.detectChanges();
|
||||||
|
comp.showScopeSelector = true;
|
||||||
const testCommunity = objects[1];
|
const testCommunity = objects[1];
|
||||||
comp.scope = testCommunity.id;
|
comp.selectedScope.next(testCommunity);
|
||||||
|
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
tick();
|
tick();
|
||||||
const scopeSelect = de.query(By.css('select')).nativeElement;
|
const scopeSelect = de.query(By.css('.scope-button')).nativeElement;
|
||||||
|
|
||||||
expect(scopeSelect.value).toBe(testCommunity.id);
|
expect(scopeSelect.textContent).toBe(testCommunity.name);
|
||||||
}));
|
}));
|
||||||
// it('should call updateSearch when clicking the submit button with correct parameters', fakeAsync(() => {
|
// it('should call updateSearch when clicking the submit button with correct parameters', fakeAsync(() => {
|
||||||
// comp.query = 'Test String'
|
// comp.query = 'Test String'
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
import { Component, EventEmitter, Input, Output } from '@angular/core';
|
import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core';
|
||||||
import { DSpaceObject } from '../../core/shared/dspace-object.model';
|
import { DSpaceObject } from '../../core/shared/dspace-object.model';
|
||||||
import { Router } from '@angular/router';
|
import { Router } from '@angular/router';
|
||||||
import { isNotEmpty } from '../empty.util';
|
import { isNotEmpty } from '../empty.util';
|
||||||
@@ -6,6 +6,12 @@ import { SearchService } from '../../core/shared/search/search.service';
|
|||||||
import { currentPath } from '../utils/route.utils';
|
import { currentPath } from '../utils/route.utils';
|
||||||
import { PaginationService } from '../../core/pagination/pagination.service';
|
import { PaginationService } from '../../core/pagination/pagination.service';
|
||||||
import { SearchConfigurationService } from '../../core/shared/search/search-configuration.service';
|
import { SearchConfigurationService } from '../../core/shared/search/search-configuration.service';
|
||||||
|
import { NgbModal } from '@ng-bootstrap/ng-bootstrap';
|
||||||
|
import { ScopeSelectorModalComponent } from './scope-selector-modal/scope-selector-modal.component';
|
||||||
|
import { take } from 'rxjs/operators';
|
||||||
|
import { BehaviorSubject } from 'rxjs';
|
||||||
|
import { DSpaceObjectDataService } from '../../core/data/dspace-object-data.service';
|
||||||
|
import { getFirstSucceededRemoteDataPayload } from '../../core/shared/operators';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This component renders a simple item page.
|
* This component renders a simple item page.
|
||||||
@@ -22,7 +28,7 @@ import { SearchConfigurationService } from '../../core/shared/search/search-conf
|
|||||||
/**
|
/**
|
||||||
* Component that represents the search form
|
* Component that represents the search form
|
||||||
*/
|
*/
|
||||||
export class SearchFormComponent {
|
export class SearchFormComponent implements OnInit {
|
||||||
/**
|
/**
|
||||||
* The search query
|
* The search query
|
||||||
*/
|
*/
|
||||||
@@ -39,12 +45,9 @@ export class SearchFormComponent {
|
|||||||
@Input()
|
@Input()
|
||||||
scope = '';
|
scope = '';
|
||||||
|
|
||||||
@Input() currentUrl: string;
|
selectedScope: BehaviorSubject<DSpaceObject> = new BehaviorSubject<DSpaceObject>(undefined);
|
||||||
|
|
||||||
/**
|
@Input() currentUrl: string;
|
||||||
* The available scopes
|
|
||||||
*/
|
|
||||||
@Input() scopes: DSpaceObject[];
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Whether or not the search button should be displayed large
|
* Whether or not the search button should be displayed large
|
||||||
@@ -61,6 +64,11 @@ export class SearchFormComponent {
|
|||||||
*/
|
*/
|
||||||
@Input() searchPlaceholder: string;
|
@Input() searchPlaceholder: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Defines whether or not to show the scope selector
|
||||||
|
*/
|
||||||
|
@Input() showScopeSelector = false;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Output the search data on submit
|
* Output the search data on submit
|
||||||
*/
|
*/
|
||||||
@@ -68,10 +76,19 @@ export class SearchFormComponent {
|
|||||||
|
|
||||||
constructor(private router: Router, private searchService: SearchService,
|
constructor(private router: Router, private searchService: SearchService,
|
||||||
private paginationService: PaginationService,
|
private paginationService: PaginationService,
|
||||||
private searchConfig: SearchConfigurationService
|
private searchConfig: SearchConfigurationService,
|
||||||
|
private modalService: NgbModal,
|
||||||
|
private dsoService: DSpaceObjectDataService
|
||||||
) {
|
) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
if (isNotEmpty(this.scope)) {
|
||||||
|
this.dsoService.findById(this.scope).pipe(getFirstSucceededRemoteDataPayload())
|
||||||
|
.subscribe((scope: DSpaceObject) => this.selectedScope.next(scope));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Updates the search when the form is submitted
|
* Updates the search when the form is submitted
|
||||||
* @param data Values submitted using the form
|
* @param data Values submitted using the form
|
||||||
@@ -85,8 +102,8 @@ export class SearchFormComponent {
|
|||||||
* Updates the search when the current scope has been changed
|
* Updates the search when the current scope has been changed
|
||||||
* @param {string} scope The new scope
|
* @param {string} scope The new scope
|
||||||
*/
|
*/
|
||||||
onScopeChange(scope: string) {
|
onScopeChange(scope: DSpaceObject) {
|
||||||
this.updateSearch({ scope });
|
this.updateSearch({ scope: scope ? scope.uuid : undefined });
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -131,4 +148,12 @@ export class SearchFormComponent {
|
|||||||
}
|
}
|
||||||
return this.getSearchLink().split('/');
|
return this.getSearchLink().split('/');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
openScopeModal() {
|
||||||
|
const ref = this.modalService.open(ScopeSelectorModalComponent);
|
||||||
|
ref.componentInstance.scopeChange.pipe(take(1)).subscribe((scope: DSpaceObject) => {
|
||||||
|
this.selectedScope.next(scope);
|
||||||
|
this.onScopeChange(scope);
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -233,6 +233,7 @@ import { OnClickMenuItemComponent } from './menu/menu-item/onclick-menu-item.com
|
|||||||
import { TextMenuItemComponent } from './menu/menu-item/text-menu-item.component';
|
import { TextMenuItemComponent } from './menu/menu-item/text-menu-item.component';
|
||||||
import { ThemedConfigurationSearchPageComponent } from '../search-page/themed-configuration-search-page.component';
|
import { ThemedConfigurationSearchPageComponent } from '../search-page/themed-configuration-search-page.component';
|
||||||
import { SearchNavbarComponent } from '../search-navbar/search-navbar.component';
|
import { SearchNavbarComponent } from '../search-navbar/search-navbar.component';
|
||||||
|
import { ScopeSelectorModalComponent } from './search-form/scope-selector-modal/scope-selector-modal.component';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Declaration needed to make sure all decorator functions are called in time
|
* Declaration needed to make sure all decorator functions are called in time
|
||||||
@@ -458,7 +459,8 @@ const COMPONENTS = [
|
|||||||
PublicationSidebarSearchListElementComponent,
|
PublicationSidebarSearchListElementComponent,
|
||||||
CollectionSidebarSearchListElementComponent,
|
CollectionSidebarSearchListElementComponent,
|
||||||
CommunitySidebarSearchListElementComponent,
|
CommunitySidebarSearchListElementComponent,
|
||||||
SearchNavbarComponent
|
SearchNavbarComponent,
|
||||||
|
ScopeSelectorModalComponent
|
||||||
];
|
];
|
||||||
|
|
||||||
const ENTRY_COMPONENTS = [
|
const ENTRY_COMPONENTS = [
|
||||||
@@ -522,7 +524,8 @@ const ENTRY_COMPONENTS = [
|
|||||||
CommunitySidebarSearchListElementComponent,
|
CommunitySidebarSearchListElementComponent,
|
||||||
LinkMenuItemComponent,
|
LinkMenuItemComponent,
|
||||||
OnClickMenuItemComponent,
|
OnClickMenuItemComponent,
|
||||||
TextMenuItemComponent
|
TextMenuItemComponent,
|
||||||
|
ScopeSelectorModalComponent
|
||||||
];
|
];
|
||||||
|
|
||||||
const SHARED_SEARCH_PAGE_COMPONENTS = [
|
const SHARED_SEARCH_PAGE_COMPONENTS = [
|
||||||
|
@@ -1203,6 +1203,12 @@
|
|||||||
|
|
||||||
"dso-selector.placeholder": "Search for a {{ type }}",
|
"dso-selector.placeholder": "Search for a {{ type }}",
|
||||||
|
|
||||||
|
"dso-selector.set-scope.community.head": "Select a search scope",
|
||||||
|
|
||||||
|
"dso-selector.set-scope.community.button": "Search all of DSpace",
|
||||||
|
|
||||||
|
"dso-selector.set-scope.community.input-header": "Search for a community or collection",
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
"confirmation-modal.export-metadata.header": "Export metadata for {{ dsoName }}",
|
"confirmation-modal.export-metadata.header": "Export metadata for {{ dsoName }}",
|
||||||
@@ -3101,6 +3107,8 @@
|
|||||||
|
|
||||||
"search.form.search_dspace": "All repository",
|
"search.form.search_dspace": "All repository",
|
||||||
|
|
||||||
|
"search.form.scope.all": "All of DSpace",
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
"search.results.head": "Search Results",
|
"search.results.head": "Search Results",
|
||||||
|
@@ -3,3 +3,5 @@
|
|||||||
|
|
||||||
@import '_bootstrap_variables.scss';
|
@import '_bootstrap_variables.scss';
|
||||||
@import '../../node_modules/bootstrap/scss/variables.scss';
|
@import '../../node_modules/bootstrap/scss/variables.scss';
|
||||||
|
|
||||||
|
$search-form-scope-max-width: 150px;
|
||||||
|
Reference in New Issue
Block a user