From: Bill Erickson Date: Mon, 4 Dec 2017 01:50:48 +0000 (-0500) Subject: LP#626157 Ang2 experiments X-Git-Url: https://old-git.evergreen-ils.org/?a=commitdiff_plain;h=fd9598c774e5866025618d74981d0ceaf2b8aff2;p=working%2FEvergreen.git LP#626157 Ang2 experiments Signed-off-by: Bill Erickson --- diff --git a/Open-ILS/webby-src/src/app/staff/catalog/catalog.component.html b/Open-ILS/webby-src/src/app/staff/catalog/catalog.component.html index 024bf0b208..106292e08d 100644 --- a/Open-ILS/webby-src/src/app/staff/catalog/catalog.component.html +++ b/Open-ILS/webby-src/src/app/staff/catalog/catalog.component.html @@ -1,4 +1,4 @@ - -

CATALOG

+ + diff --git a/Open-ILS/webby-src/src/app/staff/catalog/catalog.module.ts b/Open-ILS/webby-src/src/app/staff/catalog/catalog.module.ts index 37d31314fe..b9eee02d1a 100644 --- a/Open-ILS/webby-src/src/app/staff/catalog/catalog.module.ts +++ b/Open-ILS/webby-src/src/app/staff/catalog/catalog.module.ts @@ -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, 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 index d4f3581485..6fd5e2ece1 100644 --- a/Open-ILS/webby-src/src/app/staff/catalog/catalog.service.ts +++ b/Open-ILS/webby-src/src/app/staff/catalog/catalog.service.ts @@ -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 { + search(context: SearchContext): Promise { 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 index 0000000000..657a47fc7c --- /dev/null +++ b/Open-ILS/webby-src/src/app/staff/catalog/resolver.service.ts @@ -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> { + + 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 { + + 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 index 0000000000..add8b0458a --- /dev/null +++ b/Open-ILS/webby-src/src/app/staff/catalog/results.component.html @@ -0,0 +1,13 @@ + +

Search Sample

+ +
+
+
{{idx + 1}}
+
{{record.title}}
+
{{record.author}}
+
{{record.genre}}
+
+
+ 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 index 0000000000..0a1c48dcb1 --- /dev/null +++ b/Open-ILS/webby-src/src/app/staff/catalog/results.component.ts @@ -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() { + } +} + diff --git a/Open-ILS/webby-src/src/app/staff/catalog/routing.module.ts b/Open-ILS/webby-src/src/app/staff/catalog/routing.module.ts index 3cafbb4d13..50fdc00fb6 100644 --- a/Open-ILS/webby-src/src/app/staff/catalog/routing.module.ts +++ b/Open-ILS/webby-src/src/app/staff/catalog/routing.module.ts @@ -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 index 0000000000..2ac2a97f7b --- /dev/null +++ b/Open-ILS/webby-src/src/app/staff/catalog/search-context.ts @@ -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 index 0000000000..0e2fc75478 --- /dev/null +++ b/Open-ILS/webby-src/src/app/staff/catalog/search-form.component.html @@ -0,0 +1,251 @@ + + +
+
+
+
+
+ +
+
+ +
+
+
+ +
+
+ +
+
+
+ +
+
+
+ + +
+
+
+
+ + + + +
+
+
+ + + 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 index 0000000000..911d05407d --- /dev/null +++ b/Open-ILS/webby-src/src/app/staff/catalog/search-form.component.ts @@ -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 index 3e5aebc1ed..0000000000 --- a/Open-ILS/webby-src/src/app/staff/catalog/search.component.html +++ /dev/null @@ -1,12 +0,0 @@ - -

Search Sample

- -
-
-
{{idx + 1}}
-
{{record.title}}
-
{{record.author}}
-
{{record.genre}}
-
-
- 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 index 39a6ec8aac..0000000000 --- a/Open-ILS/webby-src/src/app/staff/catalog/search.component.ts +++ /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'); - }); - }); - } -} - diff --git a/Open-ILS/webby-src/src/app/staff/resolver.service.ts b/Open-ILS/webby-src/src/app/staff/resolver.service.ts index 492663f0a9..66903db6f1 100644 --- a/Open-ILS/webby-src/src/app/staff/resolver.service.ts +++ b/Open-ILS/webby-src/src/app/staff/resolver.service.ts @@ -42,6 +42,7 @@ export class EgStaffResolver implements Resolve> { 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> { }, 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'); } ); }); diff --git a/Open-ILS/webby-src/src/app/staff/staff.component.ts b/Open-ILS/webby-src/src/app/staff/staff.component.ts index e4808a9efe..965f01e497 100644 --- a/Open-ILS/webby-src/src/app/staff/staff.component.ts +++ b/Open-ILS/webby-src/src/app/staff/staff.component.ts @@ -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) {