LP1899406 Staff catalog view basket selection user/berick/lp1899406-staff-cat-view-basket-selection
authorBill Erickson <berickxx@gmail.com>
Wed, 14 Oct 2020 20:53:59 +0000 (16:53 -0400)
committerBill Erickson <berickxx@gmail.com>
Wed, 14 Oct 2020 20:54:02 +0000 (16:54 -0400)
In the Angular staff catalog, support applying basket actions to
selected items within a basket, without selection/desection impacting
the contents of the basket.

Note that 'Export All Basket Records' applies to the entire basket and
is not affected by the selection.  All other actions, besides View and
Clear Basket, are affected by the selection.

Signed-off-by: Bill Erickson <berickxx@gmail.com>
Open-ILS/src/eg2/src/app/staff/catalog/basket-actions.component.html
Open-ILS/src/eg2/src/app/staff/catalog/basket-actions.component.ts
Open-ILS/src/eg2/src/app/staff/catalog/basket-selection.service.ts [new file with mode: 0644]
Open-ILS/src/eg2/src/app/staff/catalog/catalog.module.ts
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 752557c..0ffb9d3 100644 (file)
@@ -27,9 +27,9 @@
         <button class="dropdown-item"
           (click)="applyAction('email')" i18n>Email Title Details</button>
         <button class="dropdown-item"
-          (click)="applyAction('bucket')" i18n>Add Basket to Bucket</button>
+          (click)="applyAction('bucket')" i18n>Add to Bucket</button>
         <button class="dropdown-item"
-          (click)="applyAction('export_marc')" i18n>Export Records</button>
+          (click)="applyAction('export_marc')" i18n>Export All Basket Records</button>
         <button class="dropdown-item"
           (click)="applyAction('clear')" i18n>Clear Basket</button>
       </div>
index b5f3572..42f6d5c 100644 (file)
@@ -6,6 +6,7 @@ import {AuthService} from '@eg/core/auth.service';
 import {PrintService} from '@eg/share/print/print.service';
 import {BucketDialogComponent
     } from '@eg/staff/share/buckets/bucket-dialog.component';
+import {BasketSelectionService} from './basket-selection.service';
 
 @Component({
   selector: 'eg-catalog-basket-actions',
@@ -23,7 +24,8 @@ export class BasketActionsComponent implements OnInit {
         private net: NetService,
         private auth: AuthService,
         private printer: PrintService,
-        private basket: BasketService
+        private basket: BasketService,
+        private basketSelect: BasketSelectionService
     ) {
         this.basketAction = '';
     }
@@ -53,14 +55,14 @@ export class BasketActionsComponent implements OnInit {
                 break;
 
             case 'hold':
-                this.basket.getRecordIds().then(ids => {
+                this.basketSelect.getRecordIds().then(ids => {
                     this.router.navigate(['/staff/catalog/hold/T'],
                         {queryParams: {target: ids}});
                 });
                 break;
 
             case 'print':
-                this.basket.getRecordIds().then(ids => {
+                this.basketSelect.getRecordIds().then(ids => {
                     this.net.request(
                         'open-ils.search',
                         'open-ils.search.biblio.record.print', ids
@@ -78,7 +80,7 @@ export class BasketActionsComponent implements OnInit {
                 break;
 
             case 'email':
-                this.basket.getRecordIds().then(ids => {
+                this.basketSelect.getRecordIds().then(ids => {
                     this.net.request(
                         'open-ils.search',
                         'open-ils.search.biblio.record.email',
@@ -95,7 +97,7 @@ export class BasketActionsComponent implements OnInit {
                 break;
 
             case 'bucket':
-                this.basket.getRecordIds().then(ids => {
+                this.basketSelect.getRecordIds().then(ids => {
                     this.addToBucketDialog.bucketClass = 'biblio';
                     this.addToBucketDialog.itemIds = ids;
                     this.addToBucketDialog.open({size: 'lg'});
diff --git a/Open-ILS/src/eg2/src/app/staff/catalog/basket-selection.service.ts b/Open-ILS/src/eg2/src/app/staff/catalog/basket-selection.service.ts
new file mode 100644 (file)
index 0000000..892de60
--- /dev/null
@@ -0,0 +1,68 @@
+import {Injectable, EventEmitter} from '@angular/core';
+import {BasketService} from '@eg/share/catalog/basket.service';
+import {Observable} from 'rxjs';
+
+/*
+ * Provides a way to select / deselect items in the basket view,
+ * with the same API as the basket service, without affecting the
+ * contents of the basket.
+ */
+
+@Injectable()
+export class BasketSelectionService {
+
+    idList: number[];
+
+    constructor(private basket: BasketService) {
+        this.idList = [];
+    }
+
+    init(): Promise<any> {
+        return this.basket.getRecordIds().then(ids => this.setRecordIds(ids));
+    }
+
+    hasRecordId(id: number): boolean {
+        return this.idList.indexOf(Number(id)) > -1;
+    }
+
+    recordCount(): number {
+        return this.idList.length;
+    }
+
+    getRecordIds(): Promise<number[]> {
+        return Promise.resolve(this.idList);
+    }
+
+    setRecordIds(ids: number[]): Promise<number[]> {
+        this.idList = ids;
+        return Promise.resolve(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); // OK if empty
+    }
+
+    removeAllRecordIds(): Promise<number[]> {
+        return this.setRecordIds([]);
+    }
+}
+
+
index 9b7d57a..e0bdb10 100644 (file)
@@ -31,6 +31,7 @@ import {CnBrowseResultsComponent} from './cnbrowse/results.component';
 import {SearchTemplatesComponent} from './search-templates.component';
 import {MarcEditModule} from '@eg/staff/share/marc-edit/marc-edit.module';
 import {PreferencesComponent} from './prefs.component';
+import {BasketSelectionService} from './basket-selection.service';
 
 @NgModule({
   declarations: [
@@ -69,7 +70,8 @@ import {PreferencesComponent} from './prefs.component';
     MarcEditModule
   ],
   providers: [
-    StaffCatalogService
+    StaffCatalogService,
+    BasketSelectionService
   ]
 })
 
index bd066a7..eb22bd8 100644 (file)
@@ -10,6 +10,7 @@ import {CatalogUrlService} from '@eg/share/catalog/catalog-url.service';
 import {StaffCatalogService} from '../catalog.service';
 import {BasketService} from '@eg/share/catalog/basket.service';
 import {CourseService} from '@eg/staff/share/course.service';
+import {BasketSelectionService} from '../basket-selection.service';
 
 @Component({
   selector: 'eg-catalog-result-record',
@@ -32,6 +33,8 @@ export class ResultRecordComponent implements OnInit, OnDestroy {
     hasCourse = false;
     courses: any[] = [];
 
+    recordSelector: BasketService |  BasketSelectionService;
+
     constructor(
         private router: Router,
         private org: OrgService,
@@ -39,7 +42,8 @@ export class ResultRecordComponent implements OnInit, OnDestroy {
         private catUrl: CatalogUrlService,
         private staffCat: StaffCatalogService,
         private basket: BasketService,
-        private course: CourseService
+        private course: CourseService,
+        private basketSelect: BasketSelectionService
     ) {}
 
     ngOnInit() {
@@ -51,6 +55,12 @@ export class ResultRecordComponent implements OnInit, OnDestroy {
         this.basketSub = this.basket.onChange.subscribe(() => {
             this.isRecordSelected = this.basket.hasRecordId(this.summary.id);
         });
+
+        if (this.searchContext.showBasket) {
+            this.recordSelector = this.basketSelect;
+        } else {
+            this.recordSelector = this.basket;
+        }
     }
 
     ngOnDestroy() {
@@ -127,9 +137,9 @@ export class ResultRecordComponent implements OnInit, OnDestroy {
 
     toggleBasketEntry() {
         if (this.isRecordSelected) {
-            return this.basket.addRecordIds([this.summary.id]);
+            return this.recordSelector.addRecordIds([this.summary.id]);
         } else {
-            return this.basket.removeRecordIds([this.summary.id]);
+            return this.recordSelector.removeRecordIds([this.summary.id]);
         }
     }
 }
index 515a376..00323d9 100644 (file)
@@ -32,7 +32,7 @@
 <!-- header, pager, and list of records -->
 <div id="staff-catalog-results-container" *ngIf="searchHasResults()">
   <div class="row">
-    <div class="col-lg-2" *ngIf="!searchContext.basket">
+    <div class="col-lg-2" *ngIf="!searchContext.showBasket">
       <ng-container *ngIf="searchContext.termSearch.browseEntry">
         <h3 i18n>Results for browse "{{searchContext.termSearch.browseEntry.value()}}"</h3>
       </ng-container>
         <h3 i18n>Search Results ({{searchContext.result.count}})</h3>
       </ng-container>
     </div>
-    <div class="col-lg-2" *ngIf="searchContext.basket">
+    <div class="col-lg-2" *ngIf="searchContext.showBasket">
       <h3 i18n>Basket View</h3>
     </div>
     <div class="col-lg-2">
-      <label class="checkbox" *ngIf="!searchContext.basket">
+      <label class="checkbox" *ngIf="!searchContext.showBasket">
         <input type='checkbox' [(ngModel)]="allRecsSelected" 
             (change)="toggleAllRecsSelected()"/>
         <span class="pl-1" i18n>Select {{searchContext.pager.rowNumber(0)}} - 
   </div>
   <div>
     <div class="row mt-2">
-      <div class="col-lg-2" *ngIf="!searchContext.basket">
+      <div class="col-lg-2" *ngIf="!searchContext.showBasket">
         <eg-catalog-result-facets></eg-catalog-result-facets>
       </div>
       <div
-        [ngClass]="{'col-lg-10': !searchContext.basket, 'col-lg-12': searchContext.basket}">
+        [ngClass]="{'col-lg-10': !searchContext.showBasket, 'col-lg-12': searchContext.showBasket}">
         <div *ngFor="let summary of searchContext.result.records; let idx = index">
           <div *ngIf="summary">
             <eg-catalog-result-record [summary]="summary" [index]="idx">
index edcb381..2cc6604 100644 (file)
@@ -10,6 +10,7 @@ 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';
+import {BasketSelectionService} from '../basket-selection.service';
 
 @Component({
   selector: 'eg-catalog-results',
@@ -29,6 +30,8 @@ export class ResultsComponent implements OnInit, OnDestroy {
     routeSub: Subscription;
     basketSub: Subscription;
 
+    recordSelector: BasketService |  BasketSelectionService;
+
     constructor(
         private route: ActivatedRoute,
         private pcrud: PcrudService,
@@ -36,7 +39,8 @@ export class ResultsComponent implements OnInit, OnDestroy {
         private bib: BibRecordService,
         private catUrl: CatalogUrlService,
         private staffCat: StaffCatalogService,
-        private basket: BasketService
+        private basket: BasketService,
+        private basketSelect: BasketSelectionService
     ) {}
 
     ngOnInit() {
@@ -68,6 +72,13 @@ export class ResultsComponent implements OnInit, OnDestroy {
         // Watch for basket changes applied by other components.
         this.basketSub = this.basket.onChange.subscribe(
             () => this.applyRecordSelection());
+
+        if (this.searchContext.showBasket) {
+            this.recordSelector = this.basketSelect;
+            this.basketSelect.init();
+        } else {
+            this.recordSelector = this.basket;
+        }
     }
 
     ngOnDestroy() {
@@ -84,7 +95,7 @@ export class ResultsComponent implements OnInit, OnDestroy {
         const ids = this.searchContext.currentResultIds();
         let allChecked = true;
         ids.forEach(id => {
-            if (!this.basket.hasRecordId(id)) {
+            if (!this.recordSelector.hasRecordId(id)) {
                 allChecked = false;
             }
         });
@@ -121,9 +132,9 @@ export class ResultsComponent implements OnInit, OnDestroy {
         const ids = this.searchContext.currentResultIds();
 
         if (this.allRecsSelected) {
-            this.basket.addRecordIds(ids);
+            this.recordSelector.addRecordIds(ids);
         } else {
-            this.basket.removeRecordIds(ids);
+            this.recordSelector.removeRecordIds(ids);
         }
     }
 }