let method = 'open-ils.search.biblio.multiclass.query';
if (ctx.isStaff) method += '.staff';
+ // Grab a few pages worth of IDs
+ let limit = Math.max(ctx.pager.limit, 100);
+
+ // TODO: reset context result IDs for all searches except for paging
+
return new Promise((resolve, reject) => {
this.net.request(
'open-ils.search', method, {
offset : ctx.pager.offset
}, fullQuery, true
).subscribe(result => {
- ctx.result = result;
- ctx.result.records = [];
- ctx.pager.resultCount = result.count;
+ this.applyResultData(ctx, result);
ctx.searchState = CatalogSearchState.COMPLETE;
resolve();
});
})
}
+ applyResultData(ctx: CatalogSearchContext, result: any): void {
+ ctx.result = result;
+ ctx.pager.resultCount = result.count;
+
+ // records[] tracks the current page of bib summaries.
+ result.records = [];
+
+ // If this is a new search, reset the result IDs collection.
+ if (this.lastFacetKey != result.facet_key) ctx.resultIds = [];
+
+ result.ids.forEach((blob, idx) => {ctx.addResultId(blob[0], idx)});
+ }
+
fetchBibSummaries(ctx: CatalogSearchContext): Promise<any> {
let promises = [];
- ctx.result.ids.forEach((blob, idx) => {
+ let depth = ctx.global ?
+ ctx.org.root().ou_type().depth() :
+ ctx.searchOrg.ou_type().depth();
+
+ ctx.currentResultIds().forEach((recId, idx) => {
promises.push(
- this.getBibSummary(blob[0],
- ctx.searchOrg.id(),
- ctx.global ?
- ctx.org.root().ou_type().depth() :
- ctx.searchOrg.ou_type().depth()
- ).then(
+ this.getBibSummary(recId, ctx.searchOrg.id(), depth)
+ .then(
// idx maintains result sort order
summary => ctx.result.records[idx] = summary
)
result: any = {};
searchState: CatalogSearchState = CatalogSearchState.PENDING;
+ // List of IDs in page/offset context.
+ resultIds: number[] = [];
+
// Utility stuff
pager: Pager;
org: EgOrgService;
this.reset();
}
+ // List of result IDs for the current page of data.
+ currentResultIds(): number[] {
+ let ids = [];
+ for (
+ let idx = this.pager.offset;
+ idx < this.pager.offset + this.pager.limit;
+ idx++
+ ) {ids.push(this.resultIds[idx])}
+ return ids;
+ }
+
+ addResultId(id: number, resultIdx: number ): void {
+ this.resultIds[resultIdx + this.pager.offset] = id;
+ }
+
+ // Return the record at the requested index.
+ resultIdAt(index: number): number {
+ return this.resultIds[index] || null;
+ }
+
+ // Return the index of the requested record
+ indexForResult(id: number): number {
+ for (let i = 0; i < this.resultIds.length; i++) {
+ if (this.resultIds[i] == id)
+ return i;
+ }
+ return null;
+ }
+
/**
* Reset search parameters. This does not reset global filters
* like limit-to-available and search-global.
border-left: 1px solid rgba(0,0,0,0.2);
*/
}
+/*
+.eg-copies .pagination {margin: 0px 0px 0px 0px}
+*/
padding: .25rem;
}
</style>
-<div class='eg-copies card w-100'>
- <div class="card-header font-weight-bold d-flex bg-info">
- <div class="flex-1" i18n>Location</div>
- <div class="flex-1" i18n>Call Number / Copy Notes</div>
- <div class="flex-1" i18n>Barcode</div>
- <div class="flex-1" i18n>Shelving Location</div>
- <div class="flex-1" i18n>Circulation Modifier</div>
- <div class="flex-1" i18n>Age Hold Protection</div>
- <div class="flex-1" i18n>Active/Create Date</div>
- <div class="flex-1" i18n>Holdable?</div>
- <div class="flex-1" i18n>Status</div>
- <div class="flex-1" i18n>Due Date</div>
- </div>
- <div class="card-body">
- <ul class="list-group list-group-flush" *ngIf="copies && copies.length">
- <li class="list-group-item" *ngFor="let copy of copies">
- <div class="d-flex">
- <div class="flex-1" i18n>{{orgName(copy.circ_lib)}}</div>
- <div class="flex-1" i18n>
- {{copy.call_number_prefix_label}}
- {{copy.call_number_label}}
- {{copy.call_number_suffix_label}}
- </div>
- <div class="flex-1" i18n>
- {{copy.barcode}}
- <a class="pl-1" href="/eg/staff/cat/item/{{copy.id}}" i18n>View</a>
- |
- <a class="pl-1" href="/eg/staff/cat/item/{{copy.id}}/edit" i18n>Edit</a>
- </div>
- <div class="flex-1" i18n>{{copy.copy_location}}</div>
- <div class="flex-1" i18n>{{copy.circ_modifier || ''}}</div>
- <div class="flex-1" i18n>{{copy.age_protect}}</div>
- <div class="flex-1" i18n>
- {{copy.active_date || copy.create_date | date:'shortDate'}}
- </div>
- <div class="flex-1">
- <span *ngIf="holdable(copy)" i18n>Yes</span>
- <span *ngIf="!holdable(copy)" i18n>No</span>
+<div class='eg-copies w-100'>
+ <ul class="pagination mb-1">
+ <li class="page-item" [ngClass]="{disabled : pager.offset == 0}">
+ <a class="no-href page-link"
+ i18n-aria-label aria-label="Start" (click)="firstPage()">
+ <span i18n>Start</span>
+ </a>
+ </li>
+ <li class="page-item" [ngClass]="{disabled : pager.offset == 0}">
+ <a class="no-href page-link"
+ i18n-aria-label aria-label="Previous" (click)="prevPage()">
+ <span i18n>Previous</span>
+ </a>
+ </li>
+ <!-- note disable logic is incomplete -->
+ <li class="page-item"
+ [ngClass]="{disabled: copies.length < pager.limit}">
+ <a class="no-href page-link"
+ i18n-aria-label aria-label="Next" (click)="nextPage()">
+ <span i18n>Next</span>
+ </a>
+ </li>
+ </ul>
+ <div class='card w-100'>
+ <div class="card-header font-weight-bold d-flex bg-info">
+ <div class="flex-1" i18n>Location</div>
+ <div class="flex-1" i18n>Call Number / Copy Notes</div>
+ <div class="flex-1" i18n>Barcode</div>
+ <div class="flex-1" i18n>Shelving Location</div>
+ <div class="flex-1" i18n>Circulation Modifier</div>
+ <div class="flex-1" i18n>Age Hold Protection</div>
+ <div class="flex-1" i18n>Active/Create Date</div>
+ <div class="flex-1" i18n>Holdable?</div>
+ <div class="flex-1" i18n>Status</div>
+ <div class="flex-1" i18n>Due Date</div>
+ </div>
+ <div class="card-body">
+ <ul class="list-group list-group-flush" *ngIf="copies && copies.length">
+ <li class="list-group-item" *ngFor="let copy of copies; let idx = index"
+ [ngClass]="{'list-group-item-info': (idx % 2) == 1}">
+ <div class="d-flex">
+ <div class="flex-1" i18n>{{orgName(copy.circ_lib)}}</div>
+ <div class="flex-1" i18n>
+ {{copy.call_number_prefix_label}}
+ {{copy.call_number_label}}
+ {{copy.call_number_suffix_label}}
+ </div>
+ <div class="flex-1" i18n>
+ {{copy.barcode}}
+ <a class="pl-1" href="/eg/staff/cat/item/{{copy.id}}" i18n>View</a>
+ |
+ <a class="pl-1" href="/eg/staff/cat/item/{{copy.id}}/edit" i18n>Edit</a>
+ </div>
+ <div class="flex-1" i18n>{{copy.copy_location}}</div>
+ <div class="flex-1" i18n>{{copy.circ_modifier || ''}}</div>
+ <div class="flex-1" i18n>{{copy.age_protect}}</div>
+ <div class="flex-1" i18n>
+ {{copy.active_date || copy.create_date | date:'shortDate'}}
+ </div>
+ <div class="flex-1">
+ <span *ngIf="holdable(copy)" i18n>Yes</span>
+ <span *ngIf="!holdable(copy)" i18n>No</span>
+ </div>
+ <div class="flex-1" i18n>{{copy.copy_status}}</div>
+ <div class="flex-1" i18n>{{copy.due_date | date:'shortDate'}}</div>
</div>
- <div class="flex-1" i18n>{{copy.copy_status}}</div>
- <div class="flex-1" i18n>{{copy.due_date | date:'shortDate'}}</div>
- </div>
- </li>
- </ul>
+ </li>
+ </ul>
+ </div>
</div>
</div>
export class CopiesComponent implements OnInit {
pager: Pager;
- copies: any[];
+ copies: any[]
recId: number;
initDone: boolean = false;
&& copy.status_holdable == 't';
}
+ firstPage(): void {
+ this.pager.offset = 0;
+ this.fetchCopies();
+ }
+ prevPage(): void {
+ this.pager.decrement();
+ this.fetchCopies();
+ }
+ nextPage(): void {
+ this.pager.increment();
+ this.fetchCopies();
+ }
+
}
/* Bootstrap default is 20px */
+/*
.pagination {margin: 0px 0px 0px 0px}
+*/
-<ul class="pagination">
- <li class="page-item">
+<ul class="pagination mb-0" *ngIf="index !== null">
+ <li class="page-item" [ngClass]="{disabled : index == 0}">
<a class="no-href page-link"
- i18n-aria-label aria-label="Start"
- (click)="firstRecord()">
+ i18n-aria-label aria-label="Start" (click)="firstRecord()">
<span i18n>Start</span>
</a>
</li>
- <li class="page-item"
- [ngClass]="{disabled : index == 0}">
+ <li class="page-item" [ngClass]="{disabled : index == 0}">
<a class="no-href page-link"
- i18n-aria-label aria-label="Previous"
- (click)="prevRecord()">
+ i18n-aria-label aria-label="Previous" (click)="prevRecord()">
<span i18n>Previous</span>
</a>
</li>
<li class="page-item"
[ngClass]="{disabled : index >= searchContext.result.count - 1}">
<a class="no-href page-link"
- i18n-aria-label aria-label="Next"
- (click)="nextRecord()">
+ i18n-aria-label aria-label="Next" (click)="nextRecord()">
<span i18n>Next</span>
</a>
</li>
- <li class="page-item">
+ <li class="page-item"
+ [ngClass]="{disabled : index >= searchContext.result.count - 1}">
<a class="no-href page-link"
- i18n-aria-label aria-label="End"
- (click)="lastRecord()">
+ i18n-aria-label aria-label="End" (click)="lastRecord()">
<span i18n>End</span>
</a>
</li>
<li class="page-item">
<a class="no-href page-link"
- i18n-aria-label aria-label="Back to Results"
- (click)="returnToSearch()">
+ i18n-aria-label aria-label="Back to Results" (click)="returnToSearch()">
<span i18n>
Back to Results ({{index + 1}} / {{searchContext.result.count}})
</span>
export class RecordPaginationComponent implements OnInit {
id: number;
- index: number = 0;
+ index: number;
initDone: boolean = false;
searchContext: CatalogSearchContext;
}
firstRecord(): void {
+ this.findRecordAtIndex(0).then(id => {
+ let params = this.catUrl.toUrlParams(this.searchContext);
+ this.router.navigate(
+ ['/staff/catalog/record/' + id], {queryParams: params});
+ });
}
lastRecord(): void {
+ this.findRecordAtIndex(
+ this.searchContext.result.count - 1
+ ).then(id => {
+ let params = this.catUrl.toUrlParams(this.searchContext);
+ this.router.navigate(
+ ['/staff/catalog/record/' + id], {queryParams: params});
+ });
}
nextRecord(): void {
return idx + this.searchContext.pager.offset;
}
+ // Find the position of the current record in the search results
+ // If no results are present or the record is not found, expand
+ // the search scope to find the record.
setIndex(): Promise<void> {
this.searchContext = this.staffCat.searchContext;
this.index = null;
return new Promise((resolve, reject) => {
- this.index = this.findIndexInResults();
-
- if (this.index !== null) {
- resolve();
- return;
- }
-
- console.debug('Paginator re-searching...');
+ this.index = this.searchContext.indexForResult(this.id);
+ if (this.index !== null) return resolve();
return this.refreshSearch().then(ok => {
- this.index = this.findIndexInResults();
+ this.index = this.searchContext.indexForResult(this.id);
+ if (this.index === null) console.warn(
+ 'No search results found containing the focused record.');
resolve();
});
});
}
- findIndexInResults(): number {
- let index = null;
- if (this.searchContext.result
- && this.searchContext.result.ids) {
- this.searchContext.result.ids.forEach((recIdBlob, idx) => {
- if (+recIdBlob[0] == this.id) index = idx;
- });
- }
- return index;
- }
-
+ // Find the record ID at the specified search index.
+ // If no data exists for the requested index, expand the search
+ // to include data for that index.
findRecordAtIndex(index: number): Promise<number> {
- // First see if the select record sits in the current page
+ // First see if the selected record sits in the current page
// of search results.
return new Promise((resolve, reject) => {
-
- // See if the record is avaialable in the current search page.
- let found = false;
- if (this.searchContext.result
- && this.searchContext.result.ids) {
- this.searchContext.result.ids.forEach((recIdBlob, idx) => {
- if (this.searchIndex(idx) == index) {
- found = true;
- resolve(recIdBlob[0]);
+ let id = this.searchContext.resultIdAt(index);
+ if (id) return resolve(id);
+
+ console.debug(
+ 'Record paginator unable to find record at index ' + index);
+
+ // If we have to re-run the search to find the record,
+ // expand the search limit out just enough to find the
+ // requested record plus one more.
+ return this.refreshSearch(index + 2).then(
+ ok => {
+ let id = this.searchContext.resultIdAt(index);
+ if (id) {
+ resolve(id);
+ } else {
+ reject('no record found');
}
- });
- }
-
- if (!found) {
- // TODO: re-run search but only for the next/prev page of results
- console.debug(
- 'Record paginator unable to find record at index ' + index);
- return this.refreshSearch();
- }
+ }
+ );
});
}
- refreshSearch(): Promise<void> {
+ refreshSearch(limit?: number): Promise<void> {
+
+ console.debug('paginator refreshing search');
if (!this.searchContext.isSearchable())
return Promise.resolve();
let origPager = this.searchContext.pager;
let tmpPager = new Pager();
- tmpPager.limit = 1000;
+ tmpPager.limit = limit || 1000;
this.searchContext.pager = tmpPager;
return this.cat.search(this.searchContext)
.then(
- ok => { this.searchContext.pager = origPager; console.log(this.searchContext.result); },
+ ok => { this.searchContext.pager = origPager; },
notOk => { this.searchContext.pager = origPager }
);
}
+ returnToSearch(): void {
+ // Fire the main search. This will direct us back to /results/
+ this.staffCat.search();
+ }
+
}
// Watch for URL record ID changes
this.route.paramMap.subscribe((params: ParamMap) => {
this.recordId = +params.get('id');
- console.log('record starting with id ' + this.recordId);
this.loadRecord();
})
}
+++ /dev/null
-import {Injectable} from '@angular/core';
-import {EgCatalogService} from '@eg/share/catalog/catalog.service';
-import {EgCatalogUrlService} from '@eg/share/catalog/catalog-url.service';
-import {CatalogSearchContext} from '@eg/share/catalog/search-context';
-import {StaffCatalogService} from '../staff-catalog.service';
-import {EgPcrudService} from '@eg/core/pcrud';
-
-/**
- * Shared bits needed by the staff version of the catalog.
- */
-
-@Injectable()
-export class StaffRecordService {
-
- recordId: number;
- bibSummary: any; // TODO: bib summary class
- searchContext: CatalogSearchContext;
-
- constructor(
- private pcrud: EgPcrudService,
- private cat: EgCatalogService,
- private staffCat: StaffCatalogService,
- private catUrl: EgCatalogUrlService
- ) { }
-
- setRecord(id: number): void {
- this.recordId = id;
- this.searchContext = this.staffCat.searchContext;
-
- // If a search is encoded in the URL, be sure we have the
- // relevant search
-
- this.cat.getBibSummary(
- this.recordId,
- this.searchContext.searchOrg.id(),
- this.searchContext.searchOrg.ou_type().depth()
- ).then(summary => {
- this.bibSummary = 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;
- })
-
- // probably other stuff
- });
- }
-}
-
-