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