]> git.evergreen-ils.org Git - Evergreen.git/blob - Open-ILS/src/eg2/src/app/staff/catalog/search-templates.component.ts
LP1615805 No inputs after submit in patron search (AngularJS)
[Evergreen.git] / Open-ILS / src / eg2 / src / app / staff / catalog / search-templates.component.ts
1 import {Component, OnInit, Input, ViewChild} from '@angular/core';
2 import {OrgService} from '@eg/core/org.service';
3 import {StoreService} from '@eg/core/store.service';
4 import {ServerStoreService} from '@eg/core/server-store.service';
5 import {PcrudService} from '@eg/core/pcrud.service';
6 import {DialogComponent} from '@eg/share/dialog/dialog.component';
7 import {ConfirmDialogComponent} from '@eg/share/dialog/confirm.component';
8 import {StringService} from '@eg/share/string/string.service';
9 import {CatalogService} from '@eg/share/catalog/catalog.service';
10 import {CatalogUrlService} from '@eg/share/catalog/catalog-url.service';
11 import {CatalogSearchContext, CatalogSearchState} from '@eg/share/catalog/search-context';
12 import {StaffCatalogService} from './catalog.service';
13 import {AnonCacheService} from '@eg/share/util/anon-cache.service';
14 import {NgbModal, NgbModalOptions} from '@ng-bootstrap/ng-bootstrap';
15
16 const SAVED_TEMPLATES_SETTING = 'eg.catalog.search_templates';
17 const RECENT_SEARCHES_KEY = 'eg.catalog.recent_searches';
18
19 class SearchTemplate {
20     name: string;
21     params: any = {}; // routerLink-compatible URL params object
22     addTime?: number;
23     constructor(name: string, params: any) {
24         this.name = name;
25         this.params = params;
26     }
27 }
28
29 @Component({
30   selector: 'eg-catalog-search-templates',
31   templateUrl: 'search-templates.component.html'
32 })
33 export class SearchTemplatesComponent extends DialogComponent implements OnInit {
34
35     recentSearchesCount = 0;
36     context: CatalogSearchContext;
37     templates: SearchTemplate[] = [];
38     searches: SearchTemplate[] = [];
39     searchesCacheKey: string;
40     templateName: string;
41
42     @Input() searchTab: string;
43
44     @ViewChild('confirmDelete', { static: true }) confirmDelete: ConfirmDialogComponent;
45     @ViewChild('confirmDeleteAll', { static: true }) confirmDeleteAll: ConfirmDialogComponent;
46     @ViewChild('confirmDeleteSearches', { static: true }) confirmDeleteSearches: ConfirmDialogComponent;
47
48     constructor(
49         private org: OrgService,
50         private store: StoreService,             // anon cache key
51         private serverStore: ServerStoreService, // search templates
52         private cache: AnonCacheService,         // recent searches
53         private strings: StringService,
54         private cat: CatalogService,
55         private catUrl: CatalogUrlService,
56         private staffCat: StaffCatalogService,
57         private modal: NgbModal) {
58         super(modal);
59
60         this.store.addLoginSessionKey(RECENT_SEARCHES_KEY);
61     }
62
63     ngOnInit() {
64         this.context = this.staffCat.searchContext;
65
66         this.serverStore.getItem('opac.staff_saved_search.size')
67         .then(size => {
68             if (!size) { return; }
69
70             this.recentSearchesCount = Number(size);
71
72             this.getSearches().then(_ => {
73                 this.searches.forEach(
74                     s => s.params.ridx = ++this.staffCat.routeIndex);
75
76                 // Save the search that runs on page load.
77                 this.saveSearch(this.context);
78                 // Watch for new searches
79                 this.cat.onSearchComplete.subscribe(ctx => this.saveSearch(ctx));
80             });
81         });
82
83         this.getTemplates();
84     }
85
86     selectedTemplate(): string {
87         return this.staffCat.selectedTemplate;
88     }
89
90     getSearches(): Promise<any> {
91         this.searches = [];
92
93         if (this.searchesCacheKey) {
94             // We've already started saving searches in the current instance.
95
96             return this.cache.getItem(this.searchesCacheKey, 'searches')
97                 .then(searches => this.searches = searches || []);
98         }
99
100         const cacheKey = this.store.getLoginSessionItem(RECENT_SEARCHES_KEY);
101
102         if (cacheKey) {
103             // We have a saved search key, see if we have any searches.
104
105             this.searchesCacheKey = cacheKey;
106             return this.cache.getItem(this.searchesCacheKey, 'searches')
107                 .then(searches => this.searches = searches || []);
108
109         } else {
110             // No saved searches in progress.  Start from scratch.
111
112             return this.cache.setItem(null, 'searches', []) // generates cache key
113             .then(cKey => {
114                 this.searchesCacheKey = cKey;
115                 this.store.setLoginSessionItem(RECENT_SEARCHES_KEY, cKey);
116             });
117         }
118     }
119
120     searchSelected(search: SearchTemplate) {
121         // increment the router index in case the template is used
122         // twice in a row.
123         search.params.ridx = ++this.staffCat.routeIndex;
124     }
125
126     // Returns searches most recent first
127     sortSearches(): SearchTemplate[] {
128         return this.searches.sort((a, b) => a.addTime > b.addTime ? -1 : 1);
129     }
130
131     deleteSearches() {
132         this.confirmDeleteSearches.open().subscribe(yes => {
133             if (!yes) { return; }
134             this.searches = [];
135             this.cache.setItem(this.searchesCacheKey, 'searches', []);
136         });
137     }
138
139     getSearchPath(search: SearchTemplate): string {
140         return search.params.searchTab === 'browse' ?
141             '/staff/catalog/browse' : '/staff/catalog/search';
142     }
143
144     saveSearch(context: CatalogSearchContext) {
145
146         let matchFound = false;
147         this.searches.forEach(sch => {
148             const tmpCtx = this.catUrl.fromUrlHash(sch.params);
149             if (tmpCtx.equals(context)) {
150                 matchFound = true;
151             }
152         });
153
154         if (matchFound) { return; }
155
156         let query: string;
157         switch (this.searchTab) {
158             case 'term':
159                 query = context.termSearch.query[0];
160                 break;
161             case 'marc':
162                 query = context.marcSearch.values[0];
163                 break;
164             case 'ident':
165                 query = context.identSearch.value;
166                 break;
167             case 'browse':
168                 query = context.browseSearch.value;
169                 break;
170             case 'cnbrowse':
171                 query = context.cnBrowseSearch.value;
172                 break;
173         }
174
175         if (!query) {
176             // no query means nothing was searchable.
177             return;
178         }
179
180         this.strings.interpolate(
181             'eg.catalog.recent_search.label',
182             {query: query, tab: this.searchTab}
183
184         ).then(txt => {
185
186             const urlParams = this.prepareSearch(context);
187             const search = new SearchTemplate(txt, urlParams);
188             search.addTime = new Date().getTime();
189
190             this.searches.unshift(search);
191
192             if (this.searches.length > this.recentSearchesCount) {
193                 // this bit of magic will lop off the end of the array.
194                 this.searches.length = this.recentSearchesCount;
195             }
196
197             this.cache.setItem(
198                 this.searchesCacheKey, 'searches', this.searches)
199             .then(_ => search.params.ridx = ++this.staffCat.routeIndex);
200         });
201     }
202
203     getTemplates(): Promise<any> {
204         this.templates = [];
205
206         return this.serverStore.getItem(SAVED_TEMPLATES_SETTING).then(
207             templates => {
208                 if (templates && templates.length) {
209                     this.templates = templates;
210
211                     // route index required to force the route to take
212                     // effect.  See ./catalog.service.ts
213                     this.templates.forEach(tmpl =>
214                         tmpl.params.ridx = ++this.staffCat.routeIndex);
215                 }
216             }
217         );
218     }
219
220     sortTemplates(): SearchTemplate[] {
221         return this.templates.sort((a, b) =>
222             a.name.toLowerCase() < b.name.toLowerCase() ? -1 : 1);
223     }
224
225     templateSelected(tmpl: SearchTemplate) {
226         this.staffCat.selectedTemplate = tmpl.name;
227         // increment the router index in case the template is used
228         // twice in a row.
229         tmpl.params.ridx = ++this.staffCat.routeIndex;
230         console.log('selected template = ', this.staffCat.selectedTemplate);
231     }
232
233     // Adds dummy query content to the context object so the
234     // CatalogUrlService will recognize the content as searchable
235     // and therefor URL-encodable.
236     addDummyQueries(context: CatalogSearchContext) {
237         context.termSearch.query = context.termSearch.query.map(q => 'x');
238         context.marcSearch.values = context.marcSearch.values.map(q => 'x');
239         context.browseSearch.value = 'x';
240         context.identSearch.value = 'x';
241     }
242
243     // Remove the dummy query content before saving the search template.
244     removeDummyQueries(urlParams: any) {
245
246         if (Array.isArray(urlParams.query)) {
247             const arr = urlParams.query as Array<string>;
248             urlParams.query = arr.map(q => '');
249         } else {
250             urlParams.query = '';
251         }
252
253         if (Array.isArray(urlParams.marcValue)) {
254             const arr = urlParams.marcValue as Array<string>;
255             urlParams.marcValue = arr.map(q => '');
256         } else {
257             urlParams.marcValue = '';
258         }
259
260         urlParams.identQuery = '';
261         urlParams.browseTerm = '';
262     }
263
264     // Prepares a save-able URL params hash from the current context.
265     prepareSearch(ctx: CatalogSearchContext,
266         withDummyData?: boolean): {[key: string]: string | string[]} {
267
268         const context = ctx.clone();
269
270         if (withDummyData) {
271             this.addDummyQueries(context);
272         }
273
274         context.scrub(this.searchTab);
275
276         const urlParams = this.catUrl.toUrlParams(context);
277
278         if (withDummyData) {
279             this.removeDummyQueries(urlParams);
280         }
281
282         // Some data should not go into the template.
283         delete urlParams.org;
284         delete urlParams.ridx;
285
286         urlParams.searchTab = this.searchTab;
287
288         return urlParams;
289     }
290
291     saveTemplate(): Promise<any> {
292         if (!this.templateName) { return Promise.resolve(); }
293
294         this.staffCat.selectedTemplate = this.templateName;
295
296         const urlParams = this.prepareSearch(this.context, true);
297
298         this.templates.push(
299             new SearchTemplate(this.templateName, urlParams));
300
301         return this.applyTemplateChanges().then(_ => this.close());
302     }
303
304     applyTemplateChanges(): Promise<any> {
305         return this.serverStore.setItem(SAVED_TEMPLATES_SETTING, this.templates);
306     }
307
308     deleteTemplate() {
309         this.confirmDelete.open().subscribe(yes => {
310             if (!yes) { return; }
311
312             const templates: SearchTemplate[] = [];
313             this.templates.forEach(tmpl => {
314                 if (tmpl.name !== this.staffCat.selectedTemplate) {
315                     templates.push(tmpl);
316                 }
317             });
318
319             this.templates = templates;
320             this.staffCat.selectedTemplate = '';
321             this.applyTemplateChanges();
322         });
323     }
324
325     deleteAllTemplates() {
326         this.confirmDeleteAll.open().subscribe(yes => {
327             if (!yes) { return; }
328             this.templates = [];
329             this.staffCat.selectedTemplate = '';
330             this.applyTemplateChanges();
331         });
332     }
333 }
334
335