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 = {};
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.
&& 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 = '';
<!--
TODO focus search input
-->
-<div id='staffcat-search-form' class='pb-2 mb-3'>
+<div id='staffcat-search-form' class='pb-2 mb-4'>
<div class="row"
*ngFor="let q of searchContext.query; let idx = index; trackBy:trackByIdx">
<div class="col-lg-8 d-flex">
*ngIf="!showAdvanced()"
[disabled]="searchIsActive()"
(click)="toggleAdvancedSearch()" i18n>
- More Filters
+ More Options
</button>
<button class="btn btn-outline-secondary" type="button"
*ngIf="showAdvanced()"
(click)="toggleAdvancedSearch()" i18n>
- Hide Filters
+ Hide Options
</button>
<button class="btn btn-info ml-1" type="button"
(click)="goToBrowse()" i18n>
<eg-catalog-basket-actions></eg-catalog-basket-actions>
</div>
</div>
- <div class="row pt-2" *ngIf="showAdvanced()">
- <div class="col-lg-2">
- <select class="form-control" multiple="true"
- [(ngModel)]="searchContext.ccvmFilters.item_type">
- <option value='' i18n>All Item Types</option>
- <option *ngFor="let itemType of ccvmMap.item_type"
- value="{{itemType.code()}}">{{itemType.value()}}</option>
- </select>
- </div>
- <div class="col-lg-2">
- <select class="form-control" multiple="true"
- [(ngModel)]="searchContext.ccvmFilters.item_form">
- <option value='' i18n>All Item Forms</option>
- <option *ngFor="let itemForm of ccvmMap.item_form"
- value="{{itemForm.code()}}">{{itemForm.value()}}</option>
- </select>
- </div>
- <div class="col-lg-2">
- <select class="form-control"
- [(ngModel)]="searchContext.ccvmFilters.item_lang" multiple="true">
- <option value='' i18n>All Languages</option>
- <option *ngFor="let lang of ccvmMap.item_lang"
- value="{{lang.code()}}">{{lang.value()}}</option>
- </select>
- </div>
- <div class="col-lg-2">
- <select class="form-control"
- [(ngModel)]="searchContext.ccvmFilters.audience" multiple="true">
- <option value='' i18n>All Audiences</option>
- <option *ngFor="let audience of ccvmMap.audience"
- value="{{audience.code()}}">{{audience.value()}}</option>
- </select>
- </div>
- <div class="col-lg-2">
- <select class="form-control"
- [(ngModel)]="searchContext.identQueryType">
- <option i18n value="identifier|isbn">ISBN</option>
- <option i18n value="identifier|issn">ISSN</option>
- <option i18n disabled value="cnbrowse">Call Number (Shelf Browse)</option>
- <option i18n value="identifier|lccn">LCCN</option>
- <option i18n value="identifier|tcn">TCN</option>
- <option i18n disabled value="item_barcode">Item Barcode</option>
- </select>
- </div>
- <div class="col-lg-2">
- <input id='ident-query-input' type="text" class="form-control"
- [(ngModel)]="searchContext.identQuery"
- (keyup.enter)="formEnter('ident')"
- placeholder="Numeric Query..."/>
- </div>
- </div>
- <div class="row pt-2" *ngIf="showAdvanced()">
- <div class="col-lg-2">
- <select class="form-control"
- [(ngModel)]="searchContext.ccvmFilters.vr_format" multiple="true">
- <option value='' i18n>All Video Formats</option>
- <option *ngFor="let vrFormat of ccvmMap.vr_format"
- value="{{vrFormat.code()}}">{{vrFormat.value()}}</option>
- </select>
- </div>
- <div class="col-lg-2">
- <select class="form-control"
- [(ngModel)]="searchContext.ccvmFilters.bib_level" multiple="true">
- <option value='' i18n>All Bib Levels</option>
- <option *ngFor="let bibLevel of ccvmMap.bib_level"
- value="{{bibLevel.code()}}">{{bibLevel.value()}}</option>
- </select>
- </div>
- <div class="col-lg-2">
- <select class="form-control"
- [(ngModel)]="searchContext.ccvmFilters.lit_form" multiple="true">
- <option value='' i18n>All Literary Forms</option>
- <option *ngFor="let litForm of ccvmMap.lit_form"
- value="{{litForm.code()}}">{{litForm.value()}}</option>
- </select>
- </div>
- <div class="col-lg-2">
- <ng-container *ngIf="copyLocations.length > 0">
- <select class="form-control"
- [(ngModel)]="searchContext.copyLocations" multiple="true">
- <option value='' i18n>All Copy Locations</option>
- <option *ngFor="let loc of copyLocations" value="{{loc.id()}}" i18n>
- {{loc.name()}} ({{orgName(loc.owning_lib())}})
- </option>
- </select>
- </ng-container>
- </div>
+
+ <div class="p-2 m-2" *ngIf="showAdvanced()">
+ <ngb-tabset #searchTabs [activeId]="searchTab" (tabChange)="onTabChange($event)">
+ <ngb-tab title="Search Filters" i18n-title id="filters">
+ <ng-template ngbTabContent>
+ <div class="row mt-3">
+ <div class="col-lg-2">
+ <select class="form-control" multiple="true"
+ [(ngModel)]="searchContext.ccvmFilters.item_type">
+ <option value='' i18n>All Item Types</option>
+ <option *ngFor="let itemType of ccvmMap.item_type"
+ value="{{itemType.code()}}">{{itemType.value()}}</option>
+ </select>
+ </div>
+ <div class="col-lg-2">
+ <select class="form-control" multiple="true"
+ [(ngModel)]="searchContext.ccvmFilters.item_form">
+ <option value='' i18n>All Item Forms</option>
+ <option *ngFor="let itemForm of ccvmMap.item_form"
+ value="{{itemForm.code()}}">{{itemForm.value()}}</option>
+ </select>
+ </div>
+ <div class="col-lg-2">
+ <select class="form-control"
+ [(ngModel)]="searchContext.ccvmFilters.item_lang" multiple="true">
+ <option value='' i18n>All Languages</option>
+ <option *ngFor="let lang of ccvmMap.item_lang"
+ value="{{lang.code()}}">{{lang.value()}}</option>
+ </select>
+ </div>
+ <div class="col-lg-2">
+ <select class="form-control"
+ [(ngModel)]="searchContext.ccvmFilters.audience" multiple="true">
+ <option value='' i18n>All Audiences</option>
+ <option *ngFor="let audience of ccvmMap.audience"
+ value="{{audience.code()}}">{{audience.value()}}</option>
+ </select>
+ </div>
+ </div>
+ <div class="row pt-2">
+ <div class="col-lg-2">
+ <select class="form-control"
+ [(ngModel)]="searchContext.ccvmFilters.vr_format" multiple="true">
+ <option value='' i18n>All Video Formats</option>
+ <option *ngFor="let vrFormat of ccvmMap.vr_format"
+ value="{{vrFormat.code()}}">{{vrFormat.value()}}</option>
+ </select>
+ </div>
+ <div class="col-lg-2">
+ <select class="form-control"
+ [(ngModel)]="searchContext.ccvmFilters.bib_level" multiple="true">
+ <option value='' i18n>All Bib Levels</option>
+ <option *ngFor="let bibLevel of ccvmMap.bib_level"
+ value="{{bibLevel.code()}}">{{bibLevel.value()}}</option>
+ </select>
+ </div>
+ <div class="col-lg-2">
+ <select class="form-control"
+ [(ngModel)]="searchContext.ccvmFilters.lit_form" multiple="true">
+ <option value='' i18n>All Literary Forms</option>
+ <option *ngFor="let litForm of ccvmMap.lit_form"
+ value="{{litForm.code()}}">{{litForm.value()}}</option>
+ </select>
+ </div>
+ <div class="col-lg-2">
+ <ng-container *ngIf="copyLocations.length > 0">
+ <select class="form-control"
+ [(ngModel)]="searchContext.copyLocations" multiple="true">
+ <option value='' i18n>All Copy Locations</option>
+ <option *ngFor="let loc of copyLocations" value="{{loc.id()}}" i18n>
+ {{loc.name()}} ({{orgName(loc.owning_lib())}})
+ </option>
+ </select>
+ </ng-container>
+ </div>
+ </div>
+ </ng-template>
+ </ngb-tab>
+ <ngb-tab title="Numeric Search" i18n-title id="ident">
+ <ng-template ngbTabContent>
+ <div class="row mt-3">
+ <div class="col-lg-12">
+ <div class="form-inline">
+ <label for="ident-type" i18n>Query Type</label>
+ <select class="form-control ml-2" name="ident-type"
+ [(ngModel)]="searchContext.identQueryType">
+ <option i18n value="identifier|isbn">ISBN</option>
+ <option i18n value="identifier|issn">ISSN</option>
+ <option i18n disabled value="cnbrowse">Call Number (Shelf Browse)</option>
+ <option i18n value="identifier|lccn">LCCN</option>
+ <option i18n value="identifier|tcn">TCN</option>
+ <option i18n disabled value="item_barcode">Item Barcode</option>
+ </select>
+ <label for="ident-value" class="ml-2" i18n>Value</label>
+ <input name="ident-value" id='ident-query-input'
+ type="text" class="form-control ml-2"
+ [(ngModel)]="searchContext.identQuery"
+ (keyup.enter)="formEnter('ident')"
+ placeholder="Numeric Query..."/>
+ <button class="btn btn-success ml-2" type="button"
+ [disabled]="searchIsActive()"
+ (click)="formEnter('ident')" i18n>Search</button>
+ </div>
+ </div>
+ </div>
+ </ng-template>
+ </ngb-tab>
+ <ngb-tab title="MARC Search" i18n-title id="marc">
+ <ng-template ngbTabContent>
+ <div class="row mt-3">
+ <div class="col-lg-12">
+ <div class="form-inline mt-2"
+ *ngFor="let q of searchContext.marcValue; let idx = index; trackBy:trackByIdx">
+ <label for="marc-tag-{{idx}}" i18n>Tag</label>
+ <input class="form-control ml-2" size="3" type="text" name="marc-tag-{{idx}}"
+ id="{{ idx == 0 ? 'first-marc-tag' : '' }}"
+ [(ngModel)]="searchContext.marcTag[idx]"/>
+ <label for="marc-subfield-{{idx}}" class="ml-2" i18n>Subfield</label>
+ <input class="form-control ml-2" size="1" type="text" name="marc-subfield-{{idx}}"
+ [(ngModel)]="searchContext.marcSubfield[idx]"/>
+ <label for="marc-value-{{idx}}" class="ml-2" i18n>Value</label>
+ <input class="form-control ml-2" type="text" name="marc-value-{{idx}}"
+ [(ngModel)]="searchContext.marcValue[idx]"/>
+ <button class="btn btn-sm material-icon-button ml-2"
+ (click)="addMarcSearchRow(idx + 1)">
+ <span class="material-icons">add_circle_outline</span>
+ </button>
+ <button class="btn btn-sm material-icon-button ml-2"
+ [disabled]="searchContext.marcValue.length < 2"
+ (click)="delMarcSearchRow(idx)">
+ <span class="material-icons">remove_circle_outline</span>
+ </button>
+ <button *ngIf="idx == 0" class="btn btn-success ml-2"
+ (click)="formEnter('marc')" i18n>Submit</button>
+ </div>
+ </div>
+ </div>
+ </ng-template>
+ </ngb-tab>
+ </ngb-tabset>
</div>
</div>
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',
cmfMap: {[cmf: string]: IdlObject} = {};
showAdvancedSearch = false;
copyLocations: IdlObject[];
+ searchTab: string;
+ //@ViewChild('searchTabs') searchTabs: NgbTabset;
constructor(
private renderer: Renderer2,
private staffCat: StaffCatalogService
) {
this.copyLocations = [];
+ this.searchTab = 'filters';
}
ngOnInit() {
// 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.
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.
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;
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;
this.searchContext.identQueryType = qt;
}
break;
+
+ case 'marc':
+ this.searchContext.identQuery = null;
+ this.searchContext.query[0] = ''; // prevent term queries
+ break;
}
this.searchByForm();