</span>
</ng-template>
+<!-- MARC edit-ception! -->
+<eg-marc-editor-dialog #marcEditDialog recordType="authority">
+</eg-marc-editor-dialog>
+
<!-- display a single heading as MARC or as the human friendlier string -->
<ng-template #headingField let-field="field" let-from="from" let-also="also">
- <button class="btn btn-sm btn-outline-info p-1 mr-1"
+ <button class="btn btn-sm p-1 mr-1"
+ [ngClass]="{'btn-outline-primary': !(from || also), 'btn-outline-info': (from || also)}"
(click)="applyHeading(field)" i18n>Apply</button>
<ng-container *ngIf="showAs == 'heading'">
<span *ngIf="from" i18n>See From: {{field.heading}}</span>
<div class="ml-2 p-1">
<div class="mb-1" i18n>Create new authority from this field</div>
<div>
- <button class="btn btn-outline-info" [disabled]="true">
- Immediately
- </button>
- <button class="btn btn-outline-info ml-2" [disabled]="true">
- Create and Edit
- </button>
+ <button class="btn btn-outline-info"
+ (click)="createNewAuthority()">Immediately</button>
+ <button class="btn btn-outline-info ml-2"
+ (click)="createNewAuthority(true)">Create and Edit</button>
</div>
</div>
</div>
-import {Component, Input, Output, OnInit, EventEmitter} from '@angular/core';
+import {Component, ViewChild, Input, Output, OnInit, EventEmitter} from '@angular/core';
import {NetService} from '@eg/core/net.service';
+import {OrgService} from '@eg/core/org.service';
+import {AuthService} from '@eg/core/auth.service';
import {PcrudService} from '@eg/core/pcrud.service';
import {DialogComponent} from '@eg/share/dialog/dialog.component';
import {NgbModal} from '@ng-bootstrap/ng-bootstrap';
import {MarcField} from './marcrecord';
+import {MarcEditContext} from './editor-context';
import {Pager} from '@eg/share/util/pager';
+import {MarcEditorDialogComponent} from './editor-dialog.component';
/**
* MARC Authority Linking Dialog
@Input() thesauri: string = null;
@Input() controlSet: number = null;
@Input() pager: Pager;
+ @Input() context: MarcEditContext;
browseData: any[] = [];
selectedSubfields: string[] = [];
+ cni: string; // Control Number Identifier
+
+ @ViewChild('marcEditDialog', {static: false})
+ marcEditDialog: MarcEditorDialogComponent;
+
constructor(
private modal: NgbModal,
+ private auth: AuthService,
+ private org: OrgService,
private pcrud: PcrudService,
private net: NetService) {
super(modal);
initData() {
- this.pager.offset = 0;
+ this.pager.offset = 0;
+
+ this.org.settings(['cat.marc_control_number_identifier']).then(s => {
+ this.cni = s['cat.marc_control_number_identifier'] ||
+ 'Set cat.marc_control_number_identifier in Library Settings';
+ });
- this.pcrud.search('acsbf',
+ this.pcrud.search('acsbf',
{tag: this.bibField.tag},
{flesh: 1, flesh_fields: {acsbf: ['authority_field']}},
{atomic: true, anonymous: true}
return this.authMeta ?
this.authMeta.sf_list().includes(sf) : false;
}
+
+ setSubfieldZero(authId: number) {
+ const sfZero = this.bibField.subfields.filter(sf => sf[0] === '0')[0];
+ if (sfZero) {
+ this.context.deleteSubfield(this.bibField, sfZero);
+ }
+ this.context.insertSubfield(this.bibField,
+ ['0', `(${this.cni})${authId}`, this.bibField.subfields.length]);
+ }
+
+ createNewAuthority(editFirst?: boolean) {
+
+ const method = editFirst ?
+ 'open-ils.cat.authority.record.create_from_bib.readonly' :
+ 'open-ils.cat.authority.record.create_from_bib';
+
+ this.net.request(
+ 'open-ils.cat', method,
+ this.fieldHash(), this.cni, this.auth.token()
+ ).subscribe(record => {
+ if (editFirst) {
+ this.marcEditDialog.recordXml = record;
+ this.marcEditDialog.open({size: 'xl'})
+ .subscribe(saveEvent => {
+ if (saveEvent && saveEvent.recordId) {
+ this.setSubfieldZero(saveEvent.recordId);
+ }
+ this.close();
+ });
+ } else {
+ this.setSubfieldZero(record.id());
+ this.close();
+ }
+ });
+ }
}
+
import {MarcEditContext, FieldFocusRequest, MARC_EDITABLE_FIELD_TYPE,
TextUndoRedoAction} from './editor-context';
import {ContextMenuEntry} from '@eg/share/context-menu/context-menu.service';
-import {TagTableService} from './tagtable.service';
import {StringComponent} from '@eg/share/string/string.component';
+import {TagTable} from './tagtable.service';
/**
* MARC Editable Content Component
@ViewChild('insertAfter', {static: false}) insertAfterStr: StringComponent;
@ViewChild('deleteField', {static: false}) deleteFieldStr: StringComponent;
- constructor(
- private renderer: Renderer2,
- private tagTable: TagTableService) {}
+ constructor(private renderer: Renderer2) {}
+
+ tt(): TagTable { // for brevity
+ return this.context.tagTable;
+ }
ngOnInit() {
this.setupFieldType();
}
applyFFOptions() {
- return this.tagTable.getFfFieldMeta(
- this.fixedFieldCode, this.record.recordType())
+ return this.tt().getFfFieldMeta(this.fixedFieldCode)
.then(fieldMeta => {
if (fieldMeta) {
this.maxLength = fieldMeta.length || 1;
return this.tagContextMenuEntries();
case 'sfc':
- return this.tagTable.getSubfieldCodes(this.field.tag);
+ return this.tt().getSubfieldCodes(this.field.tag);
case 'sfv':
- return this.tagTable.getSubfieldValues(
+ return this.tt().getSubfieldValues(
this.field.tag, this.subfield[0]);
case 'ind1':
case 'ind2':
- return this.tagTable.getIndicatorValues(
+ return this.tt().getIndicatorValues(
this.field.tag, this.fieldType);
case 'ffld':
- return this.tagTable.getFfValues(
- this.fixedFieldCode, this.record.recordType());
+ return this.tt().getFfValues(this.fixedFieldCode);
}
return null;
{divider: true}
);
- this.tagTable.getFieldTags().forEach(e => this.tagMenuEntries.push(e));
+ this.tt().getFieldTags().forEach(e => this.tagMenuEntries.push(e));
return this.tagMenuEntries;
}
import {EventEmitter} from '@angular/core';
import {MarcRecord, MarcField, MarcSubfield} from './marcrecord';
import {NgbPopover} from '@ng-bootstrap/ng-bootstrap';
+import {TagTable} from './tagtable.service';
/* Per-instance MARC editor context. */
undoStack: UndoRedoAction[] = [];
redoStack: UndoRedoAction[] = [];
+ tagTable: TagTable;
+
// True if any changes have been made.
// For the 'rich' editor, this is any un-do-able actions.
// For the text edtior it's any text change.
--- /dev/null
+
+<ng-template #dialogContent>
+ <div class="modal-header bg-info">
+ <h4 class="modal-title" i18n>MARC Editor</h4>
+ <button type="button" class="close"
+ i18n-aria-label aria-label="Close" (click)="close()">
+ <span aria-hidden="true">×</span>
+ </button>
+ </div>
+ <div class="modal-body">
+ <eg-marc-editor #marcEditor (recordSaved)="handleRecordSaved($event)"
+ [recordType]="recordType" [recordXml]="recordXml"></eg-marc-editor>
+ </div>
+</ng-template>
--- /dev/null
+import {Component, Input, Output, OnInit, EventEmitter} from '@angular/core';
+import {Observable} from 'rxjs';
+import {NetService} from '@eg/core/net.service';
+import {OrgService} from '@eg/core/org.service';
+import {AuthService} from '@eg/core/auth.service';
+import {PcrudService} from '@eg/core/pcrud.service';
+import {DialogComponent} from '@eg/share/dialog/dialog.component';
+import {NgbModal, NgbModalRef, NgbModalOptions} from '@ng-bootstrap/ng-bootstrap';
+import {MarcEditContext} from './editor-context';
+
+
+/**
+ * Spawn a MARC editor within a dialog.
+ */
+
+@Component({
+ selector: 'eg-marc-editor-dialog',
+ templateUrl: './editor-dialog.component.html'
+})
+
+export class MarcEditorDialogComponent
+ extends DialogComponent implements OnInit {
+
+ @Input() context: MarcEditContext;
+ @Input() recordXml: string;
+ @Input() recordType: 'biblio' | 'authority' = 'biblio';
+
+ constructor(
+ private modal: NgbModal,
+ private auth: AuthService,
+ private org: OrgService,
+ private pcrud: PcrudService,
+ private net: NetService) {
+ super(modal);
+ }
+
+ ngOnInit() {}
+
+ handleRecordSaved(saved) {
+ this.close(saved);
+ }
+}
+
+
</span>
</h3>
- <div class="mr-2">
- <eg-combobox #sourceSelector
- [entries]="sources"
- placeholder="Select a Source..."
- i18n-placeholder>
- </eg-combobox>
- </div>
+ <ng-container *ngIf="recordType === 'biblio'">
+ <div class="mr-2">
+ <eg-combobox #sourceSelector
+ [entries]="sources"
+ placeholder="Select a Source..."
+ i18n-placeholder>
+ </eg-combobox>
+ </div>
+ </ng-container>
<ng-container *ngIf="record && record.id">
<button *ngIf="!record.deleted" class="btn btn-warning"
interface MarcSavedEvent {
marcXml: string;
bibSource?: number;
+ recordId?: number;
}
/**
this.fromId(id);
}
+ get recordId(): number {
+ return this.record ? this.record.id : null;
+ }
+
@Input() set recordXml(xml: string) {
if (xml) {
this.fromXml(xml);
this.store.getItem('cat.marcedit.flateditor').then(
useFlat => this.editorTab = useFlat ? 'flat' : 'rich');
+ if (this.recordType !== 'biblio') { return; }
+
this.pcrud.retrieveAll('cbs').subscribe(
src => this.sources.push({id: +src.id(), label: src.source()}),
_ => {},
let sourceName: string = null;
let sourceId: number = null;
- if (this.sourceSelector.selected) {
+ if (this.sourceSelector && this.sourceSelector.selected) {
sourceName = this.sourceSelector.selected.label;
sourceId = this.sourceSelector.selected.id;
}
+ const emission = {
+ marcXml: xml, bibSource: sourceId, recordId: this.recordId};
+
if (this.inPlaceMode) {
// Let the caller have the modified XML and move on.
- this.recordSaved.emit({marcXml: xml, bibSource: sourceId});
+ this.recordSaved.emit(emission);
return Promise.resolve();
}
+ let promise;
+
if (this.record.id) { // Editing an existing record
- const method = 'open-ils.cat.biblio.record.xml.update';
+ promise = this.modifyRecord(xml, sourceName, sourceId);
- return this.net.request('open-ils.cat', method,
- this.auth.token(), this.record.id, xml, sourceName
- ).toPromise().then(response => {
+ } else {
- const evt = this.evt.parse(response);
- if (evt) {
- console.error(evt);
- this.failMsg.current().then(msg => this.toast.warning(msg));
- this.dataSaving = false;
- return;
- }
+ promise = this.createRecord(xml, sourceName);
+ }
- this.successMsg.current().then(msg => this.toast.success(msg));
- this.recordSaved.emit({marcXml: xml, bibSource: sourceId});
- return response;
- });
+ // NOTE we do not reinitialize our record with the MARC returned
+ // from the server after a create/update, which means our record
+ // may be out of sync, e.g. missing 901* values. It's the
+ // callers onsibility to tear us down and rebuild us.
+ return promise.then(marcXml => {
+ if (!marcXml) { return null; }
+ this.successMsg.current().then(msg => this.toast.success(msg));
+ emission.marcXml = marcXml;
+ emission.recordId = this.recordId;
+ this.recordSaved.emit(emission);
+ return marcXml;
+ });
+ }
- } else {
- // TODO: create a new record
- }
+ modifyRecord(marcXml: string, sourceName: string, sourceId: number): Promise<any> {
+ const method = 'open-ils.cat.biblio.record.marc.replace';
+
+ return this.net.request('open-ils.cat', method,
+ this.auth.token(), this.record.id, marcXml, sourceName
+
+ ).toPromise().then(response => {
+
+ const evt = this.evt.parse(response);
+ if (evt) {
+ console.error(evt);
+ this.failMsg.current().then(msg => this.toast.warning(msg));
+ this.dataSaving = false;
+ return null;
+ }
+
+ return response.marc();
+ });
+ }
+
+ createRecord(marcXml: string, sourceName?: string): Promise<any> {
+
+ const method = this.recordType === 'biblio' ?
+ 'open-ils.cat.biblio.record.xml.create' :
+ 'open-ils.cat.authority.record.import';
+
+ return this.net.request('open-ils.cat', method,
+ this.auth.token(), marcXml, sourceName
+ ).toPromise().then(response => {
+
+ const evt = this.evt.parse(response);
+
+ if (evt) {
+ console.error(evt);
+ this.failMsg.current().then(msg => this.toast.warning(msg));
+ this.dataSaving = false;
+ return null;
+ }
+
+ this.record.id = response.id();
+ return response.marc();
+ });
}
fromId(id: number): Promise<any> {
}
return this.fromId(this.record.id)
.then(_ => this.recordSaved.emit(
- {marcXml: this.record.toXml()}));
+ {marcXml: this.record.toXml(), recordId: this.recordId}));
});
});
}
return this.fromId(this.record.id)
.then(_ => this.recordSaved.emit(
- {marcXml: this.record.toXml()}));
+ {marcXml: this.record.toXml(), recordId: this.recordId}));
});
});
}
fieldMeta: IdlObject;
randId = Math.floor(Math.random() * 10000000);
- constructor(private tagTable: TagTableService) {}
+ constructor() {}
ngOnInit() {
this.init().then(_ =>
// If no field metadata is found for this fixed field code and
// record type combo, the field will be hidden in the UI.
- return this.tagTable.getFfFieldMeta(
- this.fieldCode, this.record.recordType())
+ return this.context.tagTable.getFfFieldMeta(this.fieldCode)
.then(fieldMeta => this.fieldMeta = fieldMeta);
}
}
import {TagTableService} from './tagtable.service';
import {EditableContentComponent} from './editable-content.component';
import {AuthorityLinkingDialogComponent} from './authority-linking-dialog.component';
+import {MarcEditorDialogComponent} from './editor-dialog.component';
@NgModule({
declarations: [
FixedFieldsEditorComponent,
FixedFieldComponent,
EditableContentComponent,
+ MarcEditorDialogComponent,
AuthorityLinkingDialogComponent
],
imports: [
</div>
</ng-container>
-<eg-authority-linking-dialog #authLinker></eg-authority-linking-dialog>
+<eg-authority-linking-dialog #authLinker [context]="context">
+</eg-authority-linking-dialog>
<ng-template #subfieldChunk let-field="field" let-subfield="subfield">
<div class="col-lg-3">
<div><button class="btn btn-outline-dark"
(click)="showHelp = !showHelp" i18n>Help</button></div>
- <div class="mt-2"><button class="btn btn-outline-dark"
- (click)="validate()" i18n>Validate</button></div>
+ <ng-container *ngIf="context.recordType === 'biblio'">
+ <div class="mt-2"><button class="btn btn-outline-dark"
+ (click)="validate()" i18n>Validate</button></div>
+ </ng-container>
<div class="mt-2">
<button type="button" class="btn btn-outline-info"
[disabled]="undoCount() < 1" (click)="undo()">
if (!this.record) { return Promise.resolve(); }
return Promise.all([
- this.tagTable.loadTagTable({marcRecordType: this.context.recordType}),
- this.tagTable.getFfPosTable(this.record.recordType()),
- this.tagTable.getFfValueTable(this.record.recordType()),
+ this.tagTable.loadTags({
+ marcRecordType: this.context.recordType,
+ ffType: this.record.recordType()
+ }).then(table => this.context.tagTable = table),
this.tagTable.getControlledBibTags().then(
- tags => this.controlledBibTags = tags)
+ tags => this.controlledBibTags = tags),
+ this.fetchSettings()
]).then(_ =>
// setTimeout forces all of our sub-components to rerender
// themselves each time init() is called. Without this,
);
}
+ fetchSettings(): Promise<any> {
+ // Fetch at rich editor load time to cache.
+ return this.org.settings(['cat.marc_control_number_identifier']);
+ }
+
stackSubfieldsChange() {
if (this.stackSubfields) {
this.store.setItem('cat.marcedit.stack_subfields', true);
validate() {
const fields = [];
- this.record.fields.filter(f => this.isControlledBibTag(f.tag))
+ this.record.fields.filter(f => this.isControlledBibTag(f.tag))
.forEach(f => {
f.authValid = false;
fields.push({
tag: f.tag,
ind1: f.ind1,
ind2: f.ind2,
- subfields: f.subfields.map(sf => ({code: sf[0], value: sf[1]}))
+ subfields: f.subfields.map(sf => [sf[0], sf[1]])
});
});
openLinkerDialog(field: MarcField) {
this.authLinker.bibField = field;
- this.authLinker.open({size: 'lg'}).subscribe(newField => {
+ this.authLinker.open({size: 'xl'}).subscribe(newField => {
if (!newField) { return; }
// Performs an insert followed by a delete, so the two
import {AuthService} from '@eg/core/auth.service';
import {NetService} from '@eg/core/net.service';
import {PcrudService} from '@eg/core/pcrud.service';
-import {EventService} from '@eg/core/event.service';
import {ContextMenuEntry} from '@eg/share/context-menu/context-menu.service';
+const DEFAULT_MARC_FORMAT = 'marc21';
+
interface TagTableSelector {
marcFormat?: string;
- marcRecordType?: string;
+ marcRecordType: 'biblio' | 'authority' | 'serial';
+
+ // MARC record fixed field "Type" value.
+ ffType: string;
}
-const defaultTagTableSelector: TagTableSelector = {
- marcFormat : 'marc21',
- marcRecordType : 'biblio'
-};
+export class TagTable {
-@Injectable()
-export class TagTableService {
+ store: StoreService;
+ auth: AuthService;
+ net: NetService;
+ pcrud: PcrudService;
- // Current set of tags in list and map form.
- tagMap: {[tag: string]: any} = {};
- ffPosMap: {[rtype: string]: any[]} = {};
- ffValueMap: {[rtype: string]: any} = {};
- controlledBibTags: string[];
+ selector: TagTableSelector;
- extractedValuesCache:
- {[valueType: string]: {[which: string]: any}} = {};
+ // Current set of tags in list and map form.
+ tagMap: {[tag: string]: any};
+ ffPosTable: any;
+ ffValueTable: any;
+ fieldTags: ContextMenuEntry[];
+
+ // Cache of compiled, sorted, munged data. Stuff the UI requests
+ // frequently for selectors, etc.
+ cache: {[valueType: string]: {[which: string]: any}} = {
+ indicators: {},
+ sfcodes: {},
+ sfvalues: {},
+ ffvalues: {}
+ };
constructor(
- private store: StoreService,
- private auth: AuthService,
- private net: NetService,
- private pcrud: PcrudService,
- private evt: EventService
+ store: StoreService,
+ auth: AuthService,
+ net: NetService,
+ pcrud: PcrudService,
+ selector: TagTableSelector
) {
+ this.store = store;
+ this.auth = auth;
+ this.net = net;
+ this.pcrud = pcrud;
+ this.selector = selector;
+ }
+
- this.extractedValuesCache = {
- fieldtags: {},
- indicators: {},
- sfcodes: {},
- sfvalues: {},
- ffvalues: {}
- };
+ load(): Promise<any> {
+ return Promise.all([
+ this.loadTagTable(),
+ this.getFfPosTable(),
+ this.getFfValueTable(),
+ ]);
}
// Various data needs munging for display. Cached the modified
// values since they are refernced repeatedly by the UI code.
fromCache(dataType: string, which?: string, which2?: string): ContextMenuEntry[] {
- const part1 = this.extractedValuesCache[dataType][which];
+ const part1 = this.cache[dataType][which];
if (which2) {
if (part1) {
return part1[which2];
toCache(dataType: string, which: string,
which2: string, values: ContextMenuEntry[]): ContextMenuEntry[] {
- const base = this.extractedValuesCache[dataType];
+ const base = this.cache[dataType];
const part1 = base[which];
if (which2) {
return values;
}
- getFfPosTable(rtype: string): Promise<any> {
- const storeKey = 'FFPosTable_' + rtype;
+ getFfPosTable(): Promise<any> {
+ const storeKey = 'FFPosTable_' + this.selector.ffType;
- if (this.ffPosMap[rtype]) {
- return Promise.resolve(this.ffPosMap[rtype]);
+ if (this.ffPosTable) {
+ return Promise.resolve(this.ffPosTable);
}
- this.ffPosMap[rtype] = this.store.getLocalItem(storeKey);
+ this.ffPosTable = this.store.getLocalItem(storeKey);
- if (this.ffPosMap[rtype]) {
- return Promise.resolve(this.ffPosMap[rtype]);
+ if (this.ffPosTable) {
+ return Promise.resolve(this.ffPosTable);
}
return this.net.request(
'open-ils.fielder', 'open-ils.fielder.cmfpm.atomic',
- {query: {tag: {'!=' : '006'}, rec_type: rtype}}
+ {query: {tag: {'!=' : '006'}, rec_type: this.selector.ffType}}
).toPromise().then(table => {
this.store.setLocalItem(storeKey, table);
- return this.ffPosMap[rtype] = table;
+ return this.ffPosTable = table;
});
}
- getFfValueTable(rtype: string): Promise<any> {
+ // ffType is the fixed field Type value. BKS, AUT, etc.
+ // See config.marc21_rec_type_map
+ getFfValueTable(): Promise<any> {
- const storeKey = 'FFValueTable_' + rtype;
+ const storeKey = 'FFValueTable_' + this.selector.ffType;
- if (this.ffValueMap[rtype]) {
- return Promise.resolve(this.ffValueMap[rtype]);
+ if (this.ffValueTable) {
+ return Promise.resolve(this.ffValueTable);
}
- this.ffValueMap[rtype] = this.store.getLocalItem(storeKey);
+ this.ffValueTable = this.store.getLocalItem(storeKey);
- if (this.ffValueMap[rtype]) {
- return Promise.resolve(this.ffValueMap[rtype]);
+ if (this.ffValueTable) {
+ return Promise.resolve(this.ffValueTable);
}
return this.net.request(
'open-ils.cat',
- 'open-ils.cat.biblio.fixed_field_values.by_rec_type', rtype
+ 'open-ils.cat.biblio.fixed_field_values.by_rec_type',
+ this.selector.ffType
).toPromise().then(table => {
this.store.setLocalItem(storeKey, table);
- return this.ffValueMap[rtype] = table;
+ return this.ffValueTable = table;
});
}
- loadTagTable(selector?: TagTableSelector): Promise<any> {
+ loadTagTable(): Promise<any> {
- if (selector) {
- if (!selector.marcFormat) {
- selector.marcFormat = defaultTagTableSelector.marcFormat;
- }
- if (!selector.marcRecordType) {
- selector.marcRecordType =
- defaultTagTableSelector.marcRecordType;
- }
- } else {
- selector = defaultTagTableSelector;
- }
+ const sel = this.selector;
const cacheKey =
- `current_tag_table_${selector.marcFormat}_${selector.marcRecordType}`;
+ `current_tag_table_${sel.marcFormat}_${sel.marcRecordType}`;
this.tagMap = this.store.getLocalItem(cacheKey);
return Promise.resolve(this.tagMap);
}
- return this.fetchTagTable(selector).then(_ => {
+ return this.fetchTagTable().then(_ => {
this.store.setLocalItem(cacheKey, this.tagMap);
return this.tagMap;
});
}
- fetchTagTable(selector?: TagTableSelector): Promise<any> {
+ fetchTagTable(): Promise<any> {
this.tagMap = [];
return this.net.request(
'open-ils.cat',
'open-ils.cat.tag_table.all.retrieve.local',
- this.auth.token(), selector.marcFormat, selector.marcRecordType
+ this.auth.token(), this.selector.marcFormat,
+ this.selector.marcRecordType
).pipe(tap(tagData => {
this.tagMap[tagData.tag] = tagData;
})).toPromise();
getFieldTags(): ContextMenuEntry[] {
- const cached = this.fromCache('fieldtags');
- if (cached) { return cached; }
+ if (!this.fieldTags) {
+ this.fieldTags = Object.keys(this.tagMap)
+ .filter(tag => Boolean(this.tagMap[tag]))
+ .map(tag => ({
+ value: tag,
+ label: `${tag}: ${this.tagMap[tag].name}`
+ }))
+ .sort((a, b) => a.label < b.label ? -1 : 1);
+ }
- return Object.keys(this.tagMap)
- .filter(tag => Boolean(this.tagMap[tag]))
- .map(tag => ({
- value: tag,
- label: `${tag}: ${this.tagMap[tag].name}`
- }))
- .sort((a, b) => a.label < b.label ? -1 : 1);
+ return this.fieldTags;
}
getSubfieldValues(tag: string, sfCode: string): ContextMenuEntry[] {
}
- getFfFieldMeta(fieldCode: string, recordType: string): Promise<IdlObject> {
- return this.getFfPosTable(recordType).then(table => {
+ getFfFieldMeta(fieldCode: string): Promise<IdlObject> {
+ return this.getFfPosTable().then(table => {
- // Note the AngJS MARC editor stores the full POS table
- // for all record types in every copy of the table, hence
- // the seemingly extraneous check in recordType.
+ // Best I can tell, the AngJS MARC editor stores the
+ // full POS table for all record types in every copy of
+ // the table, hence the seemingly extraneous check in ffType.
return table.filter(
field =>
field.fixed_field === fieldCode
- && field.rec_type === recordType
+ && field.rec_type === this.selector.ffType
)[0];
});
}
// Assumes getFfPosTable and getFfValueTable have already been
- // invoked for the request record type.
- getFfValues(fieldCode: string, recordType: string): ContextMenuEntry[] {
+ // invoked for the requested record type.
+ getFfValues(fieldCode: string): ContextMenuEntry[] {
- const cached = this.fromCache('ffvalues', recordType, fieldCode);
+ const cached = this.fromCache('ffvalues', fieldCode);
if (cached) { return cached; }
- let values = this.ffValueMap[recordType];
+ let values = this.ffValueTable;
if (!values || !values[fieldCode]) { return null; }
.map(val => ({value: val[0], label: `${val[0]}: ${val[1]}`}))
.sort((a, b) => a.label < b.label ? -1 : 1);
- return this.toCache('ffvalues', recordType, fieldCode, values);
+ return this.toCache('ffvalues', fieldCode, null, values);
+ }
+
+}
+
+@Injectable()
+export class TagTableService {
+
+ tagTables: {[marcRecordType: string]: TagTable} = {};
+ controlledBibTags: string[];
+
+ constructor(
+ private store: StoreService,
+ private auth: AuthService,
+ private net: NetService,
+ private pcrud: PcrudService,
+ ) {}
+
+ loadTags(selector: TagTableSelector): Promise<TagTable> {
+ if (!selector.marcFormat) {
+ selector.marcFormat = DEFAULT_MARC_FORMAT;
+ }
+
+ // Tag tables of a given marc record type are identical.
+ if (this.tagTables[selector.marcRecordType]) {
+ return Promise.resolve(this.tagTables[selector.marcRecordType]);
+ }
+
+ const tt = new TagTable(
+ this.store, this.auth, this.net, this.pcrud, selector);
+
+ this.tagTables[selector.marcRecordType] = tt;
+
+ return tt.load().then(_ => tt);
}
getControlledBibTags(): Promise<string[]> {
color: black;
}
+/* Allow for larger XL dialogs */
+@media (min-width: 1300px) { .modal-xl { max-width: 1200px; } }
+@media (min-width: 1600px) { .modal-xl { max-width: 1500px; } }
+@media (min-width: 1700px) { .modal-xl { max-width: 1600px; } }
+
);
# Returns the first found field.
-sub get_auth_field {
+sub get_auth_field_by_tag {
my ($atag, $cset_id) = @_;
my $e = new_editor();
# not consistent with the control set, it may produce unexpected
# results.
my $sf_list = '';
- my $acsaf = get_auth_field($atag, $cset_id);
+ my $acsaf = get_auth_field_by_tag($atag, $cset_id);
if ($acsaf) {
$sf_list = $acsaf->sf_list;
# Handle 4XX and 5XX
(my $alt_atag = $atag) =~ s/^./1/;
- $acsaf = get_auth_field($alt_atag, $cset_id) if $alt_atag ne $atag;
+ $acsaf = get_auth_field_by_tag($alt_atag, $cset_id) if $alt_atag ne $atag;
$sf_list = $acsaf->sf_list if $acsaf;
}
acsaf => ['id', 'tag', 'sf_list', 'control_set']
},
from => {acsbf => {acsaf => {}}},
- where => $where
+ where => $where,
+ order_by => [
+ {class => 'acsaf', field => 'main_entry', direction => 'desc'},
+ {class => 'acsaf', field => 'tag'}
+ ]
});
my @seen_subfields;
$record->append_fields($field);
my $match = $U->simplereq(
- 'open-ils.cat',
- 'open-ils.cat.authority.simple_heading.from_xml',
+ 'open-ils.search',
+ 'open-ils.search.authority.simple_heading.from_xml',
$record->as_xml_record, $control_set);
if ($match) {
return [] unless $bib_field;
- my $term = join(' ', map {$_->[0]} @{$bib_field->{subfields}});
+ my $term = join(' ', map {$_->[1]} @{$bib_field->{subfields}});
return [] unless $term;
my $axis = $e->json_query({
select => {abaafm => ['axis']},
from => {acsbf => {acsaf => {join => 'abaafm'}}},
- where => {'+acsbf' => {tag => $bib_field->{tag}}}
+ where => {'+acsbf' => {tag => $bib_field->{tag}}},
+ order_by => [
+ {class => 'acsaf', field => 'main_entry', direction => 'desc'},
+ {class => 'acsaf', field => 'tag'},
+
+ # This lets us favor the 'subject' axis to the 'topic' axis.
+ # Topic is a subset of subject. It's not clear if a field
+ # can link only to the 'topic' axes. In stock EG, the one
+ # 'topic' field also links to 'subject'.
+ {class => 'abaafm', field => 'axis'}
+ ]
})->[0];
return [] unless $axis && ($axis = $axis->{axis});