LP1852782 MARC editor Physical Characteristics Wizard
[Evergreen.git] / Open-ILS / src / eg2 / src / app / staff / share / marc-edit / phys-char-dialog.component.ts
1 import {Component, ViewChild, Input, Output, OnInit, EventEmitter} from '@angular/core';
2 import {IdlService, IdlObject} from '@eg/core/idl.service';
3 import {PcrudService} from '@eg/core/pcrud.service';
4 import {DialogComponent} from '@eg/share/dialog/dialog.component';
5 import {NgbModal} from '@ng-bootstrap/ng-bootstrap';
6 import {MarcEditorDialogComponent} from './editor-dialog.component';
7 import {ComboboxComponent, ComboboxEntry} from '@eg/share/combobox/combobox.component';
8
9 /**
10  * 007 Physical Characteristics Dialog
11  *
12  * Note the dialog does not many direct changes to the bib field.
13  * It simply emits the new value on close, or null of the
14  * dialog canceled.
15  */
16
17 @Component({
18   selector: 'eg-phys-char-dialog',
19   templateUrl: './phys-char-dialog.component.html'
20 })
21
22 export class PhysCharDialogComponent
23     extends DialogComponent implements OnInit {
24
25     // The 007 data
26     @Input() fieldData = '';
27
28     initialValue: string;
29
30     selectorLabel: string = null;
31     selectorValue: string;
32     selectorOptions: ComboboxEntry[] = [];
33
34     typeMap: ComboboxEntry[] = [];
35
36     sfMap: {[ptypeKey: string]: any[]} = {};
37     valueMap: {[ptypeKey: string]: ComboboxEntry[]} = {};
38
39     currentPtype: string;
40
41     // step is the 1-based position in the list of data slots for the
42     // currently selected type. step==0 means we are currently selecting
43     // the type.
44     step = 0;
45
46     // size and offset of the slot we're currently editing; this is
47     // maintained as a convenience for the highlighting of the currently
48     // active position
49     slotOffset = 0;
50     slotSize = 1;
51
52     constructor(
53         private modal: NgbModal,
54         private idl: IdlService,
55         private pcrud: PcrudService) {
56         super(modal);
57     }
58
59     ngOnInit() {
60         this.onOpen$.subscribe(_ => {
61             this.initialValue = this.fieldData;
62             this.reset();
63         });
64     }
65
66     // Chop the field data value into 3 parts, before, middle, and
67     // after, where 'middle' is the part we're currently editing.
68     splitFieldData(): string[] {
69         const data = this.fieldData;
70         return [
71             data.substring(0, this.slotOffset),
72             data.substring(this.slotOffset, this.slotOffset + this.slotSize),
73             data.substring(this.slotOffset + this.slotSize)
74         ];
75     }
76
77     setValuesForStep(): Promise<any> {
78         let promise;
79
80         if (this.step === 0) {
81             promise = this.getPhysCharTypeMap();
82         } else {
83             promise = this.currentSubfield().then(
84                 subfield => this.getPhysCharValueMap(subfield.id()));
85         }
86
87         return promise.then(list => {
88             this.selectorOptions = list;
89             this.setSelectedOptionFromField();
90             this.setLabelForStep();
91         });
92     }
93
94     setLabelForStep() {
95         if (this.step === 0) {
96             this.selectorLabel = null;  // fall back to template value
97         } else {
98             this.currentSubfield().then(sf => this.selectorLabel = sf.label());
99         }
100     }
101
102     getStepSlot(): Promise<any[]> {
103         if (this.step === 0) { return Promise.resolve([0, 1]); }
104         return this.currentSubfield()
105             .then(sf => [sf.start_pos(), sf.length()]);
106     }
107
108     setSelectedOptionFromField() {
109         this.getStepSlot().then(slot => {
110             this.slotOffset = slot[0];
111             this.slotSize = slot[1];
112             this.selectorValue =
113                 String.prototype.substr.apply(this.fieldData, slot) || ' ';
114         });
115     }
116
117     isLastStep(): boolean {
118         // This one is called w/ every digest, so avoid async
119         // calls.  Wait until we have loaded the current ptype
120         // subfields to determine if this is the last step.
121         return (
122             this.currentPtype &&
123             this.sfMap[this.currentPtype] &&
124             this.sfMap[this.currentPtype].length === this.step
125         );
126     }
127
128     selectorChanged() {
129
130         if (this.step === 0) {
131             this.currentPtype = this.selectorValue;
132             this.fieldData = this.selectorValue; // total reset
133
134         } else {
135             this.getStepSlot().then(slot => {
136
137                 let value = this.fieldData;
138                 const offset = slot[0];
139                 const size = slot[1];
140                 while (value.length < (offset + size)) { value += ' '; }
141
142                 // Apply the value to the field in the required slot,
143                 // then delete all data after "here", since those values
144                 // no longer make sense.
145                 const before = value.substr(0, offset);
146                 this.fieldData = before + this.selectorValue.padEnd(size, ' ');
147                 this.slotOffset = offset;
148                 this.slotSize = size;
149             });
150         }
151     }
152
153     next() {
154         this.step++;
155         this.setValuesForStep();
156     }
157
158     prev() {
159         this.step--;
160         this.setValuesForStep();
161     }
162
163     currentSubfield(): Promise<any> {
164         return this.getPhysCharSubfieldMap(this.currentPtype)
165         .then(sfList => sfList[this.step - 1]);
166     }
167
168     reset(clear?: boolean) {
169         this.step = 0;
170         this.slotOffset = 0;
171         this.slotSize = 1;
172         this.fieldData = clear ? ' ' : this.initialValue;
173         this.currentPtype = this.fieldData.substr(0, 1);
174         this.setValuesForStep();
175     }
176
177     getPhysCharTypeMap(): Promise<ComboboxEntry[]> {
178         if (this.typeMap.length) {
179             return Promise.resolve(this.typeMap);
180         }
181
182         return this.pcrud.retrieveAll(
183             'cmpctm', {order_by: {cmpctm: 'label'}}, {atomic: true})
184         .toPromise().then(maps => {
185             return this.typeMap = maps.map(
186                 map => ({id: map.ptype_key(), label: map.label()}));
187         });
188     }
189
190     getPhysCharSubfieldMap(ptypeKey: string): Promise<IdlObject[]> {
191
192         if (this.sfMap[ptypeKey]) {
193             return Promise.resolve(this.sfMap[ptypeKey]);
194         }
195
196         return this.pcrud.search('cmpcsm',
197             {ptype_key : ptypeKey},
198             {order_by : {cmpcsm : ['start_pos']}},
199             {atomic : true}
200         ).toPromise().then(maps => this.sfMap[ptypeKey] = maps);
201    }
202
203     getPhysCharValueMap(ptypeSubfield: string): Promise<ComboboxEntry[]> {
204
205         if (this.valueMap[ptypeSubfield]) {
206             return Promise.resolve(this.valueMap[ptypeSubfield]);
207         }
208
209         return this.pcrud.search('cmpcvm',
210             {ptype_subfield : ptypeSubfield},
211             {order_by : {cmpcsm : ['value']}},
212             {atomic : true}
213         ).toPromise().then(maps =>
214             this.valueMap[ptypeSubfield] = maps.map(
215                 map => ({id: map.value(), label: map.label()}))
216         );
217    }
218 }
219
220