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';
11 selector: 'eg-catalog-search-form',
12 styleUrls: ['search-form.component.css'],
13 templateUrl: 'search-form.component.html'
15 export class SearchFormComponent implements OnInit, AfterViewInit {
17 context: CatalogSearchContext;
18 ccvmMap: {[ccvm: string]: IdlObject[]} = {};
19 cmfMap: {[cmf: string]: IdlObject} = {};
20 showSearchFilters = false;
21 copyLocations: IdlObject[];
25 private renderer: Renderer2,
26 private router: Router,
27 private org: OrgService,
28 private cat: CatalogService,
29 private staffCat: StaffCatalogService
31 this.copyLocations = [];
35 this.ccvmMap = this.cat.ccvmMap;
36 this.cmfMap = this.cat.cmfMap;
37 this.context = this.staffCat.searchContext;
39 // Start with advanced search options open
40 // if any filters are active.
41 this.showSearchFilters = this.filtersActive();
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.
49 // Avoid changing the tab in the lifecycle hook thread.
52 // Apply a tab if none was already specified
53 if (!this.searchTab) {
54 // Assumes that only one type of search will be searchable
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';
64 this.searchTab = 'term';
65 this.refreshCopyLocations();
73 onTabChange(evt: NgbTabChangeEvent) {
74 this.searchTab = evt.nextId;
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());
83 // Select a DOM node to focus when the tab changes.
85 switch (this.searchTab) {
87 selector = '#ident-query-input';
90 selector = '#first-marc-tag';
93 selector = '#browse-term-input';
96 this.refreshCopyLocations();
97 selector = '#first-query-input';
100 this.renderer.selectRootElement(selector).focus();
104 * Display the advanced/extended search options when asked to
105 * or if any advanced options are selected.
107 showFilters(): boolean {
108 return this.showSearchFilters;
112 this.showSearchFilters = !this.showSearchFilters;
113 this.refreshCopyLocations();
116 filtersActive(): boolean {
118 if (this.context.termSearch.copyLocations[0] !== '') { return true; }
120 // ccvm filters may be present without any filters applied.
121 // e.g. if filters were applied then removed.
123 Object.keys(this.context.termSearch.ccvmFilters).forEach(ccvm => {
124 if (this.context.termSearch.ccvmFilters[ccvm][0] !== '') {
132 orgOnChange = (org: IdlObject): void => {
133 this.context.searchOrg = org;
134 this.refreshCopyLocations();
137 refreshCopyLocations() {
138 if (!this.showFilters()) { return; }
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 = [];
147 this.cat.fetchCopyLocations(org).then(() =>
148 this.copyLocations = this.cat.copyLocations
152 orgName(orgId: number): string {
153 return this.org.get(orgId).shortname();
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');
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);
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, '');
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);
182 searchByForm(): void {
183 this.context.pager.offset = 0; // New search
185 // Form search overrides basket display
186 this.context.showBasket = false;
188 switch (this.searchTab) {
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();
202 this.context.marcSearch.reset();
203 this.context.browseSearch.reset();
204 this.context.termSearch.reset();
205 this.staffCat.search();
209 this.context.browseSearch.reset();
210 this.context.termSearch.reset();
211 this.context.identSearch.reset();
212 this.staffCat.search();
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();
225 // https://stackoverflow.com/questions/42322968/angular2-dynamic-input-field-lose-focus-when-input-changes
226 trackByIdx(index: any, item: any) {
230 searchIsActive(): boolean {
231 return this.context.searchState === CatalogSearchState.SEARCHING;
235 this.router.navigate(['/staff/catalog/browse']);