diff --git a/src/app/+search-page/paginated-search-options.model.spec.ts b/src/app/+search-page/paginated-search-options.model.spec.ts index e58953c707..312e170f1b 100644 --- a/src/app/+search-page/paginated-search-options.model.spec.ts +++ b/src/app/+search-page/paginated-search-options.model.spec.ts @@ -2,7 +2,6 @@ import 'rxjs/add/observable/of'; import { SortDirection, SortOptions } from '../core/cache/models/sort-options.model'; import { PaginationComponentOptions } from '../shared/pagination/pagination-component-options.model'; import { PaginatedSearchOptions } from './paginated-search-options.model'; -import { ViewMode } from './search-options.model'; describe('PaginatedSearchOptions', () => { let options: PaginatedSearchOptions; @@ -19,7 +18,6 @@ describe('PaginatedSearchOptions', () => { options.filters = filters; options.query = query; options.scope = scope; - options.view = ViewMode.Grid; }); describe('when toRestUrl is called', () => { diff --git a/src/app/+search-page/search-filters/search-filter/search-facet-filter/search-facet-filter.component.spec.ts b/src/app/+search-page/search-filters/search-filter/search-facet-filter/search-facet-filter.component.spec.ts index fcbc31be32..f3336f780c 100644 --- a/src/app/+search-page/search-filters/search-filter/search-facet-filter/search-facet-filter.component.spec.ts +++ b/src/app/+search-page/search-filters/search-filter/search-facet-filter/search-facet-filter.component.spec.ts @@ -91,8 +91,6 @@ describe('SearchFacetFilterComponent', () => { fixture = TestBed.createComponent(SearchFacetFilterComponent); comp = fixture.componentInstance; // SearchPageComponent test instance comp.filterConfig = mockFilterConfig; - comp.filterValues = [mockValues]; - // comp.filterValues$ = new BehaviorSubject({}); filterService = (comp as any).filterService; searchService = (comp as any).searchService; spyOn(searchService, 'getFacetValuesFor').and.returnValue(mockValues); @@ -198,10 +196,9 @@ describe('SearchFacetFilterComponent', () => { }); describe('when updateFilterValueList is called', () => { - const searchOptions = new SearchOptions(); beforeEach(() => { spyOn(comp, 'showFirstPageOnly'); - comp.updateFilterValueList(searchOptions) + comp.updateFilterValueList() }); it('should call showFirstPageOnly and empty the filter', () => { @@ -211,7 +208,6 @@ describe('SearchFacetFilterComponent', () => { }); }); - describe('when findSuggestions is called with query \'test\'', () => { const query = 'test'; beforeEach(() => { diff --git a/src/app/+search-page/search-filters/search-filter/search-facet-filter/search-facet-filter.component.ts b/src/app/+search-page/search-filters/search-filter/search-facet-filter/search-facet-filter.component.ts index 36ff2e9c12..f5996ab4f0 100644 --- a/src/app/+search-page/search-filters/search-filter/search-facet-filter/search-facet-filter.component.ts +++ b/src/app/+search-page/search-filters/search-filter/search-facet-filter/search-facet-filter.component.ts @@ -189,6 +189,9 @@ export class SearchFacetFilterComponent implements OnInit, OnDestroy { this.filter = data; } + /** + * For usage of the hasValue function in the template + */ hasValue(o: any): boolean { return hasValue(o); } diff --git a/src/app/+search-page/search-filters/search-filter/search-filter.service.ts b/src/app/+search-page/search-filters/search-filter/search-filter.service.ts index 6cb781bedf..2e61b308a9 100644 --- a/src/app/+search-page/search-filters/search-filter/search-filter.service.ts +++ b/src/app/+search-page/search-filters/search-filter/search-filter.service.ts @@ -248,7 +248,7 @@ export class SearchFilterService { } /** - * Dispatches a expand action to the store for a given filter + * Dispatches an expand action to the store for a given filter * @param {string} filterName The filter for which the action is dispatched */ public expand(filterName: string): void { @@ -264,7 +264,7 @@ export class SearchFilterService { } /** - * Dispatches a initial collapse action to the store for a given filter + * Dispatches an initial collapse action to the store for a given filter * @param {string} filterName The filter for which the action is dispatched */ public initialCollapse(filterName: string): void { @@ -272,7 +272,7 @@ export class SearchFilterService { } /** - * Dispatches a initial expand action to the store for a given filter + * Dispatches an initial expand action to the store for a given filter * @param {string} filterName The filter for which the action is dispatched */ public initialExpand(filterName: string): void { @@ -288,7 +288,7 @@ export class SearchFilterService { } /** - * Dispatches a increment page action to the store for a given filter + * Dispatches an increment page action to the store for a given filter * @param {string} filterName The filter for which the action is dispatched */ public incrementPage(filterName: string): void { diff --git a/src/app/+search-page/search-filters/search-filter/search-range-filter/search-range-filter.component.spec.ts b/src/app/+search-page/search-filters/search-filter/search-range-filter/search-range-filter.component.spec.ts index 5915568033..731d8b8ca8 100644 --- a/src/app/+search-page/search-filters/search-filter/search-range-filter/search-range-filter.component.spec.ts +++ b/src/app/+search-page/search-filters/search-filter/search-range-filter/search-range-filter.component.spec.ts @@ -96,7 +96,6 @@ describe('SearchRangeFilterComponent', () => { beforeEach(() => { fixture = TestBed.createComponent(SearchRangeFilterComponent); comp = fixture.componentInstance; // SearchPageComponent test instance - comp.filterValues = [mockValues]; filterService = (comp as any).filterService; searchService = (comp as any).searchService; spyOn(searchService, 'getFacetValuesFor').and.returnValue(mockValues); @@ -104,9 +103,9 @@ describe('SearchRangeFilterComponent', () => { fixture.detectChanges(); }); - describe('when the getAddParams method is called wih a value', () => { + describe('when the getChangeParams method is called wih a value', () => { it('should return the selectedValue list with the new parameter value', () => { - const result$ = comp.getAddParams(value3); + const result$ = comp.getChangeParams(value3); result$.subscribe((result) => { expect(result[mockFilterConfig.paramName + minSuffix]).toEqual(['1990']); expect(result[mockFilterConfig.paramName + maxSuffix]).toEqual(['1992']); @@ -114,16 +113,6 @@ describe('SearchRangeFilterComponent', () => { }); }); - describe('when the getRemoveParams method is called wih a value', () => { - it('should return the selectedValue list with the parameter value left out', () => { - const result$ = comp.getRemoveParams(value1); - result$.subscribe((result) => { - expect(result[mockFilterConfig.paramName + minSuffix]).toBeNull(); - expect(result[mockFilterConfig.paramName + maxSuffix]).toBeNull(); - }); - - }); - }); describe('when the onSubmit method is called with data', () => { const searchUrl = '/search/path'; diff --git a/src/app/+search-page/search-options.model.spec.ts b/src/app/+search-page/search-options.model.spec.ts index a71d8a7427..fc4c9278d8 100644 --- a/src/app/+search-page/search-options.model.spec.ts +++ b/src/app/+search-page/search-options.model.spec.ts @@ -1,6 +1,6 @@ import 'rxjs/add/observable/of'; import { PaginatedSearchOptions } from './paginated-search-options.model'; -import { SearchOptions, ViewMode } from './search-options.model'; +import { SearchOptions } from './search-options.model'; describe('SearchOptions', () => { let options: PaginatedSearchOptions; @@ -13,7 +13,6 @@ describe('SearchOptions', () => { options.filters = filters; options.query = query; options.scope = scope; - options.view = ViewMode.Grid; }); describe('when toRestUrl is called', () => { diff --git a/src/app/+search-page/search-options.model.ts b/src/app/+search-page/search-options.model.ts index d716a17050..6ac606f5cc 100644 --- a/src/app/+search-page/search-options.model.ts +++ b/src/app/+search-page/search-options.model.ts @@ -2,20 +2,10 @@ import { isNotEmpty } from '../shared/empty.util'; import { URLCombiner } from '../core/url-combiner/url-combiner'; import 'core-js/library/fn/object/entries'; -/** - * This enumeration represents all possible ways of representing the a group of objects - */ - -export enum ViewMode { - List = 'list', - Grid = 'grid' -} - /** * This model class represents all parameters needed to request information about a certain search request */ export class SearchOptions { - view?: ViewMode = ViewMode.List; scope?: string; query?: string; filters?: any; diff --git a/src/app/+search-page/search-page.component.html b/src/app/+search-page/search-page.component.html index 6f7fdf1b1e..0112cb14c4 100644 --- a/src/app/+search-page/search-page.component.html +++ b/src/app/+search-page/search-page.component.html @@ -31,7 +31,7 @@ + [searchConfig]="searchOptions$ | async"> diff --git a/src/app/+search-page/search-page.component.ts b/src/app/+search-page/search-page.component.ts index 1cd6477e2a..267244d4d3 100644 --- a/src/app/+search-page/search-page.component.ts +++ b/src/app/+search-page/search-page.component.ts @@ -12,9 +12,9 @@ import { SearchFilterService } from './search-filters/search-filter/search-filte import { SearchResult } from './search-result.model'; import { SearchService } from './search-service/search.service'; import { SearchSidebarService } from './search-sidebar/search-sidebar.service'; -import { Subject } from 'rxjs/Subject'; import { Subscription } from 'rxjs/Subscription'; import { hasValue } from '../shared/empty.util'; +import { BehaviorSubject } from 'rxjs/BehaviorSubject'; /** * This component renders a simple item page. @@ -38,13 +38,12 @@ export class SearchPageComponent implements OnInit { /** * The current search results */ - resultsRD$: Subject>>> = new Subject(); + resultsRD$: BehaviorSubject>>> = new BehaviorSubject(null); /** * The current paginated search options */ searchOptions$: Observable; - sortConfig: SortOptions; /** * The current relevant scopes @@ -91,8 +90,7 @@ export class SearchPageComponent implements OnInit { ngOnInit(): void { this.searchOptions$ = this.filterService.getPaginatedSearchOptions(this.defaults); this.sub = this.searchOptions$.subscribe((searchOptions) => - this.service.search(searchOptions).first().subscribe((results) => this.resultsRD$.next(results)) - ); + this.service.search(searchOptions).filter((rd) => !rd.isLoading).first().subscribe((results) => this.resultsRD$.next(results))); this.scopeListRD$ = this.filterService.getCurrentScope().pipe( flatMap((scopeId) => this.service.getScopes(scopeId)) @@ -107,7 +105,7 @@ export class SearchPageComponent implements OnInit { } /** - * Set the sidebar to a expanded state + * Set the sidebar to an expanded state */ public openSidebar(): void { this.sidebarService.expand(); diff --git a/src/app/+search-page/search-page.module.ts b/src/app/+search-page/search-page.module.ts index 4611d09bd9..cfe7dc128a 100644 --- a/src/app/+search-page/search-page.module.ts +++ b/src/app/+search-page/search-page.module.ts @@ -82,5 +82,9 @@ const effects = [ SearchBooleanFilterComponent, ] }) + +/** + * This module handles all components and pipes that are necessary for the search page + */ export class SearchPageModule { } diff --git a/src/app/+search-page/search-results/search-results.component.ts b/src/app/+search-page/search-results/search-results.component.ts index cbb597419a..6399243f92 100644 --- a/src/app/+search-page/search-results/search-results.component.ts +++ b/src/app/+search-page/search-results/search-results.component.ts @@ -2,10 +2,10 @@ import { Component, Input } from '@angular/core'; import { RemoteData } from '../../core/data/remote-data'; import { DSpaceObject } from '../../core/shared/dspace-object.model'; import { fadeIn, fadeInOut } from '../../shared/animations/fade'; -import { SearchOptions, ViewMode } from '../search-options.model'; -import { SortOptions } from '../../core/cache/models/sort-options.model'; +import { SearchOptions } from '../search-options.model'; import { SearchResult } from '../search-result.model'; import { PaginatedList } from '../../core/data/paginated-list'; +import { ViewMode } from '../../core/shared/view-mode.model'; @Component({ selector: 'ds-search-results', @@ -30,13 +30,9 @@ export class SearchResultsComponent { */ @Input() searchConfig: SearchOptions; - /** - * The current sort options for the search - */ - @Input() sortConfig: SortOptions; - /** * The current view mode for the search results */ @Input() viewMode: ViewMode; + } diff --git a/src/app/+search-page/search-service/search.service.spec.ts b/src/app/+search-page/search-service/search.service.spec.ts index c611b21a85..83ca7ae3d0 100644 --- a/src/app/+search-page/search-service/search.service.spec.ts +++ b/src/app/+search-page/search-service/search.service.spec.ts @@ -5,7 +5,6 @@ import { CommonModule } from '@angular/common'; import { Component } from '@angular/core'; import { SearchService } from './search.service'; -import { ViewMode } from '../../+search-page/search-options.model'; import { RemoteDataBuildService } from '../../core/cache/builders/remote-data-build.service'; import { ActivatedRoute, Router, UrlTree } from '@angular/router'; import { RequestService } from '../../core/data/request.service'; @@ -28,6 +27,7 @@ import { SearchQueryResponse } from './search-query-response.model'; import { SearchFilterConfig } from './search-filter-config.model'; import { CollectionDataService } from '../../core/data/collection-data.service'; import { CommunityDataService } from '../../core/data/community-data.service'; +import { ViewMode } from '../../core/shared/view-mode.model'; @Component({ template: '' }) class DummyComponent { diff --git a/src/app/+search-page/search-service/search.service.ts b/src/app/+search-page/search-service/search.service.ts index 3fb809ca90..457f993b77 100644 --- a/src/app/+search-page/search-service/search.service.ts +++ b/src/app/+search-page/search-service/search.service.ts @@ -1,13 +1,14 @@ import { Injectable, OnDestroy } from '@angular/core'; import { - ActivatedRoute, NavigationExtras, Params, PRIMARY_OUTLET, Router, + ActivatedRoute, + NavigationExtras, + PRIMARY_OUTLET, + Router, UrlSegmentGroup } from '@angular/router'; import { Observable } from 'rxjs/Observable'; -import { filter, flatMap, map, tap } from 'rxjs/operators'; -import { ViewMode } from '../../+search-page/search-options.model'; +import { flatMap, map } from 'rxjs/operators'; import { RemoteDataBuildService } from '../../core/cache/builders/remote-data-build.service'; -import { SortDirection, SortOptions } from '../../core/cache/models/sort-options.model'; import { FacetConfigSuccessResponse, FacetValueSuccessResponse, @@ -25,8 +26,7 @@ import { GenericConstructor } from '../../core/shared/generic-constructor'; import { HALEndpointService } from '../../core/shared/hal-endpoint.service'; import { configureRequest } from '../../core/shared/operators'; import { URLCombiner } from '../../core/url-combiner/url-combiner'; -import { hasNoValue, hasValue, isEmpty, isNotEmpty } from '../../shared/empty.util'; -import { PaginationComponentOptions } from '../../shared/pagination/pagination-component-options.model'; +import { hasNoValue, hasValue, isNotEmpty } from '../../shared/empty.util'; import { NormalizedSearchResult } from '../normalized-search-result.model'; import { SearchOptions } from '../search-options.model'; import { SearchResult } from '../search-result.model'; @@ -44,6 +44,7 @@ import { Community } from '../../core/shared/community.model'; import { CommunityDataService } from '../../core/data/community-data.service'; import { CollectionDataService } from '../../core/data/collection-data.service'; import { Collection } from '../../core/shared/collection.model'; +import { ViewMode } from '../../core/shared/view-mode.model'; /** diff --git a/src/app/+search-page/search-settings/search-settings.component.spec.ts b/src/app/+search-page/search-settings/search-settings.component.spec.ts index b21d41e305..fb90d4b703 100644 --- a/src/app/+search-page/search-settings/search-settings.component.spec.ts +++ b/src/app/+search-page/search-settings/search-settings.component.spec.ts @@ -13,6 +13,7 @@ import { EnumKeysPipe } from '../../shared/utils/enum-keys-pipe'; import { By } from '@angular/platform-browser'; import { SearchFilterService } from '../search-filters/search-filter/search-filter.service'; import { hot } from 'jasmine-marbles'; +import { VarDirective } from '../../shared/utils/var.directive'; describe('SearchSettingsComponent', () => { @@ -56,7 +57,7 @@ describe('SearchSettingsComponent', () => { beforeEach(async(() => { TestBed.configureTestingModule({ imports: [TranslateModule.forRoot(), RouterTestingModule.withRoutes([])], - declarations: [SearchSettingsComponent, EnumKeysPipe], + declarations: [SearchSettingsComponent, EnumKeysPipe, VarDirective], providers: [ { provide: SearchService, useValue: searchServiceStub }, @@ -96,15 +97,18 @@ describe('SearchSettingsComponent', () => { }); it('it should show the order settings with the respective selectable options', () => { - const orderSetting = fixture.debugElement.query(By.css('div.result-order-settings')); - expect(orderSetting).toBeDefined(); - const childElements = orderSetting.query(By.css('.form-control')).children; - expect(childElements.length).toEqual(comp.searchOptionPossibilities.length); + (comp as any).searchOptions$.first().subscribe((options) => { + fixture.detectChanges(); + const orderSetting = fixture.debugElement.query(By.css('div.result-order-settings')); + expect(orderSetting).toBeDefined(); + const childElements = orderSetting.query(By.css('.form-control')).children; + expect(childElements.length).toEqual(comp.searchOptionPossibilities.length); + }); }); it('it should show the size settings with the respective selectable options', () => { - (comp as any).filterService.getPaginatedSearchOptions().first().subscribe((options) => { - fixture.detectChanges() + (comp as any).searchOptions$.first().subscribe((options) => { + fixture.detectChanges(); const pageSizeSetting = fixture.debugElement.query(By.css('div.page-size-settings')); expect(pageSizeSetting).toBeDefined(); const childElements = pageSizeSetting.query(By.css('.form-control')).children; @@ -114,15 +118,21 @@ describe('SearchSettingsComponent', () => { }); it('should have the proper order value selected by default', () => { - const orderSetting = fixture.debugElement.query(By.css('div.result-order-settings')); - const childElementToBeSelected = orderSetting.query(By.css('.form-control option[value="0"][selected="selected"]')) - expect(childElementToBeSelected).toBeDefined(); + (comp as any).searchOptions$.first().subscribe((options) => { + fixture.detectChanges(); + const orderSetting = fixture.debugElement.query(By.css('div.result-order-settings')); + const childElementToBeSelected = orderSetting.query(By.css('.form-control option[value="0"][selected="selected"]')); + expect(childElementToBeSelected).toBeDefined(); + }); }); it('should have the proper rpp value selected by default', () => { - const pageSizeSetting = fixture.debugElement.query(By.css('div.page-size-settings')); - const childElementToBeSelected = pageSizeSetting.query(By.css('.form-control option[value="10"][selected="selected"]')) - expect(childElementToBeSelected).toBeDefined(); + (comp as any).searchOptions$.first().subscribe((options) => { + fixture.detectChanges(); + const pageSizeSetting = fixture.debugElement.query(By.css('div.page-size-settings')); + const childElementToBeSelected = pageSizeSetting.query(By.css('.form-control option[value="10"][selected="selected"]')); + expect(childElementToBeSelected).toBeDefined(); + }); }); }); diff --git a/src/app/+search-page/search-sidebar/search-sidebar.component.ts b/src/app/+search-page/search-sidebar/search-sidebar.component.ts index a160ff6cf6..8b68cda793 100644 --- a/src/app/+search-page/search-sidebar/search-sidebar.component.ts +++ b/src/app/+search-page/search-sidebar/search-sidebar.component.ts @@ -18,7 +18,7 @@ import { Component, EventEmitter, Input, Output } from '@angular/core'; export class SearchSidebarComponent { /** - * The total amount of result + * The total amount of results */ @Input() resultCount; diff --git a/src/app/+search-page/search-sidebar/search-sidebar.service.ts b/src/app/+search-page/search-sidebar/search-sidebar.service.ts index 47749d9e0c..8cf9339c5c 100644 --- a/src/app/+search-page/search-sidebar/search-sidebar.service.ts +++ b/src/app/+search-page/search-sidebar/search-sidebar.service.ts @@ -31,7 +31,7 @@ export class SearchSidebarService { /** * Checks if the sidebar should currently be collapsed - * @returns {Observable} Emits true if our screen size is mobile or when the state in the store is currently collapsed + * @returns {Observable} Emits true if the user's screen size is mobile or when the state in the store is currently collapsed */ get isCollapsed(): Observable { return Observable.combineLatest( @@ -48,7 +48,7 @@ export class SearchSidebarService { } /** - * Dispatches a expand action to the store + * Dispatches an expand action to the store */ public expand(): void { this.store.dispatch(new SearchSidebarExpandAction()); diff --git a/src/app/core/shared/view-mode.model.ts b/src/app/core/shared/view-mode.model.ts new file mode 100644 index 0000000000..b026d68431 --- /dev/null +++ b/src/app/core/shared/view-mode.model.ts @@ -0,0 +1,8 @@ +/** + * This enumeration represents all possible ways of representing a group of objects in the UI + */ + +export enum ViewMode { + List = 'list', + Grid = 'grid' +} diff --git a/src/app/shared/input-suggestions/input-suggestions.component.ts b/src/app/shared/input-suggestions/input-suggestions.component.ts index 826c2043b4..f95fd4611d 100644 --- a/src/app/shared/input-suggestions/input-suggestions.component.ts +++ b/src/app/shared/input-suggestions/input-suggestions.component.ts @@ -9,13 +9,6 @@ import { } from '@angular/core'; import { BehaviorSubject } from 'rxjs/BehaviorSubject'; import { hasValue, isNotEmpty } from '../empty.util'; -import { ActivatedRoute } from '@angular/router'; - -/** - * 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-input-suggestions', @@ -23,28 +16,92 @@ import { ActivatedRoute } from '@angular/router'; templateUrl: './input-suggestions.component.html' }) +/** + * Component representing a form with a autocomplete functionality + */ export class InputSuggestionsComponent { + /** + * The suggestions that should be shown + */ @Input() suggestions: any[] = []; + + /** + * The time waited to detect if any other input will follow before requesting the suggestions + */ @Input() debounceTime = 500; + + /** + * Placeholder attribute for the input field + */ @Input() placeholder = ''; + + /** + * Action attribute for the form + */ @Input() action; + + /** + * Name attribute for the input field + */ @Input() name; + + /** + * Value of the input field + */ @Input() ngModel; + + /** + * Output for when the input field's value changes + */ @Output() ngModelChange = new EventEmitter(); + + /** + * Output for when the form is submitted + */ @Output() submitSuggestion = new EventEmitter(); + + /** + * Output for when a suggestion is clicked + */ @Output() clickSuggestion = new EventEmitter(); + + /** + * Output for when new suggestions should be requested + */ @Output() findSuggestions = new EventEmitter(); + + /** + * Emits true when the list of suggestions should be shown + */ show = new BehaviorSubject(false); + + /** + * Index of the currently selected suggestion + */ selectedIndex = -1; + + /** + * Reference to the input field component + */ @ViewChild('inputField') queryInput: ElementRef; + /** + * Reference to the suggestion components + */ @ViewChildren('suggestion') resultViews: QueryList; + /** + * When any of the inputs change, check if we should still show the suggestions + */ ngOnChanges(changes: SimpleChanges) { if (hasValue(changes.suggestions)) { this.show.next(isNotEmpty(changes.suggestions.currentValue)); } } + /** + * Move the focus on one of the suggestions up to the previous suggestion + * When no suggestion is currently in focus OR the first suggestion is in focus: shift to the last suggestion + */ shiftFocusUp(event: KeyboardEvent) { event.preventDefault(); if (this.selectedIndex > 0) { @@ -56,6 +113,10 @@ export class InputSuggestionsComponent { this.changeFocus(); } + /** + * Move the focus on one of the suggestions up to the next suggestion + * When no suggestion is currently in focus OR the last suggestion is in focus: shift to the first suggestion + */ shiftFocusDown(event: KeyboardEvent) { event.preventDefault(); if (this.selectedIndex >= 0) { @@ -67,26 +128,42 @@ export class InputSuggestionsComponent { this.changeFocus(); } + /** + * Perform the change of focus to the current selectedIndex + */ changeFocus() { if (this.resultViews.length > 0) { this.resultViews.toArray()[this.selectedIndex].nativeElement.focus(); } } + /** + * When any key is pressed (except for the Enter button) the query input should move to the input field + * @param {KeyboardEvent} event The keyboard event + */ onKeydown(event: KeyboardEvent) { if (event.key !== 'Enter') { this.queryInput.nativeElement.focus(); } } + /** + * Changes the show variable so the suggestion dropdown closes + */ close() { this.show.next(false); } + /** + * For usage of the isNotEmpty function in the template + */ isNotEmpty(data) { return isNotEmpty(data); } + /** + * Make sure that if a suggestion is clicked, the suggestions dropdown closes and the focus moves to the input field + */ onClickSuggestion(data) { this.clickSuggestion.emit(data); this.close(); diff --git a/src/app/shared/object-collection/object-collection.component.spec.ts b/src/app/shared/object-collection/object-collection.component.spec.ts index b27a342eac..0548efc967 100644 --- a/src/app/shared/object-collection/object-collection.component.spec.ts +++ b/src/app/shared/object-collection/object-collection.component.spec.ts @@ -1,13 +1,11 @@ import { ObjectCollectionComponent } from './object-collection.component'; -import { ViewMode } from '../../+search-page/search-options.model'; -import { element } from 'protractor'; import { By } from '@angular/platform-browser'; import { async, ComponentFixture, TestBed } from '@angular/core/testing'; -import { Config } from '../../../config/config.interface'; -import { DebugElement, NO_ERRORS_SCHEMA } from '@angular/core'; +import { NO_ERRORS_SCHEMA } from '@angular/core'; import { ActivatedRoute, Router } from '@angular/router'; import { Observable } from 'rxjs/Observable'; import { RouterStub } from '../testing/router-stub'; +import { ViewMode } from '../../core/shared/view-mode.model'; describe('ObjectCollectionComponent', () => { let fixture: ComponentFixture; diff --git a/src/app/shared/object-collection/object-collection.component.ts b/src/app/shared/object-collection/object-collection.component.ts index 3af92443c1..b4a436c5de 100644 --- a/src/app/shared/object-collection/object-collection.component.ts +++ b/src/app/shared/object-collection/object-collection.component.ts @@ -14,8 +14,8 @@ import { PaginationComponentOptions } from '../pagination/pagination-component-o import { SortDirection, SortOptions } from '../../core/cache/models/sort-options.model'; import { ListableObject } from './shared/listable-object.model'; -import { ViewMode } from '../../+search-page/search-options.model'; import { hasValue, isNotEmpty } from '../empty.util'; +import { ViewMode } from '../../core/shared/view-mode.model'; @Component({ selector: 'ds-viewable-collection', diff --git a/src/app/shared/object-collection/shared/dso-element-decorator.spec.ts b/src/app/shared/object-collection/shared/dso-element-decorator.spec.ts index 9f54bebed9..9243d13b8e 100644 --- a/src/app/shared/object-collection/shared/dso-element-decorator.spec.ts +++ b/src/app/shared/object-collection/shared/dso-element-decorator.spec.ts @@ -1,6 +1,6 @@ -import { ViewMode } from '../../../+search-page/search-options.model'; import { renderElementsFor } from './dso-element-decorator'; import { Item } from '../../../core/shared/item.model'; +import { ViewMode } from '../../../core/shared/view-mode.model'; describe('ElementDecorator', () => { const gridDecorator = renderElementsFor(Item, ViewMode.Grid); diff --git a/src/app/shared/object-collection/shared/dso-element-decorator.ts b/src/app/shared/object-collection/shared/dso-element-decorator.ts index 51aa57bc50..98650fd25b 100644 --- a/src/app/shared/object-collection/shared/dso-element-decorator.ts +++ b/src/app/shared/object-collection/shared/dso-element-decorator.ts @@ -1,6 +1,6 @@ import { GenericConstructor } from '../../../core/shared/generic-constructor'; import { ListableObject } from './listable-object.model'; -import { ViewMode } from '../../../+search-page/search-options.model'; +import { ViewMode } from '../../../core/shared/view-mode.model'; const dsoElementMap = new Map(); export function renderElementsFor(listable: GenericConstructor, viewMode: ViewMode) { diff --git a/src/app/shared/object-grid/collection-grid-element/collection-grid-element.component.ts b/src/app/shared/object-grid/collection-grid-element/collection-grid-element.component.ts index f383367e3f..a690037619 100644 --- a/src/app/shared/object-grid/collection-grid-element/collection-grid-element.component.ts +++ b/src/app/shared/object-grid/collection-grid-element/collection-grid-element.component.ts @@ -2,8 +2,8 @@ import { Component, Inject } from '@angular/core'; import { Collection } from '../../../core/shared/collection.model'; import { renderElementsFor} from '../../object-collection/shared/dso-element-decorator'; -import { ViewMode } from '../../../+search-page/search-options.model'; import { AbstractListableElementComponent } from '../../object-collection/shared/object-collection-element/abstract-listable-element.component'; +import { ViewMode } from '../../../core/shared/view-mode.model'; @Component({ selector: 'ds-collection-grid-element', diff --git a/src/app/shared/object-grid/community-grid-element/community-grid-element.component.ts b/src/app/shared/object-grid/community-grid-element/community-grid-element.component.ts index 21d73ff0fa..4df9ab9555 100644 --- a/src/app/shared/object-grid/community-grid-element/community-grid-element.component.ts +++ b/src/app/shared/object-grid/community-grid-element/community-grid-element.component.ts @@ -3,7 +3,7 @@ import { Component, Input, Inject } from '@angular/core'; import { Community } from '../../../core/shared/community.model'; import { AbstractListableElementComponent } from '../../object-collection/shared/object-collection-element/abstract-listable-element.component'; import { renderElementsFor} from '../../object-collection/shared/dso-element-decorator'; -import { ViewMode } from '../../../+search-page/search-options.model'; +import { ViewMode } from '../../../core/shared/view-mode.model'; @Component({ selector: 'ds-community-grid-element', diff --git a/src/app/shared/object-grid/item-grid-element/item-grid-element.component.ts b/src/app/shared/object-grid/item-grid-element/item-grid-element.component.ts index 5a8d46f1f2..7a679fef25 100644 --- a/src/app/shared/object-grid/item-grid-element/item-grid-element.component.ts +++ b/src/app/shared/object-grid/item-grid-element/item-grid-element.component.ts @@ -3,7 +3,7 @@ import { Component, Input, Inject } from '@angular/core'; import { Item } from '../../../core/shared/item.model'; import { renderElementsFor} from '../../object-collection/shared/dso-element-decorator'; import { AbstractListableElementComponent } from '../../object-collection/shared/object-collection-element/abstract-listable-element.component'; -import { ViewMode } from '../../../+search-page/search-options.model'; +import { ViewMode } from '../../../core/shared/view-mode.model'; @Component({ selector: 'ds-item-grid-element', diff --git a/src/app/shared/object-grid/search-result-grid-element/collection-search-result/collection-search-result-grid-element.component.ts b/src/app/shared/object-grid/search-result-grid-element/collection-search-result/collection-search-result-grid-element.component.ts index 7f8a3bb9fd..c1f27bee4f 100644 --- a/src/app/shared/object-grid/search-result-grid-element/collection-search-result/collection-search-result-grid-element.component.ts +++ b/src/app/shared/object-grid/search-result-grid-element/collection-search-result/collection-search-result-grid-element.component.ts @@ -3,8 +3,8 @@ import { Component } from '@angular/core'; import { renderElementsFor} from '../../../object-collection/shared/dso-element-decorator'; import { SearchResultGridElementComponent } from '../search-result-grid-element.component'; import { Collection } from '../../../../core/shared/collection.model'; -import { ViewMode } from '../../../../+search-page/search-options.model'; import { CollectionSearchResult } from '../../../object-collection/shared/collection-search-result.model'; +import { ViewMode } from '../../../../core/shared/view-mode.model'; @Component({ selector: 'ds-collection-search-result-grid-element', diff --git a/src/app/shared/object-grid/search-result-grid-element/community-search-result/community-search-result-grid-element.component.ts b/src/app/shared/object-grid/search-result-grid-element/community-search-result/community-search-result-grid-element.component.ts index 7c34207f5e..8fac11a6a4 100644 --- a/src/app/shared/object-grid/search-result-grid-element/community-search-result/community-search-result-grid-element.component.ts +++ b/src/app/shared/object-grid/search-result-grid-element/community-search-result/community-search-result-grid-element.component.ts @@ -2,8 +2,8 @@ import { Component } from '@angular/core'; import { Community } from '../../../../core/shared/community.model'; import { renderElementsFor } from '../../../object-collection/shared/dso-element-decorator'; import { SearchResultGridElementComponent } from '../search-result-grid-element.component'; -import { ViewMode } from '../../../../+search-page/search-options.model'; import { CommunitySearchResult } from '../../../object-collection/shared/community-search-result.model'; +import { ViewMode } from '../../../../core/shared/view-mode.model'; @Component({ selector: 'ds-community-search-result-grid-element', diff --git a/src/app/shared/object-grid/search-result-grid-element/item-search-result/item-search-result-grid-element.component.ts b/src/app/shared/object-grid/search-result-grid-element/item-search-result/item-search-result-grid-element.component.ts index 518fc23a44..232c161779 100644 --- a/src/app/shared/object-grid/search-result-grid-element/item-search-result/item-search-result-grid-element.component.ts +++ b/src/app/shared/object-grid/search-result-grid-element/item-search-result/item-search-result-grid-element.component.ts @@ -4,8 +4,8 @@ import { renderElementsFor } from '../../../object-collection/shared/dso-element import { SearchResultGridElementComponent } from '../search-result-grid-element.component'; import { Item } from '../../../../core/shared/item.model'; import { ItemSearchResult } from '../../../object-collection/shared/item-search-result.model'; -import { ViewMode } from '../../../../+search-page/search-options.model'; import { focusShadow } from '../../../../shared/animations/focus'; +import { ViewMode } from '../../../../core/shared/view-mode.model'; @Component({ selector: 'ds-item-search-result-grid-element', diff --git a/src/app/shared/object-grid/wrapper-grid-element/wrapper-grid-element.component.ts b/src/app/shared/object-grid/wrapper-grid-element/wrapper-grid-element.component.ts index f807542e76..e16aac6799 100644 --- a/src/app/shared/object-grid/wrapper-grid-element/wrapper-grid-element.component.ts +++ b/src/app/shared/object-grid/wrapper-grid-element/wrapper-grid-element.component.ts @@ -1,8 +1,8 @@ import { Component, Injector, Input, OnInit } from '@angular/core'; -import { ViewMode } from '../../../+search-page/search-options.model'; import { GenericConstructor } from '../../../core/shared/generic-constructor'; import { rendersDSOType } from '../../object-collection/shared/dso-element-decorator'; import { ListableObject } from '../../object-collection/shared/listable-object.model'; +import { ViewMode } from '../../../core/shared/view-mode.model'; @Component({ selector: 'ds-wrapper-grid-element', diff --git a/src/app/shared/object-list/collection-list-element/collection-list-element.component.ts b/src/app/shared/object-list/collection-list-element/collection-list-element.component.ts index f95c087d5c..b4e9de16b7 100644 --- a/src/app/shared/object-list/collection-list-element/collection-list-element.component.ts +++ b/src/app/shared/object-list/collection-list-element/collection-list-element.component.ts @@ -2,8 +2,8 @@ import { Component, Inject } from '@angular/core'; import { Collection } from '../../../core/shared/collection.model'; import { renderElementsFor } from '../../object-collection/shared/dso-element-decorator'; -import { ViewMode } from '../../../+search-page/search-options.model'; import { AbstractListableElementComponent } from '../../object-collection/shared/object-collection-element/abstract-listable-element.component'; +import { ViewMode } from '../../../core/shared/view-mode.model'; @Component({ selector: 'ds-collection-list-element', diff --git a/src/app/shared/object-list/community-list-element/community-list-element.component.ts b/src/app/shared/object-list/community-list-element/community-list-element.component.ts index e92c080a31..8f6870db7e 100644 --- a/src/app/shared/object-list/community-list-element/community-list-element.component.ts +++ b/src/app/shared/object-list/community-list-element/community-list-element.component.ts @@ -1,9 +1,9 @@ -import { Component, Input, Inject } from '@angular/core'; +import { Component } from '@angular/core'; import { Community } from '../../../core/shared/community.model'; import { AbstractListableElementComponent } from '../../object-collection/shared/object-collection-element/abstract-listable-element.component'; import { renderElementsFor } from '../../object-collection/shared/dso-element-decorator'; -import { ViewMode } from '../../../+search-page/search-options.model'; +import { ViewMode } from '../../../core/shared/view-mode.model'; @Component({ selector: 'ds-community-list-element', diff --git a/src/app/shared/object-list/item-list-element/item-list-element.component.ts b/src/app/shared/object-list/item-list-element/item-list-element.component.ts index e4efbd6b08..bc77b513fb 100644 --- a/src/app/shared/object-list/item-list-element/item-list-element.component.ts +++ b/src/app/shared/object-list/item-list-element/item-list-element.component.ts @@ -1,9 +1,9 @@ -import { Component, Input, Inject } from '@angular/core'; +import { Component } from '@angular/core'; import { Item } from '../../../core/shared/item.model'; import { AbstractListableElementComponent } from '../../object-collection/shared/object-collection-element/abstract-listable-element.component'; import { renderElementsFor } from '../../object-collection/shared/dso-element-decorator'; -import { ViewMode } from '../../../+search-page/search-options.model'; +import { ViewMode } from '../../../core/shared/view-mode.model'; @Component({ selector: 'ds-item-list-element', diff --git a/src/app/shared/object-list/search-result-list-element/collection-search-result/collection-search-result-list-element.component.ts b/src/app/shared/object-list/search-result-list-element/collection-search-result/collection-search-result-list-element.component.ts index 746e700f05..264f9ce1a9 100644 --- a/src/app/shared/object-list/search-result-list-element/collection-search-result/collection-search-result-list-element.component.ts +++ b/src/app/shared/object-list/search-result-list-element/collection-search-result/collection-search-result-list-element.component.ts @@ -4,8 +4,8 @@ import { renderElementsFor } from '../../../object-collection/shared/dso-element import { SearchResultListElementComponent } from '../search-result-list-element.component'; import { Collection } from '../../../../core/shared/collection.model'; -import { ViewMode } from '../../../../+search-page/search-options.model'; import { CollectionSearchResult } from '../../../object-collection/shared/collection-search-result.model'; +import { ViewMode } from '../../../../core/shared/view-mode.model'; @Component({ selector: 'ds-collection-search-result-list-element', diff --git a/src/app/shared/object-list/search-result-list-element/community-search-result/community-search-result-list-element.component.ts b/src/app/shared/object-list/search-result-list-element/community-search-result/community-search-result-list-element.component.ts index 2ca89fc9c5..227ff9a45d 100644 --- a/src/app/shared/object-list/search-result-list-element/community-search-result/community-search-result-list-element.component.ts +++ b/src/app/shared/object-list/search-result-list-element/community-search-result/community-search-result-list-element.component.ts @@ -4,8 +4,8 @@ import { renderElementsFor } from '../../../object-collection/shared/dso-element import { SearchResultListElementComponent } from '../search-result-list-element.component'; import { Community } from '../../../../core/shared/community.model'; -import { ViewMode } from '../../../../+search-page/search-options.model'; import { CommunitySearchResult } from '../../../object-collection/shared/community-search-result.model'; +import { ViewMode } from '../../../../core/shared/view-mode.model'; @Component({ selector: 'ds-community-search-result-list-element', diff --git a/src/app/shared/object-list/search-result-list-element/item-search-result/item-search-result-list-element.component.ts b/src/app/shared/object-list/search-result-list-element/item-search-result/item-search-result-list-element.component.ts index b776abc214..a0cbc2469c 100644 --- a/src/app/shared/object-list/search-result-list-element/item-search-result/item-search-result-list-element.component.ts +++ b/src/app/shared/object-list/search-result-list-element/item-search-result/item-search-result-list-element.component.ts @@ -1,12 +1,11 @@ -import { ChangeDetectorRef, Component, Inject, OnInit } from '@angular/core'; +import { Component } from '@angular/core'; import { renderElementsFor } from '../../../object-collection/shared/dso-element-decorator'; import { SearchResultListElementComponent } from '../search-result-list-element.component'; import { Item } from '../../../../core/shared/item.model'; import { ItemSearchResult } from '../../../object-collection/shared/item-search-result.model'; -import { ViewMode } from '../../../../+search-page/search-options.model'; -import { ListableObject } from '../../../object-collection/shared/listable-object.model'; import { focusBackground } from '../../../animations/focus'; +import { ViewMode } from '../../../../core/shared/view-mode.model'; @Component({ selector: 'ds-item-search-result-list-element', diff --git a/src/app/shared/object-list/wrapper-list-element/wrapper-list-element.component.ts b/src/app/shared/object-list/wrapper-list-element/wrapper-list-element.component.ts index 217c1776c5..e95def7cf7 100644 --- a/src/app/shared/object-list/wrapper-list-element/wrapper-list-element.component.ts +++ b/src/app/shared/object-list/wrapper-list-element/wrapper-list-element.component.ts @@ -1,8 +1,8 @@ import { Component, Injector, Input, OnInit } from '@angular/core'; -import { ViewMode } from '../../../+search-page/search-options.model'; import { GenericConstructor } from '../../../core/shared/generic-constructor'; import { rendersDSOType } from '../../object-collection/shared/dso-element-decorator' import { ListableObject } from '../../object-collection/shared/listable-object.model'; +import { ViewMode } from '../../../core/shared/view-mode.model'; @Component({ selector: 'ds-wrapper-list-element', diff --git a/src/app/shared/search-form/search-form.component.html b/src/app/shared/search-form/search-form.component.html index fcfe328c3a..19c6f8f1ac 100644 --- a/src/app/shared/search-form/search-form.component.html +++ b/src/app/shared/search-form/search-form.component.html @@ -1,6 +1,6 @@
- diff --git a/src/app/shared/search-form/search-form.component.ts b/src/app/shared/search-form/search-form.component.ts index f16518a51d..c9942bbf7d 100644 --- a/src/app/shared/search-form/search-form.component.ts +++ b/src/app/shared/search-form/search-form.component.ts @@ -3,6 +3,7 @@ import { SearchService } from '../../+search-page/search-service/search.service' import { DSpaceObject } from '../../core/shared/dspace-object.model'; import { Router } from '@angular/router'; import { isNotEmpty, hasValue, isEmpty, hasNoValue } from '../empty.util'; +import { QueryParamsHandling } from '@angular/router/src/config'; /** * This component renders a simple item page. @@ -15,36 +16,66 @@ import { isNotEmpty, hasValue, isEmpty, hasNoValue } from '../empty.util'; styleUrls: ['./search-form.component.scss'], templateUrl: './search-form.component.html' }) -export class SearchFormComponent { - @Input() query: string; - selectedId = ''; - @Input() currentUrl: string; - @Input() scopes: DSpaceObject[]; +/** + * Component that represents the search form + */ +export class SearchFormComponent { + /** + * The search query + */ + @Input() query: string; + + /** + * The currently selected scope object's UUID + */ @Input() - set scope(id: string) { - this.selectedId = id; - } + scope = ''; + @Input() currentUrl: string; + + /** + * The available scopes + */ + @Input() scopes: DSpaceObject[]; constructor(private router: Router) { } + /** + * Updates the search when the form is submitted + * @param data Values submitted using the form + */ onSubmit(data: any) { this.updateSearch(data); } + /** + * Updates the search when the current scope has been changed + * @param {string} scope The new scope + */ onScopeChange(scope: string) { this.updateSearch({ scope }); } + /** + * Updates the search URL + * @param data Updated parameters + */ updateSearch(data: any) { - const newUrl = hasValue(this.currentUrl) ? this.currentUrl : 'search'; + const newUrl = hasValue(this.currentUrl) ? this.currentUrl : '/search'; + let handling: QueryParamsHandling = '' ; + if (this.currentUrl === '/search') { + handling = 'merge'; + } this.router.navigate([newUrl], { queryParams: Object.assign({}, { page: 1 }, data), - queryParamsHandling: 'merge' + queryParamsHandling: handling }); } + /** + * For usage of the isNotEmpty function in the template + */ isNotEmpty(object: any) { return isNotEmpty(object); } diff --git a/src/app/shared/shared.module.ts b/src/app/shared/shared.module.ts index ffb8a7b46c..eb0495c9ac 100644 --- a/src/app/shared/shared.module.ts +++ b/src/app/shared/shared.module.ts @@ -156,6 +156,10 @@ const DIRECTIVES = [ ...ENTRY_COMPONENTS ] }) + +/** + * This module handles all components and pipes that need to be shared among multiple other modules + */ export class SharedModule { } diff --git a/src/app/shared/testing/hal-endpoint-service-stub.ts b/src/app/shared/testing/hal-endpoint-service-stub.ts index e7dbe8bea1..a1764df009 100644 --- a/src/app/shared/testing/hal-endpoint-service-stub.ts +++ b/src/app/shared/testing/hal-endpoint-service-stub.ts @@ -1,6 +1,4 @@ import { Observable } from 'rxjs/Observable'; -import { ViewMode } from '../../+search-page/search-options.model'; -import { BehaviorSubject } from 'rxjs/BehaviorSubject'; export class HALEndpointServiceStub { diff --git a/src/app/shared/testing/search-service-stub.ts b/src/app/shared/testing/search-service-stub.ts index 6d636b3743..b9c1d7533f 100644 --- a/src/app/shared/testing/search-service-stub.ts +++ b/src/app/shared/testing/search-service-stub.ts @@ -1,6 +1,6 @@ import { Observable } from 'rxjs/Observable'; -import { ViewMode } from '../../+search-page/search-options.model'; import { BehaviorSubject } from 'rxjs/BehaviorSubject'; +import { ViewMode } from '../../core/shared/view-mode.model'; export class SearchServiceStub { diff --git a/src/app/shared/truncatable/truncatable-part/truncatable-part.component.ts b/src/app/shared/truncatable/truncatable-part/truncatable-part.component.ts index 0f695625ec..fef5d7d098 100644 --- a/src/app/shared/truncatable/truncatable-part/truncatable-part.component.ts +++ b/src/app/shared/truncatable/truncatable-part/truncatable-part.component.ts @@ -1,5 +1,6 @@ import { Component, Input, OnDestroy, OnInit } from '@angular/core'; import { TruncatableService } from '../truncatable.service'; +import { hasValue } from '../../empty.util'; @Component({ selector: 'ds-truncatable-part', @@ -7,22 +8,59 @@ import { TruncatableService } from '../truncatable.service'; styleUrls: ['./truncatable-part.component.scss'] }) +/** + * Component that truncates/clamps a piece of text + * It needs a TruncatableComponent parent to identify it's current state + */ export class TruncatablePartComponent implements OnInit, OnDestroy { + /** + * Number of lines shown when the part is collapsed + */ @Input() minLines: number; + + /** + * Number of lines shown when the part is expanded. -1 indicates no limit + */ @Input() maxLines = -1; + + /** + * The identifier of the parent TruncatableComponent + */ @Input() id: string; + + /** + * Type of text, can be a h4 for headers or any other class you want to add + */ @Input() type: string; + + /** + * True if the minimal height of the part should at least be as high as it's minimum amount of lines + */ @Input() fixedHeight = false; + + /** + * Current amount of lines shown of this part + */ lines: string; + + /** + * Subscription to unsubscribe from + */ private sub; public constructor(private service: TruncatableService) { } + /** + * Initialize lines variable + */ ngOnInit() { this.setLines(); } + /** + * Subscribe to the current state to determine how much lines should be shown of this part + */ private setLines() { this.sub = this.service.isCollapsed(this.id).subscribe((collapsed: boolean) => { if (collapsed) { @@ -33,7 +71,12 @@ export class TruncatablePartComponent implements OnInit, OnDestroy { }); } + /** + * Unsubscribe from the subscription + */ ngOnDestroy(): void { - this.sub.unsubscribe(); + if (hasValue(this.sub)) { + this.sub.unsubscribe(); + } } } diff --git a/src/app/shared/truncatable/truncatable.actions.ts b/src/app/shared/truncatable/truncatable.actions.ts index 9d3a51219b..9f85d55afe 100644 --- a/src/app/shared/truncatable/truncatable.actions.ts +++ b/src/app/shared/truncatable/truncatable.actions.ts @@ -16,22 +16,43 @@ export const TruncatableActionTypes = { }; export class TruncatableAction implements Action { + /** + * UUID of the truncatable component the action is performed on, used to identify the filter + */ id: string; + + /** + * Type of action that will be performed + */ type; - constructor(name: string) { - this.id = name; + + /** + * Initialize with the truncatable component's UUID + * @param {string} id of the filter + */ + constructor(id: string) { + this.id = id; } } /* tslint:disable:max-classes-per-file */ +/** + * Used to collapse a truncatable component when it's expanded and expand it when it's collapsed + */ export class TruncatableToggleAction extends TruncatableAction { type = TruncatableActionTypes.TOGGLE; } +/** + * Used to collapse a truncatable component + */ export class TruncatableCollapseAction extends TruncatableAction { type = TruncatableActionTypes.COLLAPSE; } +/** + * Used to expand a truncatable component + */ export class TruncatableExpandAction extends TruncatableAction { type = TruncatableActionTypes.EXPAND; } diff --git a/src/app/shared/truncatable/truncatable.component.ts b/src/app/shared/truncatable/truncatable.component.ts index 81ad2b3cff..e22ce4441e 100644 --- a/src/app/shared/truncatable/truncatable.component.ts +++ b/src/app/shared/truncatable/truncatable.component.ts @@ -9,14 +9,32 @@ import { TruncatableService } from './truncatable.service'; styleUrls: ['./truncatable.component.scss'], }) + +/** + * Component that represents a section with one or more truncatable parts that all listen to this state + */ export class TruncatableComponent { + /** + * Is true when all truncatable parts in this truncatable should be expanded on loading + */ @Input() initialExpand = false; + + /** + * The unique identifier of this truncatable component + */ @Input() id: string; + + /** + * Is true when the truncatable should expand on both hover as click + */ @Input() onHover = false; public constructor(private service: TruncatableService) { } + /** + * Set the initial state + */ ngOnInit() { if (this.initialExpand) { this.service.expand(this.id); @@ -25,18 +43,27 @@ export class TruncatableComponent { } } + /** + * If onHover is true, collapses the truncatable + */ public hoverCollapse() { if (this.onHover) { this.service.collapse(this.id); } } + /** + * If onHover is true, expands the truncatable + */ public hoverExpand() { if (this.onHover) { this.service.expand(this.id); } } + /** + * Expands the truncatable when it's collapsed, collapses it when it's expanded + */ public toggle() { this.service.toggle(this.id); } diff --git a/src/app/shared/truncatable/truncatable.reducer.ts b/src/app/shared/truncatable/truncatable.reducer.ts index d9a2111682..11b4ac758d 100644 --- a/src/app/shared/truncatable/truncatable.reducer.ts +++ b/src/app/shared/truncatable/truncatable.reducer.ts @@ -1,15 +1,27 @@ import { TruncatableAction, TruncatableActionTypes } from './truncatable.actions'; +/** + * Interface that represents the state of a single truncatable + */ export interface TruncatableState { collapsed: boolean; } +/** + * Interface that represents the state of all truncatable + */ export interface TruncatablesState { [id: string]: TruncatableState } const initialState: TruncatablesState = Object.create(null); +/** + * Performs a truncatable action on the current state + * @param {TruncatablesState} state The state before the action is performed + * @param {TruncatableAction} action The action that should be performed + * @returns {TruncatablesState} The state after the action is performed + */ export function truncatableReducer(state = initialState, action: TruncatableAction): TruncatablesState { switch (action.type) { diff --git a/src/app/shared/truncatable/truncatable.service.ts b/src/app/shared/truncatable/truncatable.service.ts index 5f36c6a60d..2f707d6609 100644 --- a/src/app/shared/truncatable/truncatable.service.ts +++ b/src/app/shared/truncatable/truncatable.service.ts @@ -7,12 +7,20 @@ import { hasValue } from '../empty.util'; const truncatableStateSelector = (state: TruncatablesState) => state.truncatable; +/** + * Service responsible for truncating/clamping text and performing actions on truncatable elements + */ @Injectable() export class TruncatableService { constructor(private store: Store) { } + /** + * Checks if a trunctable component should currently be collapsed + * @param {string} id The UUID of the truncatable component + * @returns {Observable} Emits true if the state in the store is currently collapsed for the given truncatable component + */ isCollapsed(id: string): Observable { return this.store.select(truncatableByIdSelector(id)) .map((object: TruncatableState) => { @@ -24,14 +32,26 @@ export class TruncatableService { }); } + /** + * Dispatches a toggle action to the store for a given truncatable component + * @param {string} id The identifier of the truncatable for which the action is dispatched + */ public toggle(id: string): void { this.store.dispatch(new TruncatableToggleAction(id)); } + /** + * Dispatches a collapse action to the store for a given truncatable component + * @param {string} id The identifier of the truncatable for which the action is dispatched + */ public collapse(id: string): void { this.store.dispatch(new TruncatableCollapseAction(id)); } + /** + * Dispatches an expand action to the store for a given truncatable component + * @param {string} id The identifier of the truncatable for which the action is dispatched + */ public expand(id: string): void { this.store.dispatch(new TruncatableExpandAction(id)); } diff --git a/src/app/shared/utils/capitalize.pipe.ts b/src/app/shared/utils/capitalize.pipe.ts index 454eb5d845..03ad25ed6e 100644 --- a/src/app/shared/utils/capitalize.pipe.ts +++ b/src/app/shared/utils/capitalize.pipe.ts @@ -1,14 +1,18 @@ import { Pipe, PipeTransform } from '@angular/core' -/** - * Pipe to truncate a value in Angular. (Take a substring, starting at 0) - * Default value: 10 - */ @Pipe({ name: 'dsCapitalize' }) + +/** + * Pipe for capizalizing a string + */ export class CapitalizePipe implements PipeTransform { - transform(value: string, args: string[]): string { + /** + * @param {string} value String to be capitalized + * @returns {string} Capitalized version of the input value + */ + transform(value: string): string { if (value) { return value.charAt(0).toUpperCase() + value.slice(1); } diff --git a/src/app/shared/utils/click-outside.directive.ts b/src/app/shared/utils/click-outside.directive.ts index 59f5bcb1e9..e8efdf2d7a 100644 --- a/src/app/shared/utils/click-outside.directive.ts +++ b/src/app/shared/utils/click-outside.directive.ts @@ -1,9 +1,15 @@ -import {Directive, ElementRef, Output, EventEmitter, HostListener} from '@angular/core'; +import { Directive, ElementRef, Output, EventEmitter, HostListener } from '@angular/core'; @Directive({ selector: '[dsClickOutside]' }) +/** + * Directive to detect when the users clicks outside of the element the directive was put on + */ export class ClickOutsideDirective { + /** + * Emits null when the user clicks outside of the element + */ @Output() public dsClickOutside = new EventEmitter(); diff --git a/src/app/shared/utils/debounce.directive.ts b/src/app/shared/utils/debounce.directive.ts index 1f2d3354f7..f3d3e4aa25 100644 --- a/src/app/shared/utils/debounce.directive.ts +++ b/src/app/shared/utils/debounce.directive.ts @@ -8,22 +8,45 @@ import { Subject } from 'rxjs/Subject'; @Directive({ selector: '[ngModel][dsDebounce]', }) +/** + * Directive for setting a debounce time on an input field + * It will emit the input field's value when no changes were made to this value in a given debounce time + */ export class DebounceDirective implements OnInit, OnDestroy { + + /** + * Emits a value when nothing has changed in dsDebounce milliseconds + */ @Output() public onDebounce = new EventEmitter(); + /** + * The debounce time in milliseconds + */ @Input() public dsDebounce = 500; + /** + * True if no changes have been made to the input field's value + */ private isFirstChange = true; - private ngUnsubscribe: Subject = new Subject(); + + + /** + * Subject to unsubscribe from + */ + private subject: Subject = new Subject(); constructor(public model: NgControl) { } + /** + * Start listening to changes of the input field's value changes + * Emit it when the debounceTime is over without new changes + */ ngOnInit() { this.model.valueChanges - .takeUntil(this.ngUnsubscribe) + .takeUntil(this.subject) .debounceTime(this.dsDebounce) .distinctUntilChanged() .subscribe((modelValue) => { @@ -35,8 +58,11 @@ export class DebounceDirective implements OnInit, OnDestroy { }); } + /** + * Close subject + */ ngOnDestroy() { - this.ngUnsubscribe.next(); - this.ngUnsubscribe.complete(); + this.subject.next(); + this.subject.complete(); } } diff --git a/src/app/shared/utils/drag-click.directive.ts b/src/app/shared/utils/drag-click.directive.ts index ec9b02dea5..3ba9eb5986 100644 --- a/src/app/shared/utils/drag-click.directive.ts +++ b/src/app/shared/utils/drag-click.directive.ts @@ -3,15 +3,36 @@ import { Directive, EventEmitter, HostListener, Output } from '@angular/core'; @Directive({ selector: '[dsDragClick]' }) + +/** + * Directive for preventing drag events being misinterpret as clicks + * The difference is made using the time the mouse button is pushed down + */ export class DragClickDirective { + /** + * The start time of the mouse down event in milliseconds + */ private start; + + /** + * Emits a click event when the click is perceived as an actual click and not a drag + */ @Output() actualClick = new EventEmitter(); + /** + * When the mouse button is pushed down, register the start time + * @param event Mouse down event + */ @HostListener('mousedown', ['$event']) mousedownEvent(event) { this.start = new Date(); } + /** + * When the mouse button is let go of, check how long if it was down for + * If the mouse button was down for more than 250ms, don't emit a click event + * @param event Mouse down event + */ @HostListener('mouseup', ['$event']) mouseupEvent(event) { const end: any = new Date(); diff --git a/src/app/shared/utils/emphasize.pipe.ts b/src/app/shared/utils/emphasize.pipe.ts index 506974c569..613074a308 100644 --- a/src/app/shared/utils/emphasize.pipe.ts +++ b/src/app/shared/utils/emphasize.pipe.ts @@ -1,7 +1,13 @@ import { Pipe, PipeTransform } from '@angular/core'; @Pipe({ name: 'dsEmphasize' }) +/** + * Pipe for emphasizing a part of a string by surrounding it with tags + */ export class EmphasizePipe implements PipeTransform { + /** + * Characters that should be escaped + */ specials = [ // order matters for these '-' @@ -22,14 +28,28 @@ export class EmphasizePipe implements PipeTransform { , '$' , '|' ]; + /** + * Regular expression for escaping the string we're trying to find + */ regex = RegExp('[' + this.specials.join('\\') + ']', 'g'); + /** + * + * @param haystack The string which we want to partly highlight + * @param needle The string that should become emphasized in the haystack string + * @returns {any} Transformed haystack with the needle emphasized + */ transform(haystack, needle): any { const escaped = this.escapeRegExp(needle); const reg = new RegExp(escaped, 'gi'); return haystack.replace(reg, '$&'); } + /** + * + * @param str Escape special characters in the string we're looking for + * @returns {any} The escaped version of the input string + */ escapeRegExp(str) { return str.replace(this.regex, '\\$&'); } diff --git a/src/app/shared/utils/enum-keys-pipe.ts b/src/app/shared/utils/enum-keys-pipe.ts index 82893b886f..0a4a445c02 100644 --- a/src/app/shared/utils/enum-keys-pipe.ts +++ b/src/app/shared/utils/enum-keys-pipe.ts @@ -1,8 +1,16 @@ import { Pipe, PipeTransform } from '@angular/core'; @Pipe({ name: 'dsKeys' }) +/** + * Pipe for parsing all values of an enumeration to an array of key-value pairs + */ export class EnumKeysPipe implements PipeTransform { - transform(value, args: string[]): any { + + /** + * @param value An enumeration + * @returns {any} Array with all keys and values of the input enumeration + */ + transform(value): any { const keys = []; for (const enumMember in value) { if (!isNaN(parseInt(enumMember, 10))) { diff --git a/src/app/shared/utils/object-keys-pipe.ts b/src/app/shared/utils/object-keys-pipe.ts index ce554a0ca1..fd3d018b88 100644 --- a/src/app/shared/utils/object-keys-pipe.ts +++ b/src/app/shared/utils/object-keys-pipe.ts @@ -1,7 +1,15 @@ import { PipeTransform, Pipe } from '@angular/core'; @Pipe({name: 'dsObjectKeys'}) +/** + * Pipe for parsing all keys of an object to an array of key-value pairs + */ export class ObjectKeysPipe implements PipeTransform { + + /** + * @param value An object + * @returns {any} Array with all keys the input object + */ transform(value, args:string[]): any { const keys = []; Object.keys(value).forEach((k) => keys.push(k)); diff --git a/src/app/shared/view-mode-switch/view-mode-switch.component.spec.ts b/src/app/shared/view-mode-switch/view-mode-switch.component.spec.ts index 1b4bf6da46..1211b4bc88 100644 --- a/src/app/shared/view-mode-switch/view-mode-switch.component.spec.ts +++ b/src/app/shared/view-mode-switch/view-mode-switch.component.spec.ts @@ -8,8 +8,8 @@ import { ChangeDetectionStrategy, Component } from '@angular/core'; import { SearchService } from '../../+search-page/search-service/search.service'; import { ViewModeSwitchComponent } from './view-mode-switch.component'; -import { ViewMode } from '../../+search-page/search-options.model'; import { SearchServiceStub } from '../testing/search-service-stub'; +import { ViewMode } from '../../core/shared/view-mode.model'; @Component({ template: '' }) class DummyComponent { } diff --git a/src/app/shared/view-mode-switch/view-mode-switch.component.ts b/src/app/shared/view-mode-switch/view-mode-switch.component.ts index f6e04816fb..d34a4b4d60 100644 --- a/src/app/shared/view-mode-switch/view-mode-switch.component.ts +++ b/src/app/shared/view-mode-switch/view-mode-switch.component.ts @@ -1,7 +1,7 @@ import { Subscription } from 'rxjs/Subscription'; import { Component, OnInit, OnDestroy } from '@angular/core'; -import { ViewMode } from '../../+search-page/search-options.model'; import { SearchService } from './../../+search-page/search-service/search.service'; +import { ViewMode } from '../../core/shared/view-mode.model'; /** * Component to switch between list and grid views.