From: Bill Erickson Date: Mon, 12 Nov 2018 23:22:30 +0000 (-0500) Subject: record checkboxes continued; anon cache, etc. X-Git-Url: https://old-git.evergreen-ils.org/?a=commitdiff_plain;h=30390db72c094f082fd35291909d5ec933339bf1;p=working%2FEvergreen.git record checkboxes continued; anon cache, etc. Signed-off-by: Bill Erickson --- diff --git a/Open-ILS/src/eg2/src/app/share/catalog/basket.service.ts b/Open-ILS/src/eg2/src/app/share/catalog/basket.service.ts new file mode 100644 index 0000000000..46efcc4671 --- /dev/null +++ b/Open-ILS/src/eg2/src/app/share/catalog/basket.service.ts @@ -0,0 +1,82 @@ +import {Injectable} from '@angular/core'; +import {Observable} from 'rxjs/Observable'; +import {StoreService} from '@eg/core/store.service'; +import {NetService} from '@eg/core/net.service'; +import {PcrudService} from '@eg/core/pcrud.service'; +import {AnonCacheService} from '@eg/share/util/anon-cache.service'; + +// Baskets are stored in an anonymous cache using the cache key stored +// in a LoginSessionItem (i.e. cookie) at name BASKET_CACHE_KEY_COOKIE. +// The list is stored under attribute BASKET_CACHE_ATTR. +// Avoid conflicts with the AngularJS embedded catalog basket by +// using a different value for the cookie name, since our version +// stores all cookies as JSON, unlike the TPAC. +const BASKET_CACHE_KEY_COOKIE = 'basket'; +const BASKET_CACHE_ATTR = 'recordIds'; + +@Injectable() +export class BasketService { + + idList: number[]; + + constructor( + private net: NetService, + private pcrud: PcrudService, + private store: StoreService, + private anonCache: AnonCacheService + ) { this.idList = []; } + + hasRecordId(id: number): boolean { + return this.idList.indexOf(Number(id)) > -1; + } + + recordCount(): number { + return this.idList.length; + } + + // TODO: Add server-side API for sorting a set of bibs by ID. + // See EGCatLoader/Container::fetch_mylist + getRecordIds(): Promise { + const cacheKey = this.store.getLoginSessionItem(BASKET_CACHE_KEY_COOKIE); + this.idList = []; + + if (!cacheKey) { return Promise.resolve(this.idList); } + + return this.anonCache.getItem(cacheKey, BASKET_CACHE_ATTR).then( + list => { + if (!list) {return this.idList}; + this.idList = list.map(id => Number(id)); + return this.idList; + } + ); + } + + setRecordIds(ids: number[]): Promise { + this.idList = ids; + + // If we have no cache key, that's OK, assume this is the first + // attempt at adding a value and let the server create the cache + // key for us, then store the value in our cookie. + const cacheKey = this.store.getLoginSessionItem(BASKET_CACHE_KEY_COOKIE); + + return this.anonCache.setItem(cacheKey, BASKET_CACHE_ATTR, this.idList) + .then(cacheKey => { + this.store.setLoginSessionItem(BASKET_CACHE_KEY_COOKIE, cacheKey); + return this.idList; + }); + } + + addRecordIds(ids: number[]): Promise { + ids = ids.filter(id => !this.hasRecordId(id)); // avoid dupes + return this.setRecordIds( + this.idList.concat(ids.map(id => Number(id)))); + } + + removeRecordIds(ids: number[]): Promise { + const wantedIds = this.idList.filter( + id => ids.indexOf(Number(id)) < 0); + return this.setRecordIds(wantedIds); + } +} + + diff --git a/Open-ILS/src/eg2/src/app/share/catalog/catalog-common.module.ts b/Open-ILS/src/eg2/src/app/share/catalog/catalog-common.module.ts index c370b300c9..eeaf38af29 100644 --- a/Open-ILS/src/eg2/src/app/share/catalog/catalog-common.module.ts +++ b/Open-ILS/src/eg2/src/app/share/catalog/catalog-common.module.ts @@ -1,6 +1,8 @@ import {NgModule} from '@angular/core'; import {EgCommonModule} from '@eg/common.module'; import {CatalogService} from './catalog.service'; +import {AnonCacheService} from '@eg/share/util/anon-cache.service' +import {BasketService} from './basket.service'; import {CatalogUrlService} from './catalog-url.service'; import {BibRecordService} from './bib-record.service'; import {UnapiService} from './unapi.service'; @@ -18,10 +20,12 @@ import {MarcHtmlComponent} from './marc-html.component'; MarcHtmlComponent ], providers: [ + AnonCacheService, CatalogService, CatalogUrlService, UnapiService, - BibRecordService + BibRecordService, + BasketService, ] }) diff --git a/Open-ILS/src/eg2/src/app/share/util/anon-cache.service.ts b/Open-ILS/src/eg2/src/app/share/util/anon-cache.service.ts new file mode 100644 index 0000000000..29c168dc71 --- /dev/null +++ b/Open-ILS/src/eg2/src/app/share/util/anon-cache.service.ts @@ -0,0 +1,59 @@ +/** + * Service for communicating with the server-side "anonymous" cache. + */ +import {Injectable} from '@angular/core'; +import {Observable} from 'rxjs/Observable'; +import {StoreService} from '@eg/core/store.service'; +import {NetService} from '@eg/core/net.service'; + +// All anon-cache data is stored in a single blob per user session. +// Value is generated on the server with the first call to set_value +// and stored locally as a LoginSession item (cookie). + +@Injectable() +export class AnonCacheService { + + constructor(private store: StoreService, private net: NetService) {} + + getItem(cacheKey: string, attr: string): Promise { + return this.net.request( + 'open-ils.actor', + 'open-ils.actor.anon_cache.get_value', cacheKey, attr + ).toPromise(); + } + + // Apply 'value' to field 'attr' in the object cached at 'cacheKey'. + // If no cacheKey is provided, the server will generate one. + // Returns a promised resolved with the cache key. + setItem(cacheKey: string, attr: string, value: any): Promise { + return this.net.request( + 'open-ils.actor', + 'open-ils.actor.anon_cache.set_value', + cacheKey, attr, value + ).toPromise().then(cacheKey => { + if (cacheKey) { + return cacheKey; + } else { + return Promise.reject( + `Could not apply a value for attr=${attr} cacheKey=${cacheKey}`); + } + }) + } + + removeItem(cacheKey: string, attr: string): Promise { + return this.net.request( + 'open-ils.actor', + 'open-ils.actor.anon_cache.set_value', + cacheKey, attr, null + ).toPromise(); + } + + clear(cacheKey: string): Promise { + return this.net.request( + 'open-ils.actor', + 'open-ils.actor.anon_cache.delete_session', cacheKey + ).toPromise(); + } +} + + diff --git a/Open-ILS/src/eg2/src/app/staff/catalog/catalog.component.ts b/Open-ILS/src/eg2/src/app/staff/catalog/catalog.component.ts index 8b2206c2f5..0e2fc98884 100644 --- a/Open-ILS/src/eg2/src/app/staff/catalog/catalog.component.ts +++ b/Open-ILS/src/eg2/src/app/staff/catalog/catalog.component.ts @@ -1,18 +1,25 @@ import {Component, OnInit} from '@angular/core'; import {StaffCatalogService} from './catalog.service'; +import {BasketService} from '@eg/share/catalog/basket.service'; @Component({ templateUrl: 'catalog.component.html' }) export class CatalogComponent implements OnInit { - constructor(private staffCat: StaffCatalogService) {} + constructor( + private basket: BasketService, + private staffCat: StaffCatalogService + ) {} ngOnInit() { // Create the search context that will be used by all of my // child components. After initial creation, the context is // reset and updated as needed to apply new search parameters. this.staffCat.createContext(); + + // Cache the basket on page load. + this.basket.getRecordIds(); } } diff --git a/Open-ILS/src/eg2/src/app/staff/catalog/result/record.component.css b/Open-ILS/src/eg2/src/app/staff/catalog/result/record.component.css new file mode 100644 index 0000000000..3077d9ac93 --- /dev/null +++ b/Open-ILS/src/eg2/src/app/staff/catalog/result/record.component.css @@ -0,0 +1,15 @@ + +/** + * Force the jacket image column to consume a consistent amount of + * horizontal space, while allowing some room for the browser to + * render the correct aspect ratio. + */ +.record-jacket-div { + width: 68px; +} + +.record-jacket-div img { + height: 100%; + max-height:80px; + max-width: 54px; +} diff --git a/Open-ILS/src/eg2/src/app/staff/catalog/result/record.component.html b/Open-ILS/src/eg2/src/app/staff/catalog/result/record.component.html index 971f8dea12..6bf3ec9eb8 100644 --- a/Open-ILS/src/eg2/src/app/staff/catalog/result/record.component.html +++ b/Open-ILS/src/eg2/src/app/staff/catalog/result/record.component.html @@ -9,49 +9,54 @@
-
-
+ +
+
-
+ + + + -
-
-
-
- - - {{summary.display.title || ' '}} - + -
- -
-
- - - - {{iconFormatLabel(summary.attributes.icon_format[0])}} - - {{summary.display.edition}} - {{summary.display.pubdate}} +
+
+ + + + {{iconFormatLabel(summary.attributes.icon_format[0])}} + + {{summary.display.edition}} + {{summary.display.pubdate}} +
diff --git a/Open-ILS/src/eg2/src/app/staff/catalog/result/record.component.ts b/Open-ILS/src/eg2/src/app/staff/catalog/result/record.component.ts index bfcfd4572e..d0b12e892e 100644 --- a/Open-ILS/src/eg2/src/app/staff/catalog/result/record.component.ts +++ b/Open-ILS/src/eg2/src/app/staff/catalog/result/record.component.ts @@ -7,16 +7,19 @@ import {BibRecordService, BibRecordSummary} from '@eg/share/catalog/bib-record.s import {CatalogSearchContext} from '@eg/share/catalog/search-context'; import {CatalogUrlService} from '@eg/share/catalog/catalog-url.service'; import {StaffCatalogService} from '../catalog.service'; +import {BasketService} from '@eg/share/catalog/basket.service'; @Component({ selector: 'eg-catalog-result-record', - templateUrl: 'record.component.html' + templateUrl: 'record.component.html', + styleUrls: ['record.component.css'] }) export class ResultRecordComponent implements OnInit { @Input() index: number; // 0-index display row @Input() summary: BibRecordSummary; searchContext: CatalogSearchContext; + isRecordInBasket: boolean; constructor( private router: Router, @@ -25,12 +28,14 @@ export class ResultRecordComponent implements OnInit { private bib: BibRecordService, private cat: CatalogService, private catUrl: CatalogUrlService, - private staffCat: StaffCatalogService + private staffCat: StaffCatalogService, + private basket: BasketService ) {} ngOnInit() { this.searchContext = this.staffCat.searchContext; this.summary.getHoldCount(); + this.isRecordInBasket = this.basket.hasRecordId(this.summary.id); } orgName(orgId: number): string { @@ -72,6 +77,13 @@ export class ResultRecordComponent implements OnInit { ['/staff/catalog/record/' + id], {queryParams: params}); } + toggleBasketEntry() { + if (this.isRecordInBasket) { + return this.basket.addRecordIds([this.summary.id]); + } else { + return this.basket.removeRecordIds([this.summary.id]); + } + } } diff --git a/Open-ILS/src/eg2/src/app/staff/catalog/result/results.component.html b/Open-ILS/src/eg2/src/app/staff/catalog/result/results.component.html index 47589b07c4..c4845950bb 100644 --- a/Open-ILS/src/eg2/src/app/staff/catalog/result/results.component.html +++ b/Open-ILS/src/eg2/src/app/staff/catalog/result/results.component.html @@ -1,11 +1,44 @@ -
+ +
+
+
+
+ Searching.. +
+
+
+
+ + +
+
+
+
+ No Maching Items Were Found +
+
+
+
+ + + +

Search Results ({{searchContext.result.count}})

-
-
+
+ +
+
@@ -30,3 +63,4 @@
+ diff --git a/Open-ILS/src/eg2/src/app/staff/catalog/result/results.component.ts b/Open-ILS/src/eg2/src/app/staff/catalog/result/results.component.ts index e2eadc830d..39f4e5c0ee 100644 --- a/Open-ILS/src/eg2/src/app/staff/catalog/result/results.component.ts +++ b/Open-ILS/src/eg2/src/app/staff/catalog/result/results.component.ts @@ -91,6 +91,13 @@ export class ResultsComponent implements OnInit { return this.searchContext.searchState === CatalogSearchState.COMPLETE; } + searchIsActive(): boolean { + return this.searchContext.searchState === CatalogSearchState.SEARCHING; + } + + searchHasResults(): boolean { + return this.searchIsDone() && this.searchContext.result.count > 0; + } } diff --git a/Open-ILS/src/eg2/src/app/staff/catalog/search-form.component.html b/Open-ILS/src/eg2/src/app/staff/catalog/search-form.component.html index da54f4a9b9..b604c7154d 100644 --- a/Open-ILS/src/eg2/src/app/staff/catalog/search-form.component.html +++ b/Open-ILS/src/eg2/src/app/staff/catalog/search-form.component.html @@ -132,7 +132,7 @@ TODO focus search input
@@ -140,7 +140,7 @@ TODO focus search input
@@ -149,6 +149,7 @@ TODO focus search input
+