LP1860044 Angular catalog search result highlights
[working/Evergreen.git] / Open-ILS / src / eg2 / src / app / staff / catalog / record / record.component.ts
index b217e5c..b900ea8 100644 (file)
@@ -1,4 +1,4 @@
-import {Component, OnInit, Input, ViewChild} from '@angular/core';
+import {Component, OnInit, Input, ViewChild, HostListener} from '@angular/core';
 import {NgbTabset, NgbTabChangeEvent} from '@ng-bootstrap/ng-bootstrap';
 import {Router, ActivatedRoute, ParamMap} from '@angular/router';
 import {PcrudService} from '@eg/core/pcrud.service';
@@ -8,6 +8,9 @@ import {CatalogService} from '@eg/share/catalog/catalog.service';
 import {BibRecordService, BibRecordSummary} from '@eg/share/catalog/bib-record.service';
 import {StaffCatalogService} from '../catalog.service';
 import {BibSummaryComponent} from '@eg/staff/share/bib-summary/bib-summary.component';
+import {StoreService} from '@eg/core/store.service';
+import {ConfirmDialogComponent} from '@eg/share/dialog/confirm.component';
+import {MarcEditorComponent} from '@eg/staff/share/marc-edit/editor.component';
 
 @Component({
   selector: 'eg-catalog-record',
@@ -19,7 +22,12 @@ export class RecordComponent implements OnInit {
     recordTab: string;
     summary: BibRecordSummary;
     searchContext: CatalogSearchContext;
-    @ViewChild('recordTabs') recordTabs: NgbTabset;
+    @ViewChild('recordTabs', { static: true }) recordTabs: NgbTabset;
+    @ViewChild('marcEditor', {static: false}) marcEditor: MarcEditorComponent;
+    defaultTab: string; // eg.cat.default_record_tab
+
+    @ViewChild('pendingChangesDialog', {static: false})
+        pendingChangesDialog: ConfirmDialogComponent;
 
     constructor(
         private router: Router,
@@ -27,33 +35,96 @@ export class RecordComponent implements OnInit {
         private pcrud: PcrudService,
         private bib: BibRecordService,
         private cat: CatalogService,
-        private staffCat: StaffCatalogService
+        private staffCat: StaffCatalogService,
+        private store: StoreService
     ) {}
 
     ngOnInit() {
         this.searchContext = this.staffCat.searchContext;
 
+        this.defaultTab =
+            this.store.getLocalItem('eg.cat.default_record_tab')
+            || 'item_table';
+
         // Watch for URL record ID changes
+        // This includes the initial route.
+        // When applying the default configured tab, no navigation occurs
+        // to apply the tab name to the URL, it displays as the default.
+        // This is done so no intermediate redirect is required, which
+        // messes with browser back/forward navigation.
         this.route.paramMap.subscribe((params: ParamMap) => {
-            this.recordTab = params.get('tab') || 'copy_table';
+            this.recordTab = params.get('tab');
             this.recordId = +params.get('id');
             this.searchContext = this.staffCat.searchContext;
+
+            if (!this.recordTab) {
+                this.recordTab = this.defaultTab || 'item_table';
+            }
+
             this.loadRecord();
         });
     }
 
+    setDefaultTab() {
+        this.defaultTab = this.recordTab;
+        this.store.setLocalItem('eg.cat.default_record_tab', this.recordTab);
+    }
+
     // Changing a tab in the UI means changing the route.
     // Changing the route ultimately results in changing the tab.
-    onTabChange(evt: NgbTabChangeEvent) {
-        this.recordTab = evt.nextId;
+    beforeTabChange(evt: NgbTabChangeEvent) {
 
         // prevent tab changing until after route navigation
         evt.preventDefault();
 
-        let url = '/staff/catalog/record/' + this.recordId;
-        if (this.recordTab !== 'copy_table') {
-            url += '/' + this.recordTab;
+        // Protect against tab changes with dirty data.
+        this.canDeactivate().then(ok => {
+            if (ok) {
+                this.recordTab = evt.nextId;
+                this.routeToTab();
+            }
+        });
+    }
+
+    /*
+     * Handle 3 types of navigation which can cause loss of data.
+     * 1. Record detail tab navigation (see also beforeTabChange())
+     * 2. Intra-Angular route navigation away from the record detail page
+     * 3. Browser page unload/reload
+     *
+     * For the #1, and #2, display a eg confirmation dialog.
+     * For #3 use the stock browser onbeforeunload dialog.
+     *
+     * Note in this case a tab change is a route change, but it's one
+     * which does not cause RecordComponent to unload, so it has to be
+     * manually tracked in beforeTabChange().
+     */
+    @HostListener('window:beforeunload', ['$event'])
+    canDeactivate($event?: Event): Promise<boolean> {
+
+        if (this.marcEditor && this.marcEditor.changesPending()) {
+
+            // Each warning dialog clears the current "changes are pending"
+            // flag so the user is not presented with the dialog again
+            // unless new changes are made.
+            this.marcEditor.clearPendingChanges();
+
+            if ($event) { // window.onbeforeunload
+                $event.preventDefault();
+                $event.returnValue = true;
+
+            } else { // tab OR route change.
+                return this.pendingChangesDialog.open().toPromise();
+            }
+
+        } else {
+            return Promise.resolve(true);
         }
+    }
+
+    routeToTab() {
+        const url =
+            `/staff/catalog/record/${this.recordId}/${this.recordTab}`;
 
         // Retain search parameters
         this.router.navigate([url], {queryParamsHandling: 'merge'});
@@ -79,6 +150,35 @@ export class RecordComponent implements OnInit {
             this.bib.fleshBibUsers([summary.record]);
         });
     }
+
+    // Lets us intercept the summary object and augment it with
+    // search highlight data if/when it becomes available from
+    // an externally executed search.
+    summaryForDisplay(): BibRecordSummary {
+        if (!this.summary) { return null; }
+        const sum = this.summary;
+        const ctx = this.searchContext;
+
+        if (Object.keys(sum.displayHighlights).length === 0) {
+            if (ctx.highlightData[sum.id]) {
+                sum.displayHighlights = ctx.highlightData[sum.id];
+            }
+        }
+
+        return this.summary;
+    }
+
+    currentSearchOrg(): IdlObject {
+        if (this.staffCat && this.staffCat.searchContext) {
+            return this.staffCat.searchContext.searchOrg;
+        }
+        return null;
+    }
+
+    handleMarcRecordSaved() {
+        this.staffCat.currentDetailRecordSummary = null;
+        this.loadRecord();
+    }
 }