/**
- * 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
bootstrap: [EgBaseComponent]
})
-export class EgBaseModule {
- constructor(router: Router) {
- /*
- console.debug('Routes: ',
- JSON.stringify(router.config, undefined, 2));
- */
- }
-}
+export class EgBaseModule {}
+
@Injectable()
export class EgOrgService {
- private orgMap = {};
private orgList: EgIdlObject[] = [];
private orgTree: EgIdlObject; // root node + children
+ private orgMap: {[id:number] : EgIdlObject} = {};
private settingsCache: OrgSettingsBatch = {};
constructor(
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.
nodes.push(node);
if (asId) return nodes.map(n => n.id());
return nodes;
- };
+ }
// tests that a node can have users
canHaveUsers(nodeOrId): boolean {
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'));
}
}
* 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 = [];
}
isSearchable(): boolean {
- return this.query.length && this.query[0] != '';
+ return this.query.length
+ && this.query[0] != ''
+ && this.searchOrg != null;
}
compileSearch(): string {
-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',
});
this.route.data.subscribe((data: {staffResolver : any}) => {
- console.debug('EgStaff ngOnInit complete');
+ // Data fetched via EgStaffResolver is available here.
});
}
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();
}
}
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';
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,
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;
+ }
}
/**
* 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
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);
});
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[]>> {
private org: EgOrgService,
private net: EgNetService,
private auth: EgAuthService,
- private cat: EgCatalogService
+ private cat: EgCatalogService,
+ private staffCat: StaffCatalogService
) {}
resolve(
}
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);
}
+
}
// 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
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>> {
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,
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();
}
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: '',
}];
@NgModule({
- imports: [ RouterModule.forChild(routes) ],
- exports: [ RouterModule ],
- providers: [
- EgStaffResolver
- ]
+ imports: [RouterModule.forChild(routes)],
+ exports: [RouterModule],
+ providers: [EgStaffResolver]
})
export class EgStaffRoutingModule {}