dc8d8dfce83dc7e4e3fb6c0b1f3145a0dfe215e5
[working/Evergreen.git] / Open-ILS / src / eg2 / src / app / staff / catalog / record / record.component.ts
1 import {Component, OnInit, Input, ViewChild, HostListener} from '@angular/core';
2 import {NgbTabset, NgbTabChangeEvent} from '@ng-bootstrap/ng-bootstrap';
3 import {Router, ActivatedRoute, ParamMap} from '@angular/router';
4 import {PcrudService} from '@eg/core/pcrud.service';
5 import {IdlObject} from '@eg/core/idl.service';
6 import {CatalogSearchContext, CatalogSearchState} from '@eg/share/catalog/search-context';
7 import {CatalogService} from '@eg/share/catalog/catalog.service';
8 import {BibRecordService, BibRecordSummary} from '@eg/share/catalog/bib-record.service';
9 import {StaffCatalogService} from '../catalog.service';
10 import {BibSummaryComponent} from '@eg/staff/share/bib-summary/bib-summary.component';
11 import {StoreService} from '@eg/core/store.service';
12 import {ConfirmDialogComponent} from '@eg/share/dialog/confirm.component';
13 import {MarcEditorComponent} from '@eg/staff/share/marc-edit/editor.component';
14
15 @Component({
16   selector: 'eg-catalog-record',
17   templateUrl: 'record.component.html'
18 })
19 export class RecordComponent implements OnInit {
20
21     recordId: number;
22     recordTab: string;
23     summary: BibRecordSummary;
24     searchContext: CatalogSearchContext;
25     @ViewChild('recordTabs', { static: true }) recordTabs: NgbTabset;
26     @ViewChild('marcEditor', {static: false}) marcEditor: MarcEditorComponent;
27     defaultTab: string; // eg.cat.default_record_tab
28
29     @ViewChild('pendingChangesDialog', {static: false})
30         pendingChangesDialog: ConfirmDialogComponent;
31
32     constructor(
33         private router: Router,
34         private route: ActivatedRoute,
35         private pcrud: PcrudService,
36         private bib: BibRecordService,
37         private cat: CatalogService,
38         private staffCat: StaffCatalogService,
39         private store: StoreService
40     ) {}
41
42     ngOnInit() {
43         this.searchContext = this.staffCat.searchContext;
44
45         this.defaultTab =
46             this.store.getLocalItem('eg.cat.default_record_tab')
47             || 'item_table';
48
49         // Watch for URL record ID changes
50         // This includes the initial route.
51         // When applying the default configured tab, no navigation occurs
52         // to apply the tab name to the URL, it displays as the default.
53         // This is done so no intermediate redirect is required, which
54         // messes with browser back/forward navigation.
55         this.route.paramMap.subscribe((params: ParamMap) => {
56             this.recordTab = params.get('tab');
57             this.recordId = +params.get('id');
58             this.searchContext = this.staffCat.searchContext;
59
60             if (!this.recordTab) {
61                 this.recordTab = this.defaultTab || 'item_table';
62             }
63
64             this.loadRecord();
65         });
66     }
67
68     setDefaultTab() {
69         this.defaultTab = this.recordTab;
70         this.store.setLocalItem('eg.cat.default_record_tab', this.recordTab);
71     }
72
73     // Changing a tab in the UI means changing the route.
74     // Changing the route ultimately results in changing the tab.
75     beforeTabChange(evt: NgbTabChangeEvent) {
76
77         // prevent tab changing until after route navigation
78         evt.preventDefault();
79
80         // Protect against tab changes with dirty data.
81         this.canDeactivate().then(ok => {
82             if (ok) {
83                 this.recordTab = evt.nextId;
84                 this.routeToTab();
85             }
86         });
87     }
88
89     /*
90      * Handle 3 types of navigation which can cause loss of data.
91      * 1. Record detail tab navigation (see also beforeTabChange())
92      * 2. Intra-Angular route navigation away from the record detail page
93      * 3. Browser page unload/reload
94      *
95      * For the #1, and #2, display a eg confirmation dialog.
96      * For #3 use the stock browser onbeforeunload dialog.
97      *
98      * Note in this case a tab change is a route change, but it's one
99      * which does not cause RecordComponent to unload, so it has to be
100      * manually tracked in beforeTabChange().
101      */
102     @HostListener('window:beforeunload', ['$event'])
103     canDeactivate($event?: Event): Promise<boolean> {
104
105         if (this.marcEditor && this.marcEditor.changesPending()) {
106
107             // Each warning dialog clears the current "changes are pending"
108             // flag so the user is not presented with the dialog again
109             // unless new changes are made.
110             this.marcEditor.clearPendingChanges();
111
112             if ($event) { // window.onbeforeunload
113                 $event.preventDefault();
114                 $event.returnValue = true;
115
116             } else { // tab OR route change.
117                 return this.pendingChangesDialog.open().toPromise();
118             }
119
120         } else {
121             return Promise.resolve(true);
122         }
123     }
124
125     routeToTab() {
126         const url =
127             `/staff/catalog/record/${this.recordId}/${this.recordTab}`;
128
129         // Retain search parameters
130         this.router.navigate([url], {queryParamsHandling: 'merge'});
131     }
132
133     loadRecord(): void {
134
135         // Avoid re-fetching the same record summary during tab navigation.
136         if (this.staffCat.currentDetailRecordSummary &&
137             this.recordId === this.staffCat.currentDetailRecordSummary.id) {
138             this.summary = this.staffCat.currentDetailRecordSummary;
139             return;
140         }
141
142         this.summary = null;
143         this.bib.getBibSummary(
144             this.recordId,
145             this.searchContext.searchOrg.id(),
146             this.searchContext.searchOrg.ou_type().depth()).toPromise()
147         .then(summary => {
148             this.summary =
149                 this.staffCat.currentDetailRecordSummary = summary;
150             this.bib.fleshBibUsers([summary.record]);
151         });
152     }
153
154     currentSearchOrg(): IdlObject {
155         if (this.staffCat && this.staffCat.searchContext) {
156             return this.staffCat.searchContext.searchOrg;
157         }
158         return null;
159     }
160
161     handleMarcRecordSaved() {
162         this.staffCat.currentDetailRecordSummary = null;
163         this.loadRecord();
164     }
165 }
166
167