LP#626157 ang2 catalog tweaks
authorBill Erickson <berickxx@gmail.com>
Wed, 27 Dec 2017 22:52:18 +0000 (17:52 -0500)
committerBill Erickson <berickxx@gmail.com>
Wed, 27 Dec 2017 22:52:18 +0000 (17:52 -0500)
Signed-off-by: Bill Erickson <berickxx@gmail.com>
12 files changed:
Open-ILS/eg2-src/src/app/app.module.ts
Open-ILS/eg2-src/src/app/core/org.ts
Open-ILS/eg2-src/src/app/share/catalog/catalog-url.service.ts
Open-ILS/eg2-src/src/app/share/catalog/search-context.ts
Open-ILS/eg2-src/src/app/staff/app.component.ts
Open-ILS/eg2-src/src/app/staff/catalog/app.component.ts
Open-ILS/eg2-src/src/app/staff/catalog/app.service.ts
Open-ILS/eg2-src/src/app/staff/catalog/record/copies.component.ts
Open-ILS/eg2-src/src/app/staff/catalog/resolver.service.ts
Open-ILS/eg2-src/src/app/staff/catalog/result/results.component.ts
Open-ILS/eg2-src/src/app/staff/resolver.service.ts
Open-ILS/eg2-src/src/app/staff/routing.module.ts

index ce8f2c1..e7a6198 100644 (file)
@@ -1,11 +1,10 @@
 /**
- * EgBaseModule is the shared starting point for all apps.
- * It provides the root router and a simple welcome page for 
- * users that end up here accidentally.
+ * EgBaseModule is the shared starting point for all apps.  It provides
+ * the root route and core services, and a simple welcome page for users
+ * that end up here accidentally.
  */
 import {BrowserModule} from '@angular/platform-browser';
 import {NgModule} from '@angular/core';
-import {Router} from '@angular/router'; // Debugging
 import {NgbModule} from '@ng-bootstrap/ng-bootstrap'; // ng-bootstrap
 import {CookieModule} from 'ngx-cookie'; // import CookieMonster
 
@@ -48,11 +47,5 @@ import {EgOrgService} from '@eg/core/org';
   bootstrap: [EgBaseComponent]
 })
 
-export class EgBaseModule { 
-    constructor(router: Router) {
-        /*
-        console.debug('Routes: ', 
-            JSON.stringify(router.config, undefined, 2));
-        */
-    }
-}
+export class EgBaseModule {}
+
index 8508c41..0cf87a4 100644 (file)
@@ -22,9 +22,9 @@ interface OrgSettingsBatch {
 @Injectable()
 export class EgOrgService {
 
-    private orgMap = {};
     private orgList: EgIdlObject[] = [];
     private orgTree: EgIdlObject; // root node + children
+    private orgMap: {[id:number] : EgIdlObject} = {};
     private settingsCache: OrgSettingsBatch = {};
 
     constructor(
@@ -37,11 +37,11 @@ export class EgOrgService {
         if (typeof nodeOrId == 'object')
             return nodeOrId;
         return this.orgMap[nodeOrId];
-    };
+    }
 
     list(): EgIdlObject[] {
         return this.orgList;
-    };
+    }
 
     /**
      * Returns a list of org units that match the selected criteria.
@@ -95,7 +95,7 @@ export class EgOrgService {
             nodes.push(node);
         if (asId) return nodes.map(n => n.id());
         return nodes;
-    };
+    }
 
     // tests that a node can have users
     canHaveUsers(nodeOrId): boolean {
index 00f3203..932416f 100644 (file)
@@ -122,7 +122,7 @@ export class EgCatalogUrlService {
             context.addFacet(new FacetFilter(facet.c, facet.n, facet.v));
         });
 
-        context.searchOrg = 
-            this.org.get(+params.get('org')) || this.org.root();
+        if (params.get('org'))
+            context.searchOrg = this.org.get(+params.get('org'));
     }
 }
index b3c21e5..6bd5380 100644 (file)
@@ -99,15 +99,15 @@ export class CatalogSearchContext {
      * Return search context to its default state, resetting search
      * parameters and clearing any cached result data.
      * This does not reset global filters like limit-to-available 
-     * or search-global.
+     * search-global, or search-org.
      */
     reset(): void {
         this.pager.offset = 0;
         this.format = '';
         this.sort = '';
-        this.query  = [''];
-        this.fieldClass   = ['keyword'];
-        this.matchOp  = ['contains'];
+        this.query = [''];
+        this.fieldClass  = ['keyword'];
+        this.matchOp = ['contains'];
         this.joinOp = [''];
         this.ccvmFilters = {};
         this.facetFilters = [];
@@ -117,7 +117,9 @@ export class CatalogSearchContext {
     }
 
     isSearchable(): boolean {
-        return this.query.length && this.query[0] != '';
+        return this.query.length 
+            && this.query[0] != ''
+            && this.searchOrg != null;
     }
 
     compileSearch(): string {
index 27a60f9..788edc2 100644 (file)
@@ -1,7 +1,7 @@
-import { Component, OnInit } from '@angular/core';
-import { Router, ActivatedRoute, NavigationEnd } from '@angular/router';
-import { EgAuthService, EgAuthWsState } from '@eg/core/auth';
-import { EgNetService } from '@eg/core/net';
+import {Component, OnInit} from '@angular/core';
+import {Router, ActivatedRoute, NavigationEnd} from '@angular/router';
+import {EgAuthService, EgAuthWsState} from '@eg/core/auth';
+import {EgNetService} from '@eg/core/net';
 
 @Component({
   templateUrl: 'app.component.html',
@@ -45,7 +45,7 @@ export class EgStaffComponent implements OnInit {
         });
 
         this.route.data.subscribe((data: {staffResolver : any}) => {
-            console.debug('EgStaff ngOnInit complete');
+            // Data fetched via EgStaffResolver is available here.
         });
     }
 
index a5ca68f..8fffa13 100644 (file)
@@ -9,8 +9,9 @@ export class EgCatalogComponent implements OnInit {
     constructor(private staffCat: StaffCatalogService) {}
 
     ngOnInit() {
-        // Create the search context that will be used by all 
-        // of my child components.
+        // 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();
     }
 }
index 625206e..0cf04ca 100644 (file)
@@ -1,5 +1,6 @@
 import {Injectable} from '@angular/core';
 import {Router, ActivatedRoute} from '@angular/router';
+import {EgIdlObject} from '@eg/core/idl';
 import {EgOrgService} from '@eg/core/org';
 import {EgCatalogService} from '@eg/share/catalog/catalog.service';
 import {EgCatalogUrlService} from '@eg/share/catalog/catalog-url.service';
@@ -14,6 +15,11 @@ export class StaffCatalogService {
 
     searchContext: CatalogSearchContext;
     routeIndex: number = 0;
+    defaultSearchOrg: EgIdlObject;
+    defaultSearchLimit: number;
+
+    // TODO: does unapi support pref-lib for result-page copy counts?
+    prefOrg: EgIdlObject; 
 
     constructor(
         private router: Router,
@@ -31,12 +37,20 @@ export class StaffCatalogService {
         this.searchContext = 
             this.catUrl.fromUrlParams(this.route.snapshot.queryParamMap);
 
-        this.searchContext.org = this.org;
+        this.searchContext.org = this.org; // service, not searchOrg
         this.searchContext.isStaff = true;
+        this.applySearchDefaults();
+    }
+
+    applySearchDefaults(): void {
+        if (!this.searchContext.searchOrg) {
+            this.searchContext.searchOrg = 
+                this.defaultSearchOrg || this.org.root();
+        }
 
-        // TODO: UI / settings
-        if (!this.searchContext.pager.limit)
-          this.searchContext.pager.limit = 20;
+        if (!this.searchContext.pager.limit) {
+            this.searchContext.pager.limit = this.defaultSearchLimit || 20;
+        }
     }
 
     /**
@@ -45,10 +59,9 @@ export class StaffCatalogService {
      * execute the actual search.
      */
     search(): void {
-        let params = this.catUrl.toUrlParams(this.searchContext);
+        if (!this.searchContext.isSearchable()) return;
 
-        // Avoid redirect on empty-query searches
-        if (params.query[0] == '') return;
+        let params = this.catUrl.toUrlParams(this.searchContext);
         
         // Force a new search every time this method is called, even if
         // it's the same as the active search.  Since router navigation
index f234eba..a8e9417 100644 (file)
@@ -54,7 +54,7 @@ export class CopiesComponent implements OnInit {
             this.staffCat.searchContext.searchOrg.ou_type().depth(), // TODO
             this.pager.limit,
             this.pager.offset,
-            this.staffCat.searchContext.searchOrg.id() // TODO pref_ou
+            this.staffCat.prefOrg ? this.staffCat.prefOrg.id() : null 
         ).subscribe(copy => {
             this.copies.push(copy);
         });
index e364839..45fc8c5 100644 (file)
@@ -8,6 +8,7 @@ import {EgOrgService} from '@eg/core/org';
 import {EgAuthService} from '@eg/core/auth';
 import {EgPcrudService} from '@eg/core/pcrud';
 import {EgCatalogService} from '@eg/share/catalog/catalog.service';
+import {StaffCatalogService} from './app.service';
 
 @Injectable()
 export class EgCatalogResolver implements Resolve<Promise<any[]>> {
@@ -18,7 +19,8 @@ export class EgCatalogResolver implements Resolve<Promise<any[]>> {
         private org: EgOrgService,
         private net: EgNetService,
         private auth: EgAuthService,
-        private cat: EgCatalogService
+        private cat: EgCatalogService,
+        private staffCat: StaffCatalogService
     ) {}
 
     resolve(
@@ -35,9 +37,22 @@ export class EgCatalogResolver implements Resolve<Promise<any[]>> {
     }
 
     fetchSettings(): Promise<any> {
-        return this.org.settings([
-            // staff catalog org settings loaded here...
-        ]);
+        let promises = [];
+
+        promises.push(
+            this.store.getItem('eg.search.search_lib').then(
+                id => this.staffCat.defaultSearchOrg = this.org.get(id)
+            )
+        );
+
+        promises.push(
+            this.store.getItem('eg.search.pref_lib').then(
+                id => this.staffCat.prefOrg = this.org.get(id)
+            )
+        );
+
+        return Promise.all(promises);
     }
+
 }
 
index b87a2cd..ff2d36c 100644 (file)
@@ -37,7 +37,9 @@ export class ResultsComponent implements OnInit {
         // 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.
+        // searches.  
+        // 
+        // This will also fire on page load.
         this.route.queryParamMap.subscribe((params: ParamMap) => {
 
               // TODO: Angular docs suggest using switchMap(), but
index a9c393e..d37ca2f 100644 (file)
@@ -8,9 +8,7 @@ import {EgNetService} from '@eg/core/net';
 import {EgAuthService} from '@eg/core/auth';
 
 /**
- * Apply configuration, etc. required by all staff components.
- * This resolver is called before authentication is confirmed.
- * See EgStaffCommonDataResolver for staff-wide, post-auth activities.
+ * Load data used by all staff modules.
  */
 @Injectable()
 export class EgStaffResolver implements Resolve<Observable<any>> {
@@ -18,6 +16,9 @@ export class EgStaffResolver implements Resolve<Observable<any>> {
     readonly loginPath = '/staff/login';
     readonly wsRemPath = '/staff/admin/workstation/workstations/remove/';
 
+    // Tracks the primary resolve observable.
+    observer: Observer<any>;
+
     constructor(
         private router: Router, 
         private route: ActivatedRoute, 
@@ -43,38 +44,55 @@ export class EgStaffResolver implements Resolve<Observable<any>> {
         let path = state.url.split('?')[0]
         if (path == '/staff/login') return Observable.of(true);
 
-        return Observable.create(observer => {
-            this.auth.testAuthToken().then(
-                tokenOk => {
-                    console.debug('EgStaffResolver: authtoken verified');
-                    this.auth.verifyWorkstation().then(
-                        wsOk => {
-                            this.loadStartupData(observer).then(
-                                ok => observer.complete()
-                            );
-                        },
-                        wsNotOk => {
-                            if (path.indexOf(this.wsRemPath) < 0) {
-                                this.router.navigate([
-                                    this.wsRemPath + this.auth.workstation()
-                                ]);
-                            }
-                            observer.complete();
-                        }
-                    );
-                }, 
-                tokenNotOk => {
-                    // Authtoken is not OK.
-                    console.debug('EgStaffResolver: authtoken is not valid');
-                    this.auth.redirectUrl = state.url;
-                    this.router.navigate([this.loginPath]);
-                    observer.error('invalid auth');
-                }
-            );
-        });
+        let observable: Observable<any> 
+            = Observable.create(o => this.observer = o);
+
+        this.auth.testAuthToken().then(
+            tokenOk => {
+                console.debug('EgStaffResolver: authtoken verified');
+                this.auth.verifyWorkstation().then(
+                    wsOk => {
+                        this.loadStartupData()
+                        .then(ok => this.observer.complete())
+                    },
+                    wsNotOk => this.handleInvalidWorkstation(path)
+                );
+            }, 
+            tokenNotOk => this.handleInvalidToken(state)
+        );
+
+        return observable;
+    }
+
+    // A page that's not the login page was requested without a 
+    // valid auth token.  Send the caller back to the login page.
+    handleInvalidToken(state: RouterStateSnapshot): void {
+        console.debug('EgStaffResolver: authtoken is not valid');
+        this.auth.redirectUrl = state.url;
+        this.router.navigate([this.loginPath]);
+        this.observer.error('invalid or no auth token');
+    }
+
+    // For the purposes of route resolving, an invalid workstation is
+    // not an error condition when we're on the workstation admin page.
+    // However, it does mean that not all of the usual data will be
+    // loaded and available on the workstation admin page.  Once a valid
+    // workstation is applied and the user re-logs in, the page must be
+    // reloaded to fetch all of the needed staff data.
+    handleInvalidWorkstation(path: string): void {
+        if (path.indexOf(this.wsRemPath) < 0) {
+            this.router.navigate([
+                this.wsRemPath + this.auth.workstation()]);
+            this.observer.error('invalid workstation, redirecting');
+        } else {
+            this.observer.complete();
+        }
     }
 
-    loadStartupData(observer: Observer<any>): Promise<void> {
+    /**
+     * Fetches data common to all staff interfaces.
+     */
+    loadStartupData(): Promise<void> {
         console.debug('EgStaffResolver:loadStartupData()');
         return Promise.resolve();
     }
index 81c0609..a25baf9 100644 (file)
@@ -6,7 +6,8 @@ import {EgStaffLoginComponent} from './login.component';
 import {EgStaffSplashComponent} from './splash.component';
 
 // Not using 'canActivate' because it's called before all resolvers,
-// but the resolvers parse the IDL, etc.
+// even the parent resolver, but the resolvers parse the IDL, load settings, 
+// etc.  Chicken, meet egg.
 
 const routes: Routes = [{
   path: '',
@@ -35,11 +36,9 @@ const routes: Routes = [{
 }];
 
 @NgModule({
-  imports: [ RouterModule.forChild(routes) ],
-  exports: [ RouterModule ],
-  providers: [ 
-    EgStaffResolver
-  ]
+  imports: [RouterModule.forChild(routes)],
+  exports: [RouterModule],
+  providers: [EgStaffResolver]
 })
 
 export class EgStaffRoutingModule {}