joinOp: [],
matchOp: [],
facets: [],
- org: null
+ org: null,
+ limit: null,
+ offset: null
};
+ params.limit = context.pager.limit;
+ if (context.pager.offset)
+ params.offset = context.pager.offset;
+
// These fields can be copied directly into place
- ['limit','offset','format','sort','available','global']
+ ['format','sort','available','global']
.forEach(field => {
if (context[field]) {
// Only propagate applied values to the URL.
applyUrlParams(context: CatalogSearchContext, params: ParamMap): void {
// These fields can be copied directly into place
- ['limit','offset','format','sort','available','global']
+ ['format','sort','available','global']
.forEach(field => {
let val = params.get(field);
if (val !== null) context[field] = val;
});
+ if (params.get('limit'))
+ context.pager.limit = +params.get('limit');
+
+ if (params.get('offset'))
+ context.pager.offset = +params.get('offset');
['query','fieldClass','joinOp','matchOp'].forEach(field => {
let arr = params.getAll(field);
CATALOG_CCVM_FILTERS.forEach(code => {
let val = params.get(code);
- if (val) context.ccvmFilters[code] = val.split(/,/);
+ if (val) {
+ context.ccvmFilters[code] = val.split(/,/);
+ } else {
+ context.ccvmFilters[code] = [''];
+ }
});
params.getAll('facets').forEach(blob => {
context.facetFilters.push(JSON.parse(blob));
});
- context.searchOrg = this.org.get(+params.get('org'));
+ context.searchOrg =
+ this.org.get(+params.get('org')) || this.org.root();
}
}
@Injectable()
export class EgCatalogService {
- searchContext: CatalogSearchContext;
ccvmMap: {[ccvm:string] : EgIdlObject[]} = {};
cmfMap: {[cmf:string] : EgIdlObject[]} = {};
private pcrud: EgPcrudService
) {}
- // Though multiple search contexts could exist (e.g. for caching),
- // there will only be one active search context at a time.
- // Wrap in a getter for future-proofing.
- activeSearchContext(): CatalogSearchContext {
- return this.searchContext;
- }
-
- search(): Promise<void> {
- let ctx = this.activeSearchContext();
+ search(ctx: CatalogSearchContext): Promise<void> {
+ ctx.result = {};
+ ctx.searchInProgress = true;
var fullQuery = ctx.compileSearch();
console.debug(`search query: ${fullQuery}`);
+ let method = 'open-ils.search.biblio.multiclass.query';
+ if (ctx.isStaff) method += '.staff';
+
return new Promise((resolve, reject) => {
this.net.request(
- 'open-ils.search',
- 'open-ils.search.biblio.multiclass.query.staff', {
+ 'open-ils.search', method, {
limit : ctx.pager.limit,
offset : ctx.pager.offset
}, fullQuery, true
ctx.result = result;
ctx.result.records = [];
ctx.pager.resultCount = result.count;
+ ctx.searchInProgress = false;
let promises = [];
result.ids.forEach(blob => {
this.getBibSummary(blob[0],
ctx.searchOrg.id(),
ctx.global ?
- ctx.org.root().ou_typ().depth() :
+ ctx.org.root().ou_type().depth() :
ctx.searchOrg.ou_type().depth()
).then(
summary => ctx.result.records.push(summary)
ccvms : {}
};
- for (let key in UNAPI_PATHS) {
+ Object.keys(UNAPI_PATHS).forEach(key => {
try {
response[key] = eval(`summary.mods.${UNAPI_PATHS[key]}`);
} catch(E) {
response[key] = '';
}
- }
+ });
+
+ Object.keys(summary.mods.extern[0]['$']).forEach(field => {
+ if (field == 'xmlns') return;
+ response[field] = summary.mods.extern[0]['$'][field];
+ });
summary.mods.attributes[0].field.forEach(attrField => {
response.ccvms[attrField['$'].name] = {
});
//console.log(summary);
- console.log(response);
+ //console.log(response);
return response;
}
searchOrg: EgIdlObject;
ccvmFilters: {[ccvmCode:string] : string[]};
facetFilters: FacetFilter[];
+ isStaff: boolean;
// Result from most recent search.
result: any;
+ searchInProgress: boolean = false;
// Utility stuff
pager: Pager;
str += ' site(' + this.searchOrg.shortname() + ')';
Object.keys(this.ccvmFilters).forEach(field => {
- str += ' ' + field + '(' + this.ccvmFilters[field] + ')';
+ if (this.ccvmFilters[field][0] != '')
+ str += ' ' + field + '(' + this.ccvmFilters[field] + ')';
});
this.facetFilters.forEach(f => {
return Math.floor(this.offset / this.limit) + 1
}
+ increment(): void {
+ this.setPage(this.currentPage() + 1);
+ }
+
+ decrement(): void {
+ this.setPage(this.currentPage() - 1);
+ }
+
+ setPage(page: number): void {
+ this.offset = (this.limit * (page - 1));
+ }
+
pageCount(): number {
let pages = this.resultCount / this.limit;
if (Math.floor(pages) < pages)
import {Component, OnInit} from '@angular/core';
-import {ActivatedRoute} from '@angular/router';
-import {EgOrgService} from '@eg/core/org';
-import {EgCatalogService} from '@eg/share/catalog/catalog.service';
-import {CatalogSearchContext} from '@eg/share/catalog/search-context';
-import {EgCatalogUrlService} from '@eg/share/catalog/catalog-url.service';
+import {StaffCatalogService} from './staff-catalog.service';
@Component({
templateUrl: 'catalog.component.html'
})
export class EgCatalogComponent implements OnInit {
- // Main search context that lives for the duration of the catalog.
- searchContext: CatalogSearchContext;
-
- constructor(
- private route: ActivatedRoute,
- private org: EgOrgService,
- private cat: EgCatalogService,
- private catUrl: EgCatalogUrlService
- ) {}
+ constructor(private staffCat: StaffCatalogService) {}
ngOnInit() {
-
- // Initialize the search context from the load-time URL params.
- // Do this here so the search form and other context data are
- // applied on every page, not just the search results page. The
- // search results pages will handle running the actual search.
- this.cat.searchContext = this.searchContext =
- this.catUrl.fromUrlParams(this.route.snapshot.queryParamMap);
-
- this.searchContext.org = this.org; // offer a copy of the service.
+ // Create the search context that will be used by all
+ // of my child components.
+ this.staffCat.createContext();
}
}
import {ResultPaginationComponent} from './result/pagination.component';
import {ResultFacetsComponent} from './result/facets.component';
import {ResultRecordComponent} from './result/record.component';
+import {StaffCatalogService} from './staff-catalog.service';
@NgModule({
declarations: [
providers: [
EgUnapiService,
EgCatalogService,
- EgCatalogUrlService
+ EgCatalogUrlService,
+ StaffCatalogService
]
})
import {Component, OnInit, Input} from '@angular/core';
import {EgCatalogService} from '@eg/share/catalog/catalog.service';
import {CatalogSearchContext} from '@eg/share/catalog/search-context';
+import {StaffCatalogService} from '../staff-catalog.service';
@Component({
selector: 'eg-catalog-result-facets',
searchContext: CatalogSearchContext;
constructor(
- private cat: EgCatalogService
+ private cat: EgCatalogService,
+ private staffCat: StaffCatalogService
) {}
ngOnInit() {
- this.searchContext = this.cat.activeSearchContext();
+ this.searchContext = this.staffCat.searchContext;
}
}
+
+/* Bootstrap default is 20px */
+.pagination {margin: 10px 0px 10px 0px}
+
+/* style a's without hrefs
+ * TODO: consider moving this to a shared css file
+ * */
+.pagination a:hover {
+ cursor: pointer;
+}
+
-TEST PAGINATION
+
+<ul class="pagination">
+ <li class="page-item" [ngClass]="{disabled : searchContext.pager.isFirstPage()}">
+ <a (click)="prevPage()"
+ class="page-link" aria-label="[% l('Previous') %]">
+ <span aria-hidden="true">«</span>
+ </a>
+ </li>
+ <li class="page-item" *ngFor="let page of searchContext.pager.pageList()"
+ [ngClass]="{active : searchContext.pager.currentPage() == page}">
+ <a (click)="setPage(page)" class="page-link">
+ {{page}} <span class="sr-only">(current)</span></a>
+ </li>
+ <li class="page-item" [ngClass]="{disabled : searchContext.pager.isLastPage()}">
+ <a (click)="nextPage()"
+ class="page-link" aria-label="Next">
+ <span aria-hidden="true">»</span>
+ </a>
+ </li>
+</ul>
import {Component, OnInit, Input} from '@angular/core';
import {EgCatalogService} from '@eg/share/catalog/catalog.service';
import {CatalogSearchContext} from '@eg/share/catalog/search-context';
+import {StaffCatalogService} from '../staff-catalog.service';
@Component({
selector: 'eg-catalog-result-pagination',
searchContext: CatalogSearchContext;
constructor(
- private cat: EgCatalogService
+ private cat: EgCatalogService,
+ private staffCat: StaffCatalogService
) {}
ngOnInit() {
- this.searchContext = this.cat.activeSearchContext();
+ this.searchContext = this.staffCat.searchContext;
+ }
+
+ nextPage(): void {
+ this.searchContext.pager.increment();
+ this.staffCat.search();
+ }
+
+ prevPage(): void {
+ this.searchContext.pager.decrement();
+ this.staffCat.search();
+ }
+
+ setPage(page: number): void {
+ this.searchContext.pager.setPage(page);
+ this.staffCat.search();
}
}
}
+.with-material-icon {
+ display: inline-flex;
+ vertical-align: middle;
+ align-items: center;
+}
+
+<!--
+ TODO
+ routerLink's
+ egDateFilter's
+-->
<div class="cat-record-row col-12 card card-body bg-light">
<div class="row">
<div class="col-2">
<div class="row" [ngClass]="{'pt-2':copyIndex > 0}"
*ngFor="let copyCount of bibSummary.copyCounts; let copyIdx = index">
- <div class="col-12">
+ <div *ngIf="copyCount.type == 'staff'" class="col-12">
{{copyCount.available}} / {{copyCount.count}}
items @ {{orgName(copyCount.org_unit)}}
</div>
</div>
</div>
<div class="col-4">
- <!--
<div class="row">
<div class="col-4">
- <div class="pull-right weak-text-1">
- [% l('Holds: [_1]', '{{record.hold_count}}') %]
+ <div class="float-right weak-text-1">
+ Holds: {{bibSummary.holdCount}}
</div>
</div>
<div class="col-8">
- <div class="pull-right weak-text-1">
- [% l('Created [_1] by [_2]',
- '{{record.bre.create_date() | date:$root.egDateFormat}}',
- '<a target="_self"
- href="./circ/patron/{{record.bre.creator().id()}}/checkout">{{record.bre.creator().usrname()}}</a>'
- ) %]
+ <div class="float-right weak-text-1">
+ Created {{bibSummary.create_date | date:'shortDate'}} by
+ <!-- creator if fleshed after the initial data set is loaded -->
+ <a *ngIf="bibSummary.creator.usrname" target="_self"
+ href="./staff/circ/patron/{{bibSummary.creator.id()}}/checkout">
+ {{bibSummary.creator.usrname()}}
+ </a>
+ <!-- add a spacer pending data to reduce page shuffle -->
+ <span *ngIf="!bibSummary.creator.usrname"> ... </span>
</div>
</div>
</div>
<div class="row pt-2">
<div class="col-4">
- <div class="pull-right weak-text-1">
- [% l('TCN: [_1]', '{{record.bre.tcn_value()}}') %]
+ <div class="float-right weak-text-1">
+ TCN: {{bibSummary.tcn_value}}
</div>
</div>
<div class="col-8">
- <div class="pull-right weak-text-1">
- [% l('Edited [_1] by [_2]',
- '{{record.bre.edit_date() | date:$root.egDateFormat}}',
- '<a target="_self"
- href="./circ/patron/{{record.bre.editor().id()}}/checkout">{{record.bre.editor().usrname()}}</a>'
- ) %]
+ <div class="float-right weak-text-1">
+ Edited {{bibSummary.edit_date | date:'shortDate'}} by
+ <a *ngIf="bibSummary.editor.usrname" target="_self"
+ href="./staff/circ/patron/{{bibSummary.editor.id()}}/checkout">
+ {{bibSummary.editor.usrname()}}
+ </a>
+ <span *ngIf="!bibSummary.editor.usrname"> ... </span>
</div>
</div>
</div>
<div class="row pt-2">
<div class="col-4">
- <div class="pull-right weak-text-1">
- [% l('ID: [_1]', '{{bibSummary.id}}') %]
+ <div class="float-right weak-text-1">
+ <span i18n>ID: {{bibSummary.id}}</span>
</div>
</div>
<div class="col-8">
- <div class="pull-right">
+ <div class="float-right">
<span>
- <button (click)="searchContext.place_hold(record)"
- class="btn btn-sm btn-success">
- <span class="glyphicon glyphicon-ok"></span>
- [% l('Place Hold') %]
+ <button (click)="placeHold()"
+ class="btn btn-sm btn-success with-material-icon weak-text-1">
+ <span class="material-icons">check</span>
+ <span i18n>Place Hold</span>
</button>
</span>
<span>
- <button (click)="searchContext.add_to_list(record)"
- class="btn btn-sm btn-info">
- <span class="glyphicon glyphicon-list-alt"></span>
- [% l('Add to List') %]
+ <button (click)="addToList()"
+ class="btn btn-sm btn-info with-material-icon weak-text-1">
+ <span class="material-icons">playlist_add_check</span>
+ <span i18n>Add to List</span>
</button>
</span>
</div>
</div>
</div>
- </div>--><!-- col -->
+ </div><!-- col -->
</div><!-- row -->
</div><!-- col -->
import {EgOrgService} from '@eg/core/org';
import {EgCatalogService} from '@eg/share/catalog/catalog.service';
import {CatalogSearchContext} from '@eg/share/catalog/search-context';
+import {EgNetService} from '@eg/core/net';
+import {StaffCatalogService} from '../staff-catalog.service';
@Component({
selector: 'eg-catalog-result-record',
constructor(
private org: EgOrgService,
- private cat: EgCatalogService
+ private net: EgNetService,
+ private cat: EgCatalogService,
+ private staffCat: StaffCatalogService
) {}
ngOnInit() {
- this.searchContext = this.cat.activeSearchContext();
+ 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);
}
orgName(orgId: number): string {
return this.org.get(orgId).shortname();
}
+ placeHold(): void {
+ alert('Placing hold on bib ' + this.bibSummary.id);
+ }
+
+ addToList(): void {
+ alert('Adding to list for bib ' + this.bibSummary.id);
+ }
+
}
-<div class="row">
- <div class="col-2">
- <!-- facets -->
- </div>
- <div class="col-10">
- <div *ngIf="searchContext.result">
- <div *ngFor="let bibSummary of searchContext.result.records; let idx = index">
- <eg-catalog-result-record [bibSummary]="bibSummary" [index]="idx">
- </eg-catalog-result-record>
+<div id="staff-catalog-results-container">
+ <div class="row">
+ <div class="col-2" style="margin-top:10px"><!--match pagination margin-->
+ <h5 i18n>Search Results ({{searchContext.result.count}})</h5>
+ </div>
+ <div class="col-1"></div>
+ <div class="col-9">
+ <div class="float-right">
+ <eg-catalog-result-pagination></eg-catalog-result-pagination>
</div>
</div>
</div>
+ <div class="row">
+ <div class="col-2">
+ <!-- facets -->
+ </div>
+ <div class="col-10">
+ <div *ngIf="searchContext.result">
+ <div *ngFor="let bibSummary of searchContext.result.records; let idx = index">
+ <eg-catalog-result-record [bibSummary]="bibSummary" [index]="idx">
+ </eg-catalog-result-record>
+ </div>
+ </div>
+ </div>
+ </div>
</div>
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 {EgOrgService} from '@eg/core/org';
+import {EgPcrudService} from '@eg/core/pcrud';
+import {StaffCatalogService} from '../staff-catalog.service';
+import {EgIdlObject} from '@eg/core/idl';
@Component({
selector: 'eg-catalog-results',
searchContext: CatalogSearchContext;
+ // Cache record creator/editor since this will likely be a
+ // reasonably small set of data w/ lots of repitition.
+ userCache: {[id:number] : EgIdlObject} = {};
+
constructor(
private route: ActivatedRoute,
- private org: EgOrgService,
+ private pcrud: EgPcrudService,
private cat: EgCatalogService,
- private catUrl: EgCatalogUrlService
+ private catUrl: EgCatalogUrlService,
+ private staffCat: StaffCatalogService
) {}
ngOnInit() {
- this.searchContext = this.cat.activeSearchContext();
+ this.searchContext = this.staffCat.searchContext;
// Our search context is initialized on page load. Once
// ResultsComponent is active, it will not be reinitialized,
searchByUrl(params: ParamMap): void {
this.catUrl.applyUrlParams(this.searchContext, params);
- this.searchContext.searchOrg = this.org.get(4); // TODO: testing
// A query string is required at minimum.
if (!this.searchContext.query[0]) return;
- this.cat.search().then(ok => {
+ this.cat.search(this.searchContext).then(ok => {
+ this.fleshSearchResults();
+ });
+ }
- // Do some post-search tidying before component rendering.
- this.searchContext.result.records.forEach(b => {
- b.copyCounts = b.copyCounts.filter(c => {
- return c.type == 'staff'
- })
- });
+ fleshSearchResults(): void {
+ let records = this.searchContext.result.records;
+ if (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.
+ let userIds: {[id:string]: boolean} = {};
+ records.forEach(recSum => {
+ if (this.userCache[recSum.creator]) {
+ recSum.creator = this.userCache[recSum.creator];
+ } else {
+ userIds[recSum.creator] = true;
+ }
+
+ if (this.userCache[recSum.editor]) {
+ recSum.editor = this.userCache[recSum.editor];
+ } else {
+ userIds[recSum.editor] = true;
+ }
+ });
- console.debug('search complete');
+ 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;
+ });
});
}
- orgName(orgId: number): string {
- return this.org.get(orgId).shortname();
- }
-
searchAuthor(bibSummary: any) {
}
}
+/*
#staffcat-search-form .eg-org-selector,
#staffcat-search-form .eg-org-selector button {
width: 100%;
text-align: left
}
+*/
+
+/*
+#staffcat-search-form select,
+#staffcat-search-form optgroup,
+#staffcat-search-form option {
+ padding: 0.15rem 0.75rem;
+}
+*/
+
+/** TODO move these to common CSS */
.flex-row {
display: flex;
}
.flex-cell {
- /* override style.css padding:4px */
flex: 1;
- padding: 0px 0px 0px 8px;
+ padding: 0px 0px 0px 7px;
}
+
.flex-2 {
flex: 2
}
-.flex-row:first-child {
+.flex-3 {
+ flex: 3
+}
+.flex-4 {
+ flex: 4
+}
+/* --- */
+
+/*
+.col-2:first-child, .flex-row, .flex-cell:first-child {
+*/
+.row, .row > div:first-child, .flex-row, .flex-cell:first-child {
padding-left: 0px;
+ margin-left: 0px;
}
.search-plus-minus {
-<div id='staffcat-search-form'>
+<!--
+TODO focus search input
+-->
+<div id='staffcat-search-form' class='mb-3'>
<div class="row"
*ngFor="let q of searchContext.query; let idx = index; trackBy:trackByIdx">
- <div class="col-md-8 flex-row">
+ <div class="col-9 flex-row">
<div class="flex-cell">
<div *ngIf="idx == 0">
<select class="form-control" [(ngModel)]="searchContext.format">
</button>
</div>
</div><!-- col -->
- <div class="col-md-4">
+ <div class="col-3">
<div *ngIf="idx == 0" class="float-right">
<button class="btn btn-success" type="button"
(click)="searchContext.pager.offset=0;searchByForm()">
Clear Form
</button>
<button class="btn btn-secondary" type="button"
- *ngIf="!showAdvancedSearch"
+ *ngIf="!showAdvanced()"
(click)="showAdvancedSearch=true">
More Filters...
</button>
<button class="btn btn-secondary" type="button"
- *ngIf="showAdvancedSearch"
+ *ngIf="showAdvanced()"
(click)="showAdvancedSearch=false">
Hide Filters
</button>
</div>
+ </div>
</div><!-- row -->
-</div>
- <!--
<div class="row">
- <div class="col-md-9 flex-row">
+ <div class="col-9 flex-row">
<div class="flex-cell">
- <eg-org-select nodefault
- selected="searchContext.searchContext_org">
+ <eg-org-select
+ [onChange]="orgOnChange"
+ [initialOrg]="searchContext.searchOrg"
+ [placeholder]="'Library'" >
</eg-org-select>
</div>
<div class="flex-cell flex-3">
<select class="form-control" [(ngModel)]="searchContext.sort">
- <option value=''>[% l('Sort by Relevance') %]</option>
- <optgroup label="[% l('Sort by Title') %]">
- <option value='titlesort'>[% l('Title: A to Z') %]</option>
- <option value='titlesort.descending'>[% l('Title: Z to A') %]</option>
+ <option value='' i18n>Sort by Relevance</option>
+ <optgroup label="Sort by Title" i18n-label>
+ <option value='titlesort' i18n>Title: A to Z</option>
+ <option value='titlesort.descending' i18n>Title: Z to A</option>
</optgroup>
- <optgroup label="[% l('Sort by Author') %]">
- <option value='authorsort'>[% l('Author: A to Z') %]</option>
- <option value='authorsort.descending'>[% l('Author: Z to A') %]</option>
+ <optgroup label="Sort by Author" i18n-label>
+ <option value='authorsort' i18n>Author: A to Z</option>
+ <option value='authorsort.descending' i18n>Author: Z to A</option>
</optgroup>
- <optgroup label="[% l('Sort by Publication Date') %]">
- <option value='pubdate'>[% l('Date: A to Z') %]</option>
- <option value='pubdate.descending'>[% l('Date: Z to A') %]</option>
+ <optgroup label="Sort by Publication Date" i18n-label>
+ <option value='pubdate' i18n>Date: A to Z</option>
+ <option value='pubdate.descending' i18n>Date: Z to A</option>
</optgroup>
- <optgroup label="[% l('Sort by Popularity') %]">
- <option value='popularity'>[% l('Most Popular') %]</option>
- <option value='poprel'>[% l('Popularity Adjusted Relevance') %]</option>
+ <optgroup label="Sort by Popularity" i18n-label>
+ <option value='popularity' i18n>Most Popular</option>
+ <option value='poprel' i18n>Popularity Adjusted Relevance</option>
</optgroup>
</select>
</div>
<div class="checkbox">
<label>
<input type="checkbox" [(ngModel)]="searchContext.available"/>
- [% l('Limit to Available') %]
+ <span i18n>Limit to Available</span>
</label>
</div>
</div>
<div class="checkbox">
<label>
<input type="checkbox" [(ngModel)]="searchContext.global"/>
- [% l('Show Results from All Libraries') %]
+ <span i18n>Show Results from All Libraries</span>
</label>
</div>
</div>
<div class="flex-cell flex-2">
- <div *ngIf="searchContext.search_state() == 'searching'">
+ <div *ngIf="searchContext.searchInProgress">
<div class="progress">
<div class="progress-bar progress-bar-striped active"
role="progressbar" aria-valuenow="100" aria-valuemin="0"
aria-valuemax="100" style="width: 100%">
- <span class="sr-only">[% l('Searching..') %]</span>
+ <span class="sr-only" i18n>Searching..</span>
</div>
</div>
</div>
</div>
</div>
</div>
-
- <div class="row pad-vert-min" ng-show="showAdvancedSearch">
- <div class="col-md-2">
- <select class="form-control" [(ngModel)]="searchContext.item_type" multiple="true">
- <option value=''>[% l('All Item Types') %]</option>
- <option *ngFor="let item_type of searchContext.ccvm_lists('item_type')"
- value="{{item_type.code()}}">{{item_type.value()}}</option>
+ <div class="row pt-2" *ngIf="showAdvanced()">
+ <div class="col-2">
+ <select class="form-control" multiple="true"
+ [(ngModel)]="searchContext.ccvmFilters.item_type">
+ <option value='' i18n>All Item Types</option>
+ <option *ngFor="let itemType of ccvmMap.item_type"
+ value="{{itemType.code()}}">{{itemType.value()}}</option>
</select>
</div>
- <div class="col-md-2">
- <select class="form-control" [(ngModel)]="searchContext.item_form" multiple="true">
- <option value=''>[% l('All Item Forms') %]</option>
- <option *ngFor="let item_form of searchContext.ccvm_lists('item_form')"
- value="{{item_form.code()}}">{{item_form.value()}}</option>
+ <div class="col-2">
+ <select class="form-control" multiple="true"
+ [(ngModel)]="searchContext.ccvmFilters.item_form">
+ <option value='' i18n>All Item Forms</option>
+ <option *ngFor="let itemForm of ccvmMap.item_form"
+ value="{{itemForm.code()}}">{{itemForm.value()}}</option>
</select>
</div>
- <div class="col-md-2">
- <select class="form-control" [(ngModel)]="searchContext.item_lang" multiple="true">
- <option value=''>[% l('All Languages') %]</option>
- <option *ngFor="let item_lang of searchContext.ccvm_lists('item_lang')"
- value="{{item_lang.code()}}">{{item_lang.value()}}</option>
+ <div class="col-2">
+ <select class="form-control"
+ [(ngModel)]="searchContext.ccvmFilters.item_lang" multiple="true">
+ <option value='' i18n>All Languages</option>
+ <option *ngFor="let lang of ccvmMap.item_lang"
+ value="{{lang.code()}}">{{lang.value()}}</option>
</select>
</div>
- <div class="col-md-2">
- <select class="form-control" [(ngModel)]="searchContext.audience" multiple="true">
- <option value=''>[% l('All Audiences') %]</option>
- <option *ngFor="let audience of searchContext.ccvm_lists('audience')"
+ <div class="col-2">
+ <select class="form-control"
+ [(ngModel)]="searchContext.ccvmFilters.audience" multiple="true">
+ <option value='' i18n>All Audiences</option>
+ <option *ngFor="let audience of ccvmMap.audience"
value="{{audience.code()}}">{{audience.value()}}</option>
</select>
</div>
</div>
- <div class="row pad-vert-min" ng-show="showAdvancedSearch">
- <div class="col-md-2">
- <select class="form-control" [(ngModel)]="searchContext.vr_format" multiple="true">
- <option value=''>[% l('All Video Formats') %]</option>
- <option *ngFor="let vr_format of searchContext.ccvm_lists('vr_format')"
- value="{{vr_format.code()}}">{{vr_format.value()}}</option>
+ <div class="row pt-2" *ngIf="showAdvanced()">
+ <div class="col-2">
+ <select class="form-control"
+ [(ngModel)]="searchContext.ccvmFilters.vr_format" multiple="true">
+ <option value='' i18n>All Video Formats</option>
+ <option *ngFor="let vrFormat of ccvmMap.vr_format"
+ value="{{vrFormat.code()}}">{{vrFormat.value()}}</option>
</select>
</div>
- <div class="col-md-2">
- <select class="form-control" [(ngModel)]="searchContext.bib_level" multiple="true">
- <option value=''>[% l('All Bib Levels') %]</option>
- <option *ngFor="let bib_level of searchContext.ccvm_lists('bib_level')"
- value="{{bib_level.code()}}">{{bib_level.value()}}</option>
+ <div class="col-2">
+ <select class="form-control"
+ [(ngModel)]="searchContext.ccvmFilters.bib_level" multiple="true">
+ <option value='' i18n>All Bib Levels</option>
+ <option *ngFor="let bibLevel of ccvmMap.bib_level"
+ value="{{bibLevel.code()}}">{{bibLevel.value()}}</option>
</select>
</div>
- <div class="col-md-2">
- <select class="form-control" [(ngModel)]="searchContext.lit_form" multiple="true">
- <option value=''>[% l('All Literary Forms') %]</option>
- <option *ngFor="let lit_form of searchContext.ccvm_lists('lit_form')"
- value="{{lit_form.code()}}">{{lit_form.value()}}</option>
+ <div class="col-2">
+ <select class="form-control"
+ [(ngModel)]="searchContext.ccvmFilters.lit_form" multiple="true">
+ <option value='' i18n>All Literary Forms</option>
+ <option *ngFor="let litForm of ccvmMap.lit_form"
+ value="{{litForm.code()}}">{{litForm.value()}}</option>
</select>
</div>
- <div class="col-md-2">
+ <div class="col-2">
<i>Copy location filter goes here...</i>
</div>
</div>
</div>
--->
import {Component, OnInit} from '@angular/core';
-import {Router, ActivatedRoute} from '@angular/router';
import {EgIdlObject} from '@eg/core/idl';
import {EgOrgService} from '@eg/core/org';
import {EgCatalogService} from '@eg/share/catalog/catalog.service';
import {CatalogSearchContext} from '@eg/share/catalog/search-context';
-import {EgCatalogUrlService} from '@eg/share/catalog/catalog-url.service';
+import {StaffCatalogService} from './staff-catalog.service';
@Component({
selector: 'eg-catalog-search-form',
ccvmMap: {[ccvm:string] : EgIdlObject[]} = {};
cmfMap: {[cmf:string] : EgIdlObject[]} = {};
showAdvancedSearch: boolean = false;
- routeIndex: number = 0;
constructor(
- private router: Router,
- private route: ActivatedRoute,
private org: EgOrgService,
private cat: EgCatalogService,
- private catUrl: EgCatalogUrlService
+ private staffCat: StaffCatalogService
) {}
ngOnInit() {
this.ccvmMap = this.cat.ccvmMap;
this.cmfMap = this.cat.cmfMap;
- this.searchContext = this.cat.activeSearchContext();
+ this.searchContext = this.staffCat.searchContext;
+ }
+
+ /**
+ * Display the advanced/extended search options when asked to
+ * or if any advanced options are selected.
+ */
+ showAdvanced(): boolean {
+ return this.showAdvancedSearch || this.hasAdvancedOptions();
+ }
+
+ hasAdvancedOptions(): boolean {
+ // ccvm filters may be present without any filters applied.
+ // e.g. if filters were applied then removed.
+ let show = false;
+ Object.keys(this.searchContext.ccvmFilters).forEach(ccvm => {
+ if (this.searchContext.ccvmFilters[ccvm][0] != '')
+ this.showAdvancedSearch = show = true;
+ });
+
+ return show;
+ }
+
+ orgOnChange = (org: EgIdlObject): void => {
+ this.searchContext.searchOrg = org;
}
addSearchRow(index: number): void {
return index;
}
- /**
- * Redirect to the search results page while propagating the current
- * search paramters into the URL. Let the search results component
- * execute the actual search.
- */
searchByForm(): void {
- this.searchContext.searchOrg = this.org.get(4); // TODO: TEST BR1
- let params = this.catUrl.toUrlParams(this.searchContext);
-
- // Force a new search every time a search is fired, even if it's
- // the same search. Since router navigation exits early when
- // the route + params is identical, add a random token to the
- // route params to force a full navigation. This also resolves
- // a problem where only removing secondary+ versions of a query
- // param fail to cause a route navigation. (E.g. going from two
- // query= params to one). Investigation pending.
- params.ridx=''+this.routeIndex++;
-
- this.router.navigate(
- ['/staff/catalog/search'], {queryParams: params});
+ this.staffCat.search();
}
}
--- /dev/null
+import {Injectable} from '@angular/core';
+import {Router, ActivatedRoute} from '@angular/router';
+import {EgOrgService} from '@eg/core/org';
+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';
+
+/**
+ * Shared bits needed by the staff version of the catalog.
+ */
+
+@Injectable()
+export class StaffCatalogService {
+
+ searchContext: CatalogSearchContext;
+ routeIndex: number = 0;
+
+ constructor(
+ private router: Router,
+ private route: ActivatedRoute,
+ private org: EgOrgService,
+ private cat: EgCatalogService,
+ private catUrl: EgCatalogUrlService
+ ) { }
+
+ createContext(): void {
+ // Initialize the search context from the load-time URL params.
+ // Do this here so the search form and other context data are
+ // applied on every page, not just the search results page. The
+ // search results pages will handle running the actual search.
+ this.searchContext =
+ this.catUrl.fromUrlParams(this.route.snapshot.queryParamMap);
+
+ this.searchContext.org = this.org;
+ this.searchContext.isStaff = true;
+ }
+
+ /**
+ * Redirect to the search results page while propagating the current
+ * search paramters into the URL. Let the search results component
+ * execute the actual search.
+ */
+ search(): void {
+ let params = this.catUrl.toUrlParams(this.searchContext);
+
+ // Force a new search every time this method is called, even if
+ // it's the same as the active search. Since router navigation
+ // exits early when the route + params is identical, add a
+ // random token to the route params to force a full navigation.
+ // This also resolves a problem where only removing secondary+
+ // versions of a query param fail to cause a route navigation.
+ // (E.g. going from two query= params to one). Investigation
+ // pending.
+ params.ridx=''+this.routeIndex++;
+
+ this.router.navigate(
+ ['/staff/catalog/search'], {queryParams: params});
+ }
+
+}
+
+
// Fires on all in-app router navigation, but not initial page load.
this.router.events.subscribe(routeEvent => {
if (routeEvent instanceof NavigationEnd) {
- console.debug(`EgStaffComponent routing to ${routeEvent.url}`);
+ //console.debug(`EgStaffComponent routing to ${routeEvent.url}`);
this.basicAuthChecks(routeEvent);
}
});
/* You can add global styles to this file, and also import other style files */
-
/** material design experiments
@import "~@angular/material/prebuilt-themes/indigo-pink.css";
*/
-/* match the font-size of the angularjs client
- -- the default BS 4 fonts are quite large */
body, .form-control, .btn {
- font-size: 14px;
+ /* This more or less matches the font size of the angularjs client.
+ * The default BS4 font of 1rem is comically large.
+ */
+ font-size: .88rem;
}