]> git.evergreen-ils.org Git - working/Evergreen.git/blob - Open-ILS/src/eg2/src/app/staff/catalog/search-form.component.ts
LP1818288 Ang staff catalog record detail holds tab/actions
[working/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         try {
101             // TODO: sometime the selector is not available in the DOM
102             // until even later (even with setTimeouts).  Need to fix this.
103             // Note the error is thrown from selectRootElement(), not the
104             // call to .focus() on a null reference.
105             this.renderer.selectRootElement(selector).focus();
106         } catch (E) {}
107     }
108
109     /**
110      * Display the advanced/extended search options when asked to
111      * or if any advanced options are selected.
112      */
113     showFilters(): boolean {
114         return this.showSearchFilters;
115     }
116
117     toggleFilters() {
118         this.showSearchFilters = !this.showSearchFilters;
119         this.refreshCopyLocations();
120     }
121
122     filtersActive(): boolean {
123
124         if (this.context.termSearch.copyLocations[0] !== '') { return true; }
125
126         // ccvm filters may be present without any filters applied.
127         // e.g. if filters were applied then removed.
128         let show = false;
129         Object.keys(this.context.termSearch.ccvmFilters).forEach(ccvm => {
130             if (this.context.termSearch.ccvmFilters[ccvm][0] !== '') {
131                 show = true;
132             }
133         });
134
135         return show;
136     }
137
138     orgOnChange = (org: IdlObject): void => {
139         this.context.searchOrg = org;
140         this.refreshCopyLocations();
141     }
142
143     refreshCopyLocations() {
144         if (!this.showFilters()) { return; }
145
146         // TODO: is this how we avoid displaying too many locations?
147         const org = this.context.searchOrg;
148         if (org.id() === this.org.root().id()) {
149             this.copyLocations = [];
150             return;
151         }
152
153         this.cat.fetchCopyLocations(org).then(() =>
154             this.copyLocations = this.cat.copyLocations
155         );
156     }
157
158     orgName(orgId: number): string {
159         return this.org.get(orgId).shortname();
160     }
161
162     addSearchRow(index: number): void {
163         this.context.termSearch.query.splice(index, 0, '');
164         this.context.termSearch.fieldClass.splice(index, 0, 'keyword');
165         this.context.termSearch.joinOp.splice(index, 0, '&&');
166         this.context.termSearch.matchOp.splice(index, 0, 'contains');
167     }
168
169     delSearchRow(index: number): void {
170         this.context.termSearch.query.splice(index, 1);
171         this.context.termSearch.fieldClass.splice(index, 1);
172         this.context.termSearch.joinOp.splice(index, 1);
173         this.context.termSearch.matchOp.splice(index, 1);
174     }
175
176     addMarcSearchRow(index: number): void {
177         this.context.marcSearch.tags.splice(index, 0, '');
178         this.context.marcSearch.subfields.splice(index, 0, '');
179         this.context.marcSearch.values.splice(index, 0, '');
180     }
181
182     delMarcSearchRow(index: number): void {
183         this.context.marcSearch.tags.splice(index, 1);
184         this.context.marcSearch.subfields.splice(index, 1);
185         this.context.marcSearch.values.splice(index, 1);
186     }
187
188     searchByForm(): void {
189         this.context.pager.offset = 0; // New search
190
191         // Form search overrides basket display
192         this.context.showBasket = false;
193
194         switch (this.searchTab) {
195
196             case 'term': // AKA keyword search
197                 this.context.marcSearch.reset();
198                 this.context.browseSearch.reset();
199                 this.context.identSearch.reset();
200                 this.context.termSearch.hasBrowseEntry = '';
201                 this.context.termSearch.browseEntry = null;
202                 this.context.termSearch.fromMetarecord = null;
203                 this.context.termSearch.facetFilters = [];
204                 this.staffCat.search();
205                 break;
206
207             case 'ident':
208                 this.context.marcSearch.reset();
209                 this.context.browseSearch.reset();
210                 this.context.termSearch.reset();
211                 this.staffCat.search();
212                 break;
213
214             case 'marc':
215                 this.context.browseSearch.reset();
216                 this.context.termSearch.reset();
217                 this.context.identSearch.reset();
218                 this.staffCat.search();
219                 break;
220
221             case 'browse':
222                 this.context.marcSearch.reset();
223                 this.context.termSearch.reset();
224                 this.context.identSearch.reset();
225                 this.context.browseSearch.pivot = null;
226                 this.staffCat.browse();
227                 break;
228         }
229     }
230
231     // https://stackoverflow.com/questions/42322968/angular2-dynamic-input-field-lose-focus-when-input-changes
232     trackByIdx(index: any, item: any) {
233        return index;
234     }
235
236     searchIsActive(): boolean {
237         return this.context.searchState === CatalogSearchState.SEARCHING;
238     }
239
240     goToBrowse() {
241         this.router.navigate(['/staff/catalog/browse']);
242     }
243 }
244
245