LP#626157 Ang2 experiments
authorBill Erickson <berickxx@gmail.com>
Wed, 6 Dec 2017 03:49:31 +0000 (22:49 -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>
18 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/unapi.ts
Open-ILS/webby-src/src/app/staff/catalog/catalog.component.ts
Open-ILS/webby-src/src/app/staff/catalog/result/facets.component.ts
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.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/staff.component.css [new file with mode: 0644]
Open-ILS/webby-src/src/app/staff/staff.component.html
Open-ILS/webby-src/src/app/staff/staff.component.ts
Open-ILS/webby-src/src/styles.css

index 9774162..7b0ce75 100644 (file)
@@ -1,40 +1,73 @@
 import {Injectable} from '@angular/core';
-import {Params} from '@angular/router';
+import {ParamMap} from '@angular/router';
+import {EgOrgService} from '@eg/core/org';
 import {CatalogSearchContext} from './search-context';
+import {CATALOG_CCVM_FILTERS} from './catalog.service';
 
 @Injectable()
 export class EgCatalogUrlService {
 
     // consider supporting a param name prefix/namespace
 
-    constructor() { }
+    constructor(private org: EgOrgService) { }
 
     /**
      * Returns a URL query structure suitable for using with 
      * router.navigate(..., {queryParams:...}).  
      * No navigation is performed within.
      */
-    toUrlParams(context: CatalogSearchContext): any {
-        let params = {};
+    toUrlParams(context: CatalogSearchContext): 
+            {[key: string]: string | string[]} {
+
+        let params = {
+            query: [],
+            fieldClass: [],
+            joinOp: [],
+            matchOp: [],
+            facets: [],
+            org: null
+        };
 
-        params.query = [];
-        params.searchClass = [];
-        params.joiner = [];
-        params.match = [];
+        // These fields can be copied directly into place
+        ['limit','offset','format','sort','available','global']
+        .forEach(field => {
+            if (context[field]) {
+                // Only propagate applied values to the URL.
+                params[field] = context[field];
+            }
+        });
 
         context.query.forEach((q, idx) => {
-            ['query', 'searchClass','joiner','match'].forEach(field => {
+            ['query', 'fieldClass','joinOp','matchOp'].forEach(field => {
+                // Propagate all array-based fields regardless of 
+                // whether a value is applied to ensure correct
+                // correlation between values.
                 params[field][idx] = context[field][idx];
             });
         });
 
+        // CCVM filters are encoded as comma-separated lists
+        Object.keys(context.ccvmFilters).forEach(code => {
+            if (context.ccvmFilters[code] && 
+                context.ccvmFilters[code][0] != '') {
+                params[code] = context.ccvmFilters[code].join(',');
+            }
+        });
+
+        // Each facet is a JSON encoded blob of class, name, and value         
+        context.facetFilters.forEach(facet => {
+            params.facets.push(JSON.stringify(facet));
+        });
+
+        params.org = context.searchOrg.id();
+
         return params;
     }
 
     /**
      * Creates a new search context from the active route params.
      */
-    fromUrlParams(params: Params): CatalogSearchContext {
+    fromUrlParams(params: ParamMap): CatalogSearchContext {
         let context = new CatalogSearchContext();
 
         this.applyUrlParams(context, params);
@@ -42,57 +75,30 @@ export class EgCatalogUrlService {
         return context;
     }
 
-    applyUrlParams(context: CatalogSearchContext, params: Params): void {
+    applyUrlParams(context: CatalogSearchContext, params: ParamMap): void {
 
         // These fields can be copied directly into place
         ['limit','offset','format','sort','available','global']
         .forEach(field => {
-            if (params[field] != undefined) {
-                context[field] = params[field];
-            }
+            let val = params.get(field);
+            if (val !== null) context[field] = val;
         });
 
-        if (typeof params.query == 'string') {
-            // Only one query set is encoded in the URL
-            context.query = [params.query];
-            if (params.searchClass) context.searchClass = [params.searchClass];
-            if (params.joiner) context.joiner = [params.joiner];
-            if (params.match) context.match = [params.match];
-
-        } else if (Array.isArray(params.query)) {
-            // Multiple query sets encoded in the URL.
-            params.query.forEach((q, idx) => {
-                context.query[idx] = params.query[idx];
-                context.searchClass[idx] = params.searchClass[idx];
-                context.joiner[idx] = params.joiner[idx];
-                context.match[idx] = params.match[idx];
-            });
-        }
-
-               /*
-                                                                               
-        // Decode and propagate array-based values                             
-        angular.forEach(scs.ccvm_list_keys, function(field) {                  
-            if (url_search[field]) {                                           
-                ctx.search_args[field] = url_search[field].split(/,/);         
-            }                                                                  
-        });                                                                    
-                                                                               
-        // if there's only a single value it will be a string.                 
-        if (typeof url_search.facet == 'string')                               
-            url_search.facet = [url_search.facet];                             
-        angular.forEach(url_search.facet, function(facet) {                    
-            console.log('parsing: ' + facet);                                  
-            ctx.search_args.facets.push(JSON.parse(facet));                    
-        });                                                                    
-                                                                               
-        // Handle special-case values                                          
-        if (url_search.org)                                                    
-            ctx.search_args.context_org = egCore.org.get(url_search.org);    
-
-               */
 
-    }
+        ['query','fieldClass','joinOp','matchOp'].forEach(field => {
+            let arr = params.getAll(field);
+            if (arr && arr.length) context[field] = arr;
+        });
 
+        CATALOG_CCVM_FILTERS.forEach(code => {
+            let val = params.get(code);
+            if (val) context.ccvmFilters[code] = val.split(/,/);
+        });
+
+        params.getAll('facets').forEach(blob => {
+            context.facetFilters.push(JSON.parse(blob));
+        });
 
+        context.searchOrg = this.org.get(+params.get('org'));
+    }
 }
index 40dc7ca..dcd6e82 100644 (file)
@@ -6,7 +6,7 @@ import {EgNetService} from '@eg/core/net';
 import {EgPcrudService} from '@eg/core/pcrud';
 import {CatalogSearchContext} from './search-context';
 
-const CCVM_FILTER_TYPES = [
+export const CATALOG_CCVM_FILTERS = [
     'item_type',
     'item_form',
     'item_lang',
@@ -21,9 +21,7 @@ const CCVM_FILTER_TYPES = [
 @Injectable()
 export class EgCatalogService {
 
-    // Search context is set by the application.
     searchContext: CatalogSearchContext;
-
     ccvmMap: {[ccvm:string] : EgIdlObject[]} = {};
     cmfMap: {[cmf:string] : EgIdlObject[]} = {};
 
@@ -34,8 +32,15 @@ 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.searchContext;
+        let ctx = this.activeSearchContext();
 
         var fullQuery = ctx.compileSearch();
 
@@ -80,7 +85,7 @@ export class EgCatalogService {
 
         return new Promise((resolve, reject) => {
             this.pcrud.search('ccvm', 
-                {ctype : CCVM_FILTER_TYPES}, {}, {atomic: true}
+                {ctype : CATALOG_CCVM_FILTERS}, {}, {atomic: true}
             ).subscribe(list => {
                 this.compileCcvms(list);
                 resolve();
@@ -154,18 +159,38 @@ export class EgCatalogService {
         const UNAPI_PATHS = {
             title : 'titleInfo[0].title[0]',
             author: 'name[0].namePart[0]',
+            edition: 'originInfo[0].edition[0]',
+            pubdate: 'originInfo[0].dateIssued[0]',
             genre:  'genre[0]._'
         }
 
-        let response = {};
+        let response = {
+            copyCounts : [],
+            ccvms : {}
+        };
+
         for (let key in UNAPI_PATHS) {
             try {
                 response[key] = eval(`summary.mods.${UNAPI_PATHS[key]}`);
             } catch(E) {
-                response[key] ='';
+                response[key] = '';
             }
         }
 
+        summary.mods.attributes[0].field.forEach(attrField => {
+            response.ccvms[attrField['$'].name] = {
+                code: attrField['_'], 
+                label: attrField['$']['coded-value']
+            };
+        });
+
+        summary.mods.holdings[0].counts[0].count.forEach(count => {
+            response.copyCounts.push(count['$']);
+        });
+
+        //console.log(summary);
+        console.log(response);
+
         return response;
     }
 }
index 896ab33..ec2e83f 100644 (file)
@@ -19,13 +19,13 @@ export class CatalogSearchContext {
     available: boolean;
     global: boolean;
     sort: string;
-    searchClass: string[];
+    fieldClass: string[];
     query: string[];
-    joiner: string[];
-    match: string[];
+    joinOp: string[];
+    matchOp: string[];
     format: string;
     searchOrg: EgIdlObject;
-    ccvmFilters: {[ccvmCode:string] : string};
+    ccvmFilters: {[ccvmCode:string] : string[]};
     facetFilters: FacetFilter[];
 
     // Result from most recent search.
@@ -45,9 +45,9 @@ export class CatalogSearchContext {
         this.format = '',
         this.sort = '',
         this.query  = [''];
-        this.searchClass   = ['keyword'];
-        this.match  = ['contains'];
-        this.joiner = [''];
+        this.fieldClass   = ['keyword'];
+        this.matchOp  = ['contains'];
+        this.joinOp = [''];
         this.available = false;
         this.global = false;
         this.ccvmFilters = {};
@@ -55,7 +55,6 @@ export class CatalogSearchContext {
     }
 
     compileSearch(): string {
-
         let str: string = '';
 
         if (this.available) str += ' #available';
@@ -119,19 +118,19 @@ export class CatalogSearchContext {
 
     compileBoolQuerySet(idx: number): string {
         let query = this.query[idx];
-        let joiner = this.joiner[idx];
-        let match = this.match[idx];
-        let searchClass = this.searchClass[idx];
+        let joinOp = this.joinOp[idx];
+        let matchOp = this.matchOp[idx];
+        let fieldClass = this.fieldClass[idx];
 
         let str = '';
         if (!query) return str;
 
-        if (idx > 0) str += ' ' + joiner + ' ';
+        if (idx > 0) str += ' ' + joinOp + ' ';
 
         str += '(';
-        if (searchClass) str += searchClass + ':';
+        if (fieldClass) str += fieldClass + ':';
 
-        switch(match) {
+        switch(matchOp) {
             case 'phrase':
                 query = this.addQuotes(this.stripQuotes(query));
                 break;
index bf2f44c..6a7ea1f 100644 (file)
@@ -37,6 +37,8 @@ export class EgUnapiService {
         let url = `${UNAPI_PATH}${params.target}/${params.id}${params.extras}/` +
             `${org.shortname()}/${depth}&format=${params.format}`;
 
+        //console.debug(`UNAPI: ${url}`);
+
         return new Promise((resolve, reject) => {
             this.http.get(url, {responseType: 'text'})
             .subscribe(xmlStr => resolve(xmlStr));
index f842350..206f8a0 100644 (file)
@@ -23,12 +23,13 @@ export class EgCatalogComponent implements OnInit {
     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.queryParams);
+            this.catUrl.fromUrlParams(this.route.snapshot.queryParamMap);
 
         this.searchContext.org = this.org; // offer a copy of the service.
-
-        console.debug(this.searchContext);
     }
 }
 
index 3690f85..a25c51d 100644 (file)
@@ -16,7 +16,7 @@ export class ResultFacetsComponent implements OnInit {
     ) {}
 
     ngOnInit() { 
-        this.searchContext = this.cat.searchContext;
+        this.searchContext = this.cat.activeSearchContext();
     }
 
 }
index 2bba534..dc73822 100644 (file)
@@ -16,7 +16,7 @@ export class ResultPaginationComponent implements OnInit {
     ) {}
 
     ngOnInit() { 
-        this.searchContext = this.cat.searchContext;
+        this.searchContext = this.cat.activeSearchContext();
     }
 
 }
index 139597f..2a1b316 100644 (file)
@@ -1,2 +1,15 @@
 
+.cat-record-row {
+  margin-bottom:10px;
+  padding:9px;
+  /*
+  background-color: #f5f5f5;
+  border: 1px solid #e3e3e3;
+  */
+}
+
+.weak-text-1 {
+  font-size: 85%;
+}
+
 
index c7e288c..86416b2 100644 (file)
@@ -1,8 +1,112 @@
 
-<div class="row">
-  <div class="col-1">{{index + 1}}</div>
-  <div class="col-3">{{bibSummary.title}}</div>
-  <div class="col-3">{{bibSummary.author}}</div>
-  <div class="col-3">{{bibSummary.genre}}</div>
-</div>
+<div class="cat-record-row col-12 card card-body bg-light">
+  <div class="row">
+    <div class="col-1">
+      <!-- TODO router links -->
+      <a href="./cat/catalog/record/{{bibSummary.id}}">
+        <img style="height:80px"
+          src="/opac/extras/ac/jacket/small/r/{{bibSummary.id}}"/>
+      </a>
+    </div>
+    <div class="col-5">
+      <div class="row">
+        <div class="col-12 font-weight-bold">
+          <!-- nbsp allows the column to take shape when no value exists -->
+          <a href="./cat/staffcat/record/{{bibSummary.id}}">
+            {{bibSummary.title || '&nbsp;'}}
+          </a>
+        </div>
+      </div>
+      <div class="row pt-2">
+        <div class="col-12">
+          <!-- nbsp allows the column to take shape when no value exists -->
+          <a href (click)="searchAuthor(bibSummary)" style="font-style:italic">
+            {{bibSummary.author || '&nbsp;'}}
+          </a>
+        </div>
+      </div>
+      <div class="row pt-2">
+        <div class="col-12">
+          <span>#{{index + 1 + searchContext.pager.offset}}</span>
+          <span>
+            <img class="pad-right-min" ng-cloak
+              src="/images/format_icons/icon_format/{{bibSummary.ccvms.icon_format.code}}.png"/>
+            <span>{{bibSummary.ccvms.icon_format.label}}</span>
+          </span>
+          <span style='pl-2'>{{bibSummary.edition}}</span>
+          <span style='pl-2'>{{bibSummary.pubdate}}</span>
+        </div>
+      </div>
+    </div>
+    <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">
+            {{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>
+        </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>
+        </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>
+        </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>
+        </div>
+      </div>
+      <div class="row pt-2">
+        <div class="col-4">
+          <div class="pull-right weak-text-1">
+            [% l('ID: [_1]', '{{bibSummary.id}}') %]
+          </div>
+        </div>
+        <div class="col-8">
+          <div class="pull-right">
+            <span>
+              <button (click)="searchContext.place_hold(record)" 
+                class="btn btn-sm btn-success">
+                <span class="glyphicon glyphicon-ok"></span>
+                [% l('Place Hold') %]
+              </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>
+            </span>
+          </div>
+        </div>
+      </div>
+    </div>--><!-- col -->
+  </div><!-- row -->
+</div><!-- col -->
 
index f3f2d7c..a84f85f 100644 (file)
@@ -1,4 +1,5 @@
 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';
 
@@ -14,11 +15,16 @@ export class ResultRecordComponent implements OnInit {
     searchContext: CatalogSearchContext;
 
     constructor(
+        private org: EgOrgService,
         private cat: EgCatalogService
     ) {}
 
     ngOnInit() { 
-        this.searchContext = this.cat.searchContext;
+        this.searchContext = this.cat.activeSearchContext();
+    }
+
+    orgName(orgId: number): string {
+        return this.org.get(orgId).shortname();
     }
 
 }
index 7e6bc66..2e9f187 100644 (file)
@@ -1,8 +1,10 @@
 import {Component, OnInit, Input} from '@angular/core';
-import {ActivatedRoute} from '@angular/router';
+import {Observable} from 'rxjs/Rx';
+import {map, switchMap, distinctUntilChanged} from 'rxjs/operators';
+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';
 
 @Component({
@@ -15,28 +17,59 @@ export class ResultsComponent implements OnInit {
     searchContext: CatalogSearchContext;
 
     constructor(
+        private route: ActivatedRoute,
         private org: EgOrgService,
-        private cat: EgCatalogService
+        private cat: EgCatalogService,
+        private catUrl: EgCatalogUrlService
     ) {}
 
     ngOnInit() { 
-        this.searchContext = this.cat.searchContext;
-        this.searchByUrl();
-    }
+        this.searchContext = this.cat.activeSearchContext();
 
-    searchByUrl(): void {
-        // skip searching when there's no query, etc.
+        // Our search context is initialized on page load.  Once
+        // ResultsComponent is active, it will not be reinitialized,
+        // even if the route parameters changes (unless we change the
+        // route reuse policy).  Watch for changes here to pick up new
+        // searches.  This will also fire on page load.
+        this.route.queryParamMap.subscribe((params: ParamMap) => {
 
+              // TODO: Angular docs suggest using switchMap(), but
+              // it's not firing for some reason.  Also, could avoid
+              // firing unnecessary searches when a param unrelated to
+              // searching is changed by .map()'ing out only the desired
+              // params and running through .distinctUntilChanged(), but
+              // .map() is not firing either.  I'm missing something.
+              this.searchByUrl(params);
+        })
+    }
+
+    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 => {
+
+            // Do some post-search tidying before component rendering.
+            this.searchContext.result.records.forEach(b => {
+                b.copyCounts = b.copyCounts.filter(c => {
+                    return c.type == 'staff'
+                })
+            });
+
             console.debug('search complete');
+
         });
+    }
 
+    orgName(orgId: number): string {
+        return this.org.get(orgId).shortname();
     }
 
+    searchAuthor(bibSummary: any) {
+    }
 }
 
 
index 377c81f..e04900c 100644 (file)
@@ -18,3 +18,9 @@
     padding-left: 0px;
 }
 
+.search-plus-minus {
+  /* Transparent background */
+  background-color: rgba(0, 0, 0, 0.0);
+  padding-left: .25rem;
+  padding-right: .25rem; /* default .5rem */
+}
index 9046fa2..09e57fb 100644 (file)
@@ -1,7 +1,7 @@
 <div id='staffcat-search-form'>
   <div class="row"
     *ngFor="let q of searchContext.query; let idx = index; trackBy:trackByIdx">
-    <div class="col-9 flex-row">
+    <div class="col-md-8 flex-row">
       <div class="flex-cell">
         <div *ngIf="idx == 0">
           <select class="form-control" [(ngModel)]="searchContext.format">
@@ -12,7 +12,7 @@
         </div>
         <div *ngIf="idx > 0">
           <select class="form-control"
-            [(ngModel)]="searchContext.joiner[idx]">
+            [(ngModel)]="searchContext.joinOp[idx]">
             <option value='&&'>And</option>
             <option value='||'>Or</option>
           </select>
@@ -20,7 +20,7 @@
       </div>
       <div class="flex-cell">
         <select class="form-control" 
-          [(ngModel)]="searchContext.searchClass[idx]">
+          [(ngModel)]="searchContext.fieldClass[idx]">
           <option value='keyword'>Keyword</option>
           <option value='title'>Title</option>
           <option value='jtitle'>Journal Title</option>
@@ -31,7 +31,7 @@
       </div>
       <div class="flex-cell">
         <select class="form-control" 
-          [(ngModel)]="searchContext.match[idx]">
+          [(ngModel)]="searchContext.matchOp[idx]">
           <option value='contains'>Contains</option>
           <option value='nocontains'>Does not contain</option>
           <option value='phrase'>Contains phrase</option>
         </div>
       </div>
       <div class="flex-cell">
-        <button class="btn btn-sm btn-default" 
+        <button class="btn btn-sm search-plus-minus"
           (click)="addSearchRow(idx + 1)">
           <span class="material-icons">add_circle_outline</span>
         </button>
-        <button class="btn btn-sm btn-default"
+        <button class="btn btn-sm search-plus-minus"
           [disabled]="searchContext.query.length < 2"
           (click)="delSearchRow(idx)">
           <span class="material-icons">remove_circle_outline</span>
         </button>
       </div>
     </div><!-- col -->
-    <div class="col-3">
-      <div *ngIf="idx == 0" class="pull-right">
+    <div class="col-md-4">
+      <div *ngIf="idx == 0" class="float-right">
         <button class="btn btn-success" type="button"
           (click)="searchContext.pager.offset=0;searchByForm()">
           Search
@@ -86,7 +86,7 @@
 
       <!--
   <div class="row">
-    <div class="col-9 flex-row">
+    <div class="col-md-9 flex-row">
       <div class="flex-cell">
         <eg-org-select nodefault 
           selected="searchContext.searchContext_org">
   </div>
 
   <div class="row pad-vert-min" ng-show="showAdvancedSearch">
-    <div class="col-2">
+    <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>
       </select>
     </div>
-    <div class="col-2">
+    <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>
       </select>
     </div>
-    <div class="col-2">
+    <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>
       </select>
     </div>
-    <div class="col-2">
+    <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>
   </div>
   <div class="row pad-vert-min" ng-show="showAdvancedSearch">
-    <div class="col-2">
+    <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>
       </select>
     </div>
-    <div class="col-2">
+    <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>
       </select>
     </div>
-    <div class="col-2">
+    <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>
       </select>
     </div>
-    <div class="col-2">
+    <div class="col-md-2">
       <i>Copy location filter goes here...</i>
     </div>
   </div>
index 95643d4..b63f62f 100644 (file)
@@ -17,6 +17,7 @@ export class SearchFormComponent implements OnInit {
     ccvmMap: {[ccvm:string] : EgIdlObject[]} = {};
     cmfMap: {[cmf:string] : EgIdlObject[]} = {};
     showAdvancedSearch: boolean = false;
+    routeIndex: number = 0;
 
     constructor(
         private router: Router,
@@ -29,21 +30,21 @@ export class SearchFormComponent implements OnInit {
     ngOnInit() { 
         this.ccvmMap = this.cat.ccvmMap;
         this.cmfMap = this.cat.cmfMap;
-        this.searchContext = this.cat.searchContext;
+        this.searchContext = this.cat.activeSearchContext();
     }
 
     addSearchRow(index: number): void {
         this.searchContext.query.splice(index, 0, '');
-        this.searchContext.searchClass.splice(index, 0, 'keyword');
-        this.searchContext.joiner.splice(index, 0, '&&');
-        this.searchContext.match.splice(index, 0, 'contains');
+        this.searchContext.fieldClass.splice(index, 0, 'keyword');
+        this.searchContext.joinOp.splice(index, 0, '&&');
+        this.searchContext.matchOp.splice(index, 0, 'contains');
     }
 
     delSearchRow(index: number): void {
         this.searchContext.query.splice(index, 1);
-        this.searchContext.searchClass.splice(index, 1);
-        this.searchContext.joiner.splice(index, 1);
-        this.searchContext.match.splice(index, 1);
+        this.searchContext.fieldClass.splice(index, 1);
+        this.searchContext.joinOp.splice(index, 1);
+        this.searchContext.matchOp.splice(index, 1);
     }
 
     checkEnter($event: any): void {
@@ -66,6 +67,16 @@ export class SearchFormComponent implements OnInit {
     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});
     }
diff --git a/Open-ILS/webby-src/src/app/staff/staff.component.css b/Open-ILS/webby-src/src/app/staff/staff.component.css
new file mode 100644 (file)
index 0000000..508d879
--- /dev/null
@@ -0,0 +1,8 @@
+#staff-content-container {
+  width: 95%;
+  margin-top:56px;
+  padding-right: 10px;
+  padding-left: 10px;
+  margin-right: auto;
+  margin-left: auto;
+}
index 544e97f..7bd463a 100644 (file)
@@ -1,9 +1,7 @@
 <!-- top navigation bar -->
 <eg-staff-nav-bar></eg-staff-nav-bar>
 
-<!-- margin is there to accommodate navbar -->
-<div style="margin-top:56px">
-
+<div id='staff-content-container'>
   <!-- page content -->
   <router-outlet></router-outlet>
 </div>
index 965f01e..15bbea2 100644 (file)
@@ -4,7 +4,8 @@ import { EgAuthService, EgAuthWsState } from '@eg/core/auth';
 import { EgNetService } from '@eg/core/net';
 
 @Component({
-  templateUrl: 'staff.component.html'
+  templateUrl: 'staff.component.html',
+  styleUrls: ['staff.component.css']
 })
 
 export class EgStaffComponent implements OnInit {
index 1ac2ee5..986ca2c 100644 (file)
@@ -5,3 +5,10 @@
 @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;
+}
+