From: Bill Erickson Date: Wed, 6 Dec 2017 03:49:31 +0000 (-0500) Subject: LP#626157 Ang2 experiments X-Git-Url: https://old-git.evergreen-ils.org/?a=commitdiff_plain;h=93df59ffbeae8e17f733b8f24f862a55ca8d3c09;p=working%2FEvergreen.git LP#626157 Ang2 experiments Signed-off-by: Bill Erickson --- diff --git a/Open-ILS/webby-src/src/app/share/catalog/catalog-url.service.ts b/Open-ILS/webby-src/src/app/share/catalog/catalog-url.service.ts index 97741621ea..7b0ce75bda 100644 --- a/Open-ILS/webby-src/src/app/share/catalog/catalog-url.service.ts +++ b/Open-ILS/webby-src/src/app/share/catalog/catalog-url.service.ts @@ -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')); + } } 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 index 40dc7cac15..dcd6e8263e 100644 --- a/Open-ILS/webby-src/src/app/share/catalog/catalog.service.ts +++ b/Open-ILS/webby-src/src/app/share/catalog/catalog.service.ts @@ -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 { - 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; } } 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 index 896ab3345a..ec2e83f6c9 100644 --- a/Open-ILS/webby-src/src/app/share/catalog/search-context.ts +++ b/Open-ILS/webby-src/src/app/share/catalog/search-context.ts @@ -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; diff --git a/Open-ILS/webby-src/src/app/share/unapi.ts b/Open-ILS/webby-src/src/app/share/unapi.ts index bf2f44c825..6a7ea1f067 100644 --- a/Open-ILS/webby-src/src/app/share/unapi.ts +++ b/Open-ILS/webby-src/src/app/share/unapi.ts @@ -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)); diff --git a/Open-ILS/webby-src/src/app/staff/catalog/catalog.component.ts b/Open-ILS/webby-src/src/app/staff/catalog/catalog.component.ts index f842350913..206f8a0e18 100644 --- a/Open-ILS/webby-src/src/app/staff/catalog/catalog.component.ts +++ b/Open-ILS/webby-src/src/app/staff/catalog/catalog.component.ts @@ -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); } } 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 index 3690f855fc..a25c51d3a6 100644 --- 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 @@ -16,7 +16,7 @@ export class ResultFacetsComponent implements OnInit { ) {} ngOnInit() { - this.searchContext = this.cat.searchContext; + this.searchContext = this.cat.activeSearchContext(); } } 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 index 2bba534e6b..dc73822dc0 100644 --- 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 @@ -16,7 +16,7 @@ export class ResultPaginationComponent implements OnInit { ) {} ngOnInit() { - this.searchContext = this.cat.searchContext; + this.searchContext = this.cat.activeSearchContext(); } } 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 index 139597f9cb..2a1b316846 100644 --- 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 @@ -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%; +} + 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 index c7e288cf27..86416b2486 100644 --- 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 @@ -1,8 +1,112 @@ -
-
{{index + 1}}
-
{{bibSummary.title}}
-
{{bibSummary.author}}
-
{{bibSummary.genre}}
-
+
+
+
+ + + + +
+
+ + +
+
+ #{{index + 1 + searchContext.pager.offset}} + + + {{bibSummary.ccvms.icon_format.label}} + + {{bibSummary.edition}} + {{bibSummary.pubdate}} +
+
+
+
+
+
+ {{copyCount.available}} / {{copyCount.count}} + items @ {{orgName(copyCount.org_unit)}} +
+
+
+
+ +
+
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 index f3f2d7c2c9..a84f85fc4c 100644 --- 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 @@ -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(); } } 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 index 7e6bc66c8c..2e9f187f25 100644 --- 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 @@ -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) { + } } 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 index 377c81f28b..e04900c8cd 100644 --- 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 @@ -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 */ +} 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 index 9046fa276f..09e57fbfcc 100644 --- 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 @@ -1,7 +1,7 @@
-
+
+ [(ngModel)]="searchContext.joinOp[idx]"> @@ -20,7 +20,7 @@
+ [(ngModel)]="searchContext.matchOp[idx]"> @@ -49,19 +49,19 @@
- -
-
-
+
+