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