--- /dev/null
+<ng-template #dialogContent>
+ <div class="modal-header bg-info">
+ <h4 class="modal-title" i18n>Record Editor</h4>
+ <button type="button" class="close"
+ i18n-aria-label aria-label="Close"
+ (click)="dismiss('cross_click')">
+ <span aria-hidden="true">×</span>
+ </button>
+ </div>
+ <div class="modal-body">
+ <form role="form">
+ <div class="form-group row" *ngFor="let field of fields">
+ <div class="col-lg-3">
+ <label for="rec-{{field.name}}">{{field.label}}</label>
+ </div>
+ <div class="col-lg-9">
+ <!-- TODO custom field templates -->
+ <span *ngIf="field.datatype == 'id' && !pkeyIsEditable">
+ {{record[field.name]()}}
+ </span>
+
+ <input *ngIf="field.datatype == 'id' && pkeyIsEditable"
+ name="{{field.name}}"
+ [readonly]="field.readOnly"
+ [required]="field.isRequired()"
+ [ngModel]="record[field.name]()"
+ (ngModelChange)="record[field.name]($event)"/>
+
+ <input *ngIf="field.datatype == 'text'"
+ name="{{field.name}}"
+ [readonly]="field.readOnly"
+ [required]="field.isRequired()"
+ [ngModel]="record[field.name]()"
+ (ngModelChange)="record[field.name]($event)"/>
+
+ </div>
+ </div>
+ </form>
+ </div>
+ <div class="modal-footer">
+ <button type="button" class="btn btn-success"
+ (click)="close()" i18n>Close</button>
+ </div>
+</ng-template>
--- /dev/null
+import {Component, OnInit, Input, Output, ViewChild, EventEmitter} from '@angular/core';
+import {EgStoreService} from '@eg/core/store.service';
+import {EgOrgService} from '@eg/core/org.service';
+import {EgIdlService, EgIdlObject} from '@eg/core/idl.service';
+import {EgPcrudService} from '@eg/core/pcrud.service';
+import {EgDialogComponent} from '@eg/share/dialog/dialog.component';
+import {NgbModal} from '@ng-bootstrap/ng-bootstrap';
+
+@Component({
+ selector: 'fm-record-editor',
+ templateUrl: './fm-editor.component.html'
+})
+export class FmRecordEditorComponent
+ extends EgDialogComponent implements OnInit {
+
+ // IDL class hint (e.g. "aou")
+ @Input() idlClass: string;
+
+ // mode: 'create' for creating a new record,
+ // 'update' for editing an existing record
+ @Input() mode: string;
+
+ // record ID to update. Not all IDs are numbers.
+ @Input() recordId: string;
+
+ // TODO
+ // customFieldTemplates
+
+ // list of fields that should not be displayed
+ @Input() hiddenFields: string[] = [];
+
+ // list of fields that should always be read-only
+ @Input() readonlyFields: string[] = [];
+
+ // list of required fields; this supplements what the IDL considers
+ // required
+ @Input() requiredFields: string[] = [];
+
+ // list of org_unit fields where the selector should default to the
+ // workstation OU
+ @Input() orgDefaultAllowed: string[] = [];
+
+ // hash, keyed by field name, of functions to invoke to check
+ // whether a field is required. Each callback is passed the field
+ // name and the record and should return a boolean value. This
+ // supports cases where whether a field is required or not depends
+ // on the current value of another field.
+ @Input() isRequiredOverride:
+ {[field: string] : (field: string, record: EgIdlObject) => boolean};
+
+ // IDL record display label. Defaults to the IDL label.
+ @Input() recordLabel: string;
+
+ // Emit the modified object when the save action completes.
+ @Output() onSave$ = new EventEmitter<EgIdlObject>();
+
+ // Emit the original object when the save action is canceled.
+ @Output() onCancel$ = new EventEmitter<EgIdlObject>();
+
+ // Emit an error message when the save action fails.
+ @Output() onError$ = new EventEmitter<string>();
+
+ // IDL info for the the selected IDL class
+ idlDef: any;
+
+ // IDL record we are editing
+ record: EgIdlObject;
+
+ // Can we edit the primary key?
+ pkeyIsEditable: boolean = false;
+
+ // List of IDL field definitions. This is a subset of the full
+ // list of fields on the IDL, since some are hidden, virtual, etc.
+ fields: any[];
+
+ constructor(
+ private modal: NgbModal, // required for passing to parent
+ private idl: EgIdlService,
+ private store: EgStoreService,
+ private org: EgOrgService,
+ private pcrud: EgPcrudService
+ ) {
+ super(modal)
+ }
+
+ ngOnInit() {
+ this.idlDef = this.idl.classes[this.idlClass];
+ this.recordLabel = this.idlDef.label;
+ this.initRecord();
+ }
+
+ private initRecord(): Promise<any> {
+
+ if (this.mode == 'update') {
+ return this.pcrud.retrieve(this.idlClass, this.recordId)
+ .toPromise().then(rec => {
+ this.record = rec;
+ this.convertDatatypesToJs();
+ this.fields = this.getFieldList();
+ });
+ }
+
+ // create a new record from scratch
+ this.pkeyIsEditable = !('pkey_sequence' in this.idlDef);
+ this.record = this.idl.create(this.idlClass);
+ this.fields = this.getFieldList();
+ return Promise.resolve();
+ }
+
+ // Modifies the FM record in place, replacing IDL-compatible values
+ // with native JS values.
+ private convertDatatypesToJs() {
+ this.idlDef.fields.forEach(field => {
+ if (field.datatype == 'bool') {
+ if (this.record[field.name]() == 't') {
+ this.record[field.name](true);
+ } else if (this.record[field.name]() == 'f') {
+ this.record[field.name](false);
+ }
+ }
+ });
+ }
+
+ private flattenLinkedValues(cls: string, list: EgIdlObject[]): any[] {
+ let results: any[] = [];
+ let idField: string = this.idlDef.pkey;
+ let selector: string =
+ this.idlDef.field_map[idField].selector || idField;
+
+ return list.map(item => {
+ return {id: item[idField](), name: item[selector]()}
+ });
+ }
+
+ private getFieldList(): any[] {
+
+ let fields = this.idlDef.fields.filter(f =>
+ f.virtual != 'true' &&
+ !this.hiddenFields.includes(f.name)
+ );
+
+ fields.forEach(field => {
+ field.readOnly = this.readonlyFields.includes(field.name);
+
+ if (this.isRequiredOverride &&
+ field.name in this.isRequiredOverride) {
+ field.isRequired = () => {
+ return this.isRequiredOverride[field.name](field.name, this.record);
+ }
+ } else {
+ field.isRequired = () => {
+ return field.required ||
+ this.requiredFields.includes(field.name);
+ }
+ }
+
+ // TODO
+ });
+
+ console.debug(fields);
+ return fields;
+ }
+
+ save() {
+ // save changes here
+ this.close(/* data for caller */);
+ }
+
+ cancel() {
+ this.dismiss(/* data for caller */);
+ }
+}
+
+