From 4bea10e45c6b1eeea1e42d08a1534a6f199458b8 Mon Sep 17 00:00:00 2001 From: Bill Erickson Date: Fri, 15 Mar 2019 17:01:13 -0400 Subject: [PATCH] LP1821382 Angular staff catalog Holdings Maintenance Signed-off-by: Bill Erickson Signed-off-by: Dan Wells --- .../src/eg2/src/app/core/server-store.service.ts | 16 + .../app/share/grid/grid-body-cell.component.html | 4 +- .../src/app/share/grid/grid-column.component.ts | 3 + .../share/grid/grid-toolbar-checkbox.component.ts | 37 +- .../src/eg2/src/app/share/grid/grid.component.html | 18 +- .../src/eg2/src/app/share/grid/grid.component.ts | 6 + Open-ILS/src/eg2/src/app/share/grid/grid.ts | 11 +- .../eg2/src/app/staff/catalog/catalog.module.ts | 2 + .../staff/catalog/record/holdings.component.html | 94 +++++ .../app/staff/catalog/record/holdings.component.ts | 440 +++++++++++++++++++++ .../app/staff/catalog/record/record.component.html | 9 +- .../eg2/src/app/staff/catalog/resolver.service.ts | 9 +- 12 files changed, 624 insertions(+), 25 deletions(-) create mode 100644 Open-ILS/src/eg2/src/app/staff/catalog/record/holdings.component.html create mode 100644 Open-ILS/src/eg2/src/app/staff/catalog/record/holdings.component.ts diff --git a/Open-ILS/src/eg2/src/app/core/server-store.service.ts b/Open-ILS/src/eg2/src/app/core/server-store.service.ts index ea2d93da36..b54a4c9a87 100644 --- a/Open-ILS/src/eg2/src/app/core/server-store.service.ts +++ b/Open-ILS/src/eg2/src/app/core/server-store.service.ts @@ -60,6 +60,22 @@ export class ServerStoreService { ); } + // Sync call for items known to be cached locally. + getItemCached(key: string): any { + return this.cache[key]; + } + + // Sync batch call for items known to be cached locally + getItemBatchCached(keys: string[]): {[key: string]: any} { + const values: any = {}; + keys.forEach(key => { + if (key in this.cache) { + values[key] = this.cache[key]; + } + }); + return values; + } + // Returns a set of key/value pairs for the requested settings getItemBatch(keys: string[]): Promise { diff --git a/Open-ILS/src/eg2/src/app/share/grid/grid-body-cell.component.html b/Open-ILS/src/eg2/src/app/share/grid/grid-body-cell.component.html index 578bef5e9c..6dc4a99b56 100644 --- a/Open-ILS/src/eg2/src/app/share/grid/grid-body-cell.component.html +++ b/Open-ILS/src/eg2/src/app/share/grid/grid-body-cell.component.html @@ -1,6 +1,6 @@ @@ -8,7 +8,7 @@ ; + @Input() disableTooltip: boolean; + // get a reference to our container grid. constructor(@Host() private grid: GridComponent) {} @@ -50,6 +52,7 @@ export class GridColumnComponent implements OnInit { col.isIndex = this.index === true; col.cellTemplate = this.cellTemplate; col.cellContext = this.cellContext; + col.disableTooltip = this.disableTooltip; col.isSortable = this.sortable; col.isMultiSortable = this.multiSortable; col.datatype = this.datatype; diff --git a/Open-ILS/src/eg2/src/app/share/grid/grid-toolbar-checkbox.component.ts b/Open-ILS/src/eg2/src/app/share/grid/grid-toolbar-checkbox.component.ts index 7ee3019477..24b7616e86 100644 --- a/Open-ILS/src/eg2/src/app/share/grid/grid-toolbar-checkbox.component.ts +++ b/Open-ILS/src/eg2/src/app/share/grid/grid-toolbar-checkbox.component.ts @@ -16,29 +16,48 @@ export class GridToolbarCheckboxComponent implements OnInit { // This does NOT fire the onChange handler. @Input() initialValue: boolean; - // This is an input instead of an Output because the handler is - // passed off to the grid context for maintenance -- events - // are not fired directly from this component. @Output() onChange: EventEmitter; + private cb: GridToolbarCheckbox; + // get a reference to our container grid. constructor(@Host() private grid: GridComponent) { this.onChange = new EventEmitter(); + + // Create in constructor so we can accept values before the + // grid is fully rendered. + this.cb = new GridToolbarCheckbox(); + this.cb.isChecked = null; + this.initialValue = null; } ngOnInit() { - if (!this.grid) { console.warn('GridToolbarCheckboxComponent needs a [grid]'); return; } - const cb = new GridToolbarCheckbox(); - cb.label = this.label; - cb.onChange = this.onChange; - cb.isChecked = this.initialValue; + this.cb.label = this.label; + this.cb.onChange = this.onChange; + + if (this.cb.isChecked === null && this.initialValue !== null) { + this.cb.isChecked = this.initialValue; + } + + this.grid.context.toolbarCheckboxes.push(this.cb); + } - this.grid.context.toolbarCheckboxes.push(cb); + // Toggle the value. onChange is not fired. + toggle() { + this.cb.isChecked = !this.cb.isChecked; + } + + // Set/get the value. onChange is not fired. + checked(value?: boolean): boolean { + if (value === true || value === false) { + this.cb.isChecked = value; + } + return this.cb.isChecked; } } diff --git a/Open-ILS/src/eg2/src/app/share/grid/grid.component.html b/Open-ILS/src/eg2/src/app/share/grid/grid.component.html index 77ea0e6fb1..dd462465b0 100644 --- a/Open-ILS/src/eg2/src/app/share/grid/grid.component.html +++ b/Open-ILS/src/eg2/src/app/share/grid/grid.component.html @@ -15,12 +15,20 @@ - -
-
- Nothing to Display + +
> + +
+ +
+
+ +
+ Nothing to Display +
+
-
+
diff --git a/Open-ILS/src/eg2/src/app/share/grid/grid.component.ts b/Open-ILS/src/eg2/src/app/share/grid/grid.component.ts index 66686ef2e8..25b8d0d2cb 100644 --- a/Open-ILS/src/eg2/src/app/share/grid/grid.component.ts +++ b/Open-ILS/src/eg2/src/app/share/grid/grid.component.ts @@ -79,6 +79,8 @@ export class GridComponent implements OnInit, AfterViewInit, OnDestroy { // Allow the caller to jump directly to a specific page of // grid data. @Input() pageOffset: number; + // Pass in a default page size. May be overridden by settings. + @Input() pageSize: number; // If true and an idlClass is specificed, the grid assumes // datatype=link fields that link to classes which define a selector @@ -141,6 +143,10 @@ export class GridComponent implements OnInit, AfterViewInit, OnDestroy { this.context.pager.offset = this.pageOffset; } + if (this.pageSize) { + this.context.pager.limit = this.pageSize; + } + // TS doesn't seem to like: let foo = bar || () => ''; this.context.rowClassCallback = this.rowClassCallback || function () { return ''; }; diff --git a/Open-ILS/src/eg2/src/app/share/grid/grid.ts b/Open-ILS/src/eg2/src/app/share/grid/grid.ts index fbc020895d..8017938ff6 100644 --- a/Open-ILS/src/eg2/src/app/share/grid/grid.ts +++ b/Open-ILS/src/eg2/src/app/share/grid/grid.ts @@ -32,6 +32,7 @@ export class GridColumn { isDragTarget: boolean; isSortable: boolean; isMultiSortable: boolean; + disableTooltip: boolean; comparator: (valueA: any, valueB: any) => number; // True if the column was automatically generated. @@ -1033,6 +1034,7 @@ export class GridDataSource { data: any[]; sort: any[]; allRowsRetrieved: boolean; + requestingData: boolean; getRows: (pager: Pager, sort: any[]) => Observable; constructor() { @@ -1068,16 +1070,23 @@ export class GridDataSource { return Promise.resolve(); } + // If we have to call out for data, set inFetch + this.requestingData = true; + return new Promise((resolve, reject) => { let idx = pager.offset; return this.getRows(pager, this.sort).subscribe( - row => this.data[idx++] = row, + row => { + this.data[idx++] = row; + this.requestingData = false; + }, err => { console.error(`grid getRows() error ${err}`); reject(err); }, () => { this.checkAllRetrieved(pager, idx); + this.requestingData = false; resolve(); } ); diff --git a/Open-ILS/src/eg2/src/app/staff/catalog/catalog.module.ts b/Open-ILS/src/eg2/src/app/staff/catalog/catalog.module.ts index b158ac1442..1855000f60 100644 --- a/Open-ILS/src/eg2/src/app/staff/catalog/catalog.module.ts +++ b/Open-ILS/src/eg2/src/app/staff/catalog/catalog.module.ts @@ -21,6 +21,7 @@ import {PartsComponent} from './record/parts.component'; import {PartMergeDialogComponent} from './record/part-merge-dialog.component'; import {BrowseComponent} from './browse.component'; import {BrowseResultsComponent} from './browse/results.component'; +import {HoldingsMaintenanceComponent} from './record/holdings.component'; @NgModule({ declarations: [ @@ -40,6 +41,7 @@ import {BrowseResultsComponent} from './browse/results.component'; PartMergeDialogComponent, BrowseComponent, BrowseResultsComponent, + HoldingsMaintenanceComponent ], imports: [ StaffCommonModule, diff --git a/Open-ILS/src/eg2/src/app/staff/catalog/record/holdings.component.html b/Open-ILS/src/eg2/src/app/staff/catalog/record/holdings.component.html new file mode 100644 index 0000000000..3cfcb273a5 --- /dev/null +++ b/Open-ILS/src/eg2/src/app/staff/catalog/record/holdings.component.html @@ -0,0 +1,94 @@ + + + + + + + + + + Yes + + No + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ diff --git a/Open-ILS/src/eg2/src/app/staff/catalog/record/holdings.component.ts b/Open-ILS/src/eg2/src/app/staff/catalog/record/holdings.component.ts new file mode 100644 index 0000000000..0f9e4ad990 --- /dev/null +++ b/Open-ILS/src/eg2/src/app/staff/catalog/record/holdings.component.ts @@ -0,0 +1,440 @@ +import {Component, OnInit, Input, ViewChild} from '@angular/core'; +import {Observable, Observer, of} from 'rxjs'; +import {map} from 'rxjs/operators'; +import {Pager} from '@eg/share/util/pager'; +import {IdlObject} from '@eg/core/idl.service'; +import {NetService} from '@eg/core/net.service'; +import {StaffCatalogService} from '../catalog.service'; +import {OrgService} from '@eg/core/org.service'; +import {AuthService} from '@eg/core/auth.service'; +import {PcrudService} from '@eg/core/pcrud.service'; +import {GridDataSource} from '@eg/share/grid/grid'; +import {GridComponent} from '@eg/share/grid/grid.component'; +import {GridToolbarCheckboxComponent} from '@eg/share/grid/grid-toolbar-checkbox.component'; +import {ServerStoreService} from '@eg/core/server-store.service'; + + +// The holdings grid models a single HoldingsTree, composed of HoldingsTreeNodes +// flattened on-demand into a list of HoldingEntry objects. +class HoldingsTreeNode { + children: HoldingsTreeNode[]; + nodeType: 'org' | 'volume' | 'copy'; + target: any; + parentNode: HoldingsTreeNode; + expanded: boolean; + copyCount: number; + volumeCount: number; + constructor() { + this.children = []; + } +} + +class HoldingsTree { + root: HoldingsTreeNode; + constructor() { + this.root = new HoldingsTreeNode(); + } +} + +class HoldingsEntry { + index: number; + // org unit shortname, call number label, or copy barcode + locationLabel: string; + // location label indentation depth + locationDepth: number | null; + volumeCount: number | null; + copyCount: number | null; + callNumberLabel: string; + copy: IdlObject; + volume: IdlObject; + circ: IdlObject; + treeNode: HoldingsTreeNode; +} + +@Component({ + selector: 'eg-holdings-maintenance', + templateUrl: 'holdings.component.html' +}) +export class HoldingsMaintenanceComponent implements OnInit { + + recId: number; + initDone = false; + gridDataSource: GridDataSource; + gridTemplateContext: any; + @ViewChild('holdingsGrid') holdingsGrid: GridComponent; + + // Manage visibility of various sub-sections + @ViewChild('volsCheckbox') volsCheckbox: GridToolbarCheckboxComponent; + @ViewChild('copiesCheckbox') copiesCheckbox: GridToolbarCheckboxComponent; + @ViewChild('emptyVolsCheckbox') emptyVolsCheckbox: GridToolbarCheckboxComponent; + @ViewChild('emptyLibsCheckbox') emptyLibsCheckbox: GridToolbarCheckboxComponent; + + contextOrg: IdlObject; + holdingsTree: HoldingsTree; + holdingsTreeOrgCache: {[id: number]: HoldingsTreeNode}; + refreshHoldings: boolean; + gridIndex: number; + + // List of copies whose due date we need to retrieve. + itemCircsNeeded: IdlObject[]; + + // When true draw the grid based on the stored preferences. + // When not true, render based on the current "expanded" state of each node. + // Rendering from prefs happens on initial load and when any prefs change. + renderFromPrefs: boolean; + rowClassCallback: (row: any) => string; + + @Input() set recordId(id: number) { + this.recId = id; + // Only force new data collection when recordId() + // is invoked after ngInit() has already run. + if (this.initDone) { + this.refreshHoldings = true; + this.holdingsGrid.reload(); + } + } + + constructor( + private net: NetService, + private org: OrgService, + private auth: AuthService, + private pcrud: PcrudService, + private staffCat: StaffCatalogService, + private store: ServerStoreService + ) { + // Set some sane defaults before settings are loaded. + this.contextOrg = this.org.get(this.auth.user().ws_ou()); + this.gridDataSource = new GridDataSource(); + this.refreshHoldings = true; + this.renderFromPrefs = true; + + this.rowClassCallback = (row: any): string => { + if (row.volume && !row.copy) { + return 'bg-info'; + } + } + + this.gridTemplateContext = { + toggleExpandRow: (row: HoldingsEntry) => { + row.treeNode.expanded = !row.treeNode.expanded; + + if (!row.treeNode.expanded) { + // When collapsing a node, all child nodes should be + // collapsed as well. + const traverse = (node: HoldingsTreeNode) => { + node.expanded = false; + node.children.forEach(traverse); + } + traverse(row.treeNode); + } + + this.holdingsGrid.reload(); + }, + + copyIsHoldable: (copy: IdlObject): boolean => { + return copy.holdable() === 't' + && copy.location().holdable() === 't' + && copy.status().holdable() === 't'; + } + } + } + + ngOnInit() { + this.initDone = true; + + // These are pre-cached via the resolver. + const settings = this.store.getItemBatchCached([ + 'cat.holdings_show_empty_org', + 'cat.holdings_show_empty', + 'cat.holdings_show_copies', + 'cat.holdings_show_vols' + ]); + + this.volsCheckbox.checked(settings['cat.holdings_show_vols']); + this.copiesCheckbox.checked(settings['cat.holdings_show_copies']); + this.emptyVolsCheckbox.checked(settings['cat.holdings_show_empty']); + this.emptyLibsCheckbox.checked(settings['cat.holdings_show_empty_org']); + + this.gridDataSource.getRows = (pager: Pager, sort: any[]) => { + return this.fetchHoldings(pager); + }; + } + + ngAfterViewInit() { + + } + + toggleShowCopies(value: boolean) { + this.store.setItem('cat.holdings_show_copies', value); + if (value) { + // Showing copies implies showing volumes + this.volsCheckbox.checked(true); + } + this.renderFromPrefs = true; + this.holdingsGrid.reload(); + } + + toggleShowVolumes(value: boolean) { + this.store.setItem('cat.holdings_show_vols', value); + if (!value) { + // Hiding volumes implies hiding empty vols and copies. + this.copiesCheckbox.checked(false); + this.emptyVolsCheckbox.checked(false); + } + this.renderFromPrefs = true; + this.holdingsGrid.reload(); + } + + toggleShowEmptyVolumes(value: boolean) { + this.store.setItem('cat.holdings_show_empty', value); + if (value) { + this.volsCheckbox.checked(true); + } + this.renderFromPrefs = true; + this.holdingsGrid.reload(); + } + + toggleShowEmptyLibs(value: boolean) { + this.store.setItem('cat.holdings_show_empty_org', value); + this.renderFromPrefs = true; + this.holdingsGrid.reload(); + } + + onRowActivate(row: any) { + if (row.copy) { + // Launch copy editor? + } else { + this.gridTemplateContext.toggleExpandRow(row); + } + } + + initHoldingsTree() { + + // The initial tree simply matches the org unit tree + const traverseOrg = (node: HoldingsTreeNode) => { + node.expanded = true; + node.target.children().forEach((org: IdlObject) => { + const nodeChild = new HoldingsTreeNode(); + nodeChild.nodeType = 'org'; + nodeChild.target = org; + nodeChild.parentNode = node; + node.children.push(nodeChild); + this.holdingsTreeOrgCache[org.id()] = nodeChild; + traverseOrg(nodeChild); + }); + } + + this.holdingsTree = new HoldingsTree(); + this.holdingsTree.root.nodeType = 'org'; + this.holdingsTree.root.target = this.org.root(); + + this.holdingsTreeOrgCache = {}; + this.holdingsTreeOrgCache[this.org.root().id()] = this.holdingsTree.root; + + traverseOrg(this.holdingsTree.root); + } + + // Org node children are sorted with any child org nodes pushed to the + // front, followed by the call number nodes sorted alphabetcially by label. + // TODO: prefix/suffix + sortOrgNodeChildren(node: HoldingsTreeNode) { + node.children = node.children.sort((a, b) => { + if (a.nodeType === 'org') { + if (b.nodeType === 'org') { + return a.target.shortname() < b.target.shortname() ? -1 : 1; + } else { + return -1; + } + } else if (b.nodeType === 'org') { + return 1; + } else { + return a.target.label() < b.target.label() ? -1 : 1; + } + }); + } + + // Sets call number and copy count sums to nodes that need it. + // Applies the initial expansed state of each container node. + setTreeCounts(node: HoldingsTreeNode) { + + if (node.nodeType === 'org') { + node.copyCount = 0; + node.volumeCount = 0; + } else if(node.nodeType === 'volume') { + node.copyCount = 0; + } + + let hasChildOrgWithData = false; + let hasChildOrgSansData = false; + node.children.forEach(child => { + this.setTreeCounts(child); + if (node.nodeType === 'org') { + node.copyCount += child.copyCount; + if (child.nodeType === 'volume') { + node.volumeCount++; + } else { + hasChildOrgWithData = child.volumeCount > 0; + hasChildOrgSansData = child.volumeCount === 0; + node.volumeCount += child.volumeCount; + } + } else if (node.nodeType === 'volume') { + node.copyCount = node.children.length; + if (this.renderFromPrefs) { + node.expanded = this.copiesCheckbox.checked(); + } + } + }); + + if (this.renderFromPrefs && node.nodeType === 'org') { + if (node.copyCount > 0 && this.volsCheckbox.checked()) { + node.expanded = true; + } else if (node.volumeCount > 0 && this.emptyVolsCheckbox.checked()) { + node.expanded = true; + } else if (hasChildOrgWithData) { + node.expanded = true; + } else if (hasChildOrgSansData && this.emptyLibsCheckbox.checked()) { + node.expanded = true; + } else { + node.expanded = false; + } + } + } + + // Create HoldingsEntry objects for tree nodes that should be displayed + // and relays them to the grid via the observer. + propagateTreeEntries(observer: Observer, node: HoldingsTreeNode) { + const entry = new HoldingsEntry(); + entry.treeNode = node; + entry.index = this.gridIndex++; + + switch(node.nodeType) { + case 'org': + if (this.renderFromPrefs && node.volumeCount === 0 + && !this.emptyLibsCheckbox.checked()) { + return; + } + entry.locationLabel = node.target.shortname(); + entry.locationDepth = node.target.ou_type().depth(); + entry.copyCount = node.copyCount; + entry.volumeCount = node.volumeCount; + this.sortOrgNodeChildren(node); + break; + + case 'volume': + entry.locationLabel = node.target.label(); // TODO prefix/suffix + entry.locationDepth = node.parentNode.target.ou_type().depth() + 1; + entry.callNumberLabel = entry.locationLabel; + entry.volume = node.target; + entry.copyCount = node.copyCount; + break; + + case 'copy': + entry.locationLabel = node.target.barcode(); + entry.locationDepth = node.parentNode.parentNode.target.ou_type().depth() + 2; + entry.callNumberLabel = node.parentNode.target.label() // TODO + entry.volume = node.parentNode.target; + entry.copy = node.target; + entry.circ = node.target._circ; + break; + } + + // Tell the grid about the node entry + observer.next(entry); + + if (node.expanded) { + // Process the child nodes. + node.children.forEach(child => + this.propagateTreeEntries(observer, child)); + } + } + + // Turns the tree into a list of entries for grid display + flattenHoldingsTree(observer: Observer) { + this.gridIndex = 0; + this.setTreeCounts(this.holdingsTree.root); + this.propagateTreeEntries(observer, this.holdingsTree.root); + observer.complete(); + this.renderFromPrefs = false; + } + + + fetchHoldings(pager: Pager): Observable { + if (!this.recId) { return of([]); } + + return new Observable(observer => { + + if (!this.refreshHoldings) { + this.flattenHoldingsTree(observer); + return; + } + + this.initHoldingsTree(); + this.itemCircsNeeded = []; + + this.pcrud.search('acn', + { record: this.recId, + owning_lib: this.org.ancestors(this.contextOrg, true), + deleted: 'f', + label: {'!=' : '##URI##'} + }, { + flesh: 3, + flesh_fields: { + acp: ['status', 'location', 'circ_lib', 'parts', + 'age_protect', 'copy_alerts', 'latest_inventory'], + acn: ['prefix', 'suffix', 'copies'], + acli: ['inventory_workstation'] + } + } + ).subscribe( + vol => this.appendVolume(vol), + err => {}, + () => { + this.refreshHoldings = false; + this.fetchCircs().then( + ok => this.flattenHoldingsTree(observer) + ); + } + ); + }); + } + + // Retrieve circulation objects for checked out items. + fetchCircs(): Promise { + const copyIds = this.itemCircsNeeded.map(copy => copy.id()); + if (copyIds.length === 0) { return Promise.resolve(); } + + return this.pcrud.search('circ', { + target_copy: copyIds, + checkin_time: null + }).pipe(map(circ => { + const copy = this.itemCircsNeeded.filter( + c => Number(c.id()) === Number(circ.target_copy()))[0]; + copy._circ = circ; + })).toPromise(); + } + + appendVolume(volume: IdlObject) { + + const volNode = new HoldingsTreeNode(); + volNode.parentNode = this.holdingsTreeOrgCache[volume.owning_lib()]; + volNode.parentNode.children.push(volNode); + volNode.nodeType = 'volume'; + volNode.target = volume; + + volume.copies() + .sort((a: IdlObject, b: IdlObject) => a.barcode() < b.barcode() ? -1 : 1) + .forEach((copy: IdlObject) => { + const copyNode = new HoldingsTreeNode(); + copyNode.parentNode = volNode; + volNode.children.push(copyNode); + copyNode.nodeType = 'copy'; + copyNode.target = copy; + const stat = Number(copy.status().id()); + if (stat === 1 /* checked out */ || stat === 16 /* long overdue */) { + this.itemCircsNeeded.push(copy); + } + }); + } +} + + diff --git a/Open-ILS/src/eg2/src/app/staff/catalog/record/record.component.html b/Open-ILS/src/eg2/src/app/staff/catalog/record/record.component.html index ad75118182..b583cf7e71 100644 --- a/Open-ILS/src/eg2/src/app/staff/catalog/record/record.component.html +++ b/Open-ILS/src/eg2/src/app/staff/catalog/record/record.component.html @@ -66,13 +66,8 @@ -
- Holdings not yet implemented. See the - - AngularJS Holdings Tab. - -
+ +
diff --git a/Open-ILS/src/eg2/src/app/staff/catalog/resolver.service.ts b/Open-ILS/src/eg2/src/app/staff/catalog/resolver.service.ts index 7dde4b4635..f4f5d9719b 100644 --- a/Open-ILS/src/eg2/src/app/staff/catalog/resolver.service.ts +++ b/Open-ILS/src/eg2/src/app/staff/catalog/resolver.service.ts @@ -40,7 +40,14 @@ export class CatalogResolver implements Resolve> { return this.store.getItemBatch([ 'eg.search.search_lib', - 'eg.search.pref_lib' + 'eg.search.pref_lib', + 'cat.holdings_show_empty_org', + 'cat.holdings_show_empty', + 'cat.marcedit.stack_subfields', + 'cat.marcedit.flateditor', + 'eg.cat.record.summary.collapse', + 'cat.holdings_show_copies', + 'cat.holdings_show_vols' ]).then(settings => { this.staffCat.defaultSearchOrg = this.org.get(settings['eg.search.search_lib']); -- 2.11.0