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';
16 const SAVED_TEMPLATES_SETTING = 'eg.catalog.search_templates';
17 const RECENT_SEARCHES_KEY = 'eg.catalog.recent_searches';
19 class SearchTemplate {
21 params: any = {}; // routerLink-compatible URL params object
23 constructor(name: string, params: any) {
30 selector: 'eg-catalog-search-templates',
31 templateUrl: 'search-templates.component.html'
33 export class SearchTemplatesComponent extends DialogComponent implements OnInit {
35 recentSearchesCount = 0;
36 context: CatalogSearchContext;
37 templates: SearchTemplate[] = [];
38 searches: SearchTemplate[] = [];
39 searchesCacheKey: string;
42 @Input() searchTab: string;
44 @ViewChild('confirmDelete', { static: true }) confirmDelete: ConfirmDialogComponent;
45 @ViewChild('confirmDeleteAll', { static: true }) confirmDeleteAll: ConfirmDialogComponent;
46 @ViewChild('confirmDeleteSearches', { static: true }) confirmDeleteSearches: ConfirmDialogComponent;
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) {
60 this.store.addLoginSessionKey(RECENT_SEARCHES_KEY);
64 this.context = this.staffCat.searchContext;
66 this.serverStore.getItem('opac.staff_saved_search.size')
68 if (!size) { return; }
70 this.recentSearchesCount = Number(size);
72 this.getSearches().then(_ => {
73 this.searches.forEach(
74 s => s.params.ridx = ++this.staffCat.routeIndex);
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));
86 selectedTemplate(): string {
87 return this.staffCat.selectedTemplate;
90 getSearches(): Promise<any> {
93 if (this.searchesCacheKey) {
94 // We've already started saving searches in the current instance.
96 return this.cache.getItem(this.searchesCacheKey, 'searches')
97 .then(searches => this.searches = searches || []);
100 const cacheKey = this.store.getLoginSessionItem(RECENT_SEARCHES_KEY);
103 // We have a saved search key, see if we have any searches.
105 this.searchesCacheKey = cacheKey;
106 return this.cache.getItem(this.searchesCacheKey, 'searches')
107 .then(searches => this.searches = searches || []);
110 // No saved searches in progress. Start from scratch.
112 return this.cache.setItem(null, 'searches', []) // generates cache key
114 this.searchesCacheKey = cKey;
115 this.store.setLoginSessionItem(RECENT_SEARCHES_KEY, cKey);
120 searchSelected(search: SearchTemplate) {
121 // increment the router index in case the template is used
123 search.params.ridx = ++this.staffCat.routeIndex;
126 // Returns searches most recent first
127 sortSearches(): SearchTemplate[] {
128 return this.searches.sort((a, b) => a.addTime > b.addTime ? -1 : 1);
132 this.confirmDeleteSearches.open().subscribe(yes => {
133 if (!yes) { return; }
135 this.cache.setItem(this.searchesCacheKey, 'searches', []);
139 getSearchPath(search: SearchTemplate): string {
140 return search.params.searchTab === 'browse' ?
141 '/staff/catalog/browse' : '/staff/catalog/search';
144 saveSearch(context: CatalogSearchContext) {
146 let matchFound = false;
147 this.searches.forEach(sch => {
148 const tmpCtx = this.catUrl.fromUrlHash(sch.params);
149 if (tmpCtx.equals(context)) {
154 if (matchFound) { return; }
157 switch (this.searchTab) {
159 query = context.termSearch.query[0];
162 query = context.marcSearch.values[0];
165 query = context.identSearch.value;
168 query = context.browseSearch.value;
171 query = context.cnBrowseSearch.value;
176 // no query means nothing was searchable.
180 this.strings.interpolate(
181 'eg.catalog.recent_search.label',
182 {query: query, tab: this.searchTab}
186 const urlParams = this.prepareSearch(context);
187 const search = new SearchTemplate(txt, urlParams);
188 search.addTime = new Date().getTime();
190 this.searches.unshift(search);
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;
198 this.searchesCacheKey, 'searches', this.searches)
199 .then(_ => search.params.ridx = ++this.staffCat.routeIndex);
203 getTemplates(): Promise<any> {
206 return this.serverStore.getItem(SAVED_TEMPLATES_SETTING).then(
208 if (templates && templates.length) {
209 this.templates = templates;
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);
220 sortTemplates(): SearchTemplate[] {
221 return this.templates.sort((a, b) =>
222 a.name.toLowerCase() < b.name.toLowerCase() ? -1 : 1);
225 templateSelected(tmpl: SearchTemplate) {
226 this.staffCat.selectedTemplate = tmpl.name;
227 // increment the router index in case the template is used
229 tmpl.params.ridx = ++this.staffCat.routeIndex;
230 console.log('selected template = ', this.staffCat.selectedTemplate);
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';
243 // Remove the dummy query content before saving the search template.
244 removeDummyQueries(urlParams: any) {
246 if (Array.isArray(urlParams.query)) {
247 const arr = urlParams.query as Array<string>;
248 urlParams.query = arr.map(q => '');
250 urlParams.query = '';
253 if (Array.isArray(urlParams.marcValue)) {
254 const arr = urlParams.marcValue as Array<string>;
255 urlParams.marcValue = arr.map(q => '');
257 urlParams.marcValue = '';
260 urlParams.identQuery = '';
261 urlParams.browseTerm = '';
264 // Prepares a save-able URL params hash from the current context.
265 prepareSearch(ctx: CatalogSearchContext,
266 withDummyData?: boolean): {[key: string]: string | string[]} {
268 const context = ctx.clone();
271 this.addDummyQueries(context);
274 context.scrub(this.searchTab);
276 const urlParams = this.catUrl.toUrlParams(context);
279 this.removeDummyQueries(urlParams);
282 // Some data should not go into the template.
283 delete urlParams.org;
284 delete urlParams.ridx;
286 urlParams.searchTab = this.searchTab;
291 saveTemplate(): Promise<any> {
292 if (!this.templateName) { return Promise.resolve(); }
294 this.staffCat.selectedTemplate = this.templateName;
296 const urlParams = this.prepareSearch(this.context, true);
299 new SearchTemplate(this.templateName, urlParams));
301 return this.applyTemplateChanges().then(_ => this.close());
304 applyTemplateChanges(): Promise<any> {
305 return this.serverStore.setItem(SAVED_TEMPLATES_SETTING, this.templates);
309 this.confirmDelete.open().subscribe(yes => {
310 if (!yes) { return; }
312 const templates: SearchTemplate[] = [];
313 this.templates.forEach(tmpl => {
314 if (tmpl.name !== this.staffCat.selectedTemplate) {
315 templates.push(tmpl);
319 this.templates = templates;
320 this.staffCat.selectedTemplate = '';
321 this.applyTemplateChanges();
325 deleteAllTemplates() {
326 this.confirmDeleteAll.open().subscribe(yes => {
327 if (!yes) { return; }
329 this.staffCat.selectedTemplate = '';
330 this.applyTemplateChanges();