mirror of
https://github.com/DSpace/dspace-angular.git
synced 2025-10-07 18:14:17 +00:00
reinstated select multiple behavior
This commit is contained in:
@@ -5,7 +5,6 @@ import { GenericItemPageFieldComponent } from '../../field-components/specific-f
|
|||||||
import { TruncatePipe } from '../../../../shared/utils/truncate.pipe';
|
import { TruncatePipe } from '../../../../shared/utils/truncate.pipe';
|
||||||
import { ITEM } from '../../../../shared/items/switcher/item-type-switcher.component';
|
import { ITEM } from '../../../../shared/items/switcher/item-type-switcher.component';
|
||||||
import { ItemDataService } from '../../../../core/data/item-data.service';
|
import { ItemDataService } from '../../../../core/data/item-data.service';
|
||||||
import { SearchFixedFilterService } from '../../../../core/shared/search/search-fixed-filter.service';
|
|
||||||
import { TruncatableService } from '../../../../shared/truncatable/truncatable.service';
|
import { TruncatableService } from '../../../../shared/truncatable/truncatable.service';
|
||||||
import { ChangeDetectionStrategy, NO_ERRORS_SCHEMA } from '@angular/core';
|
import { ChangeDetectionStrategy, NO_ERRORS_SCHEMA } from '@angular/core';
|
||||||
import { Item } from '../../../../core/shared/item.model';
|
import { Item } from '../../../../core/shared/item.model';
|
||||||
@@ -28,12 +27,6 @@ describe('PublicationComponent', () => {
|
|||||||
let comp: PublicationComponent;
|
let comp: PublicationComponent;
|
||||||
let fixture: ComponentFixture<PublicationComponent>;
|
let fixture: ComponentFixture<PublicationComponent>;
|
||||||
|
|
||||||
const searchFixedFilterServiceStub = {
|
|
||||||
/* tslint:disable:no-empty */
|
|
||||||
getQueryByRelations: () => {}
|
|
||||||
/* tslint:enable:no-empty */
|
|
||||||
};
|
|
||||||
|
|
||||||
beforeEach(async(() => {
|
beforeEach(async(() => {
|
||||||
TestBed.configureTestingModule({
|
TestBed.configureTestingModule({
|
||||||
imports: [TranslateModule.forRoot({
|
imports: [TranslateModule.forRoot({
|
||||||
@@ -46,7 +39,6 @@ describe('PublicationComponent', () => {
|
|||||||
providers: [
|
providers: [
|
||||||
{provide: ITEM, useValue: mockItem},
|
{provide: ITEM, useValue: mockItem},
|
||||||
{provide: ItemDataService, useValue: {}},
|
{provide: ItemDataService, useValue: {}},
|
||||||
{provide: SearchFixedFilterService, useValue: searchFixedFilterServiceStub},
|
|
||||||
{provide: TruncatableService, useValue: {}}
|
{provide: TruncatableService, useValue: {}}
|
||||||
],
|
],
|
||||||
|
|
||||||
|
@@ -10,7 +10,6 @@ import { ChangeDetectionStrategy, DebugElement, NO_ERRORS_SCHEMA } from '@angula
|
|||||||
import { ITEM } from '../../../../shared/items/switcher/item-type-switcher.component';
|
import { ITEM } from '../../../../shared/items/switcher/item-type-switcher.component';
|
||||||
import { TruncatePipe } from '../../../../shared/utils/truncate.pipe';
|
import { TruncatePipe } from '../../../../shared/utils/truncate.pipe';
|
||||||
import { isNotEmpty } from '../../../../shared/empty.util';
|
import { isNotEmpty } from '../../../../shared/empty.util';
|
||||||
import { SearchFixedFilterService } from '../../../../core/shared/search/search-fixed-filter.service';
|
|
||||||
import { RelationshipType } from '../../../../core/shared/item-relationships/relationship-type.model';
|
import { RelationshipType } from '../../../../core/shared/item-relationships/relationship-type.model';
|
||||||
import { PaginatedList } from '../../../../core/data/paginated-list';
|
import { PaginatedList } from '../../../../core/data/paginated-list';
|
||||||
import { RemoteData } from '../../../../core/data/remote-data';
|
import { RemoteData } from '../../../../core/data/remote-data';
|
||||||
@@ -39,12 +38,6 @@ export function getItemPageFieldsTest(mockItem: Item, component) {
|
|||||||
let comp: any;
|
let comp: any;
|
||||||
let fixture: ComponentFixture<any>;
|
let fixture: ComponentFixture<any>;
|
||||||
|
|
||||||
const searchFixedFilterServiceStub = {
|
|
||||||
/* tslint:disable:no-empty */
|
|
||||||
getQueryByRelations: () => {}
|
|
||||||
/* tslint:enable:no-empty */
|
|
||||||
};
|
|
||||||
|
|
||||||
beforeEach(async(() => {
|
beforeEach(async(() => {
|
||||||
TestBed.configureTestingModule({
|
TestBed.configureTestingModule({
|
||||||
imports: [TranslateModule.forRoot({
|
imports: [TranslateModule.forRoot({
|
||||||
@@ -57,7 +50,6 @@ export function getItemPageFieldsTest(mockItem: Item, component) {
|
|||||||
providers: [
|
providers: [
|
||||||
{provide: ITEM, useValue: mockItem},
|
{provide: ITEM, useValue: mockItem},
|
||||||
{provide: ItemDataService, useValue: {}},
|
{provide: ItemDataService, useValue: {}},
|
||||||
{provide: SearchFixedFilterService, useValue: searchFixedFilterServiceStub},
|
|
||||||
{provide: TruncatableService, useValue: {}}
|
{provide: TruncatableService, useValue: {}}
|
||||||
],
|
],
|
||||||
|
|
||||||
@@ -366,13 +358,6 @@ describe('ItemComponent', () => {
|
|||||||
authority: '123'
|
authority: '123'
|
||||||
}
|
}
|
||||||
] as MetadataValue[];
|
] as MetadataValue[];
|
||||||
const mockItemDataService = Object.assign({
|
|
||||||
findById: (id) => {
|
|
||||||
if (id === relatedItem.id) {
|
|
||||||
return observableOf(new RemoteData(false, false, true, null, relatedItem))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}) as ItemDataService;
|
|
||||||
|
|
||||||
let representations: Observable<MetadataRepresentation[]>;
|
let representations: Observable<MetadataRepresentation[]>;
|
||||||
|
|
||||||
|
@@ -4,13 +4,11 @@ import { NoopAnimationsModule } from '@angular/platform-browser/animations';
|
|||||||
import { TranslateModule } from '@ngx-translate/core';
|
import { TranslateModule } from '@ngx-translate/core';
|
||||||
import { NO_ERRORS_SCHEMA } from '@angular/core';
|
import { NO_ERRORS_SCHEMA } from '@angular/core';
|
||||||
import { FormsModule } from '@angular/forms';
|
import { FormsModule } from '@angular/forms';
|
||||||
import { SearchFixedFilterService } from '../../../../core/shared/search/search-fixed-filter.service';
|
|
||||||
import { Item } from '../../../../core/shared/item.model';
|
import { Item } from '../../../../core/shared/item.model';
|
||||||
|
|
||||||
describe('RelatedEntitiesSearchComponent', () => {
|
describe('RelatedEntitiesSearchComponent', () => {
|
||||||
let comp: RelatedEntitiesSearchComponent;
|
let comp: RelatedEntitiesSearchComponent;
|
||||||
let fixture: ComponentFixture<RelatedEntitiesSearchComponent>;
|
let fixture: ComponentFixture<RelatedEntitiesSearchComponent>;
|
||||||
let fixedFilterService: SearchFixedFilterService;
|
|
||||||
|
|
||||||
const mockItem = Object.assign(new Item(), {
|
const mockItem = Object.assign(new Item(), {
|
||||||
id: 'id1'
|
id: 'id1'
|
||||||
@@ -18,17 +16,11 @@ describe('RelatedEntitiesSearchComponent', () => {
|
|||||||
const mockRelationType = 'publicationsOfAuthor';
|
const mockRelationType = 'publicationsOfAuthor';
|
||||||
const mockRelationEntityType = 'publication';
|
const mockRelationEntityType = 'publication';
|
||||||
const mockFilter= `f.${mockRelationType}=${mockItem.id}`;
|
const mockFilter= `f.${mockRelationType}=${mockItem.id}`;
|
||||||
const fixedFilterServiceStub = {
|
|
||||||
getFilterByRelation: () => mockFilter
|
|
||||||
};
|
|
||||||
|
|
||||||
beforeEach(async(() => {
|
beforeEach(async(() => {
|
||||||
TestBed.configureTestingModule({
|
TestBed.configureTestingModule({
|
||||||
imports: [TranslateModule.forRoot(), NoopAnimationsModule, FormsModule],
|
imports: [TranslateModule.forRoot(), NoopAnimationsModule, FormsModule],
|
||||||
declarations: [RelatedEntitiesSearchComponent],
|
declarations: [RelatedEntitiesSearchComponent],
|
||||||
providers: [
|
|
||||||
{ provide: SearchFixedFilterService, useValue: fixedFilterServiceStub }
|
|
||||||
],
|
|
||||||
schemas: [NO_ERRORS_SCHEMA]
|
schemas: [NO_ERRORS_SCHEMA]
|
||||||
}).compileComponents();
|
}).compileComponents();
|
||||||
}));
|
}));
|
||||||
@@ -36,7 +28,6 @@ describe('RelatedEntitiesSearchComponent', () => {
|
|||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
fixture = TestBed.createComponent(RelatedEntitiesSearchComponent);
|
fixture = TestBed.createComponent(RelatedEntitiesSearchComponent);
|
||||||
comp = fixture.componentInstance;
|
comp = fixture.componentInstance;
|
||||||
fixedFilterService = (comp as any).fixedFilterService;
|
|
||||||
comp.relationType = mockRelationType;
|
comp.relationType = mockRelationType;
|
||||||
comp.item = mockItem;
|
comp.item = mockItem;
|
||||||
comp.relationEntityType = mockRelationEntityType;
|
comp.relationEntityType = mockRelationEntityType;
|
||||||
|
@@ -28,7 +28,6 @@ import { SearchFilterService } from '../core/shared/search/search-filter.service
|
|||||||
import { RoleDirective } from '../shared/roles/role.directive';
|
import { RoleDirective } from '../shared/roles/role.directive';
|
||||||
import { RoleService } from '../core/roles/role.service';
|
import { RoleService } from '../core/roles/role.service';
|
||||||
import { MockRoleService } from '../shared/mocks/mock-role-service';
|
import { MockRoleService } from '../shared/mocks/mock-role-service';
|
||||||
import { SearchFixedFilterService } from '../core/shared/search/search-fixed-filter.service';
|
|
||||||
|
|
||||||
describe('MyDSpacePageComponent', () => {
|
describe('MyDSpacePageComponent', () => {
|
||||||
let comp: MyDSpacePageComponent;
|
let comp: MyDSpacePageComponent;
|
||||||
@@ -80,11 +79,6 @@ describe('MyDSpacePageComponent', () => {
|
|||||||
collapse: () => this.isCollapsed = observableOf(true),
|
collapse: () => this.isCollapsed = observableOf(true),
|
||||||
expand: () => this.isCollapsed = observableOf(false)
|
expand: () => this.isCollapsed = observableOf(false)
|
||||||
};
|
};
|
||||||
const mockFixedFilterService: SearchFixedFilterService = {
|
|
||||||
getQueryByFilterName: (filter: string) => {
|
|
||||||
return observableOf(undefined)
|
|
||||||
}
|
|
||||||
} as SearchFixedFilterService;
|
|
||||||
|
|
||||||
beforeEach(async(() => {
|
beforeEach(async(() => {
|
||||||
TestBed.configureTestingModule({
|
TestBed.configureTestingModule({
|
||||||
@@ -124,10 +118,6 @@ describe('MyDSpacePageComponent', () => {
|
|||||||
provide: RoleService,
|
provide: RoleService,
|
||||||
useValue: new MockRoleService()
|
useValue: new MockRoleService()
|
||||||
},
|
},
|
||||||
{
|
|
||||||
provide: SearchFixedFilterService,
|
|
||||||
useValue: mockFixedFilterService
|
|
||||||
}
|
|
||||||
],
|
],
|
||||||
schemas: [NO_ERRORS_SCHEMA]
|
schemas: [NO_ERRORS_SCHEMA]
|
||||||
}).overrideComponent(MyDSpacePageComponent, {
|
}).overrideComponent(MyDSpacePageComponent, {
|
||||||
|
@@ -24,7 +24,6 @@ import { SEARCH_CONFIG_SERVICE } from '../+my-dspace-page/my-dspace-page.compone
|
|||||||
import { RouteService } from '../shared/services/route.service';
|
import { RouteService } from '../shared/services/route.service';
|
||||||
import { SearchConfigurationServiceStub } from '../shared/testing/search-configuration-service-stub';
|
import { SearchConfigurationServiceStub } from '../shared/testing/search-configuration-service-stub';
|
||||||
import { PaginatedSearchOptions } from '../shared/search/paginated-search-options.model';
|
import { PaginatedSearchOptions } from '../shared/search/paginated-search-options.model';
|
||||||
import { SearchFixedFilterService } from '../core/shared/search/search-fixed-filter.service';
|
|
||||||
|
|
||||||
let comp: SearchPageComponent;
|
let comp: SearchPageComponent;
|
||||||
let fixture: ComponentFixture<SearchPageComponent>;
|
let fixture: ComponentFixture<SearchPageComponent>;
|
||||||
@@ -88,11 +87,6 @@ const routeServiceStub = {
|
|||||||
return observableOf('')
|
return observableOf('')
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
const mockFixedFilterService: SearchFixedFilterService = {
|
|
||||||
getQueryByFilterName: (filter: string) => {
|
|
||||||
return observableOf(undefined)
|
|
||||||
}
|
|
||||||
} as SearchFixedFilterService;
|
|
||||||
|
|
||||||
export function configureSearchComponentTestingModule(compType) {
|
export function configureSearchComponentTestingModule(compType) {
|
||||||
TestBed.configureTestingModule({
|
TestBed.configureTestingModule({
|
||||||
@@ -125,10 +119,6 @@ export function configureSearchComponentTestingModule(compType) {
|
|||||||
provide: SearchFilterService,
|
provide: SearchFilterService,
|
||||||
useValue: {}
|
useValue: {}
|
||||||
},
|
},
|
||||||
{
|
|
||||||
provide: SearchFixedFilterService,
|
|
||||||
useValue: mockFixedFilterService
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
provide: SearchConfigurationService,
|
provide: SearchConfigurationService,
|
||||||
useValue: {
|
useValue: {
|
||||||
|
@@ -8,19 +8,23 @@ import { RemoteData } from '../data/remote-data';
|
|||||||
import { ResourceType } from './resource-type';
|
import { ResourceType } from './resource-type';
|
||||||
import { ListableObject } from '../../shared/object-collection/shared/listable-object.model';
|
import { ListableObject } from '../../shared/object-collection/shared/listable-object.model';
|
||||||
import { hasNoValue } from '../../shared/empty.util';
|
import { hasNoValue } from '../../shared/empty.util';
|
||||||
|
import { excludeFromEquals } from '../utilities/equals.decorators';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An abstract model class for a DSpaceObject.
|
* An abstract model class for a DSpaceObject.
|
||||||
*/
|
*/
|
||||||
export class DSpaceObject extends ListableObject implements CacheableObject {
|
export class DSpaceObject extends ListableObject implements CacheableObject {
|
||||||
|
|
||||||
|
@excludeFromEquals
|
||||||
private _name: string;
|
private _name: string;
|
||||||
|
|
||||||
|
@excludeFromEquals
|
||||||
self: string;
|
self: string;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The human-readable identifier of this DSpaceObject
|
* The human-readable identifier of this DSpaceObject
|
||||||
*/
|
*/
|
||||||
|
@excludeFromEquals
|
||||||
id: string;
|
id: string;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -31,6 +35,7 @@ export class DSpaceObject extends ListableObject implements CacheableObject {
|
|||||||
/**
|
/**
|
||||||
* A string representing the kind of DSpaceObject, e.g. community, item, …
|
* A string representing the kind of DSpaceObject, e.g. community, item, …
|
||||||
*/
|
*/
|
||||||
|
@excludeFromEquals
|
||||||
type: ResourceType;
|
type: ResourceType;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -50,6 +55,7 @@ export class DSpaceObject extends ListableObject implements CacheableObject {
|
|||||||
/**
|
/**
|
||||||
* All metadata of this DSpaceObject
|
* All metadata of this DSpaceObject
|
||||||
*/
|
*/
|
||||||
|
@excludeFromEquals
|
||||||
metadata: MetadataMap;
|
metadata: MetadataMap;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -62,11 +68,13 @@ export class DSpaceObject extends ListableObject implements CacheableObject {
|
|||||||
/**
|
/**
|
||||||
* An array of DSpaceObjects that are direct parents of this DSpaceObject
|
* An array of DSpaceObjects that are direct parents of this DSpaceObject
|
||||||
*/
|
*/
|
||||||
|
@excludeFromEquals
|
||||||
parents: Observable<RemoteData<DSpaceObject[]>>;
|
parents: Observable<RemoteData<DSpaceObject[]>>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The DSpaceObject that owns this DSpaceObject
|
* The DSpaceObject that owns this DSpaceObject
|
||||||
*/
|
*/
|
||||||
|
@excludeFromEquals
|
||||||
owner: Observable<RemoteData<DSpaceObject>>;
|
owner: Observable<RemoteData<DSpaceObject>>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@@ -8,12 +8,25 @@ class Dog extends EquatableObject<Dog> {
|
|||||||
@excludeFromEquals
|
@excludeFromEquals
|
||||||
public ballsCaught: number;
|
public ballsCaught: number;
|
||||||
|
|
||||||
@fieldsForEquals('name', 'age')
|
public owner: Owner;
|
||||||
public owner: {
|
|
||||||
name: string;
|
@fieldsForEquals('name')
|
||||||
age: number;
|
public favouriteToy: { name: string, colour: string };
|
||||||
favouriteFood: string;
|
}
|
||||||
|
|
||||||
|
class Owner extends EquatableObject<Owner> {
|
||||||
|
@excludeFromEquals
|
||||||
|
favouriteFood: string;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
public name: string,
|
||||||
|
public age: number,
|
||||||
|
favouriteFood: string
|
||||||
|
) {
|
||||||
|
super();
|
||||||
|
this.favouriteFood = favouriteFood;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fdescribe('equatable', () => {
|
fdescribe('equatable', () => {
|
||||||
@@ -24,12 +37,14 @@ fdescribe('equatable', () => {
|
|||||||
dogRoger = new Dog();
|
dogRoger = new Dog();
|
||||||
dogRoger.name = 'Roger';
|
dogRoger.name = 'Roger';
|
||||||
dogRoger.ballsCaught = 6;
|
dogRoger.ballsCaught = 6;
|
||||||
dogRoger.owner = { name: 'Tommy', age: 16, favouriteFood: 'spaghetti' };
|
dogRoger.owner = new Owner('Tommy', 16, 'spaghetti');
|
||||||
|
dogRoger.favouriteToy = { name: 'Twinky', colour: 'red' };
|
||||||
|
|
||||||
dogMissy = new Dog();
|
dogMissy = new Dog();
|
||||||
dogMissy.name = 'Missy';
|
dogMissy.name = 'Missy';
|
||||||
dogMissy.ballsCaught = 9;
|
dogMissy.ballsCaught = 9;
|
||||||
dogMissy.owner = { name: 'Jenny', age: 29, favouriteFood: 'pizza' };
|
dogMissy.owner = new Owner('Jenny', 29, 'pizza');
|
||||||
|
dogRoger.favouriteToy = { name: 'McSqueak', colour: 'grey' };
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return false when the other object is undefined', () => {
|
it('should return false when the other object is undefined', () => {
|
||||||
@@ -66,5 +81,33 @@ fdescribe('equatable', () => {
|
|||||||
const isEqual = dogRoger.equals(copyOfDogRoger);
|
const isEqual = dogRoger.equals(copyOfDogRoger);
|
||||||
expect(isEqual).toBe(false);
|
expect(isEqual).toBe(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should return true when the other object\'s nested object only differs in fields that are marked as excludeFromEquals, when the nested object is not marked decorated with @fieldsForEquals', () => {
|
||||||
|
const copyOfDogRoger = cloneDeep(dogRoger);
|
||||||
|
copyOfDogRoger.owner.favouriteFood = 'Sushi';
|
||||||
|
const isEqual = dogRoger.equals(copyOfDogRoger);
|
||||||
|
expect(isEqual).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return false when the other object\'s nested object differs in fields that are not marked as excludeFromEquals, when the nested object is not marked decorated with @fieldsForEquals', () => {
|
||||||
|
const copyOfDogRoger = cloneDeep(dogRoger);
|
||||||
|
copyOfDogRoger.owner.age = 36;
|
||||||
|
const isEqual = dogRoger.equals(copyOfDogRoger);
|
||||||
|
expect(isEqual).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return true when the other object\'s nested object does not differ in fields that are listed inside the nested @fieldsForEquals decorator', () => {
|
||||||
|
const copyOfDogRoger = cloneDeep(dogRoger);
|
||||||
|
copyOfDogRoger.favouriteToy.colour = 'green';
|
||||||
|
const isEqual = dogRoger.equals(copyOfDogRoger);
|
||||||
|
expect(isEqual).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return false when the other object\'s nested object differs in fields that are listed inside the nested @fieldsForEquals decorator', () => {
|
||||||
|
const copyOfDogRoger = cloneDeep(dogRoger);
|
||||||
|
copyOfDogRoger.favouriteToy.name = 'Mister Bone';
|
||||||
|
const isEqual = dogRoger.equals(copyOfDogRoger);
|
||||||
|
expect(isEqual).toBe(false);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@@ -33,7 +33,7 @@ export abstract class EquatableObject<T> {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
const excludedKeys = getExcludedFromEqualsFor(this.constructor);
|
const excludedKeys = getExcludedFromEqualsFor(this.constructor);
|
||||||
const keys = Object.keys(this).filter((key) => excludedKeys.findIndex((excludedKey) => key === excludedKey) < 0);
|
const keys = Object.keys(this).filter((key) => !excludedKeys.includes(key));
|
||||||
return equalsByFields(this, other, keys);
|
return equalsByFields(this, other, keys);
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -19,6 +19,51 @@
|
|||||||
<button class="btn btn-outline-secondary" type="submit">Go</button>
|
<button class="btn btn-outline-secondary" type="submit">Go</button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
<div *ngIf="repeatable" class="position-absolute">
|
||||||
|
<div class="input-group mb-3">
|
||||||
|
<div class="input-group-prepend">
|
||||||
|
<div class="input-group-text">
|
||||||
|
<!-- In theory we don't need separate checkboxes for this,
|
||||||
|
but I wasn't able to get this to work correctly without them.
|
||||||
|
Checkboxes that are in the indeterminate state always switch to checked when clicked
|
||||||
|
This seemed like the cleanest and clearest solution to solve this issue for now.
|
||||||
|
-->
|
||||||
|
<input *ngIf="!allSelected && !(someSelected$ | async)"
|
||||||
|
type="checkbox"
|
||||||
|
[indeterminate]="false"
|
||||||
|
(change)="selectAll()">
|
||||||
|
<input *ngIf="!allSelected && (someSelected$ | async)"
|
||||||
|
type="checkbox"
|
||||||
|
[indeterminate]="true"
|
||||||
|
(change)="deselectAll()">
|
||||||
|
<input *ngIf="allSelected" type="checkbox"
|
||||||
|
[checked]="true"
|
||||||
|
(change)="deselectAll()">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div ngbDropdown class="input-group-append">
|
||||||
|
<button *ngIf="selectAllLoading" type="button"
|
||||||
|
class="btn btn-outline-secondary rounded-right">
|
||||||
|
<span class="spinner-border spinner-border-sm" role="status"
|
||||||
|
aria-hidden="true"></span>
|
||||||
|
<span class="sr-only">Loading...</span>
|
||||||
|
</button>
|
||||||
|
<button *ngIf="!selectAllLoading" id="resultdropdown" type="button"
|
||||||
|
ngbDropdownToggle
|
||||||
|
class="btn btn-outline-secondary dropdown-toggle-split"
|
||||||
|
data-toggle="dropdown" aria-haspopup="true"
|
||||||
|
aria-expanded="false">
|
||||||
|
<span class="sr-only">Toggle Dropdown</span>
|
||||||
|
</button>
|
||||||
|
<div ngbDropdownMenu aria-labelledby="resultdropdown">
|
||||||
|
<button class="dropdown-item" (click)="selectPage(resultsRD?.payload?.page)">Select page</button>
|
||||||
|
<button class="dropdown-item" (click)="deselectPage(resultsRD?.payload?.page)">Deselect page</button>
|
||||||
|
<button class="dropdown-item" (click)="selectAll()">Select all</button>
|
||||||
|
<button class="dropdown-item" (click)="deselectAll()">Deselect all</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<ds-search-results [searchResults]="resultsRD"
|
<ds-search-results [searchResults]="resultsRD"
|
||||||
[sortConfig]="this.searchConfig?.sort"
|
[sortConfig]="this.searchConfig?.sort"
|
||||||
[searchConfig]="this.searchConfig"
|
[searchConfig]="this.searchConfig"
|
||||||
@@ -29,10 +74,9 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-footer">
|
<div class="modal-footer">
|
||||||
<small>Selected {{(selection | async)?.length || 0}} items</small>
|
<small>Selected {{(selection$ | async)?.length || 0}} items</small>
|
||||||
<div>
|
<div>
|
||||||
<button type="button" class="btn btn-outline-secondary" (click)="modal.dismiss()">Cancel
|
<button type="button" class="btn btn-outline-secondary" (click)="modal.dismiss()">Cancel</button>
|
||||||
</button>
|
|
||||||
<button type="button" class="btn btn-danger" (click)="close()">Ok</button>
|
<button type="button" class="btn btn-danger" (click)="close()">Ok</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
@@ -4,4 +4,8 @@
|
|||||||
|
|
||||||
.modal-footer {
|
.modal-footer {
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
|
||||||
|
.position-absolute {
|
||||||
|
right: $spacer;
|
||||||
}
|
}
|
@@ -8,15 +8,16 @@ import { PaginatedSearchOptions } from '../../../../../search/paginated-search-o
|
|||||||
import { DSpaceObject } from '../../../../../../core/shared/dspace-object.model';
|
import { DSpaceObject } from '../../../../../../core/shared/dspace-object.model';
|
||||||
import { PaginationComponentOptions } from '../../../../../pagination/pagination-component-options.model';
|
import { PaginationComponentOptions } from '../../../../../pagination/pagination-component-options.model';
|
||||||
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap';
|
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap';
|
||||||
import { hasValue } from '../../../../../empty.util';
|
import { hasValue, isNotEmpty } from '../../../../../empty.util';
|
||||||
import { concat, filter, map, multicast, switchMap, take, takeWhile } from 'rxjs/operators';
|
import { concat, map, multicast, switchMap, take, takeWhile, tap } from 'rxjs/operators';
|
||||||
import { NavigationEnd, Router } from '@angular/router';
|
import { Router } from '@angular/router';
|
||||||
import { SEARCH_CONFIG_SERVICE } from '../../../../../../+my-dspace-page/my-dspace-page.component';
|
import { SEARCH_CONFIG_SERVICE } from '../../../../../../+my-dspace-page/my-dspace-page.component';
|
||||||
import { SearchConfigurationService } from '../../../../../../core/shared/search/search-configuration.service';
|
import { SearchConfigurationService } from '../../../../../../core/shared/search/search-configuration.service';
|
||||||
import { SelectableListService } from '../../../../../object-list/selectable-list/selectable-list.service';
|
import { SelectableListService } from '../../../../../object-list/selectable-list/selectable-list.service';
|
||||||
import { SelectableListState } from '../../../../../object-list/selectable-list/selectable-list.reducer';
|
import { SelectableListState } from '../../../../../object-list/selectable-list/selectable-list.reducer';
|
||||||
import { ListableObject } from '../../../../../object-collection/shared/listable-object.model';
|
import { ListableObject } from '../../../../../object-collection/shared/listable-object.model';
|
||||||
import { RouteService } from '../../../../../services/route.service';
|
import { RouteService } from '../../../../../services/route.service';
|
||||||
|
import { getSucceededRemoteData } from '../../../../../../core/shared/operators';
|
||||||
|
|
||||||
const RELATION_TYPE_FILTER_PREFIX = 'f.entityType=';
|
const RELATION_TYPE_FILTER_PREFIX = 'f.entityType=';
|
||||||
|
|
||||||
@@ -40,12 +41,16 @@ export class DsDynamicLookupRelationModalComponent implements OnInit {
|
|||||||
searchConfig: PaginatedSearchOptions;
|
searchConfig: PaginatedSearchOptions;
|
||||||
repeatable: boolean;
|
repeatable: boolean;
|
||||||
searchQuery;
|
searchQuery;
|
||||||
|
allSelected: boolean;
|
||||||
|
someSelected$: Observable<boolean>;
|
||||||
|
selectAllLoading: boolean;
|
||||||
initialPagination = Object.assign(new PaginationComponentOptions(), {
|
initialPagination = Object.assign(new PaginationComponentOptions(), {
|
||||||
id: 'submission-relation-list',
|
id: 'submission-relation-list',
|
||||||
pageSize: 10
|
pageSize: 10
|
||||||
});
|
});
|
||||||
selection: Observable<ListableObject[]>;
|
selection$: Observable<ListableObject[]>;
|
||||||
fixedFilter: string;
|
fixedFilter: string;
|
||||||
|
|
||||||
constructor(public modal: NgbActiveModal, private searchService: SearchService, private router: Router, private selectableListService: SelectableListService, private searchConfigService: SearchConfigurationService, private routeService: RouteService) {
|
constructor(public modal: NgbActiveModal, private searchService: SearchService, private router: Router, private selectableListService: SelectableListService, private searchConfigService: SearchConfigurationService, private routeService: RouteService) {
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -54,7 +59,8 @@ export class DsDynamicLookupRelationModalComponent implements OnInit {
|
|||||||
this.fixedFilter = RELATION_TYPE_FILTER_PREFIX + this.fieldName;
|
this.fixedFilter = RELATION_TYPE_FILTER_PREFIX + this.fieldName;
|
||||||
this.routeService.setParameter('fixedFilterQuery', this.fixedFilter);
|
this.routeService.setParameter('fixedFilterQuery', this.fixedFilter);
|
||||||
|
|
||||||
this.selection = this.selectableListService.getSelectableList(this.listId).pipe(map((listState: SelectableListState) => hasValue(listState) && hasValue(listState.selection) ? listState.selection : []));
|
this.selection$ = this.selectableListService.getSelectableList(this.listId).pipe(map((listState: SelectableListState) => hasValue(listState) && hasValue(listState.selection) ? listState.selection : []));
|
||||||
|
this.someSelected$ = this.selection$.pipe(map((selection) => isNotEmpty(selection)));
|
||||||
this.resultsRD$ = this.searchConfigService.paginatedSearchOptions.pipe(
|
this.resultsRD$ = this.searchConfigService.paginatedSearchOptions.pipe(
|
||||||
map((options) => {
|
map((options) => {
|
||||||
return Object.assign(new PaginatedSearchOptions({}), options, { fixedFilter: RELATION_TYPE_FILTER_PREFIX + this.fieldName })
|
return Object.assign(new PaginatedSearchOptions({}), options, { fixedFilter: RELATION_TYPE_FILTER_PREFIX + this.fieldName })
|
||||||
@@ -93,4 +99,36 @@ export class DsDynamicLookupRelationModalComponent implements OnInit {
|
|||||||
queryParams: Object.assign({}, { page: 1, query: this.searchQuery }),
|
queryParams: Object.assign({}, { page: 1, query: this.searchQuery }),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
selectPage(page: SearchResult<DSpaceObject>[]) {
|
||||||
|
this.selectableListService.select(this.listId, page);
|
||||||
|
}
|
||||||
|
|
||||||
|
deselectPage(page: SearchResult<DSpaceObject>[]) {
|
||||||
|
this.allSelected = false;
|
||||||
|
this.selectableListService.deselect(this.listId, page);
|
||||||
|
}
|
||||||
|
|
||||||
|
selectAll() {
|
||||||
|
this.allSelected = true;
|
||||||
|
this.selectAllLoading = true;
|
||||||
|
const fullPagination = Object.assign(new PaginationComponentOptions(), {
|
||||||
|
query: this.searchQuery,
|
||||||
|
currentPage: 1,
|
||||||
|
pageSize: Number.POSITIVE_INFINITY
|
||||||
|
});
|
||||||
|
const fullSearchConfig = Object.assign(this.searchConfig, { pagination: fullPagination });
|
||||||
|
const results = this.searchService.search(fullSearchConfig);
|
||||||
|
results.pipe(
|
||||||
|
getSucceededRemoteData(),
|
||||||
|
map((resultsRD) => resultsRD.payload.page),
|
||||||
|
tap(() => this.selectAllLoading = false),
|
||||||
|
).subscribe((results) => this.selectableListService.select(this.listId, results));
|
||||||
|
}
|
||||||
|
|
||||||
|
deselectAll() {
|
||||||
|
this.allSelected = false;
|
||||||
|
this.selectableListService.deselectAll(this.listId);
|
||||||
|
}
|
||||||
}
|
}
|
@@ -50,7 +50,6 @@ export class SearchFiltersComponent implements OnInit {
|
|||||||
private filterService: SearchFilterService,
|
private filterService: SearchFilterService,
|
||||||
private router: Router,
|
private router: Router,
|
||||||
@Inject(SEARCH_CONFIG_SERVICE) private searchConfigService: SearchConfigurationService) {
|
@Inject(SEARCH_CONFIG_SERVICE) private searchConfigService: SearchConfigurationService) {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
|
@@ -1,6 +1,7 @@
|
|||||||
import { DSpaceObject } from '../../core/shared/dspace-object.model';
|
import { DSpaceObject } from '../../core/shared/dspace-object.model';
|
||||||
import { MetadataMap } from '../../core/shared/metadata.models';
|
import { MetadataMap } from '../../core/shared/metadata.models';
|
||||||
import { ListableObject } from '../object-collection/shared/listable-object.model';
|
import { ListableObject } from '../object-collection/shared/listable-object.model';
|
||||||
|
import { excludeFromEquals, fieldsForEquals } from '../../core/utilities/equals.decorators';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents a search result object of a certain (<T>) DSpaceObject
|
* Represents a search result object of a certain (<T>) DSpaceObject
|
||||||
@@ -9,14 +10,12 @@ export class SearchResult<T extends DSpaceObject> extends ListableObject {
|
|||||||
/**
|
/**
|
||||||
* The DSpaceObject that was found
|
* The DSpaceObject that was found
|
||||||
*/
|
*/
|
||||||
|
@fieldsForEquals('uuid')
|
||||||
indexableObject: T;
|
indexableObject: T;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The metadata that was used to find this item, hithighlighted
|
* The metadata that was used to find this item, hithighlighted
|
||||||
*/
|
*/
|
||||||
|
@excludeFromEquals
|
||||||
hitHighlights: MetadataMap;
|
hitHighlights: MetadataMap;
|
||||||
|
|
||||||
get id(): string {
|
|
||||||
return this.indexableObject.id;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@@ -27,15 +27,14 @@ import { SubmissionJsonPatchOperationsService } from '../../../core/submission/s
|
|||||||
import { SubmissionJsonPatchOperationsServiceStub } from '../../../shared/testing/submission-json-patch-operations-service-stub';
|
import { SubmissionJsonPatchOperationsServiceStub } from '../../../shared/testing/submission-json-patch-operations-service-stub';
|
||||||
import { JsonPatchOperationsBuilder } from '../../../core/json-patch/builder/json-patch-operations-builder';
|
import { JsonPatchOperationsBuilder } from '../../../core/json-patch/builder/json-patch-operations-builder';
|
||||||
import { JsonPatchOperationPathCombiner } from '../../../core/json-patch/builder/json-patch-operation-path-combiner';
|
import { JsonPatchOperationPathCombiner } from '../../../core/json-patch/builder/json-patch-operation-path-combiner';
|
||||||
import { Community } from '../../../core/shared/community.model';
|
|
||||||
import { RemoteData } from '../../../core/data/remote-data';
|
import { RemoteData } from '../../../core/data/remote-data';
|
||||||
import { PaginatedList } from '../../../core/data/paginated-list';
|
import { PaginatedList } from '../../../core/data/paginated-list';
|
||||||
import { PageInfo } from '../../../core/shared/page-info.model';
|
import { PageInfo } from '../../../core/shared/page-info.model';
|
||||||
import { Collection } from '../../../core/shared/collection.model';
|
import { Collection } from '../../../core/shared/collection.model';
|
||||||
import { createTestComponent } from '../../../shared/testing/utils';
|
import { createTestComponent } from '../../../shared/testing/utils';
|
||||||
import { cold } from 'jasmine-marbles';
|
import { cold } from 'jasmine-marbles';
|
||||||
import { SearchResult } from '../../../+search-page/search-result.model';
|
import { SearchResult } from '../../../shared/search/search-result.model';
|
||||||
import { SearchService } from '../../../+search-page/search-service/search.service';
|
import { SearchService } from '../../../core/shared/search/search.service';
|
||||||
|
|
||||||
const mockCommunity1Collection1 = Object.assign(new Collection(), {
|
const mockCommunity1Collection1 = Object.assign(new Collection(), {
|
||||||
name: 'Community 1-Collection 1',
|
name: 'Community 1-Collection 1',
|
||||||
|
@@ -12,15 +12,7 @@ import {
|
|||||||
import { FormControl } from '@angular/forms';
|
import { FormControl } from '@angular/forms';
|
||||||
|
|
||||||
import { BehaviorSubject, combineLatest, Observable, of as observableOf, Subscription } from 'rxjs';
|
import { BehaviorSubject, combineLatest, Observable, of as observableOf, Subscription } from 'rxjs';
|
||||||
import {
|
import { debounceTime, distinctUntilChanged, filter, map, startWith } from 'rxjs/operators';
|
||||||
debounceTime,
|
|
||||||
distinctUntilChanged,
|
|
||||||
filter,
|
|
||||||
find,
|
|
||||||
map,
|
|
||||||
mergeMap,
|
|
||||||
startWith
|
|
||||||
} from 'rxjs/operators';
|
|
||||||
|
|
||||||
import { Collection } from '../../../core/shared/collection.model';
|
import { Collection } from '../../../core/shared/collection.model';
|
||||||
import { CommunityDataService } from '../../../core/data/community-data.service';
|
import { CommunityDataService } from '../../../core/data/community-data.service';
|
||||||
@@ -32,12 +24,12 @@ import { PaginatedList } from '../../../core/data/paginated-list';
|
|||||||
import { SubmissionService } from '../../submission.service';
|
import { SubmissionService } from '../../submission.service';
|
||||||
import { SubmissionObject } from '../../../core/submission/models/submission-object.model';
|
import { SubmissionObject } from '../../../core/submission/models/submission-object.model';
|
||||||
import { SubmissionJsonPatchOperationsService } from '../../../core/submission/submission-json-patch-operations.service';
|
import { SubmissionJsonPatchOperationsService } from '../../../core/submission/submission-json-patch-operations.service';
|
||||||
import { SearchService } from '../../../+search-page/search-service/search.service';
|
|
||||||
import { PaginatedSearchOptions } from '../../../+search-page/paginated-search-options.model';
|
|
||||||
import { PaginationComponentOptions } from '../../../shared/pagination/pagination-component-options.model';
|
import { PaginationComponentOptions } from '../../../shared/pagination/pagination-component-options.model';
|
||||||
import { DSpaceObjectType } from '../../../core/shared/dspace-object-type.model';
|
import { DSpaceObjectType } from '../../../core/shared/dspace-object-type.model';
|
||||||
import { getSucceededRemoteData } from '../../../core/shared/operators';
|
import { getSucceededRemoteData } from '../../../core/shared/operators';
|
||||||
import { SearchResult } from '../../../+search-page/search-result.model';
|
import { SearchService } from '../../../core/shared/search/search.service';
|
||||||
|
import { PaginatedSearchOptions } from '../../../shared/search/paginated-search-options.model';
|
||||||
|
import { SearchResult } from '../../../shared/search/search-result.model';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An interface to represent a collection entry
|
* An interface to represent a collection entry
|
||||||
|
Reference in New Issue
Block a user