fc7ba54ea1b4cb840a63d22a7ebe8701661e5d50
[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 {AuthService} from '@eg/core/auth.service';
7 import {CatalogSearchContext, CatalogSearchState} from '@eg/share/catalog/search-context';
8 import {CatalogService} from '@eg/share/catalog/catalog.service';
9 import {BibRecordService, BibRecordSummary} from '@eg/share/catalog/bib-record.service';
10 import {StaffCatalogService} from '../catalog.service';
11 import {BibSummaryComponent} from '@eg/staff/share/bib-summary/bib-summary.component';
12 import {StoreService} from '@eg/core/store.service';
13 import {ConfirmDialogComponent} from '@eg/share/dialog/confirm.component';
14 import {MarcEditorComponent} from '@eg/staff/share/marc-edit/editor.component';
15 import {HoldingsMaintenanceComponent} from './holdings.component';
16 import {HoldingsService} from '@eg/staff/share/holdings/holdings.service';
17
18 @Component({
19   selector: 'eg-catalog-record',
20   templateUrl: 'record.component.html'
21 })
22 export class RecordComponent implements OnInit {
23
24     recordId: number;
25     recordTab: string;
26     summary: BibRecordSummary;
27     searchContext: CatalogSearchContext;
28     @ViewChild('recordTabs', { static: true }) recordTabs: NgbTabset;
29     @ViewChild('marcEditor', {static: false}) marcEditor: MarcEditorComponent;
30
31     @ViewChild('holdingsMaint', {static: false})
32         holdingsMaint: HoldingsMaintenanceComponent;
33
34     defaultTab: string; // eg.cat.default_record_tab
35
36     @ViewChild('pendingChangesDialog', {static: false})
37         pendingChangesDialog: ConfirmDialogComponent;
38
39     constructor(
40         private router: Router,
41         private route: ActivatedRoute,
42         private pcrud: PcrudService,
43         private auth: AuthService,
44         private bib: BibRecordService,
45         private cat: CatalogService,
46         private staffCat: StaffCatalogService,
47         private holdings: HoldingsService,
48         private store: StoreService
49     ) {}
50
51     ngOnInit() {
52         this.searchContext = this.staffCat.searchContext;
53
54         this.defaultTab =
55             this.store.getLocalItem('eg.cat.default_record_tab')
56             || 'item_table';
57
58         // Watch for URL record ID changes
59         // This includes the initial route.
60         // When applying the default configured tab, no navigation occurs
61         // to apply the tab name to the URL, it displays as the default.
62         // This is done so no intermediate redirect is required, which
63         // messes with browser back/forward navigation.
64         this.route.paramMap.subscribe((params: ParamMap) => {
65             this.recordTab = params.get('tab');
66             this.recordId = +params.get('id');
67             this.searchContext = this.staffCat.searchContext;
68
69             if (!this.recordTab) {
70                 this.recordTab = this.defaultTab || 'item_table';
71             }
72
73             this.loadRecord();
74         });
75     }
76
77     setDefaultTab() {
78         this.defaultTab = this.recordTab;
79         this.store.setLocalItem('eg.cat.default_record_tab', this.recordTab);
80     }
81
82     // Changing a tab in the UI means changing the route.
83     // Changing the route ultimately results in changing the tab.
84     beforeTabChange(evt: NgbTabChangeEvent) {
85
86         // prevent tab changing until after route navigation
87         evt.preventDefault();
88
89         // Protect against tab changes with dirty data.
90         this.canDeactivate().then(ok => {
91             if (ok) {
92                 this.recordTab = evt.nextId;
93                 this.routeToTab();
94             }
95         });
96     }
97
98     /*
99      * Handle 3 types of navigation which can cause loss of data.
100      * 1. Record detail tab navigation (see also beforeTabChange())
101      * 2. Intra-Angular route navigation away from the record detail page
102      * 3. Browser page unload/reload
103      *
104      * For the #1, and #2, display a eg confirmation dialog.
105      * For #3 use the stock browser onbeforeunload dialog.
106      *
107      * Note in this case a tab change is a route change, but it's one
108      * which does not cause RecordComponent to unload, so it has to be
109      * manually tracked in beforeTabChange().
110      */
111     @HostListener('window:beforeunload', ['$event'])
112     canDeactivate($event?: Event): Promise<boolean> {
113
114         if (this.marcEditor && this.marcEditor.changesPending()) {
115
116             // Each warning dialog clears the current "changes are pending"
117             // flag so the user is not presented with the dialog again
118             // unless new changes are made.
119             this.marcEditor.clearPendingChanges();
120
121             if ($event) { // window.onbeforeunload
122                 $event.preventDefault();
123                 $event.returnValue = true;
124
125             } else { // tab OR route change.
126                 return this.pendingChangesDialog.open().toPromise();
127             }
128
129         } else {
130             return Promise.resolve(true);
131         }
132     }
133
134     routeToTab() {
135         const url =
136             `/staff/catalog/record/${this.recordId}/${this.recordTab}`;
137
138         // Retain search parameters
139         this.router.navigate([url], {queryParamsHandling: 'merge'});
140     }
141
142     loadRecord(): void {
143
144         // Avoid re-fetching the same record summary during tab navigation.
145         if (this.staffCat.currentDetailRecordSummary &&
146             this.recordId === this.staffCat.currentDetailRecordSummary.id) {
147             this.summary = this.staffCat.currentDetailRecordSummary;
148             return;
149         }
150
151         this.summary = null;
152         this.bib.getBibSummary(
153             this.recordId,
154             this.searchContext.searchOrg.id(),
155             this.searchContext.searchOrg.ou_type().depth()).toPromise()
156         .then(summary => {
157             this.summary =
158                 this.staffCat.currentDetailRecordSummary = summary;
159         });
160     }
161
162     // Lets us intercept the summary object and augment it with
163     // search highlight data if/when it becomes available from
164     // an externally executed search.
165     summaryForDisplay(): BibRecordSummary {
166         if (!this.summary) { return null; }
167         const sum = this.summary;
168         const ctx = this.searchContext;
169
170         if (Object.keys(sum.displayHighlights).length === 0) {
171             if (ctx.highlightData[sum.id]) {
172                 sum.displayHighlights = ctx.highlightData[sum.id];
173             }
174         }
175
176         return this.summary;
177     }
178
179     currentSearchOrg(): IdlObject {
180         if (this.staffCat && this.staffCat.searchContext) {
181             return this.staffCat.searchContext.searchOrg;
182         }
183         return null;
184     }
185
186     handleMarcRecordSaved() {
187         this.staffCat.currentDetailRecordSummary = null;
188         this.loadRecord();
189     }
190
191     // Our actions component broadcast a request to add holdings.
192     // If our Holdings Maintenance component is active/visible, ask
193     // it to figure out what data to pass to the holdings editor.
194     // Otherwise, just tell it to create a new call number and
195     // copy at the current working location.
196     addHoldingsRequested() {
197         if (this.holdingsMaint && this.holdingsMaint.holdingsGrid) {
198             this.holdingsMaint.openHoldingAdd(
199                 this.holdingsMaint.holdingsGrid.context.getSelectedRows(),
200                 true, true
201             );
202
203         } else {
204
205             this.holdings.spawnAddHoldingsUi(
206                 this.recordId, null, [{owner: this.auth.user().ws_ou()}]);
207         }
208     }
209 }
210
211