LP#626157 Ang2 experiments
authorBill Erickson <berickxx@gmail.com>
Mon, 4 Dec 2017 21:20:18 +0000 (16:20 -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>
28 files changed:
Open-ILS/webby-src/src/app/share/catalog/catalog.service.ts [new file with mode: 0644]
Open-ILS/webby-src/src/app/share/catalog/search-context.ts [new file with mode: 0644]
Open-ILS/webby-src/src/app/share/util/pager.ts [new file with mode: 0644]
Open-ILS/webby-src/src/app/staff/catalog/catalog.component.html
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/catalog.service.ts [deleted file]
Open-ILS/webby-src/src/app/staff/catalog/resolver.service.ts
Open-ILS/webby-src/src/app/staff/catalog/result/facets.component.css [new file with mode: 0644]
Open-ILS/webby-src/src/app/staff/catalog/result/facets.component.html [new file with mode: 0644]
Open-ILS/webby-src/src/app/staff/catalog/result/facets.component.ts [new file with mode: 0644]
Open-ILS/webby-src/src/app/staff/catalog/result/pagination.component.css [new file with mode: 0644]
Open-ILS/webby-src/src/app/staff/catalog/result/pagination.component.html [new file with mode: 0644]
Open-ILS/webby-src/src/app/staff/catalog/result/pagination.component.ts [new file with mode: 0644]
Open-ILS/webby-src/src/app/staff/catalog/result/record.component.css [new file with mode: 0644]
Open-ILS/webby-src/src/app/staff/catalog/result/record.component.html [new file with mode: 0644]
Open-ILS/webby-src/src/app/staff/catalog/result/record.component.ts [new file with mode: 0644]
Open-ILS/webby-src/src/app/staff/catalog/result/results.component.css [new file with mode: 0644]
Open-ILS/webby-src/src/app/staff/catalog/result/results.component.html [new file with mode: 0644]
Open-ILS/webby-src/src/app/staff/catalog/result/results.component.ts [new file with mode: 0644]
Open-ILS/webby-src/src/app/staff/catalog/results.component.html [deleted file]
Open-ILS/webby-src/src/app/staff/catalog/results.component.ts [deleted file]
Open-ILS/webby-src/src/app/staff/catalog/routing.module.ts
Open-ILS/webby-src/src/app/staff/catalog/search-context.ts [deleted file]
Open-ILS/webby-src/src/app/staff/catalog/search-form.component.css [new file with mode: 0644]
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/index.html

diff --git a/Open-ILS/webby-src/src/app/share/catalog/catalog.service.ts b/Open-ILS/webby-src/src/app/share/catalog/catalog.service.ts
new file mode 100644 (file)
index 0000000..9e7adac
--- /dev/null
@@ -0,0 +1,171 @@
+import {Injectable} from '@angular/core';
+import {EgOrgService} from '@eg/core/org';
+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 {CatalogSearchContext} from './search-context';
+
+const CCVM_FILTER_TYPES = [
+    'item_type',
+    'item_form',
+    'item_lang',
+    'audience',
+    'audience_group',
+    'vr_format',
+    'bib_level',
+    'lit_form',
+    'search_format'
+];
+
+@Injectable()
+export class EgCatalogService {
+
+    searchContext: CatalogSearchContext;
+    ccvmMap: {[ccvm:string] : EgIdlObject[]} = {};
+    cmfMap: {[cmf:string] : EgIdlObject[]} = {};
+
+    constructor(
+        private net: EgNetService,
+        private org: EgOrgService,
+        private unapi: EgUnapiService,
+        private pcrud: EgPcrudService
+    ) {
+        this.searchContext = new CatalogSearchContext();
+        this.searchContext.org = this.org;
+    }
+
+    search(context: CatalogSearchContext): Promise<void> {
+
+        var fullQuery = context.compileSearch();
+
+        console.debug(`search query: ${fullQuery}`);
+
+        return new Promise((resolve, reject) => {
+
+            this.net.request(
+                'open-ils.search',
+                'open-ils.search.biblio.multiclass.query.staff', {
+                    limit : context.pager.limit, 
+                    offset : context.pager.offset
+                }, fullQuery, true
+            ).subscribe(result => {
+                context.result = result;
+                context.result.records = [];
+                context.pager.resultCount = result.count;
+
+                let promises = [];
+                result.ids.forEach(blob => {
+                    promises.push(
+                        this.getBibSummary(blob[0], 
+                            context.searchOrg.id(), 
+                            context.global ? 
+                                context.org.root().ou_typ().depth() :
+                                context.searchOrg.ou_type().depth()
+                        ).then(
+                            summary => context.result.records.push(summary)
+                        )
+                    );
+                });
+
+                Promise.all(promises).then(ok => resolve());
+            });
+        })
+    }
+
+    fetchCcvms(): Promise<void> {
+
+        if (Object.keys(this.ccvmMap).length) 
+            return Promise.resolve();
+
+        return new Promise((resolve, reject) => {
+            this.pcrud.search('ccvm', 
+                {ctype : CCVM_FILTER_TYPES}, {}, {atomic: true}
+            ).subscribe(list => {
+                this.compileCcvms(list);
+                resolve();
+            })
+        });
+    }
+
+    compileCcvms(ccvms : EgIdlObject[]): void {
+        ccvms.forEach(ccvm => {
+            if (!this.ccvmMap[ccvm.ctype()])
+                this.ccvmMap[ccvm.ctype()] = [];
+            this.ccvmMap[ccvm.ctype()].push(ccvm);
+        });
+
+        Object.keys(this.ccvmMap).forEach(cType => {
+            this.ccvmMap[cType] = 
+                this.ccvmMap[cType].sort((a, b) => {
+                    return a.value() < b.value() ? -1 : 1;
+                });
+        });
+    }
+
+
+    fetchCmfs(): Promise<void> {
+        // At the moment, we only need facet CMFs.
+        if (Object.keys(this.cmfMap).length) 
+            return Promise.resolve();
+
+        return new Promise((resolve, reject) => {
+            this.pcrud.search('cmf', 
+                {facet_field : 't'}, {}, {atomic : true}
+            ).subscribe(
+                cmfs => {
+                    cmfs.forEach(c => this.cmfMap[c.id()] = c);
+                    resolve();
+                }
+            )
+        });
+    }
+
+
+    /**
+     * Bib record via UNAPI as mods32 (for now) with holdings summary
+     * and record attributes.
+     */
+    getBibSummary(bibId: number, orgId: number, depth: number): Promise<any> {
+        return new Promise((resolve, reject) => {
+            this.unapi.getAsObject({
+                target: 'bre', 
+                id: bibId, 
+                extras: '{bre.extern,holdings_xml,mra}', 
+                format: 'mods32', 
+                orgId: orgId,
+                depth: depth
+            }).then(summary => {
+                summary = this.translateBibSummary(summary);
+                summary.id = bibId;
+                resolve(summary);
+            });
+        });
+    }
+
+    /**
+     * Probably don't want to require navigating the bare UNAPI
+     * blob in the template, plus that's quite a lot of stuff
+     * to sit in the scope / watch for changes.  Translate the 
+     * UNAPI content into a more digestable form.
+     * TODO: Add display field support
+     */
+    translateBibSummary(summary: any): any { // TODO: bib summary interface
+        const UNAPI_PATHS = {
+            title : 'titleInfo[0].title[0]',
+            author: 'name[0].namePart[0]',
+            genre:  'genre[0]._'
+        }
+
+        let response = {};
+        for (let key in UNAPI_PATHS) {
+            try {
+                response[key] = eval(`summary.mods.${UNAPI_PATHS[key]}`);
+            } catch(E) {
+                response[key] ='';
+            }
+        }
+
+        return response;
+    }
+}
diff --git a/Open-ILS/webby-src/src/app/share/catalog/search-context.ts b/Open-ILS/webby-src/src/app/share/catalog/search-context.ts
new file mode 100644 (file)
index 0000000..1599ff7
--- /dev/null
@@ -0,0 +1,173 @@
+import {EgOrgService} from '@eg/core/org';
+import {EgIdlObject} from '@eg/core/idl';
+import {Pager} from '@eg/share/util/pager';
+import {Params} from '@angular/router';
+
+
+// Document and enforce facet filter entries.
+interface FacetFilter {
+    facetClass: string;
+    facetName: string;
+    facetValue: string;
+}
+
+// Not an angular service.
+// It's conceviable there could be multiple contexts.
+export class CatalogSearchContext {
+
+    // Search options and filters
+    available: boolean;
+    global: boolean;
+    sort: string;
+    searchClass: string[];
+    query: string[];
+    joiner: string[];
+    match: string[];
+    format: string;
+    searchOrg: EgIdlObject;
+    ccvmFilters: {[ccvmCode:string] : string};
+    facetFilters: FacetFilter[];
+
+    // Result from most recent search.
+    result: any; 
+
+    // Utility stuff
+    pager: Pager;
+    org: EgOrgService;
+
+    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 + ')';
+    }
+
+    /**
+     * Returns a URL query structure suitable for using with 
+     * router.navigate(..., {queryParams:...}).  
+     * No navigation is performed within.
+     */
+    toUrl(): any {
+        let query = {};
+
+        return query;
+    }
+
+    /**
+     * Absorbs URL values from the active route and applies them to 
+     * this search context instance.
+     */
+    fromUrl(params: Params): void {
+        //queryParamMap
+    }
+}
+
+
diff --git a/Open-ILS/webby-src/src/app/share/util/pager.ts b/Open-ILS/webby-src/src/app/share/util/pager.ts
new file mode 100644 (file)
index 0000000..bcec127
--- /dev/null
@@ -0,0 +1,35 @@
+
+/**
+ * Utility class for manage paged information.
+ */
+export 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;
+    }
+}
index 106292e..2d4c9b1 100644 (file)
@@ -1,3 +1,4 @@
+<!-- search form sits atop every catalog page -->
 <eg-catalog-search-form></eg-catalog-search-form>
 
 <router-outlet></router-outlet>
index 5b66182..e75fc89 100644 (file)
@@ -6,9 +6,11 @@ import {ActivatedRoute} from '@angular/router';
   templateUrl: 'catalog.component.html'
 })
 export class EgCatalogComponent implements OnInit {
-    constructor() {}
 
-    ngOnInit() { }
+    constructor(private route: ActivatedRoute) {}
+
+    ngOnInit() {
+    }
 
 }
 
index b9eee02..347a277 100644 (file)
@@ -1,18 +1,25 @@
 import {CommonModule} from '@angular/common';
 import {NgModule} from '@angular/core';
 import {EgStaffModule} from '../staff.module';
-import {EgCatalogRoutingModule} from './routing.module';
 import {EgUnapiService} from '@eg/share/unapi';
+import {EgCatalogRoutingModule} from './routing.module';
+import {EgCatalogService} from '@eg/share/catalog/catalog.service';
 import {EgCatalogComponent} from './catalog.component';
-import {EgCatalogSearchFormComponent} from './search-form.component';
-import {EgCatalogResultsComponent} from './results.component';
-import {EgCatalogService} from './catalog.service';
+import {SearchFormComponent} from './search-form.component';
+import {ResultsComponent} from './result/results.component';
+import {ResultPaginationComponent} from './result/pagination.component';
+import {ResultFacetsComponent} from './result/facets.component';
+import {ResultRecordComponent} 
+  from './result/record.component';
 
 @NgModule({
   declarations: [
     EgCatalogComponent,
-    EgCatalogSearchFormComponent,
-    EgCatalogResultsComponent
+    ResultsComponent,
+    SearchFormComponent,
+    ResultRecordComponent,
+    ResultFacetsComponent,
+    ResultPaginationComponent
   ],
   imports: [
     EgStaffModule,
diff --git a/Open-ILS/webby-src/src/app/staff/catalog/catalog.service.ts b/Open-ILS/webby-src/src/app/staff/catalog/catalog.service.ts
deleted file mode 100644 (file)
index 6fd5e2e..0000000
+++ /dev/null
@@ -1,167 +0,0 @@
-import {Injectable, EventEmitter} from '@angular/core';
-import {EgOrgService} from '@eg/core/org';
-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',
-    'item_form',
-    'item_lang',
-    'audience',
-    'audience_group',
-    'vr_format',
-    'bib_level',
-    'lit_form',
-    'search_format'
-];
-
-@Injectable()
-export class EgCatalogService {
-
-    searchContext: SearchContext;
-    ccvmMap: {[ccvm:string] : EgIdlObject[]} = {};
-    cmfMap: {[cmf:string] : EgIdlObject[]} = {};
-
-    constructor(
-        private net: EgNetService,
-        private org: EgOrgService,
-        private unapi: EgUnapiService,
-        private pcrud: EgPcrudService
-    ) {
-        this.searchContext = new SearchContext();
-        this.searchContext.org = this.org;
-    }
-
-    search(context: SearchContext): Promise<void> {
-
-        var fullQuery = context.compileSearch();
-
-        console.debug(`search query: ${fullQuery}`);
-
-        return new Promise((resolve, reject) => {
-
-            this.net.request(
-                'open-ils.search',
-                'open-ils.search.biblio.multiclass.query.staff', {
-                    limit : context.pager.limit, 
-                    offset : context.pager.offset
-                }, fullQuery, true
-            ).subscribe(result => {
-                context.result = result;
-                context.result.records = [];
-                context.pager.resultCount = result.count;
-
-                let promises = [];
-                result.ids.forEach(blob => {
-                    promises.push(
-                        this.getBibSummary(blob[0], 
-                            context.searchOrg.id(), 
-                            context.global ? 
-                                context.org.root().ou_typ().depth() :
-                                context.searchOrg.ou_type().depth()
-                        ).then(
-                            summary => context.result.records.push(summary)
-                        )
-                    );
-                });
-
-                Promise.all(promises).then(ok => resolve());
-            });
-        })
-    }
-
-    fetchCcvms(): Promise<void> {
-
-        if (Object.keys(this.ccvmMap).length) 
-            return Promise.resolve();
-
-        return new Promise((resolve, reject) => {
-            this.pcrud.search('ccvm', 
-                {ctype : CCVM_FILTER_TYPES}, {}, {atomic: true}
-            ).subscribe(list => {
-                this.compileCcvms(list);
-                resolve();
-            })
-        });
-    }
-
-    compileCcvms(ccvms : EgIdlObject[]): void {
-        ccvms.forEach(ccvm => {
-            if (!this.ccvmMap[ccvm.ctype()])
-                this.ccvmMap[ccvm.ctype()] = [];
-            this.ccvmMap[ccvm.ctype()].push(ccvm);
-        });
-
-        Object.keys(this.ccvmMap).forEach(cType => {
-            this.ccvmMap[cType] = 
-                this.ccvmMap[cType].sort((a, b) => {
-                    return a.value() < b.value() ? -1 : 1;
-                });
-        });
-    }
-
-
-    fetchCmfs(): Promise<void> {
-        // At the moment, we only need facet CMFs.
-        if (Object.keys(this.cmfMap).length) 
-            return Promise.resolve();
-
-        return new Promise((resolve, reject) => {
-            this.pcrud.search('cmf', 
-                {facet_field : 't'}, {}, {atomic : true}
-            ).subscribe(
-                cmfs => {
-                    cmfs.forEach(c => this.cmfMap[c.id()] = c);
-                    resolve();
-                }
-            )
-        });
-    }
-
-    /**
-     * Probably don't want to require navigating the bare UNAPI
-     * blob in the template, plus that's quite a lot of stuff
-     * to sit in the scope / watch for changes.  Translate the 
-     * UNAPI content into a more digestable form.
-     * TODO: Add display field support
-     */
-    translateBibSummary(summary: any): any { // TODO: bib summary type
-        const UNAPI_PATHS = {
-            title : 'titleInfo[0].title[0]',
-            author: 'name[0].namePart[0]',
-            genre:  'genre[0]._'
-        }
-
-        let response = {};
-        for (let key in UNAPI_PATHS) {
-            try {
-                response[key] = eval(`summary.mods.${UNAPI_PATHS[key]}`);
-            } catch(E) {
-                response[key] ='';
-            }
-        }
-
-        return response;
-    }
-
-    getBibSummary(bibId: number, orgId: number, depth: number): Promise<any> {
-        return new Promise((resolve, reject) => {
-            this.unapi.getAsObject({
-                target: 'bre', 
-                id: bibId, 
-                extras: '{bre.extern,holdings_xml,mra}', 
-                format: 'mods32', 
-                orgId: orgId,
-                depth: depth
-            }).then(summary => {
-                summary = this.translateBibSummary(summary);
-                summary.id = bibId;
-                resolve(summary);
-            });
-        });
-    }
-
-}
index 657a47f..e948cbb 100644 (file)
@@ -7,7 +7,7 @@ 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'
+import {EgCatalogService} from '@eg/share/catalog/catalog.service';
 
 @Injectable()
 export class EgCatalogResolver implements Resolve<Promise<any[]>> {
diff --git a/Open-ILS/webby-src/src/app/staff/catalog/result/facets.component.css b/Open-ILS/webby-src/src/app/staff/catalog/result/facets.component.css
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/Open-ILS/webby-src/src/app/staff/catalog/result/facets.component.html b/Open-ILS/webby-src/src/app/staff/catalog/result/facets.component.html
new file mode 100644 (file)
index 0000000..124d5e7
--- /dev/null
@@ -0,0 +1 @@
+TEST FACETS
diff --git a/Open-ILS/webby-src/src/app/staff/catalog/result/facets.component.ts b/Open-ILS/webby-src/src/app/staff/catalog/result/facets.component.ts
new file mode 100644 (file)
index 0000000..3690f85
--- /dev/null
@@ -0,0 +1,24 @@
+import {Component, OnInit, Input} from '@angular/core';
+import {EgCatalogService} from '@eg/share/catalog/catalog.service';
+import {CatalogSearchContext} from '@eg/share/catalog/search-context';
+
+@Component({
+  selector: 'eg-catalog-result-facets',
+  styleUrls: ['facets.component.css'],
+  templateUrl: 'facets.component.html'
+})
+export class ResultFacetsComponent implements OnInit {
+
+    searchContext: CatalogSearchContext;
+
+    constructor(
+        private cat: EgCatalogService
+    ) {}
+
+    ngOnInit() { 
+        this.searchContext = this.cat.searchContext;
+    }
+
+}
+
+
diff --git a/Open-ILS/webby-src/src/app/staff/catalog/result/pagination.component.css b/Open-ILS/webby-src/src/app/staff/catalog/result/pagination.component.css
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/Open-ILS/webby-src/src/app/staff/catalog/result/pagination.component.html b/Open-ILS/webby-src/src/app/staff/catalog/result/pagination.component.html
new file mode 100644 (file)
index 0000000..23a9e63
--- /dev/null
@@ -0,0 +1 @@
+TEST PAGINATION
diff --git a/Open-ILS/webby-src/src/app/staff/catalog/result/pagination.component.ts b/Open-ILS/webby-src/src/app/staff/catalog/result/pagination.component.ts
new file mode 100644 (file)
index 0000000..2bba534
--- /dev/null
@@ -0,0 +1,24 @@
+import {Component, OnInit, Input} from '@angular/core';
+import {EgCatalogService} from '@eg/share/catalog/catalog.service';
+import {CatalogSearchContext} from '@eg/share/catalog/search-context';
+
+@Component({
+  selector: 'eg-catalog-result-pagination',
+  styleUrls: ['pagination.component.css'],
+  templateUrl: 'pagination.component.html'
+})
+export class ResultPaginationComponent implements OnInit {
+
+    searchContext: CatalogSearchContext;
+
+    constructor(
+        private cat: EgCatalogService
+    ) {}
+
+    ngOnInit() { 
+        this.searchContext = this.cat.searchContext;
+    }
+
+}
+
+
diff --git a/Open-ILS/webby-src/src/app/staff/catalog/result/record.component.css b/Open-ILS/webby-src/src/app/staff/catalog/result/record.component.css
new file mode 100644 (file)
index 0000000..139597f
--- /dev/null
@@ -0,0 +1,2 @@
+
+
diff --git a/Open-ILS/webby-src/src/app/staff/catalog/result/record.component.html b/Open-ILS/webby-src/src/app/staff/catalog/result/record.component.html
new file mode 100644 (file)
index 0000000..c7e288c
--- /dev/null
@@ -0,0 +1,8 @@
+
+<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>
+
diff --git a/Open-ILS/webby-src/src/app/staff/catalog/result/record.component.ts b/Open-ILS/webby-src/src/app/staff/catalog/result/record.component.ts
new file mode 100644 (file)
index 0000000..f3f2d7c
--- /dev/null
@@ -0,0 +1,26 @@
+import {Component, OnInit, Input} from '@angular/core';
+import {EgCatalogService} from '@eg/share/catalog/catalog.service';
+import {CatalogSearchContext} from '@eg/share/catalog/search-context';
+
+@Component({
+  selector: 'eg-catalog-result-record',
+  styleUrls: ['record.component.css'],
+  templateUrl: 'record.component.html'
+})
+export class ResultRecordComponent implements OnInit {
+
+    @Input() index: number;  // 0-index display row
+    @Input() bibSummary: any;
+    searchContext: CatalogSearchContext;
+
+    constructor(
+        private cat: EgCatalogService
+    ) {}
+
+    ngOnInit() { 
+        this.searchContext = this.cat.searchContext;
+    }
+
+}
+
+
diff --git a/Open-ILS/webby-src/src/app/staff/catalog/result/results.component.css b/Open-ILS/webby-src/src/app/staff/catalog/result/results.component.css
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/Open-ILS/webby-src/src/app/staff/catalog/result/results.component.html b/Open-ILS/webby-src/src/app/staff/catalog/result/results.component.html
new file mode 100644 (file)
index 0000000..d0aca38
--- /dev/null
@@ -0,0 +1,15 @@
+
+<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>
+
diff --git a/Open-ILS/webby-src/src/app/staff/catalog/result/results.component.ts b/Open-ILS/webby-src/src/app/staff/catalog/result/results.component.ts
new file mode 100644 (file)
index 0000000..045c050
--- /dev/null
@@ -0,0 +1,24 @@
+import {Component, OnInit, Input} from '@angular/core';
+import {EgCatalogService} from '@eg/share/catalog/catalog.service';
+import {CatalogSearchContext} from '@eg/share/catalog/search-context';
+
+@Component({
+  selector: 'eg-catalog-results',
+  styleUrls: ['results.component.css'],
+  templateUrl: 'results.component.html'
+})
+export class ResultsComponent implements OnInit {
+
+    searchContext: CatalogSearchContext;
+
+    constructor(
+        private cat: EgCatalogService
+    ) {}
+
+    ngOnInit() { 
+        this.searchContext = this.cat.searchContext;
+    }
+
+}
+
+
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
deleted file mode 100644 (file)
index add8b04..0000000
+++ /dev/null
@@ -1,13 +0,0 @@
-
-<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
deleted file mode 100644 (file)
index 0a1c48d..0000000
+++ /dev/null
@@ -1,26 +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} 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 50fdc00..ed8a552 100644 (file)
@@ -1,7 +1,7 @@
 import {NgModule} from '@angular/core';
 import {RouterModule, Routes} from '@angular/router';
 import {EgCatalogComponent} from './catalog.component';
-import {EgCatalogResultsComponent} from './results.component';
+import {ResultsComponent} from './result/results.component';
 import {EgCatalogResolver} from './resolver.service';
 
 const routes: Routes = [{ 
@@ -10,7 +10,7 @@ const routes: Routes = [{
   resolve: {catResolver : EgCatalogResolver},
   children : [{
     path: 'search',
-    component: EgCatalogResultsComponent,
+    component: ResultsComponent,
   }]
 }];
 
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
deleted file mode 100644 (file)
index 2ac2a97..0000000
+++ /dev/null
@@ -1,175 +0,0 @@
-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.css b/Open-ILS/webby-src/src/app/staff/catalog/search-form.component.css
new file mode 100644 (file)
index 0000000..377c81f
--- /dev/null
@@ -0,0 +1,20 @@
+#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;
+}
+
index 0e2fc75..9046fa2 100644 (file)
@@ -1,27 +1,3 @@
-
-<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>
 
       <!--
-    <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">
index 911d054..9148e25 100644 (file)
@@ -1,32 +1,37 @@
 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';
+import {EgOrgService} from '@eg/core/org';
+import {EgCatalogService} from '@eg/share/catalog/catalog.service';
+import {CatalogSearchContext} from '@eg/share/catalog/search-context';
 
 @Component({
   selector: 'eg-catalog-search-form',
+  styleUrls: ['search-form.component.css'],
   templateUrl: 'search-form.component.html'
 })
-export class EgCatalogSearchFormComponent implements OnInit {
+export class SearchFormComponent implements OnInit {
 
-    searchContext: SearchContext;
     ccvmMap: {[ccvm:string] : EgIdlObject[]} = {};
     cmfMap: {[cmf:string] : EgIdlObject[]} = {};
+    searchContext: CatalogSearchContext;
     showAdvancedSearch: boolean = false;
 
     constructor(
-        private auth: EgAuthService,
+        private route: ActivatedRoute,
         private org: EgOrgService,
         private cat: EgCatalogService
     ) {}
 
     ngOnInit() { 
-        this.searchContext = this.cat.searchContext;
         this.ccvmMap = this.cat.ccvmMap;
         this.cmfMap = this.cat.cmfMap;
+        this.searchContext = this.cat.searchContext;
+
+        // Sync the context with the load-time URL params and launch
+        // a new search if necessary.
+        this.searchContext.fromUrl(this.route.snapshot.queryParams);
+        this.searchByUrl();
     }
 
     addSearchRow(index: number): void {
@@ -44,7 +49,6 @@ export class EgCatalogSearchFormComponent implements OnInit {
     }
 
     checkEnter($event: any): void {
-        console.log($event.keyCode);
         if ($event.keyCode == 13) {
             this.searchContext.pager.offset = 0;
             this.searchByForm();
@@ -57,12 +61,13 @@ export class EgCatalogSearchFormComponent implements OnInit {
     }
 
     searchByForm(): void {
-        console.log('searchByForm()');
-        this.searchContext.searchOrg = this.org.get(4); // TEST BR1
+        this.searchContext.searchOrg = this.org.get(4); // TODO: TEST BR1
         this.cat.search(this.searchContext).then(ok => {
             console.debug('search complete');
         });
+    }
 
+    searchByUrl(): void {
     }
 
 }
index 3d65f2d..a876726 100644 (file)
@@ -2,7 +2,7 @@
 <html lang="en">
 <head>
   <meta charset="utf-8">
-  <title i18n="Page Title">Eg</title>
+  <title i18n="Page Title">AngEG</title>
   <base href="/webby">
 
   <meta name="viewport" content="width=device-width, initial-scale=1">