LPXXX initial keyboard handling
authorBill Erickson <berickxx@gmail.com>
Wed, 20 Nov 2019 16:37:30 +0000 (11:37 -0500)
committerBill Erickson <berickxx@gmail.com>
Fri, 6 Dec 2019 15:37:03 +0000 (10:37 -0500)
Signed-off-by: Bill Erickson <berickxx@gmail.com>
Open-ILS/src/eg2/src/app/staff/share/marc-edit/editable-content.component.html
Open-ILS/src/eg2/src/app/staff/share/marc-edit/editable-content.component.ts
Open-ILS/src/eg2/src/app/staff/share/marc-edit/editor-context.ts
Open-ILS/src/eg2/src/app/staff/share/marc-edit/marcrecord.ts
Open-ILS/src/eg2/src/app/staff/share/marc-edit/rich-editor.component.html
Open-ILS/src/eg2/src/app/staff/share/marc-edit/rich-editor.component.ts

index b667c7a..c6fdf63 100644 (file)
@@ -4,6 +4,7 @@
     id='{{randId}}' 
     class="d-inline-block p-1 text-break {{moreClasses}}"
     [attr.tabindex]="readOnly ? -1 : ''"
+    (keydown)="inputKeyDown($event)"
     (focus)="focusDiv($event)"
     (blur)="propagateTouch(); valueChange()">
   </div>
@@ -17,6 +18,7 @@
     [maxlength]="maxLength || ''"
     [disabled]="readOnly" 
     [attr.tabindex]="readOnly ? -1 : ''"
+    (keydown)="inputKeyDown($event)"
     (focus)="$event.target.select()"
     (blur)="propagateTouch()"
     [(ngModel)]="content"
index 465832c..4372a6f 100644 (file)
@@ -19,13 +19,15 @@ import {MarcEditContext} from './editor-context';
   }]
 })
 
-export class EditableContentComponent implements OnInit, AfterViewInit, ControlValueAccessor {
+export class EditableContentComponent 
+  implements OnInit, AfterViewInit, ControlValueAccessor {
 
     @Input() context: MarcEditContext;
     @Input() readOnly: boolean;
     @Input() content: string;
-    @Input() maxLength: number;
-    @Input() bigText: boolean;
+    @Input() fieldId: number;
+
+    @Input() dataType: 'tag' | 'indicator' | 'subfield' | 'text';
 
     // space-separated list of additional CSS classes to append
     @Input() moreClasses: string;
@@ -33,7 +35,9 @@ export class EditableContentComponent implements OnInit, AfterViewInit, ControlV
     get record(): MarcRecord { return this.context.record; }
 
     randId: number;
-    editDiv: any; // used for bigText
+    editInput: any; // used for bigText
+    maxLength: number;
+    bigText: boolean;
 
     // Stub functions required by ControlValueAccessor
     propagateChange = (_: any) => {};
@@ -43,15 +47,42 @@ export class EditableContentComponent implements OnInit, AfterViewInit, ControlV
         this.randId = Math.floor(Math.random() * 100000);
     }
 
-    ngOnInit() {}
+    ngOnInit() {
+        this.inspectDataType();
+    }
 
-    ngAfterViewInit() {
-        if (this.bigText) {
-            this.editDiv = // numeric id requires [id=...] query selector
-                this.renderer.selectRootElement(`[id='${this.randId}']`);
+    inspectDataType() {
+        switch (this.dataType) {
+
+            case 'tag':
+                this.maxLength = 3;
+
+                // Arrow navigation focuses tags
+                this.context.fieldFocusRequest.subscribe(fieldId => {
+                    if (fieldId === this.fieldId && this.editInput) {
+                        this.editInput.focus();
+                    }
+                });
+                break;
+
+            case 'indicator':
+            case 'subfield':
+                this.maxLength = 1;
+                break;
+
+            // use sfv, etc.
+            case 'text':
+                this.maxLength = null;
+                this.bigText = true;
+                break;
         }
     }
 
+    ngAfterViewInit() {
+        this.editInput = // numeric id requires [id=...] query selector
+            this.renderer.selectRootElement(`[id='${this.randId}']`);
+    }
+
     inputSize(): number {
         // give at least 2 chars space and grow with the content
         return Math.max(2, (this.content || '').length) * 1.1;
@@ -70,8 +101,8 @@ export class EditableContentComponent implements OnInit, AfterViewInit, ControlV
     }
 
     valueChange() {
-        if (this.editDiv) {
-            this.content = this.editDiv.innerText;
+        if (this.bigText && this.editInput) {
+            this.content = this.editInput.innerText;
         }
         this.propagateChange(this.content);
     }
@@ -81,8 +112,8 @@ export class EditableContentComponent implements OnInit, AfterViewInit, ControlV
             content = '';
         }
         this.content = content;
-        if (this.editDiv) {
-            this.editDiv.innerText = content;
+        if (this.bigText && this.editInput) {
+            this.editInput.innerText = content;
         }
     }
 
@@ -93,5 +124,22 @@ export class EditableContentComponent implements OnInit, AfterViewInit, ControlV
     registerOnTouched(fn) {
         this.propagateTouch = fn;
     }
+
+    inputKeyDown(evt: KeyboardEvent) {
+        console.log(evt.key);
+
+        switch (evt.key) {                                                     
+            case 'ArrowDown':  
+                if (evt.ctrlKey) {
+                    // Copy the field down
+                } else {
+                    // Navigate down one field
+                    const field = this.record.getNextField(this.fieldId);
+                    if (field) {
+                        this.context.requestFieldFocus(field.fieldId);
+                    }
+                }
+        }
+    }
 }
 
index 4c30256..31705a9 100644 (file)
@@ -2,9 +2,12 @@ import {EventEmitter} from '@angular/core';
 import {MarcRecord} from './marcrecord';
 import {NgbPopover} from '@ng-bootstrap/ng-bootstrap';
 
+/* Per-instance MARC editor context. */
+
 export class MarcEditContext {
 
     recordChange: EventEmitter<MarcRecord>;
+    fieldFocusRequest: EventEmitter<number>;
 
     popOvers: NgbPopover[] = [];
 
@@ -12,6 +15,7 @@ export class MarcEditContext {
     set record(r: MarcRecord) {
         if (r !== this._record) {
             this._record = r;
+            this._record.stampFieldIds();
             this.recordChange.emit(r);
         }
     }
@@ -22,6 +26,7 @@ export class MarcEditContext {
 
     constructor() {
         this.recordChange = new EventEmitter<MarcRecord>();
+        this.fieldFocusRequest = new EventEmitter<number>();
     }
 
     // NgbPopovers don't always close when we want them to,
@@ -29,5 +34,9 @@ export class MarcEditContext {
     closePopovers() {
         this.popOvers.forEach(p => p.close());
     }
+
+    requestFieldFocus(id: number) {
+        this.fieldFocusRequest.emit(id);
+    }
 }
 
index 58179b3..58ec9f1 100644 (file)
@@ -19,7 +19,7 @@ export class MarcRecord {
     fixedFieldChange: EventEmitter<string>;
 
     get leader(): string {
-      return this.record.leader;
+        return this.record.leader;
     }
 
     set leader(l: string) {
@@ -27,7 +27,7 @@ export class MarcRecord {
     }
 
     get fields(): any[] {
-      return this.record.fields;
+       return this.record.fields;
     }
 
     set fields(f: any[]) {
@@ -66,5 +66,31 @@ export class MarcRecord {
         this.fixedFieldChange.emit(fieldCode);
         return response;
     }
+
+    // Give each field an identifier so it may be referenced later.
+    stampFieldIds() {
+        this.fields.forEach(f => 
+            f.fieldId = Math.floor(Math.random() * 100000));
+    }
+
+    getField(id: number) {
+        return this.fields.filter(f => f.fieldId === id)[0];
+    }
+
+    getPreviousField(id: number) {
+        for (let idx = 0; idx < this.fields.length; idx++) {
+            if (this.fields[idx].fieldId === id) {
+                return this.fields[idx - 1];
+            }
+        }
+    }
+
+    getNextField(id: number) {
+        for (let idx = 0; idx < this.fields.length; idx++) {
+            if (this.fields[idx].fieldId === id) {
+                return this.fields[idx + 1];
+            }
+        }
+    }
 }
 
index 6be1151..c44630e 100644 (file)
         <eg-fixed-fields-editor [context]="context"></eg-fixed-fields-editor>
       </div>
     </div>
+
+    <!-- LEADER -->
     <div class="row pt-0 pb-0 pl-3 form-horizontal">
-      <eg-marc-editable-content [context]="context" i18n-ngModel [ngModel]="'LDR'" 
-        [maxLength]="3" [readOnly]="true"></eg-marc-editable-content>
-      <eg-marc-editable-content [context]="context" [(ngModel)]="record.leader" 
-        [maxLength]="record.leader.length"></eg-marc-editable-content>
+      <eg-marc-editable-content 
+        [context]="context"
+        dataType="tag"
+        i18n-ngModel [ngModel]="'LDR'" 
+        [readOnly]="true">
+      </eg-marc-editable-content>
+
+      <eg-marc-editable-content
+        [context]="context"
+        dataType="leader"
+        [(ngModel)]="record.leader" 
+        [maxLength]="record.leader.length">
+      </eg-marc-editable-content>
     </div>
+
+    <!-- CONTROL FIELDS -->
     <div class="row pt-0 pb-0 pl-3 form-horizontal" 
       *ngFor="let field of controlFields()">
-      <eg-marc-editable-content [context]="context" [(ngModel)]="field.tag" 
-        [maxLength]="3"></eg-marc-editable-content>
-      <eg-marc-editable-content [context]="context" [(ngModel)]="field.data">
+      <eg-marc-editable-content
+        dataType="tag"
+        [context]="context"
+        [(ngModel)]="field.tag" 
+        [fieldId]="field.fieldId"
+        [maxLength]="3">
+      </eg-marc-editable-content>
+
+      <eg-marc-editable-content
+        [context]="context" 
+        dataType="tag"
+        [fieldId]="field.fieldId"
+        [(ngModel)]="field.data">
       </eg-marc-editable-content>
     </div>
+
+    <!-- data fields -->
     <div class="row pt-0 pb-0 pl-3 form-horizontal" 
       *ngFor="let field of dataFields()">
-      <eg-marc-editable-content [context]="context" [(ngModel)]="field.tag" 
-        [maxLength]="3"></eg-marc-editable-content>
-      <eg-marc-editable-content [context]="context" [(ngModel)]="field.ind1" 
-        [maxLength]="1"></eg-marc-editable-content>
-      <eg-marc-editable-content [context]="context" [(ngModel)]="field.ind2" 
-        [maxLength]="1"></eg-marc-editable-content>
 
+      <!-- TAG -->
+      <eg-marc-editable-content   
+        dataType="tag"
+        [context]="context" 
+        [(ngModel)]="field.tag" 
+        [fieldId]="field.fieldId" 
+        [maxLength]="3">
+      </eg-marc-editable-content>
+
+      <!-- INDICATOR 1 -->
+      <eg-marc-editable-content 
+        [context]="context"
+        [(ngModel)]="field.ind1" 
+        [fieldId]="field.fieldId"
+        [maxLength]="1">
+      </eg-marc-editable-content>
+
+      <!-- INDICATOR 2 -->
+      <eg-marc-editable-content 
+        [context]="context" 
+        [(ngModel)]="field.ind2" 
+        [fieldId]="field.fieldId" 
+        [maxLength]="1">
+      </eg-marc-editable-content>
+
+      <!-- SUBFIELDS -->
       <ng-container *ngFor="let subfield of field.subfields">
 
-        <!-- subfield decorator -->
-        <eg-marc-editable-content [readOnly]="true" 
+        <!-- SUBFIELD DECORATOR/DELIMITER -->
+        <eg-marc-editable-content 
           moreClasses="text-primary border-right-0 bg-transparent" 
-          i18n-ngModel [ngModel]="'‡'"></eg-marc-editable-content>
+          [readOnly]="true" 
+          i18n-ngModel [ngModel]="'‡'">
+        </eg-marc-editable-content>
 
-        <!-- subfield character -->
-        <eg-marc-editable-content [context]="context" [(ngModel)]="subfield[0]"
+        <!-- SUBFIELD CHARACTER -->
+        <eg-marc-editable-content 
           moreClasses="text-info border-left-0"
-          [maxLength]="1"></eg-marc-editable-content>
+          [(ngModel)]="subfield[0]"
+          [context]="context"
+          [fieldId]="field.fieldId" 
+          [maxLength]="1">
+        </eg-marc-editable-content>
 
-        <!-- subfield value -->
-        <eg-marc-editable-content [context]="context" 
-          [bigText]="true" [(ngModel)]="subfield[1]">
+        <!-- SUBFIELD VALUE -->
+        <eg-marc-editable-content
+          [context]="context" 
+          [bigText]="true"
+          [fieldId]="field.fieldId" 
+          [(ngModel)]="subfield[1]">
         </eg-marc-editable-content>
       </ng-container>
     </div>
index 4d4f079..7532e6f 100644 (file)
@@ -53,7 +53,6 @@ export class MarcRichEditorComponent implements OnInit {
     dataFields(): any[] {
         return this.record.fields.filter(f => !f.isControlfield());
     }
-
 }