record checkboxes continued
authorBill Erickson <berickxx@gmail.com>
Tue, 13 Nov 2018 16:09:11 +0000 (11:09 -0500)
committerBill Erickson <berickxx@gmail.com>
Fri, 30 Nov 2018 16:34:20 +0000 (11:34 -0500)
Signed-off-by: Bill Erickson <berickxx@gmail.com>
Open-ILS/src/eg2/src/app/share/catalog/basket.service.ts
Open-ILS/src/eg2/src/app/share/catalog/catalog.service.ts
Open-ILS/src/eg2/src/app/staff/catalog/result/record.component.html
Open-ILS/src/eg2/src/app/staff/catalog/result/record.component.ts
Open-ILS/src/eg2/src/app/staff/catalog/result/results.component.html
Open-ILS/src/eg2/src/app/staff/catalog/result/results.component.ts

index 46efcc4..7db2749 100644 (file)
@@ -1,4 +1,4 @@
-import {Injectable} from '@angular/core';
+import {Injectable, EventEmitter} from '@angular/core';
 import {Observable} from 'rxjs/Observable';
 import {StoreService} from '@eg/core/store.service';
 import {NetService} from '@eg/core/net.service';
@@ -19,12 +19,18 @@ export class BasketService {
 
     idList: number[];
 
+    // Fired every time our list of ID's are updated.
+    onChange: EventEmitter<number[]>;
+
     constructor(
         private net: NetService,
         private pcrud: PcrudService,
         private store: StoreService,
         private anonCache: AnonCacheService
-    ) { this.idList = []; }
+    ) { 
+        this.idList = []; 
+        this.onChange = new EventEmitter<number[]>();
+    }
 
     hasRecordId(id: number): boolean {
         return this.idList.indexOf(Number(id)) > -1;
@@ -62,20 +68,31 @@ export class BasketService {
         return this.anonCache.setItem(cacheKey, BASKET_CACHE_ATTR, this.idList)
         .then(cacheKey => {
             this.store.setLoginSessionItem(BASKET_CACHE_KEY_COOKIE, cacheKey);
+            this.onChange.emit(this.idList);
             return this.idList;
         });
     }
 
     addRecordIds(ids: number[]): Promise<number[]> {
         ids = ids.filter(id => !this.hasRecordId(id)); // avoid dupes
+
+        if (ids.length === 0) { 
+            return Promise.resolve(this.idList); 
+        }
         return this.setRecordIds(
             this.idList.concat(ids.map(id => Number(id))));
     }
 
     removeRecordIds(ids: number[]): Promise<number[]> {
+
+        if (this.idList.length === 0) {
+            return Promise.resolve(this.idList);
+        }
+
         const wantedIds = this.idList.filter(
             id => ids.indexOf(Number(id)) < 0);
-        return this.setRecordIds(wantedIds);
+
+        return this.setRecordIds(wantedIds); // OK if empty
     }
 }
 
index 95967cb..2dc07c5 100644 (file)
@@ -1,4 +1,4 @@
-import {Injectable} from '@angular/core';
+import {Injectable, EventEmitter} from '@angular/core';
 import {Observable} from 'rxjs/Observable';
 import {mergeMap} from 'rxjs/operators/mergeMap';
 import {map} from 'rxjs/operators/map';
@@ -37,6 +37,9 @@ export class CatalogService {
     lastFacetData: any;
     lastFacetKey: string;
 
+    // Allow anyone to watch for completed searches.
+    onSearchComplete: EventEmitter<CatalogSearchContext>;
+
     constructor(
         private idl: IdlService,
         private net: NetService,
@@ -44,7 +47,10 @@ export class CatalogService {
         private unapi: UnapiService,
         private pcrud: PcrudService,
         private bibService: BibRecordService
-    ) {}
+    ) {
+        this.onSearchComplete = new EventEmitter<CatalogSearchContext>();
+        
+    }
 
     search(ctx: CatalogSearchContext): Promise<void> {
         ctx.searchState = CatalogSearchState.SEARCHING;
@@ -67,6 +73,7 @@ export class CatalogService {
             ).subscribe(result => {
                 this.applyResultData(ctx, result);
                 ctx.searchState = CatalogSearchState.COMPLETE;
+                this.onSearchComplete.emit(ctx);
                 resolve();
             });
         });
index 6bf3ec9..54b79c1 100644 (file)
@@ -17,7 +17,7 @@
           <span class="font-weight-bold font-italic">
             {{index + 1 + searchContext.pager.offset}}.
           </span>
-          <input class="pl-1" type='checkbox' [(ngModel)]="isRecordInBasket"
+          <input class="pl-1" type='checkbox' [(ngModel)]="isRecordSelected"
             (change)="toggleBasketEntry()"/>
         </label>
         <!-- XXX hard-coded width so columns align vertically regardless
index d0b12e8..cb6567f 100644 (file)
@@ -1,4 +1,5 @@
-import {Component, OnInit, Input} from '@angular/core';
+import {Component, OnInit, OnDestroy, Input} from '@angular/core';
+import {Subscription} from 'rxjs/Subscription';
 import {Router} from '@angular/router';
 import {OrgService} from '@eg/core/org.service';
 import {NetService} from '@eg/core/net.service';
@@ -14,12 +15,13 @@ import {BasketService} from '@eg/share/catalog/basket.service';
   templateUrl: 'record.component.html',
   styleUrls: ['record.component.css']
 })
-export class ResultRecordComponent implements OnInit {
+export class ResultRecordComponent implements OnInit, OnDestroy {
 
     @Input() index: number;  // 0-index display row
     @Input() summary: BibRecordSummary;
     searchContext: CatalogSearchContext;
-    isRecordInBasket: boolean;
+    isRecordSelected: boolean;
+    basketSub: Subscription;
 
     constructor(
         private router: Router,
@@ -35,7 +37,16 @@ export class ResultRecordComponent implements OnInit {
     ngOnInit() {
         this.searchContext = this.staffCat.searchContext;
         this.summary.getHoldCount();
-        this.isRecordInBasket = this.basket.hasRecordId(this.summary.id);
+        this.isRecordSelected = this.basket.hasRecordId(this.summary.id);
+
+        // Watch for basket changes caused by other components
+        this.basketSub = this.basket.onChange.subscribe(() => {
+            this.isRecordSelected = this.basket.hasRecordId(this.summary.id);
+        });
+    }
+
+    ngOnDestroy() {
+        this.basketSub.unsubscribe();
     }
 
     orgName(orgId: number): string {
@@ -78,7 +89,7 @@ export class ResultRecordComponent implements OnInit {
     }
 
     toggleBasketEntry() {
-        if (this.isRecordInBasket) {
+        if (this.isRecordSelected) {
             return this.basket.addRecordIds([this.summary.id]);
         } else {
             return this.basket.removeRecordIds([this.summary.id]);
index c484595..1f2ba30 100644 (file)
@@ -32,7 +32,8 @@
     </div>
     <div class="col-lg-2">
       <label class="checkbox">
-        <input type='checkbox'/>
+        <input type='checkbox' [(ngModel)]="allRecsSelected" 
+            (change)="toggleAllRecsSelected()"/>
         <span class="pl-1" i18n>Select {{searchContext.pager.rowNumber(0)}} - 
           {{searchContext.pager.rowNumber(searchContext.pager.limit - 1)}}
         </span>
index 39f4e5c..c4efdb2 100644 (file)
@@ -1,5 +1,6 @@
-import {Component, OnInit, Input} from '@angular/core';
+import {Component, OnInit, OnDestroy, Input} from '@angular/core';
 import {Observable} from 'rxjs/Observable';
+import {Subscription} from 'rxjs/Subscription';
 import {map, switchMap, distinctUntilChanged} from 'rxjs/operators';
 import {ActivatedRoute, ParamMap} from '@angular/router';
 import {CatalogService} from '@eg/share/catalog/catalog.service';
@@ -9,12 +10,13 @@ import {CatalogSearchContext, CatalogSearchState} from '@eg/share/catalog/search
 import {PcrudService} from '@eg/core/pcrud.service';
 import {StaffCatalogService} from '../catalog.service';
 import {IdlObject} from '@eg/core/idl.service';
+import {BasketService} from '@eg/share/catalog/basket.service';
 
 @Component({
   selector: 'eg-catalog-results',
   templateUrl: 'results.component.html'
 })
-export class ResultsComponent implements OnInit {
+export class ResultsComponent implements OnInit, OnDestroy {
 
     searchContext: CatalogSearchContext;
 
@@ -22,13 +24,19 @@ export class ResultsComponent implements OnInit {
     // reasonably small set of data w/ lots of repitition.
     userCache: {[id: number]: IdlObject} = {};
 
+    allRecsSelected: boolean;
+
+    searchSub: Subscription;
+    routeSub: Subscription;
+
     constructor(
         private route: ActivatedRoute,
         private pcrud: PcrudService,
         private cat: CatalogService,
         private bib: BibRecordService,
         private catUrl: CatalogUrlService,
-        private staffCat: StaffCatalogService
+        private staffCat: StaffCatalogService,
+        private basket: BasketService
     ) {}
 
     ngOnInit() {
@@ -41,7 +49,8 @@ export class ResultsComponent implements OnInit {
         // searches.
         //
         // This will also fire on page load.
-        this.route.queryParamMap.subscribe((params: ParamMap) => {
+        this.routeSub = 
+            this.route.queryParamMap.subscribe((params: ParamMap) => {
 
               // TODO: Angular docs suggest using switchMap(), but
               // it's not firing for some reason.  Also, could avoid
@@ -51,6 +60,28 @@ export class ResultsComponent implements OnInit {
               // .map() is not firing either.  I'm missing something.
               this.searchByUrl(params);
         });
+
+        // After each completed search, update the record selector.
+        this.searchSub = this.cat.onSearchComplete.subscribe(
+            ctx => this.applyRecordSelection());
+    }
+
+    ngOnDestroy() {
+        this.routeSub.unsubscribe();
+        this.searchSub.unsubscribe();
+    }
+
+    // Apply the select-all checkbox when all visible records
+    // are selected.
+    applyRecordSelection() {
+        const ids = this.searchContext.currentResultIds();
+        let allChecked = true;
+        ids.forEach(id => {
+            if (!this.basket.hasRecordId(id)) { 
+                allChecked = false; 
+            }
+        });
+        this.allRecsSelected = allChecked;
     }
 
     searchByUrl(params: ParamMap): void {
@@ -67,16 +98,27 @@ export class ResultsComponent implements OnInit {
         }
     }
 
-    // Avoid starting to display records until the first few are ready
-    // to reduce page shuffling.
+    // Records file into place randomly as the server returns data.
+    // To reduce page display shuffling, avoid showing the list of
+    // records until the first few are ready to render.
     shouldStartRendering(): boolean {
-        return (
-            this.searchContext.result &&
-            this.searchContext.result.records && (   
-                this.searchContext.result.records.length === 0 || 
-                this.searchContext.result.records[0]
-            )
-        );
+
+        if (this.searchHasResults()) {
+            const pageCount = this.searchContext.currentResultIds().length;
+            switch (pageCount) {
+                case 1:
+                    return this.searchContext.result.records[0];
+                case 2:
+                    return this.searchContext.result.records[0]
+                        && this.searchContext.result.records[1];
+                default:
+                    return this.searchContext.result.records[0]
+                        && this.searchContext.result.records[1]
+                        && this.searchContext.result.records[2];
+            }
+        }
+
+        return false;
     }
 
     fleshSearchResults(): void {
@@ -98,6 +140,16 @@ export class ResultsComponent implements OnInit {
     searchHasResults(): boolean {
         return this.searchIsDone() && this.searchContext.result.count > 0;
     }
+
+    toggleAllRecsSelected() {
+        const ids = this.searchContext.currentResultIds();
+
+        if (this.allRecsSelected) {
+            this.basket.addRecordIds(ids);
+        } else {
+            this.basket.removeRecordIds(ids);
+        }
+    }
 }