LP#626157 Ang2 experiments
authorBill Erickson <berickxx@gmail.com>
Mon, 4 Dec 2017 01:50:48 +0000 (20:50 -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>
14 files changed:
Open-ILS/webby-src/src/app/staff/catalog/catalog.component.html
Open-ILS/webby-src/src/app/staff/catalog/catalog.module.ts
Open-ILS/webby-src/src/app/staff/catalog/catalog.service.ts
Open-ILS/webby-src/src/app/staff/catalog/resolver.service.ts [new file with mode: 0644]
Open-ILS/webby-src/src/app/staff/catalog/results.component.html [new file with mode: 0644]
Open-ILS/webby-src/src/app/staff/catalog/results.component.ts [new file with mode: 0644]
Open-ILS/webby-src/src/app/staff/catalog/routing.module.ts
Open-ILS/webby-src/src/app/staff/catalog/search-context.ts [new file with mode: 0644]
Open-ILS/webby-src/src/app/staff/catalog/search-form.component.html [new file with mode: 0644]
Open-ILS/webby-src/src/app/staff/catalog/search-form.component.ts [new file with mode: 0644]
Open-ILS/webby-src/src/app/staff/catalog/search.component.html [deleted file]
Open-ILS/webby-src/src/app/staff/catalog/search.component.ts [deleted file]
Open-ILS/webby-src/src/app/staff/resolver.service.ts
Open-ILS/webby-src/src/app/staff/staff.component.ts

index 37d3131..b9eee02 100644 (file)
@@ -4,13 +4,15 @@ import {EgStaffModule} from '../staff.module';
 import {EgCatalogRoutingModule} from './routing.module';
 import {EgUnapiService} from '@eg/share/unapi';
 import {EgCatalogComponent} from './catalog.component';
-import {EgCatalogSearchComponent} from './search.component';
+import {EgCatalogSearchFormComponent} from './search-form.component';
+import {EgCatalogResultsComponent} from './results.component';
 import {EgCatalogService} from './catalog.service';
 
 @NgModule({
   declarations: [
     EgCatalogComponent,
-    EgCatalogSearchComponent
+    EgCatalogSearchFormComponent,
+    EgCatalogResultsComponent
   ],
   imports: [
     EgStaffModule,
index d4f3581..6fd5e2e 100644 (file)
@@ -4,6 +4,7 @@ import {EgUnapiService} from '@eg/share/unapi';
 import {EgIdlObject} from '@eg/core/idl';
 import {EgNetService} from '@eg/core/net';
 import {EgPcrudService} from '@eg/core/pcrud';
+import {SearchContext} from './search-context';
 
 const CCVM_FILTER_TYPES = [
     'item_type',
@@ -17,161 +18,10 @@ const CCVM_FILTER_TYPES = [
     'search_format'
 ];
 
-class Paginator {
-    offset: number = 0;
-    limit: number = 15;
-    resultCount: number;
-
-    isFirstPage(): boolean {
-        return this.offset == 0;
-    }
-
-    isLastPage(): boolean {
-        return this.currentPage() == this.pageCount();
-    }
-
-    currentPage(): number {
-        return Math.floor(this.offset / this.limit) + 1
-    }
-
-    pageCount(): number {
-        let pages = this.resultCount / this.limit;
-        if (Math.floor(pages) < pages)
-            pages = Math.floor(pages) + 1;
-        return pages;
-    }
-
-    pageList(): number[] {
-        let list = [];
-        for(let i = 1; i <= this.pageCount(); i++)
-            list.push(i);
-        return list;
-    }
-}
-
-export class FacetFilter {
-    facetClass: string;
-    facetName: string;
-    facetValue: string;
-}
-
-export class CatalogContext {
-    available: boolean = false;
-    global: boolean = false;
-    sort: string;
-    searchClass: string[];
-    query: string[];
-    joiner: string[];
-    match: string[];
-    format: string;
-    searchOrg: EgIdlObject;
-    ccvmFilters: {[ccvm:string] : string} = {};
-    facetFilters: FacetFilter[] = [];
-    paginator: Paginator = new Paginator();
-    result: any;
-    org: EgOrgService;
-
-    compileSearch(): string {
-
-        let str: string = '';
-
-        if (this.available) str += ' #available';
-
-        if (this.sort) {
-            // e.g. title, title.descending
-            let parts = this.sort.split(/\./);
-            if (parts[1]) str += ' #descending';
-            str += ' sort(' + parts[0] + ')';
-        }
-
-        // -------
-        // Compile boolean sub-query components
-        if (str.length) str += ' ';
-        let qcount = this.query.length;
-
-        // if we multiple boolean query components, wrap them in parens.
-        if (qcount > 1) str += '(';
-        this.query.forEach((q, idx) => {
-            str += this.compileBoolQuerySet(idx)
-        });
-        if (qcount > 1) str += ')';
-        // -------
-
-        if (this.format) {
-            str += ' format(' + this.format + ')';
-        }
-
-        if (this.global) {
-            str += ' depth(' +
-                this.org.root().ou_type().depth() + ')';
-        }
-
-        str += ' site(' + this.searchOrg.shortname() + ')';
-
-        Object.keys(this.ccvmFilters).forEach(field => {
-            str += ' ' + field + '(' + this.ccvmFilters[field] + ')';
-        });
-
-        this.facetFilters.forEach(f => {
-            str += ' ' + f.facetClass + '|'
-                + f.facetName + '[' + f.facetValue + ']';
-        });
-
-        return str;
-    }
-
-    stripQuotes(query: string): string {
-        return query.replace(/"/g, ''); 
-    }
-
-    stripAnchors(query: string): string {
-        return query.replace(/[\^\$]/g, ''); 
-    }
-
-    addQuotes(query: string): string {
-        if (query.match(/ /))
-            return '"' + query + '"'
-        return query;
-    };
-
-    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 str = '';
-        if (!query) return str;
-
-        if (idx > 0) str += ' ' + joiner + ' ';
-
-        str += '(';
-        if (searchClass) str += searchClass + ':';
-
-        switch(match) {
-            case 'phrase':
-                query = this.addQuotes(this.stripQuotes(query));
-                break;
-            case 'nocontains':
-                query = '-' + this.addQuotes(this.stripQuotes(query));
-                break;
-            case 'exact':
-                query = '^' + this.stripAnchors(query) + '$';
-                break;
-            case 'starts':
-                query = this.addQuotes('^' + 
-                    this.stripAnchors(this.stripQuotes(query)));
-                break;
-        }
-
-        return str + query + ')';
-    }
-}
-
-
 @Injectable()
 export class EgCatalogService {
 
+    searchContext: SearchContext;
     ccvmMap: {[ccvm:string] : EgIdlObject[]} = {};
     cmfMap: {[cmf:string] : EgIdlObject[]} = {};
 
@@ -180,10 +30,12 @@ export class EgCatalogService {
         private org: EgOrgService,
         private unapi: EgUnapiService,
         private pcrud: EgPcrudService
-    ) {}
-
+    ) {
+        this.searchContext = new SearchContext();
+        this.searchContext.org = this.org;
+    }
 
-    search(context: CatalogContext): Promise<void> {
+    search(context: SearchContext): Promise<void> {
 
         var fullQuery = context.compileSearch();
 
@@ -194,13 +46,13 @@ export class EgCatalogService {
             this.net.request(
                 'open-ils.search',
                 'open-ils.search.biblio.multiclass.query.staff', {
-                    limit : context.paginator.limit, 
-                    offset : context.paginator.offset
+                    limit : context.pager.limit, 
+                    offset : context.pager.offset
                 }, fullQuery, true
             ).subscribe(result => {
                 context.result = result;
                 context.result.records = [];
-                context.paginator.resultCount = result.count;
+                context.pager.resultCount = result.count;
 
                 let promises = [];
                 result.ids.forEach(blob => {
@@ -230,7 +82,6 @@ export class EgCatalogService {
             this.pcrud.search('ccvm', 
                 {ctype : CCVM_FILTER_TYPES}, {}, {atomic: true}
             ).subscribe(list => {
-                console.debug(list);
                 this.compileCcvms(list);
                 resolve();
             })
diff --git a/Open-ILS/webby-src/src/app/staff/catalog/resolver.service.ts b/Open-ILS/webby-src/src/app/staff/catalog/resolver.service.ts
new file mode 100644 (file)
index 0000000..657a47f
--- /dev/null
@@ -0,0 +1,35 @@
+import {Injectable} from '@angular/core';
+import {Location} from '@angular/common';
+import {Observable, Observer} from 'rxjs/Rx';
+import {Router, Resolve, RouterStateSnapshot,
+        ActivatedRouteSnapshot} from '@angular/router';
+import {EgStoreService} from '@eg/core/store';
+import {EgNetService} from '@eg/core/net';
+import {EgAuthService} from '@eg/core/auth';
+import {EgPcrudService} from '@eg/core/pcrud';
+import {EgCatalogService} from './catalog.service'
+
+@Injectable()
+export class EgCatalogResolver implements Resolve<Promise<any[]>> {
+
+    constructor(
+        private router: Router, 
+        private ngLocation: Location,
+        private store: EgStoreService,
+        private net: EgNetService,
+        private auth: EgAuthService,
+        private cat: EgCatalogService
+    ) {}
+
+    resolve(
+        route: ActivatedRouteSnapshot, 
+        state: RouterStateSnapshot): Promise<any[]> {
+
+        console.debug('EgCatalogResolver:resolve()');
+
+        return Promise.all([
+            this.cat.fetchCcvms()
+        ]);
+    }
+}
+
diff --git a/Open-ILS/webby-src/src/app/staff/catalog/results.component.html b/Open-ILS/webby-src/src/app/staff/catalog/results.component.html
new file mode 100644 (file)
index 0000000..add8b04
--- /dev/null
@@ -0,0 +1,13 @@
+
+<h2>Search Sample</h2>
+
+<div *ngIf="searchContext.result">
+  <div class="row" 
+    *ngFor="let record of searchContext.result.records; let idx = index">
+    <div class="col-1">{{idx + 1}}</div>
+    <div class="col-3">{{record.title}}</div>
+    <div class="col-3">{{record.author}}</div>
+    <div class="col-3">{{record.genre}}</div>
+  </div>
+</div>
+
diff --git a/Open-ILS/webby-src/src/app/staff/catalog/results.component.ts b/Open-ILS/webby-src/src/app/staff/catalog/results.component.ts
new file mode 100644 (file)
index 0000000..0a1c48d
--- /dev/null
@@ -0,0 +1,26 @@
+import {Component, OnInit} from '@angular/core';
+import {ActivatedRoute} from '@angular/router';
+import {EgAuthService} from '@eg/core/auth';
+import {EgOrgService} from '@eg/core/org';
+import {EgCatalogService} from './catalog.service';
+import {SearchContext} from './search-context';
+
+@Component({
+  templateUrl: 'results.component.html'
+})
+export class EgCatalogResultsComponent implements OnInit {
+
+    searchContext: SearchContext;
+
+    constructor(
+        private auth: EgAuthService,
+        private org: EgOrgService,
+        private cat: EgCatalogService
+    ) {
+        this.searchContext = this.cat.searchContext;
+    }
+
+    ngOnInit() { 
+    }
+}
+
index 3cafbb4..50fdc00 100644 (file)
@@ -1,20 +1,23 @@
 import {NgModule} from '@angular/core';
 import {RouterModule, Routes} from '@angular/router';
 import {EgCatalogComponent} from './catalog.component';
-import {EgCatalogSearchComponent} from './search.component';
+import {EgCatalogResultsComponent} from './results.component';
+import {EgCatalogResolver} from './resolver.service';
 
 const routes: Routes = [{ 
   path: '',
   component: EgCatalogComponent,
+  resolve: {catResolver : EgCatalogResolver},
   children : [{
     path: 'search',
-    component: EgCatalogSearchComponent,
+    component: EgCatalogResultsComponent,
   }]
 }];
 
 @NgModule({
-  imports: [ RouterModule.forChild(routes) ],
-  exports: [ RouterModule ]
+  imports: [RouterModule.forChild(routes)],
+  exports: [RouterModule],
+  providers: [EgCatalogResolver ]
 })
 
 export class EgCatalogRoutingModule {}
diff --git a/Open-ILS/webby-src/src/app/staff/catalog/search-context.ts b/Open-ILS/webby-src/src/app/staff/catalog/search-context.ts
new file mode 100644 (file)
index 0000000..2ac2a97
--- /dev/null
@@ -0,0 +1,175 @@
+import {EgOrgService} from '@eg/core/org';
+import {EgIdlObject} from '@eg/core/idl';
+
+class Pager {
+    offset: number = 0;
+    limit: number = 15;
+    resultCount: number;
+
+    isFirstPage(): boolean {
+        return this.offset == 0;
+    }
+
+    isLastPage(): boolean {
+        return this.currentPage() == this.pageCount();
+    }
+
+    currentPage(): number {
+        return Math.floor(this.offset / this.limit) + 1
+    }
+
+    pageCount(): number {
+        let pages = this.resultCount / this.limit;
+        if (Math.floor(pages) < pages)
+            pages = Math.floor(pages) + 1;
+        return pages;
+    }
+
+    pageList(): number[] {
+        let list = [];
+        for(let i = 1; i <= this.pageCount(); i++)
+            list.push(i);
+        return list;
+    }
+}
+
+interface FacetFilter {
+    facetClass: string;
+    facetName: string;
+    facetValue: string;
+}
+
+
+export class SearchContext {
+    available: boolean;
+    global: boolean;
+    sort: string;
+    searchClass: string[];
+    query: string[];
+    joiner: string[];
+    match: string[];
+    format: string;
+    searchOrg: EgIdlObject;
+    ccvmFilters: {[ccvm:string] : string};
+    facetFilters: FacetFilter[];
+    pager: Pager;
+    org: EgOrgService;
+    result: any;
+
+    constructor() {
+        this.pager = new Pager();
+        this.reset();
+    }
+
+    reset(): void {
+        this.pager.offset = 0,
+        this.format = '',
+        this.sort = '',
+        this.query  = [''];
+        this.searchClass   = ['keyword'];
+        this.match  = ['contains'];
+        this.joiner = [''];
+        this.available = false;
+        this.global = false;
+        this.ccvmFilters = {};
+        this.facetFilters = [];
+    }
+
+    compileSearch(): string {
+
+        let str: string = '';
+
+        if (this.available) str += ' #available';
+
+        if (this.sort) {
+            // e.g. title, title.descending
+            let parts = this.sort.split(/\./);
+            if (parts[1]) str += ' #descending';
+            str += ' sort(' + parts[0] + ')';
+        }
+
+        // -------
+        // Compile boolean sub-query components
+        if (str.length) str += ' ';
+        let qcount = this.query.length;
+
+        // if we multiple boolean query components, wrap them in parens.
+        if (qcount > 1) str += '(';
+        this.query.forEach((q, idx) => {
+            str += this.compileBoolQuerySet(idx)
+        });
+        if (qcount > 1) str += ')';
+        // -------
+
+        if (this.format) {
+            str += ' format(' + this.format + ')';
+        }
+
+        if (this.global) {
+            str += ' depth(' +
+                this.org.root().ou_type().depth() + ')';
+        }
+
+        str += ' site(' + this.searchOrg.shortname() + ')';
+
+        Object.keys(this.ccvmFilters).forEach(field => {
+            str += ' ' + field + '(' + this.ccvmFilters[field] + ')';
+        });
+
+        this.facetFilters.forEach(f => {
+            str += ' ' + f.facetClass + '|'
+                + f.facetName + '[' + f.facetValue + ']';
+        });
+
+        return str;
+    }
+
+    stripQuotes(query: string): string {
+        return query.replace(/"/g, ''); 
+    }
+
+    stripAnchors(query: string): string {
+        return query.replace(/[\^\$]/g, ''); 
+    }
+
+    addQuotes(query: string): string {
+        if (query.match(/ /))
+            return '"' + query + '"'
+        return query;
+    };
+
+    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 str = '';
+        if (!query) return str;
+
+        if (idx > 0) str += ' ' + joiner + ' ';
+
+        str += '(';
+        if (searchClass) str += searchClass + ':';
+
+        switch(match) {
+            case 'phrase':
+                query = this.addQuotes(this.stripQuotes(query));
+                break;
+            case 'nocontains':
+                query = '-' + this.addQuotes(this.stripQuotes(query));
+                break;
+            case 'exact':
+                query = '^' + this.stripAnchors(query) + '$';
+                break;
+            case 'starts':
+                query = this.addQuotes('^' + 
+                    this.stripAnchors(this.stripQuotes(query)));
+                break;
+        }
+
+        return str + query + ')';
+    }
+}
+
+
diff --git a/Open-ILS/webby-src/src/app/staff/catalog/search-form.component.html b/Open-ILS/webby-src/src/app/staff/catalog/search-form.component.html
new file mode 100644 (file)
index 0000000..0e2fc75
--- /dev/null
@@ -0,0 +1,251 @@
+
+<style>
+  /** TODO: clean up some inline styles */
+
+  #staffcat-search-form .eg-org-selector,
+  #staffcat-search-form .eg-org-selector button {
+    width: 100%;
+    text-align: left
+  }
+  .flex-row {
+    display: flex;
+  }
+  .flex-cell {
+    /* override style.css padding:4px */
+    flex: 1;
+    padding: 0px 0px 0px 8px;
+  }
+  .flex-2 {
+    flex: 2
+  }
+  .flex-row:first-child {
+    padding-left: 0px;
+  }
+</style>
+<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="flex-cell">
+        <div *ngIf="idx == 0">
+          <select class="form-control" [(ngModel)]="searchContext.format">
+            <option value=''>All Formats</option>
+            <option *ngFor="let fmt of ccvmMap.search_format"
+              value="{{fmt.code()}}">{{fmt.value()}}</option>
+          </select>
+        </div>
+        <div *ngIf="idx > 0">
+          <select class="form-control"
+            [(ngModel)]="searchContext.joiner[idx]">
+            <option value='&&'>And</option>
+            <option value='||'>Or</option>
+          </select>
+        </div>
+      </div>
+      <div class="flex-cell">
+        <select class="form-control" 
+          [(ngModel)]="searchContext.searchClass[idx]">
+          <option value='keyword'>Keyword</option>
+          <option value='title'>Title</option>
+          <option value='jtitle'>Journal Title</option>
+          <option value='author'>Author</option>
+          <option value='subject'>Subject</option>
+          <option value='series'>Series</option>
+        </select>
+      </div>
+      <div class="flex-cell">
+        <select class="form-control" 
+          [(ngModel)]="searchContext.match[idx]">
+          <option value='contains'>Contains</option>
+          <option value='nocontains'>Does not contain</option>
+          <option value='phrase'>Contains phrase</option>
+          <option value='exact'>Matches exactly</option>
+          <option value='starts'>Starts with</option>
+        </select>
+      </div>
+      <div class="flex-cell flex-2">
+        <div class="form-group">
+          <input type="text" class="form-control"
+            TODOfocus-me="searchContext.focus_query[idx]"
+            [(ngModel)]="searchContext.query[idx]"
+            (keyup)="checkEnter($event)"
+            placeholder="Query..."/>
+        </div>
+      </div>
+      <div class="flex-cell">
+        <button class="btn btn-sm btn-default" 
+          (click)="addSearchRow(idx + 1)">
+          <span class="material-icons">add_circle_outline</span>
+        </button>
+        <button class="btn btn-sm btn-default"
+          [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">
+        <button class="btn btn-success" type="button"
+          (click)="searchContext.pager.offset=0;searchByForm()">
+          Search
+        </button>
+        <button class="btn btn-warning" type="button"
+          (click)="searchContext.reset()">
+          Clear Form
+        </button>
+        <button class="btn btn-secondary" type="button"
+          *ngIf="!showAdvancedSearch"
+          (click)="showAdvancedSearch=true">
+          More Filters...
+        </button>
+        <button class="btn btn-secondary" type="button"
+          *ngIf="showAdvancedSearch"
+          (click)="showAdvancedSearch=false">
+          Hide Filters
+        </button>
+      </div>
+  </div><!-- row -->
+</div>
+
+      <!--
+    <div class="col-3">
+      <div *ngIf="idx == 0" class="pull-right">
+        <button class="btn btn-success" type="button"
+          ng-click="searchContext.offset=0;searchContext.search_by_form()">
+          [% l('Search') %]
+        </button>
+        <button class="btn btn-warning" type="button"
+          ng-click="searchContext.reset_form()">
+          [% l('Clear Form') %]
+        </button>
+        <button class="btn btn-default" type="button"
+          *ngIf="!showAdvancedSearch"
+          ng-click="showAdvancedSearch=true">
+          [% l('More Filters...') %]
+        </button>
+        <button class="btn btn-default" type="button"
+          *ngIf="showAdvancedSearch"
+          ng-click="showAdvancedSearch=false">
+          [% l('Hide Filters') %]
+        </button>
+      </div>
+    </div>
+  </div>
+  <div class="row">
+    <div class="col-9 flex-row">
+      <div class="flex-cell">
+        <eg-org-select nodefault 
+          selected="searchContext.searchContext_org">
+        </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>
+          </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>
+          <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>
+          <optgroup label="[% l('Sort by Popularity') %]">
+            <option value='popularity'>[% l('Most Popular') %]</option>
+            <option value='poprel'>[% l('Popularity Adjusted Relevance') %]</option>
+          </optgroup>
+        </select>
+      </div>
+      <div class="flex-cell flex-2">
+        <div class="checkbox">
+          <label>
+            <input type="checkbox" [(ngModel)]="searchContext.available"/>
+            [% l('Limit to Available') %]
+          </label>
+        </div>
+      </div>
+      <div class="flex-cell flex-4">
+        <div class="checkbox">
+          <label>
+            <input type="checkbox" [(ngModel)]="searchContext.global"/>
+            [% l('Show Results from All Libraries') %]
+          </label>
+        </div>
+      </div>
+      <div class="flex-cell flex-2">
+        <div *ngIf="searchContext.search_state() == 'searching'">
+          <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>
+            </div>
+          </div>
+        </div>
+      </div>
+    </div>
+  </div>
+
+  <div class="row pad-vert-min" ng-show="showAdvancedSearch">
+    <div class="col-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">
+      <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">
+      <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">
+      <select class="form-control" [(ngModel)]="searchContext.audience" multiple="true">
+        <option value=''>[% l('All Audiences') %]</option>
+        <option *ngFor="let audience of searchContext.ccvm_lists('audience')"
+          value="{{audience.code()}}">{{audience.value()}}</option>
+      </select>
+    </div>
+  </div>
+  <div class="row pad-vert-min" ng-show="showAdvancedSearch">
+    <div class="col-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">
+      <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">
+      <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">
+      <i>Copy location filter goes here...</i>
+    </div>
+  </div>
+</div>
+-->
+
diff --git a/Open-ILS/webby-src/src/app/staff/catalog/search-form.component.ts b/Open-ILS/webby-src/src/app/staff/catalog/search-form.component.ts
new file mode 100644 (file)
index 0000000..911d054
--- /dev/null
@@ -0,0 +1,70 @@
+import {Component, OnInit} from '@angular/core';
+import {ActivatedRoute} from '@angular/router';
+import {EgAuthService} from '@eg/core/auth';
+import {EgOrgService} from '@eg/core/org';
+import {EgCatalogService} from './catalog.service';
+import {SearchContext} from './search-context';
+import {EgIdlObject} from '@eg/core/idl';
+
+@Component({
+  selector: 'eg-catalog-search-form',
+  templateUrl: 'search-form.component.html'
+})
+export class EgCatalogSearchFormComponent implements OnInit {
+
+    searchContext: SearchContext;
+    ccvmMap: {[ccvm:string] : EgIdlObject[]} = {};
+    cmfMap: {[cmf:string] : EgIdlObject[]} = {};
+    showAdvancedSearch: boolean = false;
+
+    constructor(
+        private auth: EgAuthService,
+        private org: EgOrgService,
+        private cat: EgCatalogService
+    ) {}
+
+    ngOnInit() { 
+        this.searchContext = this.cat.searchContext;
+        this.ccvmMap = this.cat.ccvmMap;
+        this.cmfMap = this.cat.cmfMap;
+    }
+
+    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');
+    }
+
+    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);
+    }
+
+    checkEnter($event: any): void {
+        console.log($event.keyCode);
+        if ($event.keyCode == 13) {
+            this.searchContext.pager.offset = 0;
+            this.searchByForm();
+        }
+    }
+
+    // https://stackoverflow.com/questions/42322968/angular2-dynamic-input-field-lose-focus-when-input-changes
+    trackByIdx(index: any, item: any) {
+       return index;
+    }
+
+    searchByForm(): void {
+        console.log('searchByForm()');
+        this.searchContext.searchOrg = this.org.get(4); // TEST BR1
+        this.cat.search(this.searchContext).then(ok => {
+            console.debug('search complete');
+        });
+
+    }
+
+}
+
+
diff --git a/Open-ILS/webby-src/src/app/staff/catalog/search.component.html b/Open-ILS/webby-src/src/app/staff/catalog/search.component.html
deleted file mode 100644 (file)
index 3e5aebc..0000000
+++ /dev/null
@@ -1,12 +0,0 @@
-
-<h2>Search Sample</h2>
-
-<div *ngIf="context.result">
-  <div class="row" *ngFor="let record of context.result.records; let idx = index">
-    <div class="col-1">{{idx + 1}}</div>
-    <div class="col-3">{{record.title}}</div>
-    <div class="col-3">{{record.author}}</div>
-    <div class="col-3">{{record.genre}}</div>
-  </div>
-</div>
-
diff --git a/Open-ILS/webby-src/src/app/staff/catalog/search.component.ts b/Open-ILS/webby-src/src/app/staff/catalog/search.component.ts
deleted file mode 100644 (file)
index 39a6ec8..0000000
+++ /dev/null
@@ -1,40 +0,0 @@
-import {Component, OnInit} from '@angular/core';
-import {ActivatedRoute} from '@angular/router';
-import {EgAuthService} from '@eg/core/auth';
-import {EgOrgService} from '@eg/core/org';
-import {EgCatalogService, CatalogContext} from './catalog.service';
-
-@Component({
-  templateUrl: 'search.component.html'
-})
-export class EgCatalogSearchComponent implements OnInit {
-
-    context: CatalogContext;
-
-    constructor(
-        private auth: EgAuthService,
-        private org: EgOrgService,
-        private cat: EgCatalogService
-    ) {}
-
-    ngOnInit() { 
-        this.context = new CatalogContext();
-
-        this.cat.fetchCcvms().then(ok => { // TODO: catalog resolver
-            console.log(this.cat.ccvmMap);
-
-            this.context.searchClass = ['keyword'];
-            this.context.query = ['piano'];
-            this.context.joiner = ['&&'];
-            this.context.match = ['contains'];
-            this.context.format = null;
-            this.context.org = this.org; // hmm, refactor maybe
-            this.context.searchOrg = this.org.get(4); // BR1
-
-            this.cat.search(this.context).then(ok => {
-                console.log('ALL DONE SEARCH');
-            });
-        });
-    }
-}
-
index 492663f..66903db 100644 (file)
@@ -42,6 +42,7 @@ export class EgStaffResolver implements Resolve<Observable<any>> {
         return Observable.create(observer => {
             this.auth.testAuthToken().then(
                 tokenOk => {
+                    console.debug('EgStaffResolver: authtoken verified');
                     this.auth.verifyWorkstation().then(
                         wsOk => {
                             this.loadStartupData(observer).then(
@@ -58,9 +59,10 @@ export class EgStaffResolver implements Resolve<Observable<any>> {
                 }, 
                 tokenNotOk => {
                     // Authtoken is not OK.
+                    console.debug('EgStaffResolver: authtoken is not valid');
                     this.auth.redirectUrl = state.url;
                     this.router.navigate([this.loginPath]);
-                    observer.complete();
+                    observer.error('invalid auth');
                 }
             );
         });
index e4808a9..965f01e 100644 (file)
@@ -21,6 +21,8 @@ export class EgStaffComponent implements OnInit {
 
     ngOnInit() {
 
+        console.debug('EgStaffComponent:ngOnInit()');
+
         // Fires on all in-app router navigation, but not initial page load.
         this.router.events.subscribe(routeEvent => {
             if (routeEvent instanceof NavigationEnd) {