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