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) {
62 this.context = this.staffCat.searchContext;
64 this.serverStore.getItem('opac.staff_saved_search.size')
66 if (!size) { return; }
68 this.recentSearchesCount = Number(size);
70 this.getSearches().then(_ => {
71 this.searches.forEach(
72 s => s.params.ridx = ++this.staffCat.routeIndex);
74 // Save the search that runs on page load.
75 this.saveSearch(this.context);
76 // Watch for new searches
77 this.cat.onSearchComplete.subscribe(ctx => this.saveSearch(ctx));
84 selectedTemplate(): string {
85 return this.staffCat.selectedTemplate;
88 getSearches(): Promise<any> {
91 if (this.searchesCacheKey) {
92 // We've already started saving searches in the current instance.
94 return this.cache.getItem(this.searchesCacheKey, 'searches')
95 .then(searches => this.searches = searches || []);
98 const cacheKey = this.store.getLoginSessionItem(RECENT_SEARCHES_KEY);
101 // We have a saved search key, see if we have any searches.
103 this.searchesCacheKey = cacheKey;
104 return this.cache.getItem(this.searchesCacheKey, 'searches')
105 .then(searches => this.searches = searches || []);
108 // No saved searches in progress. Start from scratch.
110 return this.cache.setItem(null, 'searches', []) // generates cache key
112 this.searchesCacheKey = cKey;
113 this.store.setLoginSessionItem(RECENT_SEARCHES_KEY, cKey);
118 searchSelected(search: SearchTemplate) {
119 // increment the router index in case the template is used
121 search.params.ridx = ++this.staffCat.routeIndex;
124 // Returns searches most recent first
125 sortSearches(): SearchTemplate[] {
126 return this.searches.sort((a, b) => a.addTime > b.addTime ? -1 : 1);
130 this.confirmDeleteSearches.open().subscribe(yes => {
131 if (!yes) { return; }
133 this.cache.setItem(this.searchesCacheKey, 'searches', []);
137 getSearchPath(search: SearchTemplate): string {
138 return search.params.searchTab === 'browse' ?
139 '/staff/catalog/browse' : '/staff/catalog/search';
142 saveSearch(context: CatalogSearchContext) {
144 let matchFound = false;
145 this.searches.forEach(sch => {
146 const tmpCtx = this.catUrl.fromUrlHash(sch.params);
147 if (tmpCtx.equals(context)) {
152 if (matchFound) { return; }
155 switch (this.searchTab) {
157 query = context.termSearch.query[0];
160 query = context.marcSearch.values[0];
163 query = context.identSearch.value;
166 query = context.browseSearch.value;
169 query = context.cnBrowseSearch.value;
174 // no query means nothing was searchable.
178 this.strings.interpolate(
179 'eg.catalog.recent_search.label',
180 {query: query, tab: this.searchTab}
184 const urlParams = this.prepareSearch(context);
185 const search = new SearchTemplate(txt, urlParams);
186 search.addTime = new Date().getTime();
188 this.searches.unshift(search);
190 if (this.searches.length > this.recentSearchesCount) {
191 // this bit of magic will lop off the end of the array.
192 this.searches.length = this.recentSearchesCount;
196 this.searchesCacheKey, 'searches', this.searches)
197 .then(_ => search.params.ridx = ++this.staffCat.routeIndex);
201 getTemplates(): Promise<any> {
204 return this.serverStore.getItem(SAVED_TEMPLATES_SETTING).then(
206 if (templates && templates.length) {
207 this.templates = templates;
209 // route index required to force the route to take
210 // effect. See ./catalog.service.ts
211 this.templates.forEach(tmpl =>
212 tmpl.params.ridx = ++this.staffCat.routeIndex);
218 sortTemplates(): SearchTemplate[] {
219 return this.templates.sort((a, b) =>
220 a.name.toLowerCase() < b.name.toLowerCase() ? -1 : 1);
223 templateSelected(tmpl: SearchTemplate) {
224 this.staffCat.selectedTemplate = tmpl.name;
225 // increment the router index in case the template is used
227 tmpl.params.ridx = ++this.staffCat.routeIndex;
228 console.log('selected template = ', this.staffCat.selectedTemplate);
231 // Adds dummy query content to the context object so the
232 // CatalogUrlService will recognize the content as searchable
233 // and therefor URL-encodable.
234 addDummyQueries(context: CatalogSearchContext) {
235 context.termSearch.query = context.termSearch.query.map(q => 'x');
236 context.marcSearch.values = context.marcSearch.values.map(q => 'x');
237 context.browseSearch.value = 'x';
238 context.identSearch.value = 'x';
241 // Remove the dummy query content before saving the search template.
242 removeDummyQueries(urlParams: any) {
244 if (Array.isArray(urlParams.query)) {
245 const arr = urlParams.query as Array<string>;
246 urlParams.query = arr.map(q => '');
248 urlParams.query = '';
251 if (Array.isArray(urlParams.marcValue)) {
252 const arr = urlParams.marcValue as Array<string>;
253 urlParams.marcValue = arr.map(q => '');
255 urlParams.marcValue = '';
258 urlParams.identQuery = '';
259 urlParams.browseTerm = '';
262 // Prepares a save-able URL params hash from the current context.
263 prepareSearch(ctx: CatalogSearchContext,
264 withDummyData?: boolean): {[key: string]: string | string[]} {
266 const context = ctx.clone();
269 this.addDummyQueries(context);
272 context.scrub(this.searchTab);
274 const urlParams = this.catUrl.toUrlParams(context);
277 this.removeDummyQueries(urlParams);
280 // Some data should not go into the template.
281 delete urlParams.org;
282 delete urlParams.ridx;
284 urlParams.searchTab = this.searchTab;
289 saveTemplate(): Promise<any> {
290 if (!this.templateName) { return Promise.resolve(); }
292 this.staffCat.selectedTemplate = this.templateName;
294 const urlParams = this.prepareSearch(this.context, true);
297 new SearchTemplate(this.templateName, urlParams));
299 return this.applyTemplateChanges().then(_ => this.close());
302 applyTemplateChanges(): Promise<any> {
303 return this.serverStore.setItem(SAVED_TEMPLATES_SETTING, this.templates);
307 this.confirmDelete.open().subscribe(yes => {
308 if (!yes) { return; }
310 const templates: SearchTemplate[] = [];
311 this.templates.forEach(tmpl => {
312 if (tmpl.name !== this.staffCat.selectedTemplate) {
313 templates.push(tmpl);
317 this.templates = templates;
318 this.staffCat.selectedTemplate = '';
319 this.applyTemplateChanges();
323 deleteAllTemplates() {
324 this.confirmDeleteAll.open().subscribe(yes => {
325 if (!yes) { return; }
327 this.staffCat.selectedTemplate = '';
328 this.applyTemplateChanges();