LP#626157 Ang2 experiments
authorBill Erickson <berickxx@gmail.com>
Wed, 6 Dec 2017 23:10:11 +0000 (18:10 -0500)
committerBill Erickson <berickxx@gmail.com>
Mon, 11 Dec 2017 17:39:51 +0000 (12:39 -0500)
Signed-off-by: Bill Erickson <berickxx@gmail.com>
21 files changed:
Open-ILS/webby-src/src/app/share/catalog/catalog-url.service.ts
Open-ILS/webby-src/src/app/share/catalog/catalog.service.ts
Open-ILS/webby-src/src/app/share/catalog/search-context.ts
Open-ILS/webby-src/src/app/share/util/pager.ts
Open-ILS/webby-src/src/app/staff/catalog/catalog.component.ts
Open-ILS/webby-src/src/app/staff/catalog/catalog.module.ts
Open-ILS/webby-src/src/app/staff/catalog/result/facets.component.ts
Open-ILS/webby-src/src/app/staff/catalog/result/pagination.component.css
Open-ILS/webby-src/src/app/staff/catalog/result/pagination.component.html
Open-ILS/webby-src/src/app/staff/catalog/result/pagination.component.ts
Open-ILS/webby-src/src/app/staff/catalog/result/record.component.css
Open-ILS/webby-src/src/app/staff/catalog/result/record.component.html
Open-ILS/webby-src/src/app/staff/catalog/result/record.component.ts
Open-ILS/webby-src/src/app/staff/catalog/result/results.component.html
Open-ILS/webby-src/src/app/staff/catalog/result/results.component.ts
Open-ILS/webby-src/src/app/staff/catalog/search-form.component.css
Open-ILS/webby-src/src/app/staff/catalog/search-form.component.html
Open-ILS/webby-src/src/app/staff/catalog/search-form.component.ts
Open-ILS/webby-src/src/app/staff/catalog/staff-catalog.service.ts [new file with mode: 0644]
Open-ILS/webby-src/src/app/staff/staff.component.ts
Open-ILS/webby-src/src/styles.css

index 7b0ce75..009f86f 100644 (file)
@@ -25,11 +25,17 @@ export class EgCatalogUrlService {
             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.
@@ -78,12 +84,17 @@ export class EgCatalogUrlService {
     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);
@@ -92,13 +103,18 @@ export class EgCatalogUrlService {
 
         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();
     }
 }
index dcd6e82..f7efc0f 100644 (file)
@@ -21,7 +21,6 @@ export const CATALOG_CCVM_FILTERS = [
 @Injectable()
 export class EgCatalogService {
 
-    searchContext: CatalogSearchContext;
     ccvmMap: {[ccvm:string] : EgIdlObject[]} = {};
     cmfMap: {[cmf:string] : EgIdlObject[]} = {};
 
@@ -32,25 +31,21 @@ export class EgCatalogService {
         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
@@ -58,6 +53,7 @@ export class EgCatalogService {
                 ctx.result = result;
                 ctx.result.records = [];
                 ctx.pager.resultCount = result.count;
+                ctx.searchInProgress = false;
 
                 let promises = [];
                 result.ids.forEach(blob => {
@@ -65,7 +61,7 @@ export class EgCatalogService {
                         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)
@@ -169,13 +165,18 @@ export class EgCatalogService {
             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] = {
@@ -189,7 +190,7 @@ export class EgCatalogService {
         });
 
         //console.log(summary);
-        console.log(response);
+        //console.log(response);
 
         return response;
     }
index ec2e83f..a77cb6c 100644 (file)
@@ -27,9 +27,11 @@ export class CatalogSearchContext {
     searchOrg: EgIdlObject;
     ccvmFilters: {[ccvmCode:string] : string[]};
     facetFilters: FacetFilter[];
+    isStaff: boolean;
 
     // Result from most recent search.
     result: any; 
+    searchInProgress: boolean = false;
 
     // Utility stuff
     pager: Pager;
@@ -91,7 +93,8 @@ export class CatalogSearchContext {
         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 => {
index bcec127..5f33d4a 100644 (file)
@@ -19,6 +19,18 @@ export class Pager {
         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)
index 206f8a0..47ec5e7 100644 (file)
@@ -1,35 +1,17 @@
 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();
     }
 }
 
index 8986b3e..f4527aa 100644 (file)
@@ -11,6 +11,7 @@ import {ResultsComponent} from './result/results.component';
 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: [
@@ -29,7 +30,8 @@ import {ResultRecordComponent} from './result/record.component';
   providers: [
     EgUnapiService,
     EgCatalogService,
-    EgCatalogUrlService
+    EgCatalogUrlService,
+    StaffCatalogService
   ]
 })
 
index a25c51d..03d8a2f 100644 (file)
@@ -1,6 +1,7 @@
 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',
@@ -12,11 +13,12 @@ export class ResultFacetsComponent implements OnInit {
     searchContext: CatalogSearchContext;
 
     constructor(
-        private cat: EgCatalogService
+        private cat: EgCatalogService,
+        private staffCat: StaffCatalogService
     ) {}
 
     ngOnInit() { 
-        this.searchContext = this.cat.activeSearchContext();
+        this.searchContext = this.staffCat.searchContext;
     }
 
 }
index e69de29..8865836 100644 (file)
@@ -0,0 +1,11 @@
+
+/* 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;
+}
+
index 23a9e63..56c69b1 100644 (file)
@@ -1 +1,20 @@
-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">&laquo;</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">&raquo;</span>
+    </a>
+  </li>
+</ul>
index dc73822..2faa4db 100644 (file)
@@ -1,6 +1,7 @@
 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',
@@ -12,11 +13,27 @@ export class ResultPaginationComponent implements OnInit {
     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();
     }
 
 }
index 86416b2..e04da00 100644 (file)
@@ -1,3 +1,8 @@
+<!-- 
+  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 -->
 
index a84f85f..3484789 100644 (file)
@@ -2,6 +2,8 @@ import {Component, OnInit, Input} from '@angular/core';
 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',
@@ -16,17 +18,35 @@ export class ResultRecordComponent implements OnInit {
 
     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);
+    }
+
 }
 
 
index d0aca38..cf88c4c 100644 (file)
@@ -1,15 +1,28 @@
 
-<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>
 
index 2e9f187..1e0b098 100644 (file)
@@ -5,7 +5,9 @@ import {ActivatedRoute, ParamMap} from '@angular/router';
 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',
@@ -16,15 +18,20 @@ export class ResultsComponent implements OnInit {
 
     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,
@@ -45,29 +52,49 @@ export class ResultsComponent implements OnInit {
 
     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) {
     }
 }
index e04900c..1208786 100644 (file)
@@ -1,21 +1,45 @@
+/*
 #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 {
index 09e57fb..1d6949b 100644 (file)
@@ -1,7 +1,10 @@
-<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">
@@ -60,7 +63,7 @@
         </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>
--->
 
index b63f62f..08ff529 100644 (file)
@@ -1,10 +1,9 @@
 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',
@@ -17,20 +16,41 @@ export class SearchFormComponent implements OnInit {
     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 {
@@ -59,26 +79,8 @@ export class SearchFormComponent implements OnInit {
        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();
     }
 }
 
diff --git a/Open-ILS/webby-src/src/app/staff/catalog/staff-catalog.service.ts b/Open-ILS/webby-src/src/app/staff/catalog/staff-catalog.service.ts
new file mode 100644 (file)
index 0000000..456d84d
--- /dev/null
@@ -0,0 +1,62 @@
+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});
+    }
+
+}
+
+
index 15bbea2..6b52db0 100644 (file)
@@ -27,7 +27,7 @@ export class EgStaffComponent implements OnInit {
         // 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);
             }
         });
index 986ca2c..e396406 100644 (file)
@@ -1,14 +1,14 @@
 /* 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;
 }