LP#1626157 fm editor custom templates; misc;
authorBill Erickson <berickxx@gmail.com>
Fri, 20 Apr 2018 19:05:32 +0000 (15:05 -0400)
committerBill Erickson <berickxx@gmail.com>
Fri, 20 Apr 2018 19:05:32 +0000 (15:05 -0400)
Signed-off-by: Bill Erickson <berickxx@gmail.com>
Open-ILS/src/eg2/src/app/share/accesskey/accesskey.directive.ts
Open-ILS/src/eg2/src/app/share/fm-editor/fm-editor.component.html
Open-ILS/src/eg2/src/app/share/fm-editor/fm-editor.component.ts
Open-ILS/src/eg2/src/app/staff/login.component.html
Open-ILS/src/eg2/src/app/staff/nav.component.css
Open-ILS/src/eg2/src/app/staff/nav.component.html
Open-ILS/src/eg2/src/app/staff/sandbox/sandbox.component.html
Open-ILS/src/eg2/src/app/staff/splash.component.html

index ddbe693..bb1fada 100644 (file)
@@ -19,8 +19,15 @@ import {EgAccessKeyService} from '@eg/share/accesskey/accesskey.service';
 })
 export class EgAccessKeyDirective implements OnInit {
 
+    // Space-separated list of key combinations
+    // E.g. "ctrl+h", "alt+h ctrl+y"
     @Input() keySpec: string;
+
+    // Description to display in the accesskey info dialog
     @Input() keyDesc: string;
+
+    // Context info to display in the accesskey info dialog
+    // E.g. "navbar"
     @Input() keyCtx: string;
     
        constructor(
@@ -28,20 +35,21 @@ export class EgAccessKeyDirective implements OnInit {
         private keyService: EgAccessKeyService
     ) { }
 
-
     ngOnInit() {
-        /*
-        console.debug(
-            `EgAccessKey assigning '${this.keySpec}' => ${this.keyDesc}`);
-        */
-
-        this.keyService.assign({
-            key: this.keySpec, 
-            desc: this.keyDesc,
-            ctx: this.keyCtx,
-            action: () => {this.elm.nativeElement.click()}
-        });
 
+        if (!this.keySpec) {
+            console.warn("EgAccessKey no keySpec provided");
+            return;
+        }
+
+        this.keySpec.split(/ /).forEach(keySpec => {
+            this.keyService.assign({
+                key: keySpec, 
+                desc: this.keyDesc,
+                ctx: this.keyCtx,
+                action: () => {this.elm.nativeElement.click()}
+            });
+        })
     }
 }
 
index f19f6bc..ab1bdfb 100644 (file)
           <label for="rec-{{field.name}}">{{field.label}}</label>
         </div>
         <div class="col-lg-7">
-          <!-- TODO custom field templates -->
-          <span *ngIf="field.datatype == 'id' && !pkeyIsEditable">
-            {{record[field.name]()}}
-          </span>
-
-          <input *ngIf="field.datatype == 'id' && pkeyIsEditable"
-            class="form-control"
-            [name]="field.name"
-            [readonly]="field.readOnly"
-            [required]="field.isRequired()"
-            [ngModel]="record[field.name]()"
-            (ngModelChange)="record[field.name]($event)"/>
 
-          <input *ngIf="field.datatype == 'text'"
-            class="form-control"
-            [name]="field.name"
-            [readonly]="field.readOnly"
-            [required]="field.isRequired()"
-            [ngModel]="record[field.name]()"
-            (ngModelChange)="record[field.name]($event)"/>
-
-          <input *ngIf="field.datatype == 'int'"
-            class="form-control"
-            type="number"
-            [name]="field.name"
-            [readonly]="field.readOnly"
-            [required]="field.isRequired()"
-            [ngModel]="record[field.name]()"
-            (ngModelChange)="record[field.name]($event)"/>
+          <span *ngIf="field.template">
+            <ng-container
+              *ngTemplateOutlet="field.template; context:customTemplateFieldContext(field)">
+            </ng-container> 
+          </span>
 
-          <input *ngIf="field.datatype == 'float'"
-            class="form-control"
-            type="number" step="0.1"
-            [name]="field.name"
-            [readonly]="field.readOnly"
-            [required]="field.isRequired()"
-            [ngModel]="record[field.name]()"
-            (ngModelChange)="record[field.name]($event)"/>
+          <span *ngIf="!field.template">
 
-          <span *ngIf="field.datatype == 'money'">
-            <!-- in read-only mode display the local-aware currency -->
-            <input *ngIf="field.readOnly"
+            <span *ngIf="field.datatype == 'id' && !pkeyIsEditable">
+              {{record[field.name]()}}
+            </span>
+  
+            <input *ngIf="field.datatype == 'id' && pkeyIsEditable"
               class="form-control"
-              type="number" step="0.1"
-              [name]="field.name"
+              name="{{field.name}}"
               [readonly]="field.readOnly"
               [required]="field.isRequired()"
-              [ngModel]="record[field.name]() | currency"/>
-
-            <input *ngIf="!field.readOnly"
+              [ngModel]="record[field.name]()"
+              (ngModelChange)="record[field.name]($event)"/>
+  
+            <input *ngIf="field.datatype == 'text'"
               class="form-control"
-              type="number" step="0.1"
-              [name]="field.name"
+              name="{{field.name}}"
               [readonly]="field.readOnly"
               [required]="field.isRequired()"
               [ngModel]="record[field.name]()"
               (ngModelChange)="record[field.name]($event)"/>
-          </span>
-
-          <input *ngIf="field.datatype == 'bool'"
-            class="form-check-input"
-            type="checkbox"
-            [name]="field.name"
-            [readonly]="field.readOnly"
-            [ngModel]="record[field.name]()"
-            (ngModelChange)="record[field.name]($event)"/>
-
-          <span *ngIf="field.datatype == 'link'"
-            [ngClass]="{nullable : !field.isRequired()}">
-            <select
+  
+            <input *ngIf="field.datatype == 'int'"
+              class="form-control"
+              type="number"
+              name="{{field.name}}"
+              [readonly]="field.readOnly"
+              [required]="field.isRequired()"
+              [ngModel]="record[field.name]()"
+              (ngModelChange)="record[field.name]($event)"/>
+  
+            <input *ngIf="field.datatype == 'float'"
               class="form-control"
-              [name]="field.name"
-              [disabled]="field.readOnly"
+              type="number" step="0.1"
+              name="{{field.name}}"
+              [readonly]="field.readOnly"
               [required]="field.isRequired()"
               [ngModel]="record[field.name]()"
-              (ngModelChange)="record[field.name]($event)">
-              <option *ngFor="let item of field.linkedValues" 
-                [value]="item.id">{{item.name}}</option>
-            </select>
-          </span>
-
-          <eg-org-select *ngIf="field.datatype == 'org_unit'"
-            [placeholder]="field.label"
-            [applyDefault]="field.orgDefaultAllowed"
-            [initialOrgId]="record[field.name]()"
-            (onChange)="record[field.name]($event)">
-          </eg-org-select>
+              (ngModelChange)="record[field.name]($event)"/>
+  
+            <span *ngIf="field.datatype == 'money'">
+              <!-- in read-only mode display the local-aware currency -->
+              <input *ngIf="field.readOnly"
+                class="form-control"
+                type="number" step="0.1"
+                name="{{field.name}}"
+                [readonly]="field.readOnly"
+                [required]="field.isRequired()"
+                [ngModel]="record[field.name]() | currency"/>
+  
+              <input *ngIf="!field.readOnly"
+                class="form-control"
+                type="number" step="0.1"
+                name="{{field.name}}"
+                [readonly]="field.readOnly"
+                [required]="field.isRequired()"
+                [ngModel]="record[field.name]()"
+                (ngModelChange)="record[field.name]($event)"/>
+            </span>
+  
+            <input *ngIf="field.datatype == 'bool'"
+              class="form-check-input"
+              type="checkbox"
+              name="{{field.name}}"
+              [readonly]="field.readOnly"
+              [ngModel]="record[field.name]()"
+              (ngModelChange)="record[field.name]($event)"/>
+  
+            <span *ngIf="field.datatype == 'link'"
+              [ngClass]="{nullable : !field.isRequired()}">
+              <select
+                class="form-control"
+                name="{{field.name}}"
+                [disabled]="field.readOnly"
+                [required]="field.isRequired()"
+                [ngModel]="record[field.name]()"
+                (ngModelChange)="record[field.name]($event)">
+                <option *ngFor="let item of field.linkedValues" 
+                  [value]="item.id">{{item.name}}</option>
+              </select>
+            </span>
+  
+            <eg-org-select *ngIf="field.datatype == 'org_unit'"
+              [placeholder]="field.label"
+              [applyDefault]="field.orgDefaultAllowed"
+              [initialOrgId]="record[field.name]()"
+              (onChange)="record[field.name]($event)">
+            </eg-org-select>
 
+          </span>
         </div>
       </div>
     </form>
index 9f13fb1..3ba94a3 100644 (file)
@@ -1,10 +1,31 @@
-import {Component, OnInit, Input, Output, ViewChild, EventEmitter} from '@angular/core';
+import {Component, OnInit, Input, 
+    Output, EventEmitter, TemplateRef} from '@angular/core';
 import {EgIdlService, EgIdlObject} from '@eg/core/idl.service';
 import {EgAuthService} from '@eg/core/auth.service';
 import {EgPcrudService} from '@eg/core/pcrud.service';
 import {EgDialogComponent} from '@eg/share/dialog/dialog.component';
 import {NgbModal, NgbModalOptions} from '@ng-bootstrap/ng-bootstrap';
 
+interface CustomFieldTemplate {
+    template: TemplateRef<any>,
+    
+    // Allow the caller to pass in a free-form context blob to 
+    // be addedto the caller's custom template context, along 
+    // with our stock context.
+    context?: [fields: string]: any
+}
+
+interface CustomFieldContext {
+    // Current create/edit/view record
+    record: EgIdlObject,
+
+    // IDL field definition blob
+    field: any, 
+
+    // additional context values passed via CustomFieldTemplate
+    [fields: string]: any;
+}
+
 @Component({
   selector: 'fm-record-editor',
   templateUrl: './fm-editor.component.html'
@@ -18,20 +39,21 @@ export class FmRecordEditorComponent
     // mode: 'create' for creating a new record,
     //       'update' for editing an existing record
     //       'view' for viewing an existing record without editing
-    @Input() mode: string;
+    @Input() mode: 'create' | 'update' | 'view' = 'create';
 
-    // Record ID to view/update.  Value is dynamic.
+    // Record ID to view/update.  Value is dynamic.  Records are not
+    // fetched until .open() is called.
     recId: any;
     @Input() set recordId(id: any) {
         if (id) this.recId = id;
     }
 
     // IDL record we are editing
-    // TODO: allow this to be provided by the caller?
+    // TODO: allow this to be update in real time by the caller?
     record: EgIdlObject;
 
-    // TODO
-    // customFieldTemplates
+    @Input() customFieldTemplates: 
+        {[fieldName:string] : CustomFieldTemplate} = {};
 
     // list of fields that should not be displayed
     @Input() hiddenFieldsList: string[] = [];
@@ -233,13 +255,27 @@ export class FmRecordEditorComponent
                     this.orgDefaultAllowedList.includes(field.name);
             }
 
-            // TODO custom field templates
+            if (this.customFieldTemplates[field.name]) {
+                field.template = this.customFieldTemplates[field.name].template;
+                field.context = this.customFieldTemplates[field.name].context;
+            }
+
         });
 
         // Wait for all network calls to complete
         return Promise.all(promises);
     }
 
+    // Returns a context object to be inserted into a custom 
+    // field template.
+    customTemplateFieldContext(fieldDef: any): FmEditorCustomFieldContext {
+        return Object.assign(
+            {   record : this.record,
+                field: fieldDef // from this.fields
+            },  fieldDef.context || {}
+        );
+    }
+
     save() {
         let recToSave = this.idl.clone(this.record);
         this.convertDatatypesToIdl(recToSave);
index dcf953f..ba474f8 100644 (file)
@@ -3,7 +3,7 @@
     <fieldset>
       <legend class="mb-0" i18n>Sign In</legend>
       <hr class="mt-1"/>
-      <form (ngSubmit)="handleSubmit()" #loginForm="ngForm">
+      <form (ngSubmit)="handleSubmit()" #loginForm="ngForm" class="form-validated">
 
         <div class="form-group row">
           <label class="col-lg-4 text-right font-weight-bold" for="username" i18n>Username</label>
index ee4f93e..63d3e37 100644 (file)
@@ -4,8 +4,8 @@
 }
 
 /* move the caret closer to the dropdown text */
-#staff-navbar .dropdown-toggle::after {
-    margin-left:0px;
+#staff-navbar {
+    padding-left: 0px;
 }
 
 #staff-navbar {
@@ -16,7 +16,7 @@
 }
 
 #staff-navbar .navbar-nav {
-  padding: 3px;
+  padding: 4px;
 }
 
 /* align top of dropdown w/ bottom of nav */
index 4b1dd4b..8ca7cce 100644 (file)
 
 
     <div class="navbar-nav mr-auto"></div>
-    <div class="navbar-nav">
+    <div class="navbar-nav" *ngIf="user">
       <span i18n>{{user}} @ {{workstation}}</span>
     </div>
-    <div class="navbar-nav">
+    <div class="navbar-nav" *ngIf="user">
       <div ngbDropdown class="nav-item dropdown" placement="bottom-right">
         <a ngbDropdownToggle i18n 
           i18n-title
           title="Log out and more..."
           class="nav-link dropdown-toggle no-caret with-material-icon">
-          <i class="material-icons">more_vert</i>
+          <i class="material-icons">list</i>
         </a>
         <div class="dropdown-menu" ngbDropdownMenu>
           <a class="dropdown-item" (click)="logout()">
index a3f24de..a0579b6 100644 (file)
@@ -2,22 +2,38 @@
 <eg-staff-banner bannerText="Sandbox" i18n-bannerText>
 </eg-staff-banner>
 
-<!-- 
-idlClass="cmrcfld"
-idlClass="cbt"
--->
-<fm-record-editor #fmRecordEditor 
-    idlClass="cmrcfld" mode="create" 
-    recordId="1" orgDefaultAllowed="owner">
-</fm-record-editor>
-<button class="btn btn-dark" (click)="fmRecordEditor.open({size:'lg'})">
-    Fm Record Editor
-</button>
+<!-- FM Editor Experiments ----------------------------- -->
+<div class="row mb-3">
+  <ng-template #descriptionTemplate 
+      let-field="field" let-record="record" let-hello="hello">
+  <!-- example custom template for editing the 'description' field -->
+    <textarea
+      placeholder="{{hello}}"
+      class="form-control"
+      name="{{field.name}}"
+      [readonly]="field.readOnly"
+      [required]="field.isRequired()"
+      [ngModel]="record[field.name]()"
+      (ngModelChange)="record[field.name]($event)">
+    </textarea>
+  </ng-template>
+  <fm-record-editor #fmRecordEditor 
+      idlClass="cmrcfld" mode="create" 
+      [customFieldTemplates]="{description:{template:descriptionTemplate,context:{'hello':'goodbye'}}}"
+      recordId="1" orgDefaultAllowed="owner">
+  </fm-record-editor>
+  <button class="btn btn-dark" (click)="fmRecordEditor.open({size:'lg'})">
+      Fm Record Editor
+  </button>
+</div>
+<!-- / FM Editor Experiments ----------------------------- -->
 
-<!-- ----- -->
-<br/><br/>
 
-<eg-progress-dialog #progressDialog>
-</eg-progress-dialog>
-<button class="btn btn-light" (click)="showProgress()">Test Progress Dialog</button>
+<!-- Progress Dialog Experiments ----------------------------- -->
+<div class="row mb-3">
+  <eg-progress-dialog #progressDialog>
+  </eg-progress-dialog>
+  <button class="btn btn-light" (click)="showProgress()">Test Progress Dialog</button>
+</div>
+<!-- /Progress Dialog Experiments ----------------------------- -->
 
index e2d97da..0018fc3 100644 (file)
@@ -7,6 +7,13 @@
       background-color: #007a54;
       color: #fff;
     }
+
+    /* Match the ang1 splash page */
+    .card-header {
+        color: #3c763d;
+        background-color: #dff0d8;
+        border-color: #d6e9c6;
+    }
 </style>
 
 <div class="container">
@@ -21,7 +28,7 @@
   <div class="row" id="splash-nav">
     <div class="col-lg-4">
       <div class="card">
-        <div class="card-header bg-evergreen">
+        <div class="card-header">
           <div class="panel-title text-center">Circulation and Patrons</div>
         </div>
         <div class="card-body">
@@ -45,7 +52,7 @@
 
     <div class="col-lg-4">
       <div class="card">
-        <div class="card-header bg-evergreen">
+        <div class="card-header">
           <div class="panel-title text-center">Item Search and Cataloging</div>
         </div>
         <div class="card-body">
@@ -89,7 +96,7 @@
 
     <div class="col-lg-4">
       <div class="card">
-        <div class="card-header bg-evergreen">
+        <div class="card-header">
           <div class="panel-title text-center">Administration</div>
         </div>
         <div class="card-body">