LP2009865 Revised search result headings & source order
[evergreen-equinox.git] / Open-ILS / src / eg2 / src / app / staff / catalog / result / results.component.ts
1 import {Component, OnInit, OnDestroy, Input} from '@angular/core';
2 import {Observable, Subscription} from 'rxjs';
3 import {tap, map, switchMap, distinctUntilChanged} from 'rxjs/operators';
4 import {Router, ActivatedRoute, ParamMap} from '@angular/router';
5 import {CatalogService} from '@eg/share/catalog/catalog.service';
6 import {BibRecordService} from '@eg/share/catalog/bib-record.service';
7 import {CatalogUrlService} from '@eg/share/catalog/catalog-url.service';
8 import {CatalogSearchContext, CatalogSearchState} from '@eg/share/catalog/search-context';
9 import {PcrudService} from '@eg/core/pcrud.service';
10 import {StaffCatalogService} from '../catalog.service';
11 import {IdlObject} from '@eg/core/idl.service';
12 import {BasketService} from '@eg/share/catalog/basket.service';
13 import {ServerStoreService} from '@eg/core/server-store.service';
14
15 @Component({
16   selector: 'eg-catalog-results',
17   templateUrl: 'results.component.html',
18   styleUrls: ['results.component.css']
19 })
20 export class ResultsComponent implements OnInit, OnDestroy {
21
22     searchContext: CatalogSearchContext;
23
24     // Cache record creator/editor since this will likely be a
25     // reasonably small set of data w/ lots of repitition.
26     userCache: {[id: number]: IdlObject} = {};
27
28     allRecsSelected: boolean;
29
30     searchSub: Subscription;
31     routeSub: Subscription;
32     basketSub: Subscription;
33     showMoreDetails = false;
34
35     constructor(
36         private route: ActivatedRoute,
37         private pcrud: PcrudService,
38         private cat: CatalogService,
39         private bib: BibRecordService,
40         private catUrl: CatalogUrlService,
41         private staffCat: StaffCatalogService,
42         private serverStore: ServerStoreService,
43         private basket: BasketService,
44         private router: Router
45     ) {}
46
47     ngOnInit() {
48         this.searchContext = this.staffCat.searchContext;
49         this.staffCat.browsePagerData = [];
50
51         // Our search context is initialized on page load.  Once
52         // ResultsComponent is active, it will not be reinitialized,
53         // even if the route parameters changes (unless we change the
54         // route reuse policy).  Watch for changes here to pick up new
55         // searches.
56         //
57         // This will also fire on page load.
58         this.routeSub =
59             this.route.queryParamMap.subscribe((params: ParamMap) => {
60
61               // TODO: Angular docs suggest using switchMap(), but
62               // it's not firing for some reason.  Also, could avoid
63               // firing unnecessary searches when a param unrelated to
64               // searching is changed by .map()'ing out only the desired
65               // params and running through .distinctUntilChanged(), but
66               // .map() is not firing either.  I'm missing something.
67               this.searchByUrl(params);
68         });
69
70         // After each completed search, update the record selector.
71         this.searchSub = this.cat.onSearchComplete.subscribe(
72             ctx => {
73                 this.jumpIfNecessary();
74                 this.applyRecordSelection();
75             }
76         );
77
78         // Watch for basket changes applied by other components.
79         this.basketSub = this.basket.onChange.subscribe(
80             () => this.applyRecordSelection());
81     }
82
83     ngOnDestroy() {
84         if (this.routeSub) {
85             this.routeSub.unsubscribe();
86             this.searchSub.unsubscribe();
87             this.basketSub.unsubscribe();
88         }
89     }
90
91     // Jump to record page if only a single hit is returned
92     // and the jump is enabled by library setting
93     jumpIfNecessary() {
94         const ids = this.searchContext.currentResultIds();
95         if (this.staffCat.jumpOnSingleHit && ids.length === 1) {
96            // this.router.navigate(['/staff/catalog/record/' + ids[0], { queryParams: this.catUrl.toUrlParams(this.searchContext) }]);
97             this.router.navigate(['/staff/catalog/record/' + ids[0]], {queryParamsHandling: 'merge'});
98         }
99     }
100
101     // Apply the select-all checkbox when all visible records
102     // are selected.
103     applyRecordSelection() {
104         const ids = this.searchContext.currentResultIds();
105         let allChecked = true;
106         ids.forEach(id => {
107             if (!this.basket.hasRecordId(id)) {
108                 allChecked = false;
109             }
110         });
111         this.allRecsSelected = allChecked;
112     }
113
114     // Pull values from the URL and run the requested search.
115     searchByUrl(params: ParamMap): void {
116         this.catUrl.applyUrlParams(this.searchContext, params);
117
118
119         if (this.searchContext.isSearchable()) {
120
121             this.serverStore.getItem('eg.staff.catalog.results.show_more')
122             .then(showMore => {
123
124                 this.showMoreDetails =
125                     this.searchContext.showResultExtras = showMore;
126
127                 if (this.staffCat.prefOrg) {
128                     this.searchContext.prefOu = this.staffCat.prefOrg.id();
129                 }
130
131                 this.cat.search(this.searchContext)
132                 .then(ok => {
133                     this.cat.fetchFacets(this.searchContext);
134                     this.cat.fetchBibSummaries(this.searchContext);
135                 });
136             });
137         }
138     }
139
140     toggleShowMore() {
141         this.showMoreDetails = !this.showMoreDetails;
142
143         this.serverStore.setItem(
144             'eg.staff.catalog.results.show_more', this.showMoreDetails)
145         .then(_ => {
146
147             this.searchContext.showResultExtras = this.showMoreDetails;
148
149             if (this.showMoreDetails) {
150                 this.staffCat.search();
151             } else {
152                 // Clear the collected copies.  No need for another search.
153                 this.searchContext.result.records.forEach(rec => rec.copies = undefined);
154             }
155         });
156     }
157
158     searchIsDone(): boolean {
159         return this.searchContext.searchState === CatalogSearchState.COMPLETE;
160     }
161
162     searchIsActive(): boolean {
163         return this.searchContext.searchState === CatalogSearchState.SEARCHING;
164     }
165
166     searchHasResults(): boolean {
167         return this.searchIsDone() && this.searchContext.result.count > 0;
168     }
169
170     toggleAllRecsSelected() {
171         const ids = this.searchContext.currentResultIds();
172
173         if (this.allRecsSelected) {
174             this.basket.addRecordIds(ids);
175         } else {
176             this.basket.removeRecordIds(ids);
177         }
178     }
179 }
180
181