1 import {Component, OnInit, AfterViewInit, Renderer2} from '@angular/core';
2 import {Router, ActivatedRoute, NavigationEnd} 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[];
24 // Display the full form if true, otherwise display the expandy.
28 private renderer: Renderer2,
29 private router: Router,
30 private route: ActivatedRoute,
31 private org: OrgService,
32 private cat: CatalogService,
33 private staffCat: StaffCatalogService
35 this.copyLocations = [];
37 // Some search scenarios, like rendering a search template,
38 // will not be searchable and thus not resovle to a specific
39 // search tab. Check to see if a specific tab is requested
41 this.route.queryParams.subscribe(params => {
42 if (params.searchTab) {
43 this.searchTab = params.searchTab;
47 this.router.events.subscribe(routeEvent => {
48 if (routeEvent instanceof NavigationEnd) {
49 if (routeEvent.url.match(/catalog\/record/)) {
50 this.showThyself = false;
52 this.showThyself = true;
59 this.ccvmMap = this.cat.ccvmMap;
60 this.cmfMap = this.cat.cmfMap;
61 this.context = this.staffCat.searchContext;
63 // Start with advanced search options open
64 // if any filters are active.
65 this.showSearchFilters = this.filtersActive();
69 // Query inputs are generated from search context data,
70 // so they are not available until after the first render.
71 // Search context data is extracted synchronously from the URL.
73 // Avoid changing the tab in the lifecycle hook thread.
76 if (this.context.identSearch.queryType === '') {
77 this.context.identSearch.queryType = 'identifier|isbn';
80 // Apply a tab if none was already specified
81 if (!this.searchTab) {
82 // Assumes that only one type of search will be searchable
84 if (this.context.marcSearch.isSearchable()) {
85 this.searchTab = 'marc';
86 } else if (this.context.identSearch.isSearchable()) {
87 this.searchTab = 'ident';
88 } else if (this.context.browseSearch.isSearchable()) {
89 this.searchTab = 'browse';
92 this.searchTab = 'term';
93 this.refreshCopyLocations();
101 onTabChange(evt: NgbTabChangeEvent) {
102 this.searchTab = evt.nextId;
104 // Focus after tab-change event has a chance to complete
105 // or the tab body and its input won't exist yet and no
106 // elements will be focus-able.
107 setTimeout(() => this.focusTabInput());
111 // Select a DOM node to focus when the tab changes.
112 let selector: string;
113 switch (this.searchTab) {
115 selector = '#ident-query-input';
118 selector = '#first-marc-tag';
121 selector = '#browse-term-input';
124 selector = '#cnbrowse-term-input';
127 this.refreshCopyLocations();
128 selector = '#first-query-input';
132 // TODO: sometime the selector is not available in the DOM
133 // until even later (even with setTimeouts). Need to fix this.
134 // Note the error is thrown from selectRootElement(), not the
135 // call to .focus() on a null reference.
136 this.renderer.selectRootElement(selector).focus();
141 * Display the advanced/extended search options when asked to
142 * or if any advanced options are selected.
144 showFilters(): boolean {
145 // Note that filters may become active due to external
146 // actions on the search context. Always show the filters
147 // if filter values are applied.
148 return this.showSearchFilters || this.filtersActive();
152 this.showSearchFilters = !this.showSearchFilters;
153 this.refreshCopyLocations();
156 filtersActive(): boolean {
158 if (this.context.termSearch.copyLocations[0] !== '') { return true; }
160 // ccvm filters may be present without any filters applied.
161 // e.g. if filters were applied then removed.
163 Object.keys(this.context.termSearch.ccvmFilters).forEach(ccvm => {
164 if (this.context.termSearch.ccvmFilters[ccvm][0] !== '') {
172 orgOnChange = (org: IdlObject): void => {
173 this.context.searchOrg = org;
174 this.refreshCopyLocations();
177 refreshCopyLocations() {
178 if (!this.showFilters()) { return; }
180 // TODO: is this how we avoid displaying too many locations?
181 const org = this.context.searchOrg;
182 if (org.id() === this.org.root().id()) {
183 this.copyLocations = [];
187 this.cat.fetchCopyLocations(org).then(() =>
188 this.copyLocations = this.cat.copyLocations
192 orgName(orgId: number): string {
193 return this.org.get(orgId).shortname();
196 addSearchRow(index: number): void {
197 this.context.termSearch.query.splice(index, 0, '');
198 this.context.termSearch.fieldClass.splice(index, 0, 'keyword');
199 this.context.termSearch.joinOp.splice(index, 0, '&&');
200 this.context.termSearch.matchOp.splice(index, 0, 'contains');
203 delSearchRow(index: number): void {
204 this.context.termSearch.query.splice(index, 1);
205 this.context.termSearch.fieldClass.splice(index, 1);
206 this.context.termSearch.joinOp.splice(index, 1);
207 this.context.termSearch.matchOp.splice(index, 1);
210 addMarcSearchRow(index: number): void {
211 this.context.marcSearch.tags.splice(index, 0, '');
212 this.context.marcSearch.subfields.splice(index, 0, '');
213 this.context.marcSearch.values.splice(index, 0, '');
216 delMarcSearchRow(index: number): void {
217 this.context.marcSearch.tags.splice(index, 1);
218 this.context.marcSearch.subfields.splice(index, 1);
219 this.context.marcSearch.values.splice(index, 1);
222 searchByForm(): void {
223 this.context.pager.offset = 0; // New search
225 // Form search overrides basket display
226 this.context.showBasket = false;
228 this.context.scrub(this.searchTab);
230 switch (this.searchTab) {
235 this.staffCat.search();
239 this.staffCat.browse();
243 this.staffCat.cnBrowse();
248 // https://stackoverflow.com/questions/42322968/angular2-dynamic-input-field-lose-focus-when-input-changes
249 trackByIdx(index: any, item: any) {
253 searchIsActive(): boolean {
254 return this.context.searchState === CatalogSearchState.SEARCHING;