LP1806087 Angular catalog Ang7 & lint repairs
[Evergreen.git] / Open-ILS / src / eg2 / src / app / staff / catalog / search-form.component.ts
1 import {Component, OnInit, AfterViewInit, Renderer2} from '@angular/core';
2 import {Router} from '@angular/router';
3 import {IdlObject} from '@eg/core/idl.service';
4 import {OrgService} from '@eg/core/org.service';
5 import {CatalogService} from '@eg/share/catalog/catalog.service';
6 import {CatalogSearchContext, CatalogSearchState} from '@eg/share/catalog/search-context';
7 import {StaffCatalogService} from './catalog.service';
8 import {NgbTabset, NgbTabChangeEvent} from '@ng-bootstrap/ng-bootstrap';
9
10 @Component({
11   selector: 'eg-catalog-search-form',
12   styleUrls: ['search-form.component.css'],
13   templateUrl: 'search-form.component.html'
14 })
15 export class SearchFormComponent implements OnInit, AfterViewInit {
16
17     context: CatalogSearchContext;
18     ccvmMap: {[ccvm: string]: IdlObject[]} = {};
19     cmfMap: {[cmf: string]: IdlObject} = {};
20     showSearchFilters = false;
21     copyLocations: IdlObject[];
22     searchTab: string;
23
24     constructor(
25         private renderer: Renderer2,
26         private router: Router,
27         private org: OrgService,
28         private cat: CatalogService,
29         private staffCat: StaffCatalogService
30     ) {
31         this.copyLocations = [];
32     }
33
34     ngOnInit() {
35         this.ccvmMap = this.cat.ccvmMap;
36         this.cmfMap = this.cat.cmfMap;
37         this.context = this.staffCat.searchContext;
38
39         // Start with advanced search options open
40         // if any filters are active.
41         this.showSearchFilters = this.filtersActive();
42     }
43
44     ngAfterViewInit() {
45         // Query inputs are generated from search context data,
46         // so they are not available until after the first render.
47         // Search context data is extracted synchronously from the URL.
48
49         // Avoid changing the tab in the lifecycle hook thread.
50         setTimeout(() => {
51
52             // Apply a tab if none was already specified
53             if (!this.searchTab) {
54                 // Assumes that only one type of search will be searchable
55                 // at any given time.
56                 if (this.context.marcSearch.isSearchable()) {
57                     this.searchTab = 'marc';
58                 } else if (this.context.identSearch.isSearchable()) {
59                     this.searchTab = 'ident';
60                 } else if (this.context.browseSearch.isSearchable()) {
61                     this.searchTab = 'browse';
62                 } else {
63                     // Default tab
64                     this.searchTab = 'term';
65                     this.refreshCopyLocations();
66                 }
67             }
68
69             this.focusTabInput();
70         });
71     }
72
73     onTabChange(evt: NgbTabChangeEvent) {
74         this.searchTab = evt.nextId;
75
76         // Focus after tab-change event has a chance to complete
77         // or the tab body and its input won't exist yet and no
78         // elements will be focus-able.
79         setTimeout(() => this.focusTabInput());
80     }
81
82     focusTabInput() {
83         // Select a DOM node to focus when the tab changes.
84         let selector;
85         switch (this.searchTab) {
86             case 'ident':
87                 selector = '#ident-query-input';
88                 break;
89             case 'marc':
90                 selector = '#first-marc-tag';
91                 break;
92             case 'browse':
93                 selector = '#browse-term-input';
94                 break;
95             default:
96                 this.refreshCopyLocations();
97                 selector = '#first-query-input';
98         }
99
100         this.renderer.selectRootElement(selector).focus();
101     }
102
103     /**
104      * Display the advanced/extended search options when asked to
105      * or if any advanced options are selected.
106      */
107     showFilters(): boolean {
108         return this.showSearchFilters;
109     }
110
111     toggleFilters() {
112         this.showSearchFilters = !this.showSearchFilters;
113         this.refreshCopyLocations();
114     }
115
116     filtersActive(): boolean {
117
118         if (this.context.termSearch.copyLocations[0] !== '') { return true; }
119
120         // ccvm filters may be present without any filters applied.
121         // e.g. if filters were applied then removed.
122         let show = false;
123         Object.keys(this.context.termSearch.ccvmFilters).forEach(ccvm => {
124             if (this.context.termSearch.ccvmFilters[ccvm][0] !== '') {
125                 show = true;
126             }
127         });
128
129         return show;
130     }
131
132     orgOnChange = (org: IdlObject): void => {
133         this.context.searchOrg = org;
134         this.refreshCopyLocations();
135     }
136
137     refreshCopyLocations() {
138         if (!this.showFilters()) { return; }
139
140         // TODO: is this how we avoid displaying too many locations?
141         const org = this.context.searchOrg;
142         if (org.id() === this.org.root().id()) {
143             this.copyLocations = [];
144             return;
145         }
146
147         this.cat.fetchCopyLocations(org).then(() =>
148             this.copyLocations = this.cat.copyLocations
149         );
150     }
151
152     orgName(orgId: number): string {
153         return this.org.get(orgId).shortname();
154     }
155
156     addSearchRow(index: number): void {
157         this.context.termSearch.query.splice(index, 0, '');
158         this.context.termSearch.fieldClass.splice(index, 0, 'keyword');
159         this.context.termSearch.joinOp.splice(index, 0, '&&');
160         this.context.termSearch.matchOp.splice(index, 0, 'contains');
161     }
162
163     delSearchRow(index: number): void {
164         this.context.termSearch.query.splice(index, 1);
165         this.context.termSearch.fieldClass.splice(index, 1);
166         this.context.termSearch.joinOp.splice(index, 1);
167         this.context.termSearch.matchOp.splice(index, 1);
168     }
169
170     addMarcSearchRow(index: number): void {
171         this.context.marcSearch.tags.splice(index, 0, '');
172         this.context.marcSearch.subfields.splice(index, 0, '');
173         this.context.marcSearch.values.splice(index, 0, '');
174     }
175
176     delMarcSearchRow(index: number): void {
177         this.context.marcSearch.tags.splice(index, 1);
178         this.context.marcSearch.subfields.splice(index, 1);
179         this.context.marcSearch.values.splice(index, 1);
180     }
181
182     searchByForm(): void {
183         this.context.pager.offset = 0; // New search
184
185         // Form search overrides basket display
186         this.context.showBasket = false;
187
188         switch (this.searchTab) {
189
190             case 'term': // AKA keyword search
191                 this.context.marcSearch.reset();
192                 this.context.browseSearch.reset();
193                 this.context.identSearch.reset();
194                 this.context.termSearch.hasBrowseEntry = '';
195                 this.context.termSearch.browseEntry = null;
196                 this.context.termSearch.fromMetarecord = null;
197                 this.context.termSearch.facetFilters = [];
198                 this.staffCat.search();
199                 break;
200
201             case 'ident':
202                 this.context.marcSearch.reset();
203                 this.context.browseSearch.reset();
204                 this.context.termSearch.reset();
205                 this.staffCat.search();
206                 break;
207
208             case 'marc':
209                 this.context.browseSearch.reset();
210                 this.context.termSearch.reset();
211                 this.context.identSearch.reset();
212                 this.staffCat.search();
213                 break;
214
215             case 'browse':
216                 this.context.marcSearch.reset();
217                 this.context.termSearch.reset();
218                 this.context.identSearch.reset();
219                 this.context.browseSearch.pivot = null;
220                 this.staffCat.browse();
221                 break;
222         }
223     }
224
225     // https://stackoverflow.com/questions/42322968/angular2-dynamic-input-field-lose-focus-when-input-changes
226     trackByIdx(index: any, item: any) {
227        return index;
228     }
229
230     searchIsActive(): boolean {
231         return this.context.searchState === CatalogSearchState.SEARCHING;
232     }
233
234     goToBrowse() {
235         this.router.navigate(['/staff/catalog/browse']);
236     }
237 }
238
239