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;
65 console.log('ngOnInit() with selected = ', this.staffCat.selectedTemplate);
67 this.org.settings('opac.staff_saved_search.size').then(sets => {
68 const size = sets['opac.staff_saved_search.size'] || 0;
69 if (!size) { return; }
71 this.recentSearchesCount = Number(size);
73 this.getSearches().then(_ => {
74 this.searches.forEach(
75 s => s.params.ridx = ++this.staffCat.routeIndex);
77 // Save the search that runs on page load.
78 this.saveSearch(this.context);
79 // Watch for new searches
80 this.cat.onSearchComplete.subscribe(ctx => this.saveSearch(ctx));
87 selectedTemplate(): string {
88 return this.staffCat.selectedTemplate;
91 getSearches(): Promise<any> {
94 if (this.searchesCacheKey) {
95 // We've already started saving searches in the current instance.
97 return this.cache.getItem(this.searchesCacheKey, 'searches')
98 .then(searches => this.searches = searches || []);
101 const cacheKey = this.store.getLoginSessionItem(RECENT_SEARCHES_KEY);
104 // We have a saved search key, see if we have any searches.
106 this.searchesCacheKey = cacheKey;
107 return this.cache.getItem(this.searchesCacheKey, 'searches')
108 .then(searches => this.searches = searches || []);
111 // No saved searches in progress. Start from scratch.
113 return this.cache.setItem(null, 'searches', []) // generates cache key
115 this.searchesCacheKey = cKey;
116 this.store.setLoginSessionItem(RECENT_SEARCHES_KEY, cKey);
121 searchSelected(search: SearchTemplate) {
122 // increment the router index in case the template is used
124 search.params.ridx = ++this.staffCat.routeIndex;
127 // Returns searches most recent first
128 sortSearches(): SearchTemplate[] {
129 return this.searches.sort((a, b) => a.addTime > b.addTime ? -1 : 1);
133 this.confirmDeleteSearches.open().subscribe(yes => {
134 if (!yes) { return; }
136 this.cache.setItem(this.searchesCacheKey, 'searches', []);
140 getSearchPath(search: SearchTemplate): string {
141 return search.params.searchTab === 'browse' ?
142 '/staff/catalog/browse' : '/staff/catalog/search';
145 saveSearch(context: CatalogSearchContext) {
147 let matchFound = false;
148 this.searches.forEach(sch => {
149 const tmpCtx = this.catUrl.fromUrlHash(sch.params);
150 if (tmpCtx.equals(context)) {
155 if (matchFound) { return; }
158 switch (this.searchTab) {
160 query = context.termSearch.query[0];
163 query = context.marcSearch.values[0];
166 query = context.identSearch.value;
169 query = context.browseSearch.value;
172 query = context.cnBrowseSearch.value;
177 // no query means nothing was searchable.
181 this.strings.interpolate(
182 'eg.catalog.recent_search.label',
183 {query: query, tab: this.searchTab}
187 const urlParams = this.prepareSearch(context);
188 const search = new SearchTemplate(txt, urlParams);
189 search.addTime = new Date().getTime();
191 this.searches.unshift(search);
193 if (this.searches.length > this.recentSearchesCount) {
194 // this bit of magic will lop off the end of the array.
195 this.searches.length = this.recentSearchesCount;
199 this.searchesCacheKey, 'searches', this.searches)
200 .then(_ => search.params.ridx = ++this.staffCat.routeIndex);
204 getTemplates(): Promise<any> {
207 return this.serverStore.getItem(SAVED_TEMPLATES_SETTING).then(
209 if (templates && templates.length) {
210 this.templates = templates;
212 // route index required to force the route to take
213 // effect. See ./catalog.service.ts
214 this.templates.forEach(tmpl =>
215 tmpl.params.ridx = ++this.staffCat.routeIndex);
221 sortTemplates(): SearchTemplate[] {
222 return this.templates.sort((a, b) =>
223 a.name.toLowerCase() < b.name.toLowerCase() ? -1 : 1);
226 templateSelected(tmpl: SearchTemplate) {
227 this.staffCat.selectedTemplate = tmpl.name;
228 // increment the router index in case the template is used
230 tmpl.params.ridx = ++this.staffCat.routeIndex;
231 console.log('selected template = ', this.staffCat.selectedTemplate);
234 // Adds dummy query content to the context object so the
235 // CatalogUrlService will recognize the content as searchable
236 // and therefor URL-encodable.
237 addDummyQueries(context: CatalogSearchContext) {
238 context.termSearch.query = context.termSearch.query.map(q => 'x');
239 context.marcSearch.values = context.marcSearch.values.map(q => 'x');
240 context.browseSearch.value = 'x';
241 context.identSearch.value = 'x';
244 // Remove the dummy query content before saving the search template.
245 removeDummyQueries(urlParams: any) {
247 if (Array.isArray(urlParams.query)) {
248 const arr = urlParams.query as Array<string>;
249 urlParams.query = arr.map(q => '');
251 urlParams.query = '';
254 if (Array.isArray(urlParams.marcValue)) {
255 const arr = urlParams.marcValue as Array<string>;
256 urlParams.marcValue = arr.map(q => '');
258 urlParams.marcValue = '';
261 urlParams.identQuery = '';
262 urlParams.browseTerm = '';
265 // Prepares a save-able URL params hash from the current context.
266 prepareSearch(ctx: CatalogSearchContext,
267 withDummyData?: boolean): {[key: string]: string | string[]} {
269 const context = ctx.clone();
272 this.addDummyQueries(context);
275 context.scrub(this.searchTab);
277 const urlParams = this.catUrl.toUrlParams(context);
280 this.removeDummyQueries(urlParams);
283 // Some data should not go into the template.
284 delete urlParams.org;
285 delete urlParams.ridx;
287 urlParams.searchTab = this.searchTab;
292 saveTemplate(): Promise<any> {
293 if (!this.templateName) { return Promise.resolve(); }
295 this.staffCat.selectedTemplate = this.templateName;
297 const urlParams = this.prepareSearch(this.context, true);
300 new SearchTemplate(this.templateName, urlParams));
302 return this.applyTemplateChanges().then(_ => this.close());
305 applyTemplateChanges(): Promise<any> {
306 return this.serverStore.setItem(SAVED_TEMPLATES_SETTING, this.templates);
310 this.confirmDelete.open().subscribe(yes => {
311 if (!yes) { return; }
313 const templates: SearchTemplate[] = [];
314 this.templates.forEach(tmpl => {
315 if (tmpl.name !== this.staffCat.selectedTemplate) {
316 templates.push(tmpl);
320 this.templates = templates;
321 this.staffCat.selectedTemplate = '';
322 this.applyTemplateChanges();
326 deleteAllTemplates() {
327 this.confirmDeleteAll.open().subscribe(yes => {
328 if (!yes) { return; }
330 this.staffCat.selectedTemplate = '';
331 this.applyTemplateChanges();