LPXXX initial keyboard handling
authorBill Erickson <berickxx@gmail.com>
Wed, 20 Nov 2019 18:28:00 +0000 (13:28 -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/rich-editor.component.html

index c6fdf63..bba29d2 100644 (file)
@@ -3,10 +3,10 @@
   <div contenteditable
     id='{{randId}}' 
     class="d-inline-block p-1 text-break {{moreClasses}}"
-    [attr.tabindex]="readOnly ? -1 : ''"
+    [attr.tabindex]="fieldText ? -1 : ''"
     (keydown)="inputKeyDown($event)"
-    (focus)="focusDiv($event)"
-    (blur)="propagateTouch(); valueChange()">
+    (focus)="focusBigText($event)"
+    (input)="bigTextValueChange()">
   </div>
 </ng-container>
 
     class="p-0 pl-1 text-break rounded-0 form-control {{moreClasses}}"
     [size]="inputSize()" 
     [maxlength]="maxLength || ''"
-    [disabled]="readOnly
-    [attr.tabindex]="readOnly ? -1 : ''"
+    [disabled]="fieldText
+    [attr.tabindex]="fieldText ? -1 : ''"
     (keydown)="inputKeyDown($event)"
     (focus)="$event.target.select()"
-    (blur)="propagateTouch()"
-    [(ngModel)]="content"
+    [ngModel]="getContent()"
+    (ngModelChange)="setContent($event)"
   />
 </ng-container>
 
index 4372a6f..af537d1 100644 (file)
@@ -1,5 +1,5 @@
 import {ElementRef, Component, Input, Output, OnInit, EventEmitter, 
-    AfterViewInit, Renderer2, forwardRef} from '@angular/core';
+    AfterViewInit, Renderer2} from '@angular/core';
 import {ControlValueAccessor, NG_VALUE_ACCESSOR} from '@angular/forms';
 import {MarcRecord} from './marcrecord';
 import {MarcEditContext} from './editor-context';
@@ -11,84 +11,120 @@ import {MarcEditContext} from './editor-context';
 @Component({
   selector: 'eg-marc-editable-content',
   templateUrl: './editable-content.component.html',
-  styleUrls: ['./editable-content.component.css'],
-  providers: [{
-    provide: NG_VALUE_ACCESSOR,
-    useExisting: forwardRef(() => EditableContentComponent),
-    multi: true
-  }]
+  styleUrls: ['./editable-content.component.css']
 })
 
-export class EditableContentComponent 
-  implements OnInit, AfterViewInit, ControlValueAccessor {
+export class EditableContentComponent implements OnInit, AfterViewInit {
 
     @Input() context: MarcEditContext;
-    @Input() readOnly: boolean;
-    @Input() content: string;
-    @Input() fieldId: number;
+    @Input() field: any; // MARC.Field
+    @Input() fieldType: 'ldr' | 'tag' | 'cfld' | 'ind' | 'sfc' | 'sfv' = null;
 
-    @Input() dataType: 'tag' | 'indicator' | 'subfield' | 'text';
+    // used when the fieldType is ambiguous. E.g. 'ind1'
+    @Input() fieldName: string = null;
+
+    // read-only field text.  E.g. 'LDR'
+    @Input() fieldText: string = null;
+
+    // array of subfield code and subfield value
+    @Input() subfield: any; // MARC.Subfield
 
     // space-separated list of additional CSS classes to append
     @Input() moreClasses: string;
 
     get record(): MarcRecord { return this.context.record; }
 
-    randId: number;
-    editInput: any; // used for bigText
-    maxLength: number;
-    bigText: boolean;
-
-    // Stub functions required by ControlValueAccessor
-    propagateChange = (_: any) => {};
-    propagateTouch = () => {};
+    bigText = false;
+    randId = Math.floor(Math.random() * 100000);
+    editInput: any; // <input/> or <div contenteditable/>
+    maxLength: number = null;
 
     constructor(private renderer: Renderer2) {
         this.randId = Math.floor(Math.random() * 100000);
     }
 
     ngOnInit() {
-        this.inspectDataType();
+        this.setupDataType();
     }
 
-    inspectDataType() {
-        switch (this.dataType) {
+    setupDataType() {
+        const content = this.getContent();
+
+        switch (this.fieldType) {
+            case 'ldr':
+                if (content) { this.maxLength = content.length; }
+                break;
 
             case 'tag':
                 this.maxLength = 3;
 
                 // Arrow navigation focuses tags
-                this.context.fieldFocusRequest.subscribe(fieldId => {
-                    if (fieldId === this.fieldId && this.editInput) {
-                        this.editInput.focus();
-                    }
-                });
+                if (this.field) { // LDR tag does not have a field
+                    this.context.fieldFocusRequest.subscribe(fieldId => {
+                        if (fieldId === this.field.fieldId && this.editInput) {
+                            this.editInput.select();
+                        }
+                    });
+                }
                 break;
 
-            case 'indicator':
-            case 'subfield':
+            case 'ind':
+            case 'sfc':
                 this.maxLength = 1;
                 break;
 
-            // use sfv, etc.
-            case 'text':
-                this.maxLength = null;
+            case 'sfv':
                 this.bigText = true;
                 break;
         }
     }
 
+    getContent(): string {
+        if (this.fieldText) { return this.fieldText; } // read-only
+
+        switch (this.fieldType) {
+            case 'ldr': return this.record.leader; 
+            case 'cfld': return this.field.data;
+            case 'tag': return this.field.tag;
+            case 'ind': return this.field[this.fieldName];
+            case 'sfc': return this.subfield[0];
+            case 'sfv': return this.subfield[1];
+        }
+    }
+
+    setContent(value: string) {
+
+        if (this.fieldText) { return; } // read-only text
+
+        switch (this.fieldType) {
+            case 'ldr': this.record.leader = value; break;
+            case 'cfld': this.field.data = value; break;
+            case 'tag': this.field.tag = value; break;
+            case 'ind': this.field[this.fieldName] = value; break;
+            case 'sfc': this.subfield[0] = value; break;
+            case 'sfv': this.subfield[1] = value; break;
+        }
+    }
+
+    // Propagate editable div content into our record
+    bigTextValueChange() {
+        this.setContent(this.editInput.innerText);
+    }
+
     ngAfterViewInit() {
         this.editInput = // numeric id requires [id=...] query selector
             this.renderer.selectRootElement(`[id='${this.randId}']`);
+
+        // Initialize the editable div
+        this.editInput.innerText = this.getContent();
     }
 
     inputSize(): number {
         // give at least 2 chars space and grow with the content
-        return Math.max(2, (this.content || '').length) * 1.1;
+        return Math.max(2, (this.getContent() || '').length) * 1.1;
     }
 
-    focusDiv($event) {
+    focusBigText($event) {
         const targetNode = $event.srcElement.firstChild;
 
         const range = document.createRange();
@@ -100,45 +136,35 @@ export class EditableContentComponent
         selection.addRange(range);
     }
 
-    valueChange() {
-        if (this.bigText && this.editInput) {
-            this.content = this.editInput.innerText;
-        }
-        this.propagateChange(this.content);
-    }
-
-    writeValue(content: string) {
-        if (content === null || content === undefined) {
-            content = '';
-        }
-        this.content = content;
-        if (this.bigText && this.editInput) {
-            this.editInput.innerText = content;
-        }
-    }
-
-    registerOnChange(fn) {
-        this.propagateChange = fn;
-    }
-
-    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);
+                    const field = this.record.getNextField(this.field.fieldId);
                     if (field) {
                         this.context.requestFieldFocus(field.fieldId);
                     }
                 }
+                evt.preventDefault();
+                break;
+
+            case 'ArrowUp':
+                if (evt.ctrlKey) {
+                    // Copy the field up
+                } else {
+                    // Navigate upone field
+                    const field = this.record.getPreviousField(this.field.fieldId);
+                    if (field) {
+                        this.context.requestFieldFocus(field.fieldId);
+                    }
+                }
+                evt.preventDefault();
+                break;
+
         }
     }
 }
index c44630e..df70af0 100644 (file)
 
     <!-- LEADER -->
     <div class="row pt-0 pb-0 pl-3 form-horizontal">
-      <eg-marc-editable-content 
-        [context]="context"
-        dataType="tag"
-        i18n-ngModel [ngModel]="'LDR'" 
-        [readOnly]="true">
+      <eg-marc-editable-content [context]="context" fieldType="tag" 
+        fieldText="LDR" i18n-fieldText>
       </eg-marc-editable-content>
 
-      <eg-marc-editable-content
-        [context]="context"
-        dataType="leader"
-        [(ngModel)]="record.leader" 
-        [maxLength]="record.leader.length">
+      <eg-marc-editable-content [context]="context" fieldType="ldr">
       </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
-        dataType="tag"
-        [context]="context"
-        [(ngModel)]="field.tag" 
-        [fieldId]="field.fieldId"
-        [maxLength]="3">
+      <eg-marc-editable-content [context]="context" fieldType="tag" [field]="field">
       </eg-marc-editable-content>
 
-      <eg-marc-editable-content
-        [context]="context" 
-        dataType="tag"
-        [fieldId]="field.fieldId"
-        [(ngModel)]="field.data">
+      <eg-marc-editable-content [context]="context" fieldType="cfld" [field]="field">
       </eg-marc-editable-content>
     </div>
 
       *ngFor="let field of dataFields()">
 
       <!-- TAG -->
-      <eg-marc-editable-content   
-        dataType="tag"
-        [context]="context" 
-        [(ngModel)]="field.tag" 
-        [fieldId]="field.fieldId" 
-        [maxLength]="3">
+      <eg-marc-editable-content [context]="context" fieldType="tag" [field]="field">
       </eg-marc-editable-content>
 
       <!-- INDICATOR 1 -->
-      <eg-marc-editable-content 
-        [context]="context"
-        [(ngModel)]="field.ind1" 
-        [fieldId]="field.fieldId"
-        [maxLength]="1">
+      <eg-marc-editable-content [context]="context" fieldType="ind" 
+        [field]="field" fieldName="ind1">
       </eg-marc-editable-content>
 
       <!-- INDICATOR 2 -->
-      <eg-marc-editable-content 
-        [context]="context" 
-        [(ngModel)]="field.ind2" 
-        [fieldId]="field.fieldId" 
-        [maxLength]="1">
+      <eg-marc-editable-content [context]="context" fieldType="ind"
+        [field]="field" fieldName="ind2">
       </eg-marc-editable-content>
 
       <!-- SUBFIELDS -->
       <ng-container *ngFor="let subfield of field.subfields">
 
         <!-- SUBFIELD DECORATOR/DELIMITER -->
-        <eg-marc-editable-content 
-          moreClasses="text-primary border-right-0 bg-transparent" 
-          [readOnly]="true" 
-          i18n-ngModel [ngModel]="'‡'">
+        <eg-marc-editable-content fieldText="‡" i18n-fieldText
+          moreClasses="text-primary border-right-0 bg-transparent">
         </eg-marc-editable-content>
 
         <!-- SUBFIELD CHARACTER -->
-        <eg-marc-editable-content 
-          moreClasses="text-info border-left-0"
-          [(ngModel)]="subfield[0]"
-          [context]="context"
-          [fieldId]="field.fieldId" 
-          [maxLength]="1">
+        <eg-marc-editable-content [context]="context" fieldType="sfc" 
+          [field]="field" [subfield]="subfield" 
+          moreClasses="text-info border-left-0">
         </eg-marc-editable-content>
 
         <!-- SUBFIELD VALUE -->
-        <eg-marc-editable-content
-          [context]="context" 
-          [bigText]="true"
-          [fieldId]="field.fieldId" 
-          [(ngModel)]="subfield[1]">
+        <eg-marc-editable-content [context]="context" fieldType="sfv"
+          [field]="field" [subfield]="subfield">
         </eg-marc-editable-content>
       </ng-container>
     </div>