UNPKG

cv-dialog-sdk

Version:

Catavolt Dialog Javascript API

407 lines (369 loc) 16 kB
import {StringDictionary} from "../util"; import { DataUrl } from '../util/DataUrl'; import { Attachment } from './Attachment'; import { AttributeCellValue } from './AttributeCellValue'; import {Details} from "./Details"; import { Dialog } from './Dialog'; import { LargeProperty } from './LargeProperty'; import { Menu } from './Menu'; import { NullRecord } from './NullRecord'; import { Property } from './Property'; import { PropertyDef } from './PropertyDef'; import { ReadLargePropertyParameters } from './ReadLargePropertyParameters'; import { Record } from './Record'; import { RecordBuffer } from './RecordBuffer'; import { RecordUtil } from './RecordUtil'; import { Redirection } from './Redirection'; import { RedirectionUtil } from './RedirectionUtil'; import {SideEffectsParameters} from "./SideEffectsParameters"; import {SideEffectsResponse} from "./SideEffectsResponse"; import {ActionIdsEnum, TypeNames} from './types'; import { ViewMode } from './types'; import { ViewModeEnum } from './types'; /** * PanContext Subtype that represents an 'Editor Dialog'. * An 'Editor' represents and is backed by a single Record and Record definition. * See {@link Record} and {@link RecordDef}. */ export class EditorDialog extends Dialog { public readonly recordId: string; private _buffer: RecordBuffer; public static getSubType(jsonObj:StringDictionary): string { if(Dialog.isSearchDialog(jsonObj)) { return 'SearchDialog'; } if(jsonObj.view && jsonObj.view.type === TypeNames.FormTypeName) { return 'FormDialog'; } return 'EditorDialog'; } public changeViewMode(viewMode: ViewMode): Promise<EditorDialog | Redirection> { if (this.viewMode !== viewMode) { return this.catavolt.dialogApi .changeMode(this.tenantId, this.sessionId, this.id, viewMode) .then((result: EditorDialog | Redirection) => { if (RedirectionUtil.isRedirection(result)) { this.updateSettingsWithNewDialogProperties((result as Redirection).referringObject); if ((result as Redirection).refreshNeeded) { this.catavolt.dataLastChangedTime = new Date(); } return result; } else { const dialog = result as EditorDialog; // any new dialog needs to be initialized with the Catavolt object dialog.initialize(this.catavolt); this.updateSettingsWithNewDialogProperties(dialog.referringObject); return dialog; } }); } } /** * Get the associated properties */ get props(): Property[] { return this.record.properties; } get propertyDefs(): PropertyDef[] { return this.recordDef.propertyDefs; } get constants(): string[]{ if(this.view instanceof Details) { return this.view.constants; } return null; } get attributeCells(): AttributeCellValue[] { if(this.view instanceof Details) { return this.view.attributeCells; } return null; } get labelsByPropName(): StringDictionary { if(this.view instanceof Details) { return this.view.labelsByPropName; } return null; } /** * Get the associated entity record * @returns {Record} */ get record(): Record { return this.buffer.toRecord(); } /** * Get the current version of the entity record, with any pending changes present * @returns {Record} */ get recordNow(): Record { return this.record; } public getAvailableValues(propName: string): Promise<any[]> { return this.catavolt.dialogApi.getAvailableValues(this.tenantId, this.sessionId, this.id, propName, { pendingWrites: this.getWriteableRecord(this.buffer.afterEffects()), type: TypeNames.AvailableValuesParametersTypeName, }); } /** * Returns whether or not this cell definition contains a binary value * * @param {AttributeCellValue} cellValue * @returns {boolean} */ public isBinary(cellValue: AttributeCellValue): boolean { const propDef = this.propDefAtName(cellValue.propertyName); return propDef && (propDef.isLargePropertyType || (propDef.isURLType && cellValue.isInlineMediaStyle)); } /** * Returns whether or not this Editor is in 'read' mode * @returns {boolean} */ get isReadMode(): boolean { return this.viewMode === ViewModeEnum.READ; } /** * Returns whether or not this property is read-only * @param propName * @returns {boolean} */ public isReadModeFor(propName: string): boolean { if (!this.isReadMode) { const propDef = this.propDefAtName(propName); return !propDef || propDef.isReadOnly; } return true; } /** * Returns whether or not this cell definition contains a binary value that should be treated as a signature control * @param cellValueDef * @returns {PropertyDef|boolean} */ public isSignature(cellValueDef: AttributeCellValue): boolean { const propDef = this.propDefAtName(cellValueDef.propertyName); return propDef.isSignatureType; } /** * Returns whether or not this property is 'writable' * @returns {boolean} */ get isWriteMode(): boolean { return this.viewMode === ViewModeEnum.WRITE; } public performMenuActionWithId(actionId: string): Promise<Redirection> { return this.invokeMenuActionWithId(actionId, { pendingWrites: this.getWriteableRecord(this.buffer.afterEffects()), type: TypeNames.ActionParametersTypeName }).then(result => { return result; }); } /** * Perform the action associated with the given Menu on this EditorDialog * Given that the Editor could possibly be destroyed as a result of this action, * any provided pending writes will be saved if present. * @param {Menu} menu * @param {Record} pendingWrites * @returns {Promise<{actionId: string} | Redirection>} */ public performMenuAction(menu: Menu): Promise<Redirection> { return this.invokeMenuAction(menu, { pendingWrites: this.getWriteableRecord(this.buffer.afterEffects()), type: TypeNames.ActionParametersTypeName }).then(result => { return result; }); } /** * Properties whose {@link PropertyDef.canCauseSideEffects} value is true, may change other underlying values in the model. * This method will update those underlying values, given the property name that is changing, and the new value. * This is frequently used with {@link EditorDialog.getAvailableValues}. When a value is selected, other properties' * available values may change. (i.e. Country, State, City dropdowns) */ public processSideEffects(propertyName: string, value: any): Promise<void> { const property = this.newProperty(propertyName, value); const sideEffectsParameters:SideEffectsParameters = { property, pendingWrites: this.getWriteableRecord(this.buffer.afterEffects()), type: TypeNames.SideEffectsParameters }; return this.catavolt.dialogApi .propertyChange(this.tenantId, this.sessionId, this.id, propertyName, sideEffectsParameters) .then((sideEffectsResponse:SideEffectsResponse) => { /* TODO */ // coorindate the the handling of this result with server-side (some of these may be null...) if(sideEffectsResponse.recordDef) { this.recordDef = sideEffectsResponse.recordDef; } const sideEffectsRecord = sideEffectsResponse.record; const originalProperties = this.buffer.before.properties; const userModifiedProperties = this.buffer.afterEffects().properties; const sideEffectsProperties = sideEffectsRecord ? sideEffectsRecord.properties.filter((prop: Property) => { return prop.name !== propertyName; }) : []; this._buffer = RecordBuffer.createRecordBuffer( this.buffer.id, RecordUtil.unionRight(originalProperties, sideEffectsProperties), RecordUtil.unionRight( originalProperties, RecordUtil.unionRight(userModifiedProperties, sideEffectsProperties) ), this.record.annotations ); return Promise.resolve(); }); } /** * Read (load) the {@link Record} assocated with this Editor * The record must be read at least once to initialize the Context * @returns {Future<Record>} */ public read(): Promise<Record> { return this.catavolt.dialogApi.getRecord(this.tenantId, this.sessionId, this.id).then((record: Record) => { this.initBuffer(record); this.lastRefreshTime = new Date(); return this.record; }); } /** * Set the value of a property in this {@link Record}. * Values may be already constructed target types (CodeRef, TimeValue, Date, etc.) * or primitives, in which case the values will be parsed and objects constructed as necessary. * @param name * @param value * @returns {any} */ public setPropertyValue(name: string, value: any): Property { const propDef: PropertyDef = this.propDefAtName(name); let property = null; if (propDef) { const parsedValue = value !== null && value !== undefined ? this.parseValue(value, propDef.propertyName) : null; property = this.buffer.setValue(propDef.propertyName, parsedValue, propDef); } return property; } public newProperty(name: string, value:any):Property { const propDef: PropertyDef = this.propDefAtName(name); const property = this.buffer.propAtName(name); let newProperty = null; if (propDef) { const parsedValue = value !== null && value !== undefined ? this.parseValue(value, propDef.propertyName) : null; newProperty = new Property(property.name, parsedValue, propDef.format, property.annotations); } return newProperty; } public newLargePropertyWithDataUrl(dataUrl: string): LargeProperty { if (dataUrl) { const urlObj: DataUrl = new DataUrl(dataUrl); return this.newLargePropertyWithEncodedData(urlObj.data, urlObj.mimeType); } return null; } public newLargePropertyWithEncodedData(encodedData: string, mimeType?: string): LargeProperty { return new LargeProperty(encodedData, mimeType); } /** * Set a binary property from a string formatted as a 'data url' * See {@link https://en.wikipedia.org/wiki/Data_URI_scheme} * @param name * @param dataUrl */ public setLargePropertyWithDataUrl(name: string, dataUrl: string): Property { if (dataUrl) { const urlObj: DataUrl = new DataUrl(dataUrl); return this.setLargePropertyWithEncodedData(name, urlObj.data, urlObj.mimeType); } else { return this.setPropertyValue(name, null); // Property is being deleted/cleared } } /** * Set a binary property with base64 encoded data * @param name * @param encodedData * @param mimeType */ public setLargePropertyWithEncodedData(name: string, encodedData: string, mimeType?: string): Property { const propDef: PropertyDef = this.propDefAtName(name); let property = null; if (propDef) { const value = new LargeProperty(encodedData, mimeType); property = this.buffer.setValue(propDef.propertyName, value, propDef); } return property; } /** * Write this record (i.e. {@link Record}} back to the server * @returns {Promise<Record | Redirection>} */ public write(): Promise<EditorDialog | Redirection> { const deltaRec: Record = this.buffer.afterEffects(); /* Write the 'special' props first */ return this.writeLargeProperties(deltaRec).then((binResult: void[]) => { return this.writeAttachments(deltaRec).then((atResult: void[]) => { /* Remove special property types before writing the actual record */ const writableRecord: Record = this.getWriteableRecord(deltaRec); return this.catavolt.dialogApi .putRecord(this.tenantId, this.sessionId, this.id, writableRecord) .then((result: EditorDialog | Redirection) => { const now = new Date(); this.catavolt.dataLastChangedTime = now; this.lastRefreshTime = now; if (RedirectionUtil.isRedirection(result)) { this.updateSettingsWithNewDialogProperties((result as Redirection).referringObject); if ((result as Redirection).refreshNeeded) { this.catavolt.dataLastChangedTime = new Date(); } return result; } else { const dialog = result as EditorDialog; // any new dialog needs to be initialized with the Catavolt object dialog.initialize(this.catavolt); this.updateSettingsWithNewDialogProperties(dialog.referringObject); return dialog; } }); }); }); } // protected methods protected getProperty(params: ReadLargePropertyParameters, propertyName: string): Promise<LargeProperty> { return this.catavolt.dialogApi.getEditorProperty(this.tenantId, this.sessionId, this.id, propertyName, params); } // Private methods /** * Get the current buffered record * @returns {RecordBuffer} */ private get buffer(): RecordBuffer { if (!this._buffer) { this._buffer = new RecordBuffer(NullRecord.singleton); } return this._buffer; } // We have to remove LargeProperties and replace Attachments // As they are written separately private getWriteableRecord(record: Record): Record { const properties = record.properties .filter((prop: Property) => { /* Remove the Binary(s) as they have been written separately */ return !this.propDefAtName(prop.name).isLargePropertyType; }) .map((prop: Property) => { /* Remove the Attachment(s) (as they have been written separately) but replace the property value with the file name of the attachment prior to writing */ if (prop.value instanceof Attachment) { const attachment = prop.value as Attachment; const propDef = this.propDefAtName(prop.name); return new Property(prop.name, attachment.name, propDef.format, prop.annotations); } else { return prop; } }); return RecordUtil.newRecord(record.id, properties, record.annotations); } private initBuffer(record: Record) { this._buffer = record ? new RecordBuffer(record) : new RecordBuffer(NullRecord.singleton); } }