<link field="authority_links" reltype="has_many" key="bib" map="" class="abl"/>
<link field="subscriptions" reltype="has_many" key="record_entry" map="" class="ssub"/>
<link field="attrs" reltype="might_have" key="id" map="" class="mra"/>
- <link field="mattrs" reltype="might_have" key="id" map="" class="mraf"/>
+ <link field="mattrs" reltype="has_many" key="id" map="" class="mraf"/>
<link field="source" reltype="has_a" key="id" map="" class="cbs"/>
<link field="display_entries" reltype="has_many" key="source" map="" class="mde"/>
<link field="flat_display_entries" reltype="has_many" key="source" map="" class="mfde"/>
@Injectable()
export class IdlService {
- classes = {}; // IDL class metadata
+ classes: any = {}; // IDL class metadata
constructors = {}; // IDL instance generators
/**
--- /dev/null
+import {Injectable} from '@angular/core';
+import {Observable} from 'rxjs/Observable';
+import {mergeMap} from 'rxjs/operators/mergeMap';
+import {from} from 'rxjs/observable/from';
+import {map} from 'rxjs/operators/map';
+import {OrgService} from '@eg/core/org.service';
+import {UnapiService} from '@eg/share/catalog/unapi.service';
+import {IdlService, IdlObject} from '@eg/core/idl.service';
+import {NetService} from '@eg/core/net.service';
+import {PcrudService} from '@eg/core/pcrud.service';
+
+export const NAMESPACE_MAPS = {
+ 'mods': 'http://www.loc.gov/mods/v3',
+ 'biblio': 'http://open-ils.org/spec/biblio/v1',
+ 'holdings': 'http://open-ils.org/spec/holdings/v1',
+ 'indexing': 'http://open-ils.org/spec/indexing/v1'
+};
+
+export const HOLDINGS_XPATH =
+ '/holdings:holdings/holdings:counts/holdings:count';
+
+@Injectable()
+export class BibRecordService {
+
+ // Cache of bib editor / creator objects
+ // Assumption is this list will be limited in size.
+ userCache: {[id: number]: IdlObject};
+
+ constructor(
+ private idl: IdlService,
+ private net: NetService,
+ private org: OrgService,
+ private unapi: UnapiService,
+ private pcrud: PcrudService
+ ) {
+ this.userCache = {};
+ }
+
+ // Avoid fetching the MARC blob by specifying which fields on the
+ // bre to select. Note that fleshed fields are explicitly selected.
+ fetchableBreFields(): string[] {
+ return this.idl.classes.bre.fields
+ .filter(f => !f.virtual && f.name !== 'marc')
+ .map(f => f.name);
+ }
+
+ // Note responses are returned upon receipt, not necessarily in
+ // request ID order.
+ getBibSummaryBatch(bibIds: number[],
+ orgId?: number, orgDepth?: number): Observable<BibRecordSummary> {
+
+ if (bibIds.length === 0) {
+ return from([]);
+ }
+
+ return this.pcrud.search('bre', {id: bibIds},
+ { flesh: 1,
+ flesh_fields: {bre: ['flat_display_entries', 'mattrs']},
+ select: {bre : this.fetchableBreFields()}
+ },
+ {anonymous: true} // skip unneccesary auth
+ ).pipe(mergeMap(bib => {
+ const summary = new BibRecordSummary(bib, orgId, orgDepth);
+ summary.net = this.net; // inject
+ summary.ingest();
+ return this.getHoldingsSummary(bib.id(), orgId, orgDepth)
+ .then(holdingsSummary => {
+ summary.holdingsSummary = holdingsSummary;
+ return summary;
+ });
+ }));
+ }
+
+ getBibSummary(bibId: number,
+ orgId?: number, orgDepth?: number): Promise<BibRecordSummary> {
+
+ return this.pcrud.retrieve('bre', bibId,
+ { flesh: 1,
+ flesh_fields: {bre: ['flat_display_entries', 'mattrs']},
+ select: {bre : this.fetchableBreFields()}
+ },
+ {anonymous: true} // skip unneccesary auth
+ ).pipe(mergeMap(bib => {
+ const summary = new BibRecordSummary(bib, orgId, orgDepth);
+ summary.net = this.net; // inject
+ summary.ingest();
+ return this.getHoldingsSummary(bib.id(), orgId, orgDepth)
+ .then(holdingsSummary => {
+ summary.holdingsSummary = holdingsSummary;
+ return summary;
+ });
+ })).toPromise();
+ }
+
+ // Flesh the creator and editor fields.
+ // Handling this separately lets us pull from the cache and
+ // avoids the requirement that the main bib query use a staff
+ // (VIEW_USER) auth token.
+ fleshBibUsers(records: IdlObject[]): Promise<void> {
+
+ const search = [];
+
+ records.forEach(rec => {
+ ['creator', 'editor'].forEach(field => {
+ const id = rec[field]();
+ if (Number.isInteger(id)) {
+ if (this.userCache[id]) {
+ rec[field](this.userCache[id]);
+ } else if (!search.includes(id)) {
+ search.push(id);
+ }
+ }
+ });
+ });
+
+ if (search.length === 0) {
+ return Promise.resolve();
+ }
+
+ return this.pcrud.search('au', {id: search})
+ .pipe(map(user => {
+ this.userCache[user.id()] = user;
+ records.forEach(rec => {
+ if (user.id() === rec.creator()) {
+ rec.creator(user);
+ }
+ if (user.id() === rec.editor()) {
+ rec.editor(user);
+ }
+ });
+ })).toPromise();
+ }
+
+ getHoldingsSummary(recordId: number,
+ orgId: number, orgDepth: number): Promise<any> {
+
+ const holdingsSummary = [];
+
+ return this.unapi.getAsXmlDocument({
+ target: 'bre',
+ id: recordId,
+ extras: '{holdings_xml}',
+ format: 'holdings_xml',
+ orgId: orgId,
+ depth: orgDepth
+ }).then(xmlDoc => {
+
+ // namespace resolver
+ const resolver: any = (prefix: string): string => {
+ return NAMESPACE_MAPS[prefix] || null;
+ };
+
+ // Extract the holdings data from the unapi xml doc
+ const result = xmlDoc.evaluate(HOLDINGS_XPATH,
+ xmlDoc, resolver, XPathResult.ANY_TYPE, null);
+
+ let node;
+ while (node = result.iterateNext()) {
+ const counts = {type : node.getAttribute('type')};
+ ['depth', 'org_unit', 'transcendant',
+ 'available', 'count', 'unshadow'].forEach(field => {
+ counts[field] = Number(node.getAttribute(field));
+ });
+ holdingsSummary.push(counts);
+ }
+
+ return holdingsSummary;
+ });
+ }
+}
+
+export class BibRecordSummary {
+
+ id: number; // == record.id() for convenience
+ orgId: number;
+ orgDepth: number;
+ record: IdlObject;
+ display: any;
+ attributes: any;
+ holdingsSummary: any;
+ holdCount: number;
+ bibCallNumber: string;
+ net: NetService;
+
+ constructor(record: IdlObject, orgId: number, orgDepth: number) {
+ this.id = record.id();
+ this.record = record;
+ this.orgId = orgId;
+ this.orgDepth = orgDepth;
+ this.display = {};
+ this.attributes = {};
+ this.bibCallNumber = null;
+ }
+
+ ingest() {
+ this.compileDisplayFields();
+ this.compileRecordAttrs();
+
+ // Normalize some data for JS consistency
+ this.record.creator(Number(this.record.creator()));
+ this.record.editor(Number(this.record.editor()));
+ }
+
+ compileDisplayFields() {
+ this.record.flat_display_entries().forEach(entry => {
+ if (entry.multi() === 't') {
+ if (this.display[entry.name()]) {
+ this.display[entry.name()].push(entry.value());
+ } else {
+ this.display[entry.name()] = [entry.value()];
+ }
+ } else {
+ this.display[entry.name()] = entry.value();
+ }
+ });
+ }
+
+ compileRecordAttrs() {
+ // Any attr can be multi-valued.
+ this.record.mattrs().forEach(attr => {
+ if (this.attributes[attr.attr()]) {
+ this.attributes[attr.attr()].push(attr.value());
+ } else {
+ this.attributes[attr.attr()] = [attr.value()];
+ }
+ });
+ }
+
+ // Get -> Set -> Return bib hold count
+ getHoldCount(): Promise<number> {
+
+ if (Number.isInteger(this.holdCount)) {
+ return Promise.resolve(this.holdCount);
+ }
+
+ return this.net.request(
+ 'open-ils.circ',
+ 'open-ils.circ.bre.holds.count', this.id
+ ).toPromise().then(count => this.holdCount = count);
+ }
+
+ // Get -> Set -> Return bib-level call number
+ getBibCallNumber(): Promise<string> {
+
+ if (this.bibCallNumber !== null) {
+ return Promise.resolve(this.bibCallNumber);
+ }
+
+ // TODO labelClass = cat.default_classification_scheme YAOUS
+ const labelClass = 1;
+
+ return this.net.request(
+ 'open-ils.cat',
+ 'open-ils.cat.biblio.record.marc_cn.retrieve',
+ this.id, labelClass
+ ).toPromise().then(cnArray => {
+ if (cnArray && cnArray.length > 0) {
+ const key1 = Object.keys(cnArray[0])[0];
+ this.bibCallNumber = cnArray[0][key1];
+ } else {
+ this.bibCallNumber = '';
+ }
+ return this.bibCallNumber;
+ });
+ }
+}
+
+
+
offset: null
};
+ params.org = context.searchOrg.id();
+
params.limit = context.pager.limit;
if (context.pager.offset) {
params.offset = context.pager.offset;
}
});
+ if (params.identQuery) {
+ // Ident queries (e.g. tcn search) discards all remaining filters
+ return params;
+ }
+
context.query.forEach((q, idx) => {
['query', 'fieldClass', 'joinOp', 'matchOp'].forEach(field => {
// Propagate all array-based fields regardless of
}));
});
- params.org = context.searchOrg.id();
-
return params;
}
import {Injectable} from '@angular/core';
+import {Observable} from 'rxjs/Observable';
+import {mergeMap} from 'rxjs/operators/mergeMap';
+import {map} from 'rxjs/operators/map';
import {OrgService} from '@eg/core/org.service';
import {UnapiService} from '@eg/share/catalog/unapi.service';
-import {IdlObject} from '@eg/core/idl.service';
+import {IdlService, IdlObject} from '@eg/core/idl.service';
import {NetService} from '@eg/core/net.service';
import {PcrudService} from '@eg/core/pcrud.service';
import {CatalogSearchContext, CatalogSearchState} from './search-context';
+import {BibRecordService, BibRecordSummary} from './bib-record.service';
+// CCVM's we care about in a catalog context
+// Don't fetch them all because there are a lot.
export const CATALOG_CCVM_FILTERS = [
'item_type',
'item_form',
'vr_format',
'bib_level',
'lit_form',
- 'search_format'
+ 'search_format',
+ 'icon_format'
];
-const MODS_XPATH_AUTO = {
- title : '/mods:mods/mods:titleInfo/mods:title',
- author: '/mods:mods/mods:name/mods:namePart',
- edition: '/mods:mods/mods:originInfo/mods:edition',
- pubdate: '/mods:mods/mods:originInfo/mods:dateIssued',
- genre: '/mods:mods/mods:genre'
-};
-
-const MODS_XPATH = {
- extern: '/mods:mods/biblio:extern',
- copyCounts: '/mods:mods/holdings:holdings/holdings:counts/holdings:count',
- attributes: '/mods:mods/indexing:attributes/indexing:field'
-};
-
-const NAMESPACE_MAPS = {
- 'mods': 'http://www.loc.gov/mods/v3',
- 'biblio': 'http://open-ils.org/spec/biblio/v1',
- 'holdings': 'http://open-ils.org/spec/holdings/v1',
- 'indexing': 'http://open-ils.org/spec/indexing/v1'
-};
-
@Injectable()
export class CatalogService {
lastFacetKey: string;
constructor(
+ private idl: IdlService,
private net: NetService,
private org: OrgService,
private unapi: UnapiService,
- private pcrud: PcrudService
+ private pcrud: PcrudService,
+ private bibService: BibRecordService
) {}
search(ctx: CatalogSearchContext): Promise<void> {
result.ids.forEach((blob, idx) => ctx.addResultId(blob[0], idx));
}
- fetchBibSummaries(ctx: CatalogSearchContext): Promise<any> {
- const promises = [];
+ // Appends records to the search result set as they arrive.
+ // Returns a void promise once all records have been retrieved
+ fetchBibSummaries(ctx: CatalogSearchContext): Promise<void> {
+
const depth = ctx.global ?
ctx.org.root().ou_type().depth() :
ctx.searchOrg.ou_type().depth();
- ctx.currentResultIds().forEach((recId, idx) => {
- promises.push(
- this.getBibSummary(recId, ctx.searchOrg.id(), depth)
- .then(
- // idx maintains result sort order
- summary => {
- if (ctx.result.records) {
- // May be reset when quickly navigating results.
- ctx.result.records[idx] = summary;
- }
- }
- )
- );
- });
-
- return Promise.all(promises);
+ return this.bibService.getBibSummaryBatch(
+ ctx.currentResultIds(), ctx.searchOrg.id(), depth)
+ .pipe(map(summary => {
+ // Responses are not necessarily returned in request-ID order.
+ const idx = ctx.currentResultIds().indexOf(summary.record.id());
+ if (ctx.result.records) {
+ // May be reset when quickly navigating results.
+ ctx.result.records[idx] = summary;
+ }
+ })).toPromise();
}
fetchFacets(ctx: CatalogSearchContext): Promise<void> {
);
});
}
-
-
- /**
- * Bib record via UNAPI as mods (for now) with holdings summary
- * and record attributes.
- */
- getBibSummary(bibId: number, orgId?: number, depth?: number): Promise<any> {
- return new Promise((resolve, reject) => {
- this.unapi.getAsXmlDocument({
- target: 'bre',
- id: bibId,
- extras: '{bre.extern,holdings_xml,mra}',
- format: 'mods32',
- orgId: orgId,
- depth: depth
- }).then(xmlDoc => {
- const summary = this.translateBibSummary(xmlDoc);
- summary.id = bibId;
- resolve(summary);
- });
- });
- }
-
- /**
- * Probably don't want to require navigating the bare UNAPI
- * blob in the template, plus that's quite a lot of stuff
- * to sit in the scope / watch for changes. Translate the
- * UNAPI content into a more digestable form.
- * TODO: Add display field support
- */
- translateBibSummary(xmlDoc: XMLDocument): any { // TODO: bib summary interface
-
- const response: any = {
- copyCounts : [],
- ccvms : {}
- };
-
- const resolver: any = (prefix: string): string => {
- return NAMESPACE_MAPS[prefix] || null;
- };
-
- Object.keys(MODS_XPATH_AUTO).forEach(key => {
- const res = xmlDoc.evaluate(MODS_XPATH_AUTO[key], xmlDoc,
- resolver, XPathResult.FIRST_ORDERED_NODE_TYPE, null);
-
- const modsNode = res.singleNodeValue;
- if (modsNode) {
- response[key] = modsNode.textContent;
- }
- });
-
- let result = xmlDoc.evaluate(MODS_XPATH.extern, xmlDoc,
- resolver, XPathResult.FIRST_ORDERED_NODE_TYPE, null);
-
- let node: any = result.singleNodeValue;
- if (node) {
- const attrs = node.attributes;
- for (let i = attrs.length - 1; i >= 0; i--) {
- response[attrs[i].name] = attrs[i].value;
- }
- }
-
- result = xmlDoc.evaluate(MODS_XPATH.attributes, xmlDoc,
- resolver, XPathResult.ANY_TYPE, null);
-
- while (node = result.iterateNext()) {
- response.ccvms[node.getAttribute('name')] = {
- code : node.textContent,
- label : node.getAttribute('coded-value')
- };
- }
-
- result = xmlDoc.evaluate(MODS_XPATH.copyCounts, xmlDoc,
- resolver, XPathResult.ANY_TYPE, null);
-
- while (node = result.iterateNext()) {
- const counts = {type : node.getAttribute('type')};
- ['depth', 'org_unit', 'transcendant',
- 'available', 'count', 'unshadow'].forEach(field => {
- counts[field] = Number(node.getAttribute(field));
- });
- response.copyCounts.push(counts);
- }
-
- response.creator = Number(response.creator);
- response.editor = Number(response.editor);
-
- // console.log(response);
- return response;
- }
}
import {GridModule} from '@eg/share/grid/grid.module';
import {UnapiService} from '@eg/share/catalog/unapi.service';
import {CatalogRoutingModule} from './routing.module';
+import {BibRecordService} from '@eg/share/catalog/bib-record.service';
import {CatalogService} from '@eg/share/catalog/catalog.service';
import {CatalogUrlService} from '@eg/share/catalog/catalog-url.service';
import {CatalogComponent} from './catalog.component';
],
providers: [
UnapiService,
+ BibRecordService,
CatalogService,
CatalogUrlService,
StaffCatalogService,
</div>
</div>
<div id='staff-catalog-bib-summary-container' class='mt-1'>
- <eg-bib-summary [bibSummary]="bibSummary">
+ <eg-bib-summary [bibSummary]="summary">
</eg-bib-summary>
</div>
<div id='staff-catalog-bib-tabs-container' class='mt-3'>
import {IdlObject} from '@eg/core/idl.service';
import {CatalogSearchContext, CatalogSearchState} from '@eg/share/catalog/search-context';
import {CatalogService} from '@eg/share/catalog/catalog.service';
+import {BibRecordService, BibRecordSummary} from '@eg/share/catalog/bib-record.service';
import {StaffCatalogService} from '../catalog.service';
import {BibSummaryComponent} from '@eg/staff/share/bib-summary/bib-summary.component';
recordId: number;
recordTab: string;
- bibSummary: any;
+ summary: BibRecordSummary;
searchContext: CatalogSearchContext;
@ViewChild('recordTabs') recordTabs: NgbTabset;
private router: Router,
private route: ActivatedRoute,
private pcrud: PcrudService,
+ private bib: BibRecordService,
private cat: CatalogService,
private staffCat: StaffCatalogService
) {}
// Avoid re-fetching the same record summary during tab navigation.
if (this.staffCat.currentDetailRecordSummary &&
this.recordId === this.staffCat.currentDetailRecordSummary.id) {
- this.bibSummary = this.staffCat.currentDetailRecordSummary;
+ this.summary = this.staffCat.currentDetailRecordSummary;
return;
}
- this.bibSummary = null;
- this.cat.getBibSummary(
+ this.summary = null;
+ this.bib.getBibSummary(
this.recordId,
this.searchContext.searchOrg.id(),
this.searchContext.searchOrg.ou_type().depth()
).then(summary => {
- this.bibSummary =
+ this.summary =
this.staffCat.currentDetailRecordSummary = summary;
- this.pcrud.search('au', {id: [summary.creator, summary.editor]})
- .subscribe(user => {
- if (user.id() === summary.creator) {
- summary.creator = user;
- }
- if (user.id() === summary.editor) {
- summary.editor = user;
- }
- });
+ this.bib.fleshBibUsers([summary.record]);
});
}
}
<div class="row">
<div class="col-lg-1">
<!-- TODO router links -->
- <a href="./cat/catalog/record/{{bibSummary.id}}">
+ <a href="./cat/catalog/record/{{summary.id}}">
<img style="height:80px"
- src="/opac/extras/ac/jacket/small/r/{{bibSummary.id}}"/>
+ src="/opac/extras/ac/jacket/small/r/{{summary.id}}"/>
</a>
</div>
<div class="col-lg-5">
#{{index + 1 + searchContext.pager.offset}}
</span>
<a href="javascript:void(0)"
- (click)="navigatToRecord(bibSummary.id)">
- {{bibSummary.title || ' '}}
+ (click)="navigatToRecord(summary.id)">
+ {{summary.display.title || ' '}}
</a>
</div>
</div>
<div class="col-lg-12">
<!-- nbsp allows the column to take shape when no value exists -->
<a href="javascript:void(0)"
- (click)="searchAuthor(bibSummary)">
- {{bibSummary.author || ' '}}
+ (click)="searchAuthor(summary)">
+ {{summary.display.author || ' '}}
</a>
</div>
</div>
<div class="row pt-2">
<div class="col-lg-12">
- <span *ngIf="bibSummary.ccvms.icon_format">
- <img class="pad-right-min"
- src="/images/format_icons/icon_format/{{bibSummary.ccvms.icon_format.code}}.png"/>
- <span>{{bibSummary.ccvms.icon_format.label}}</span>
+ <!-- only shows the first icon format -->
+ <span *ngIf="summary.attributes.icon_format[0]">
+ <img class="pr-1"
+ src="/images/format_icons/icon_format/{{summary.attributes.icon_format[0]}}.png"/>
+ <span>{{iconFormatLabel(summary.attributes.icon_format[0])}}</span>
</span>
- <span style='pl-2'>{{bibSummary.edition}}</span>
- <span style='pl-2'>{{bibSummary.pubdate}}</span>
+ <span class='pl-1'>{{summary.display.edition}}</span>
+ <span class='pl-1'>{{summary.display.pubdate}}</span>
</div>
</div>
</div>
<div class="col-lg-2">
<div class="row" [ngClass]="{'pt-2':copyIndex > 0}"
- *ngFor="let copyCount of bibSummary.copyCounts; let copyIdx = index">
+ *ngFor="let copyCount of summary.holdingsSummary; let copyIdx = index">
<div class="w-100" *ngIf="copyCount.type == 'staff'">
<div class="float-left text-left w-50">
<span class="pr-1">
<div class="col-lg-1">
<div class="row">
<div class="w-100">
- TCN: {{bibSummary.tcn_value}}
+ TCN: {{summary.record.tcn_value()}}
</div>
</div>
<div class="row">
<div class="w-100">
- Holds: {{bibSummary.holdCount}}
+ Holds: {{summary.holdCount}}
</div>
</div>
</div>
<div class="row">
<div class="col-lg-12">
<div class="float-right small-text-1">
- Created {{bibSummary.create_date | date:'shortDate'}} by
+ Created {{summary.create_date | date:'shortDate'}} by
<!-- creator if fleshed after the initial data set is loaded -->
- <a *ngIf="bibSummary.creator.usrname" target="_self"
- href="/eg/staff/circ/patron/{{bibSummary.creator.id()}}/checkout">
- {{bibSummary.creator.usrname()}}
+ <a *ngIf="summary.record.creator().usrname" target="_self"
+ href="/eg/staff/circ/patron/{{summary.record.creator().id()}}/checkout">
+ {{summary.record.creator().usrname()}}
</a>
<!-- add a spacer pending data to reduce page shuffle -->
- <span *ngIf="!bibSummary.creator.usrname"> ... </span>
+ <span *ngIf="!summary.record.creator().usrname"> ... </span>
</div>
</div>
</div>
<div class="row pt-2">
<div class="col-lg-12">
- <div class="float-right small-text-1">
- Edited {{bibSummary.edit_date | date:'shortDate'}} by
- <a *ngIf="bibSummary.editor.usrname" target="_self"
- href="/eg/staff/circ/patron/{{bibSummary.editor.id()}}/checkout">
- {{bibSummary.editor.usrname()}}
+ <div class="float-right small-text-1" i18n>
+ Edited {{summary.edit_date | date:'shortDate'}} by
+ <a *ngIf="summary.record.editor().usrname" target="_self"
+ href="/eg/staff/circ/patron/{{summary.record.editor().id()}}/checkout">
+ {{summary.record.editor().usrname()}}
</a>
- <span *ngIf="!bibSummary.editor.usrname"> ... </span>
+ <span *ngIf="!summary.record.editor().usrname"> ... </span>
</div>
</div>
</div>
import {OrgService} from '@eg/core/org.service';
import {NetService} from '@eg/core/net.service';
import {CatalogService} from '@eg/share/catalog/catalog.service';
+import {BibRecordService, BibRecordSummary} from '@eg/share/catalog/bib-record.service';
import {CatalogSearchContext} from '@eg/share/catalog/search-context';
import {CatalogUrlService} from '@eg/share/catalog/catalog-url.service';
import {StaffCatalogService} from '../catalog.service';
export class ResultRecordComponent implements OnInit {
@Input() index: number; // 0-index display row
- @Input() bibSummary: any;
+ @Input() summary: BibRecordSummary;
searchContext: CatalogSearchContext;
constructor(
private router: Router,
private org: OrgService,
private net: NetService,
+ private bib: BibRecordService,
private cat: CatalogService,
private catUrl: CatalogUrlService,
private staffCat: StaffCatalogService
ngOnInit() {
this.searchContext = this.staffCat.searchContext;
- this.fleshHoldCount();
- }
-
- fleshHoldCount(): void {
- this.net.request(
- 'open-ils.circ',
- 'open-ils.circ.bre.holds.count', this.bibSummary.id
- ).subscribe(count => this.bibSummary.holdCount = count);
+ this.summary.getHoldCount();
}
orgName(orgId: number): string {
return this.org.get(orgId).shortname();
}
+ iconFormatLabel(code: string): string {
+ if (this.cat.ccvmMap) {
+ const ccvm = this.cat.ccvmMap.icon_format.filter(
+ format => format.code() === code)[0];
+ if (ccvm) {
+ return ccvm.search_label();
+ }
+ }
+ }
+
placeHold(): void {
- alert('Placing hold on bib ' + this.bibSummary.id);
+ alert('Placing hold on bib ' + this.summary.id);
}
addToList(): void {
- alert('Adding to list for bib ' + this.bibSummary.id);
+ alert('Adding to list for bib ' + this.summary.id);
}
- searchAuthor(bibSummary: any) {
+ searchAuthor(summary: any) {
this.searchContext.reset();
this.searchContext.fieldClass = ['author'];
- this.searchContext.query = [bibSummary.author];
+ this.searchContext.query = [summary.display.author];
this.staffCat.search();
}
</div>
<div class="col-lg-10">
<div *ngIf="searchContext.result">
- <div *ngFor="let bibSummary of searchContext.result.records; let idx = index">
- <div *ngIf="bibSummary">
- <eg-catalog-result-record [bibSummary]="bibSummary" [index]="idx">
+ <div *ngFor="let summary of searchContext.result.records; let idx = index">
+ <div *ngIf="summary">
+ <eg-catalog-result-record [summary]="summary" [index]="idx">
</eg-catalog-result-record>
</div>
</div>
import {map, switchMap, distinctUntilChanged} from 'rxjs/operators';
import {ActivatedRoute, ParamMap} from '@angular/router';
import {CatalogService} from '@eg/share/catalog/catalog.service';
+import {BibRecordService} from '@eg/share/catalog/bib-record.service';
import {CatalogUrlService} from '@eg/share/catalog/catalog-url.service';
import {CatalogSearchContext, CatalogSearchState} from '@eg/share/catalog/search-context';
import {PcrudService} from '@eg/core/pcrud.service';
private route: ActivatedRoute,
private pcrud: PcrudService,
private cat: CatalogService,
+ private bib: BibRecordService,
private catUrl: CatalogUrlService,
private staffCat: StaffCatalogService
) {}
if (!records || records.length === 0) { return; }
// Flesh the creator / editor fields with the user object.
- // Handle the user fleshing here (instead of record.component so
- // we only need to grab one copy of each user.
- const userIds: {[id: number]: boolean} = {};
- records.forEach(recSum => {
- if (this.userCache[recSum.creator]) {
- recSum.creator = this.userCache[recSum.creator];
- } else {
- userIds[Number(recSum.creator)] = true;
- }
-
- if (this.userCache[recSum.editor]) {
- recSum.editor = this.userCache[recSum.editor];
- } else {
- userIds[Number(recSum.editor)] = true;
- }
- });
-
- if (!Object.keys(userIds).length) { return; }
-
- this.pcrud.search('au', {id : Object.keys(userIds)})
- .subscribe(usr => {
- this.userCache[usr.id()] = usr;
- records.forEach(recSum => {
- if (recSum.creator === usr.id()) {
- recSum.creator = usr;
- }
- if (recSum.editor === usr.id()) {
- recSum.editor = usr;
- }
- });
- });
+ this.bib.fleshBibUsers(records.map(r => r.record));
}
searchIsDone(): boolean {
<input type="text" class="form-control"
id='first-query-input'
[(ngModel)]="searchContext.query[idx]"
- (keyup.enter)="formEnter()"
+ (keyup.enter)="formEnter('query')"
placeholder="Query..."/>
</div>
<div *ngIf="idx > 0">
<input type="text" class="form-control"
[(ngModel)]="searchContext.query[idx]"
- (keyup.enter)="formEnter()"
+ (keyup.enter)="formEnter('query')"
placeholder="Query..."/>
</div>
</div>
<div class="col-lg-2">
<input id='ident-query-input' type="text" class="form-control"
[(ngModel)]="searchContext.identQuery"
- (keyup.enter)="formEnter()"
+ (keyup.enter)="formEnter('ident')"
placeholder="Numeric Query..."/>
</div>
</div>
this.searchContext.matchOp.splice(index, 1);
}
- formEnter() {
+ formEnter(source) {
this.searchContext.pager.offset = 0;
+
+ switch (source) {
+
+ case 'query': // main search form query input
+
+ // Be sure a previous ident search does not take precedence
+ // over the newly entered/submitted search query
+ this.searchContext.identQuery = null;
+ break;
+
+ case 'ident': // identifier query input
+ const iq = this.searchContext.identQuery;
+ const qt = this.searchContext.identQueryType
+ if (iq) {
+ // Ident queries ignore search-specific filters.
+ this.searchContext.reset();
+ this.searchContext.identQuery = iq;
+ this.searchContext.identQueryType = qt;
+ }
+ break;
+ }
+
this.searchByForm();
}
<li class="list-group-item">
<div class="d-flex">
<div class="flex-1 font-weight-bold" i18n>Title:</div>
- <div class="flex-3">{{summary.title}}</div>
+ <div class="flex-3">{{summary.display.title}}</div>
<div class="flex-1 font-weight-bold pl-1" i18n>Edition:</div>
- <div class="flex-1">{{summary.edition}}</div>
+ <div class="flex-1">{{summary.display.edition}}</div>
<div class="flex-1 font-weight-bold" i18n>TCN:</div>
- <div class="flex-1">{{summary.tcn_value}}</div>
+ <div class="flex-1">{{summary.record.tcn_value()}}</div>
<div class="flex-1 font-weight-bold pl-1" i18n>Created By:</div>
- <div class="flex-1" *ngIf="summary.creator.usrname">
- {{summary.creator.usrname()}}
+ <div class="flex-1" *ngIf="summary.record.creator().usrname">
+ {{summary.record.creator().usrname()}}
</div>
</div>
</li>
<li class="list-group-item" *ngIf="expandDisplay">
<div class="d-flex">
<div class="flex-1 font-weight-bold" i18n>Author:</div>
- <div class="flex-3">{{summary.author}}</div>
+ <div class="flex-3">{{summary.display.author}}</div>
<div class="flex-1 font-weight-bold pl-1" i18n>Pubdate:</div>
- <div class="flex-1">{{summary.pubdate}}</div>
+ <div class="flex-1">{{summary.display.pubdate}}</div>
<div class="flex-1 font-weight-bold" i18n>Database ID:</div>
<div class="flex-1">{{summary.id}}</div>
<div class="flex-1 font-weight-bold pl-1" i18n>Last Edited By:</div>
- <div class="flex-1" *ngIf="summary.editor.usrname">
- {{summary.editor.usrname()}}
+ <div class="flex-1" *ngIf="summary.record.editor().usrname">
+ {{summary.record.editor().usrname()}}
</div>
</div>
</li>
import {NetService} from '@eg/core/net.service';
import {PcrudService} from '@eg/core/pcrud.service';
import {CatalogService} from '@eg/share/catalog/catalog.service';
+import {BibRecordService, BibRecordSummary} from '@eg/share/catalog/bib-record.service';
@Component({
selector: 'eg-bib-summary',
@Input() recordId: number;
// Otherwise, we'll use the provided bib summary object.
- summary: any;
+ summary: BibRecordSummary;
@Input() set bibSummary(s: any) {
this.summary = s;
if (this.initDone) {
- this.fetchBibCallNumber();
+ this.summary.getBibCallNumber();
}
}
constructor(
+ private bib: BibRecordService,
private cat: CatalogService,
private net: NetService,
private pcrud: PcrudService
ngOnInit() {
this.initDone = true;
if (this.summary) {
- this.fetchBibCallNumber();
+ this.summary.getBibCallNumber();
} else {
if (this.recordId) {
this.loadSummary();
}
loadSummary(): void {
- this.cat.getBibSummary(this.recordId).then(summary => {
+ this.bib.getBibSummary(this.recordId).then(summary => {
+ summary.getBibCallNumber();
+ this.bib.fleshBibUsers([summary.record]);
this.summary = summary;
- this.fetchBibCallNumber();
-
- // Flesh the user data
- this.pcrud.search('au', {id: [summary.creator, summary.editor]})
- .subscribe(user => {
- if (user.id() === summary.creator) {
- summary.creator = user;
- }
- if (user.id() === summary.editor) {
- summary.editor = user;
- }
- });
- });
- }
-
- fetchBibCallNumber(): void {
- if (!this.summary || this.summary.callNumber) {
- return;
- }
-
- // TODO labelClass = cat.default_classification_scheme YAOUS
- const labelClass = 1;
-
- this.net.request(
- 'open-ils.cat',
- 'open-ils.cat.biblio.record.marc_cn.retrieve',
- this.summary.id, labelClass
- ).subscribe(cnArray => {
- if (cnArray && cnArray.length > 0) {
- const key1 = Object.keys(cnArray[0])[0];
- this.summary.callNumber = cnArray[0][key1];
- }
+ console.log(this.summary.display);
});
}
}