From: Bill Erickson Date: Thu, 6 Dec 2018 23:12:46 +0000 (-0500) Subject: LP1806087 Catalog marc search; formatting X-Git-Url: https://old-git.evergreen-ils.org/?a=commitdiff_plain;h=2963b5c680cc488fa4466bee9777a830ef81b79c;p=working%2FEvergreen.git LP1806087 Catalog marc search; formatting Signed-off-by: Bill Erickson --- diff --git a/Open-ILS/src/eg2/src/app/share/catalog/catalog-url.service.ts b/Open-ILS/src/eg2/src/app/share/catalog/catalog-url.service.ts index 116ab4a275..831c5d3ecc 100644 --- a/Open-ILS/src/eg2/src/app/share/catalog/catalog-url.service.ts +++ b/Open-ILS/src/eg2/src/app/share/catalog/catalog-url.service.ts @@ -32,7 +32,10 @@ export class CatalogUrlService { offset: null, copyLocations: null, browsePivot: null, - hasBrowseEntry: null + hasBrowseEntry: null, + marcTag: [''], + marcSubfield: [''], + marcValue: [''] }; params.org = context.searchOrg.id(); @@ -55,13 +58,22 @@ export class CatalogUrlService { if (params.identQuery) { // Ident queries (e.g. tcn search) discards all remaining filters return params; + } else { + // Avoid propagating the type when it's not used. + delete params.identQueryType; } - context.query.forEach((q, idx) => { + context.query.filter(q => q !== '').forEach((q, idx) => { ['query', 'fieldClass', 'joinOp', 'matchOp'].forEach(field => { // Propagate all array-based fields regardless of // whether a value is applied to ensure correct - // correlation between values. + // correlation between values + params[field][idx] = context[field][idx]; + }); + }); + + context.marcValue.filter(v => v !== '').forEach((val, idx) => { + ['marcValue', 'marcTag', 'marcSubfield'].forEach(field => { params[field][idx] = context[field][idx]; }); }); @@ -131,6 +143,13 @@ export class CatalogUrlService { } }); + ['marcValue', 'marcTag', 'marcSubfield'].forEach(field => { + const arr = params.getAll(field); + if (arr && arr.length) { + context[field] = arr; + } + }); + CATALOG_CCVM_FILTERS.forEach(code => { const val = params.get(code); if (val) { diff --git a/Open-ILS/src/eg2/src/app/share/catalog/catalog.service.ts b/Open-ILS/src/eg2/src/app/share/catalog/catalog.service.ts index aaa6b59f0b..30d9a09dfd 100644 --- a/Open-ILS/src/eg2/src/app/share/catalog/catalog.service.ts +++ b/Open-ILS/src/eg2/src/app/share/catalog/catalog.service.ts @@ -61,6 +61,8 @@ export class CatalogService { if (ctx.basket) { return this.basketSearch(ctx); + } else if (ctx.marcValue[0] !== '') { + return this.marcSearch(ctx); } else { return this.querySearch(ctx); } @@ -85,6 +87,23 @@ export class CatalogService { }); } + marcSearch(ctx: CatalogSearchContext): Promise { + let method = 'open-ils.search.biblio.marc'; + if (ctx.isStaff) { method += '.staff'; } + + const queryStruct = ctx.compileMarcSearch(); + + return this.net.request('open-ils.search', method, queryStruct) + .toPromise().then(result => { + // Match the query search return format + result.ids = result.ids.map(id => [id]); + + this.applyResultData(ctx, result); + ctx.searchState = CatalogSearchState.COMPLETE; + this.onSearchComplete.emit(ctx); + }); + } + querySearch(ctx: CatalogSearchContext): Promise { const fullQuery = ctx.compileSearch(); diff --git a/Open-ILS/src/eg2/src/app/share/catalog/search-context.ts b/Open-ILS/src/eg2/src/app/share/catalog/search-context.ts index 5d676a10c8..9dd448fd3f 100644 --- a/Open-ILS/src/eg2/src/app/share/catalog/search-context.ts +++ b/Open-ILS/src/eg2/src/app/share/catalog/search-context.ts @@ -52,6 +52,9 @@ export class CatalogSearchContext { copyLocations: string[]; // ID's, but treated as strings in the UI. browsePivot: number; hasBrowseEntry: string; // "entryId,fieldId" + marcTag: string[]; + marcSubfield: string[]; + marcValue: string[]; // Result from most recent search. result: any = {}; @@ -124,28 +127,50 @@ export class CatalogSearchContext { this.searchState = CatalogSearchState.PENDING; this.basket = false; this.copyLocations = ['']; + this.marcTag = ['']; + this.marcSubfield = ['']; + this.marcValue = ['']; } // Returns true if we have enough information to perform a search. isSearchable(): boolean { + return this.searchType() !== null; + } + + // Returns the type of search that would be performed from this + // context object if a search were run now. + // Returns NULL if no search is possible. + searchType(): string { if (this.basket) { - return true; + return 'basket'; } if (this.identQuery && this.identQueryType) { - return true; + return 'ident'; } + if (this.marcTag[0] !== '' && this.marcValue[0] !== '') { + // MARC field search + return 'marc'; + } + + // searchOrg required for all following search scenarios if (this.searchOrg === null) { - return false; + return null; } if (this.hasBrowseEntry) { - return true; + // Limit results by records that link to browse entry + return 'browse'; } - return this.query.length && this.query[0] !== ''; + // Query search + if (this.query.length && this.query[0] !== '') { + return 'query'; + } + + return null; } // Returns true if we have enough information to perform a browse. @@ -157,6 +182,35 @@ export class CatalogSearchContext { && this.searchOrg !== null; } + compileMarcSearch(): any { + const searches: any = []; + + this.marcValue.filter(v => v !== '').forEach((val, idx) => { + searches.push({ + restrict: [{ + subfield: this.marcSubfield[idx], + tag: this.marcTag[idx] + }], + term: this.marcValue[idx] + }); + }); + + const args: any = { + searches: searches, + limit : this.pager.limit, + offset : this.pager.offset, + org_unit: this.searchOrg.id() + }; + + if (this.sort) { + const parts = this.sort.split(/\./); + args.sort = parts[0]; // title, author, etc. + if (parts[1]) { args.sort_dir = 'descending' }; + } + + return args; + } + compileSearch(): string { let str = ''; diff --git a/Open-ILS/src/eg2/src/app/staff/catalog/result/results.component.ts b/Open-ILS/src/eg2/src/app/staff/catalog/result/results.component.ts index f402ea4ff3..c98512b438 100644 --- a/Open-ILS/src/eg2/src/app/staff/catalog/result/results.component.ts +++ b/Open-ILS/src/eg2/src/app/staff/catalog/result/results.component.ts @@ -90,6 +90,7 @@ export class ResultsComponent implements OnInit, OnDestroy { this.allRecsSelected = allChecked; } + // Pull values from the URL and run the requested search. searchByUrl(params: ParamMap): void { this.catUrl.applyUrlParams(this.searchContext, params); diff --git a/Open-ILS/src/eg2/src/app/staff/catalog/search-form.component.html b/Open-ILS/src/eg2/src/app/staff/catalog/search-form.component.html index cdcae0c98f..b0db7a3dc4 100644 --- a/Open-ILS/src/eg2/src/app/staff/catalog/search-form.component.html +++ b/Open-ILS/src/eg2/src/app/staff/catalog/search-form.component.html @@ -1,7 +1,7 @@ -
+
@@ -87,12 +87,12 @@ TODO focus search input *ngIf="!showAdvanced()" [disabled]="searchIsActive()" (click)="toggleAdvancedSearch()" i18n> - More Filters + More Options
-
-
- -
-
- -
-
- -
-
- -
-
- -
-
- -
-
-
-
- -
-
- -
-
- -
-
- - - -
+ +
+ + + +
+
+ +
+
+ +
+
+ +
+
+ +
+
+
+
+ +
+
+ +
+
+ +
+
+ + + +
+
+
+
+ + +
+
+
+ + + + + +
+
+
+
+
+ + +
+
+
+ + + + + + + + + +
+
+
+
+
+
diff --git a/Open-ILS/src/eg2/src/app/staff/catalog/search-form.component.ts b/Open-ILS/src/eg2/src/app/staff/catalog/search-form.component.ts index 417bb3e273..3d8a1ad66d 100644 --- a/Open-ILS/src/eg2/src/app/staff/catalog/search-form.component.ts +++ b/Open-ILS/src/eg2/src/app/staff/catalog/search-form.component.ts @@ -5,6 +5,7 @@ import {OrgService} from '@eg/core/org.service'; import {CatalogService} from '@eg/share/catalog/catalog.service'; import {CatalogSearchContext, CatalogSearchState} from '@eg/share/catalog/search-context'; import {StaffCatalogService} from './catalog.service'; +import {NgbTabset, NgbTabChangeEvent} from '@ng-bootstrap/ng-bootstrap'; @Component({ selector: 'eg-catalog-search-form', @@ -18,6 +19,8 @@ export class SearchFormComponent implements OnInit, AfterViewInit { cmfMap: {[cmf: string]: IdlObject} = {}; showAdvancedSearch = false; copyLocations: IdlObject[]; + searchTab: string; + //@ViewChild('searchTabs') searchTabs: NgbTabset; constructor( private renderer: Renderer2, @@ -27,6 +30,7 @@ export class SearchFormComponent implements OnInit, AfterViewInit { private staffCat: StaffCatalogService ) { this.copyLocations = []; + this.searchTab = 'filters'; } ngOnInit() { @@ -44,17 +48,41 @@ export class SearchFormComponent implements OnInit, AfterViewInit { // so they are not available until after the first render. // Search context data is extracted synchronously from the URL. - if (this.searchContext.identQuery) { - // Focus identifier query input if identQuery is in progress - this.renderer.selectRootElement('#ident-query-input').focus(); - } else { - // Otherwise focus the main query input - this.renderer.selectRootElement('#first-query-input').focus(); - } + // Avoid changing the tab in the lifecycle hook thread. + setTimeout(() => { + const st = this.searchContext.searchType(); + if (st === 'marc' || st === 'ident') { + this.searchTab = st; + } else { + this.searchTab = 'filters'; + } + }); this.refreshCopyLocations(); } + onTabChange(evt: NgbTabChangeEvent) { + this.searchTab = evt.nextId; + + // Select a DOM node to focus when the tab changes. + let selector; + switch (this.searchTab) { + case 'ident': + selector = '#ident-query-input'; + break; + case 'marc': + selector = '#first-marc-tag'; + break; + default: + selector = '#first-query-input'; + } + + // Call focus after tab-change event has a chance to complete + // or the tab body and its input won't exist yet. + setTimeout(() => + this.renderer.selectRootElement(selector).focus()); + } + /** * Display the advanced/extended search options when asked to * or if any advanced options are selected. @@ -72,6 +100,7 @@ export class SearchFormComponent implements OnInit, AfterViewInit { if (this.searchContext.copyLocations[0] !== '') { return true; } if (this.searchContext.identQuery) { return true; } + if (this.searchContext.marcValue[0] !== '') { return true; } // ccvm filters may be present without any filters applied. // e.g. if filters were applied then removed. @@ -123,6 +152,18 @@ export class SearchFormComponent implements OnInit, AfterViewInit { this.searchContext.matchOp.splice(index, 1); } + addMarcSearchRow(index: number): void { + this.searchContext.marcTag.splice(index, 0, ''); + this.searchContext.marcSubfield.splice(index, 0, ''); + this.searchContext.marcValue.splice(index, 0, ''); + } + + delMarcSearchRow(index: number): void { + this.searchContext.marcTag.splice(index, 1); + this.searchContext.marcSubfield.splice(index, 1); + this.searchContext.marcValue.splice(index, 1); + } + formEnter(source) { this.searchContext.pager.offset = 0; @@ -134,7 +175,9 @@ export class SearchFormComponent implements OnInit, AfterViewInit { case 'query': // main search form query input // Be sure a previous ident search does not take precedence - // over the newly entered/submitted search query + // over the new term query submitted via Enter within + // the search term/query box. + this.searchContext.marcValue[0] = ''; this.searchContext.identQuery = null; break; @@ -148,6 +191,11 @@ export class SearchFormComponent implements OnInit, AfterViewInit { this.searchContext.identQueryType = qt; } break; + + case 'marc': + this.searchContext.identQuery = null; + this.searchContext.query[0] = ''; // prevent term queries + break; } this.searchByForm();