/** * Created by rburson on 7/25/16. */ import * as React from 'react' import { CvState, CvProps, CvEvent, CvEventType, CvEventRegistry, CvResultCallback, CvContext, CvNavigationResult, CvNavigationResultUtil, CvStateChangeResult, CvStateChangeType, CvMessage, CvMessageType, CvValueProvider, CvActionFiredResult, CvActionHandlerParams, CvActionBase, CvResourceManager } from './catreact-core' import { FormContext, EntityRec, EntityRecDef, EditorContext, Try, Either, NavRequest, PaneMode, Prop, PropDef, Log, PropFormatter, DialogException } from 'catavolt-sdk' export interface CvEditorBaseState extends CvState { } export interface CvEditorBaseProps extends CvProps { formContext?:FormContext; paneRef?:number; //called detailsContext for compatibility's sake detailsContext?:EditorContext; detailsRenderer?:(cvContext:CvContext, entityRec:EntityRec, callback:CvEditorPaneCallback)=>{} errorRenderer?:(cvContext:CvContext, dialogException?:DialogException)=>{}; navigationListeners?:Array<(event:CvEvent)=>void>; stateChangeListeners?:Array<(event:CvEvent)=>void>; actionListeners?:Array<(event:CvEvent)=>void>; actionProvider?:CvValueProvider; initMode?:CvInitMode; } export interface CvEditorPaneCallback { clear():void; getPropValueForDisplay(propName:string):string; getPropValueForEdit(propName:string):string; openReadMode(resultCallback?:CvResultCallback):void; openWriteMode(resultCallback?:CvResultCallback):void; saveChanges(resultCallback?:CvResultCallback, navTarget?:string):void; setPropValue(name:string, value:any):void; setBinaryPropWithDataUrl(name:string, dataUrl:string):void; setBinaryPropWithEncodedData(name:string, encodedData:string):void; suppressEvents(suppressEvents:boolean):void; refresh():void; } export enum CvInitMode { NORMAL, DEFERRED, MANUAL } /** * Render a EditorContext */ export var CvEditorBase = { editorContext: function (nextProps, nextContext) { let editorContext:EditorContext = null; let paneRef = null; if (nextProps) { if (nextProps.detailsContext) { editorContext = nextProps.detailsContext; } else if (nextProps.paneRef !== null && nextProps.paneRef !== undefined) { const formContext:FormContext = this.formContext(nextProps, nextContext); editorContext = this._getEditorContext(formContext, nextProps.paneRef); } } else { if (this.props.detailsContext) { editorContext = this.props.detailsContext; } else { const formContext:FormContext = this.formContext(); editorContext = this._getEditorContext(formContext, this.props.paneRef); } } return editorContext; }, formContext: function (nextProps, nextContext) { if (nextProps && nextProps.formContext) { return nextProps.formContext; } else if (nextContext) { return this.firstInScope(FormContext, nextContext.cvContext.scopeCtx); } else { return this.props.formContext || this.firstInScope(FormContext); } }, getChildContext: function () { const ctx = this.getDefaultChildContext(); ctx.cvContext.scopeCtx.scopeObj = this.editorContext(); return ctx; }, refresh: function (nextProps, nextContext, callback:CvResultCallback) { const editorContext:EditorContext = this.editorContext(nextProps, nextContext); if (editorContext && this.isMounted()) { if (editorContext.isDestroyed) { callback && callback(true); if (this.isMounted()) { this.forceUpdate(); } } else { CvActionBase._publishActionStarted('#refresh', editorContext, false, this.props.actionListeners, this.eventRegistry()); editorContext.read().onComplete((entityRecTry:Try)=> { if (entityRecTry.isFailure) { const event:CvEvent = { type: CvEventType.MESSAGE, eventObj: { message: 'refresh(): failed to read editorContext', messageObj: entityRecTry.failure, type: CvMessageType.ERROR } } this.eventRegistry().publish(event, false); callback && callback(null, entityRecTry.failure); } else { callback && callback(true); if (this.isMounted()) { this.forceUpdate(); } } CvActionBase._publishActionFinished('#refresh', editorContext, this.props.actionListeners, this.eventRegistry()); }); } } }, _checkDestroyed: function () { const editorContext:EditorContext = this.editorContext(); if (editorContext && editorContext.isDestroyed) { this._publishStateChange(CvStateChangeType.DESTROYED); } }, _componentDidMount: function () { if(!this.props.initMode || this.props.initMode === CvInitMode.NORMAL) this.refresh(); if (this.props.actionProvider) { this.props.actionProvider.subscribe(this._handleAction); } (this.eventRegistry() as CvEventRegistry) .subscribe(this._dataChangeListener, CvEventType.STATE_CHANGE); }, _componentWillReceiveProps: function (nextProps, nextContext) { if (nextProps.actionProvider && nextProps.actionProvider !== this.props.actionProvider) { nextProps.actionProvider.subscribe(this._handleAction); } if(!this.props.initMode || this.props.initMode === CvInitMode.NORMAL){ if(this.editorContext() !== this.editorContext(nextProps, nextContext)) { this.refresh(nextProps, nextContext); } } }, _componentWillUnmount: function () { (this.eventRegistry() as CvEventRegistry).unsubscribe(this._dataChangeListener); }, _dataChangeListener: function (dataChangeResult:CvEvent) { if (dataChangeResult.eventObj.type === CvStateChangeType.DATA_CHANGE && dataChangeResult.eventObj.source !== this.editorContext()) { if (this.props.initMode && (this.props.initMode === CvInitMode.NORMAL || this.props.initMode === CvInitMode.DEFERRED)) { this.refresh(); } } }, _getDefaultProps: function () { return { formContext: null, paneRef: null, //called 'details' instead of 'editor' for compatibility's sake detailsContext: null, detailsRenderer: null, errorRenderer: null, navigationListeners: [], stateChangeListeners: [], actionListeners: [], actionProvider: null, initMode: CvInitMode.NORMAL } }, _getEditorContext: function (formContext:FormContext, paneRef:number) { let editorContext = null; formContext.childrenContexts.some((childContext)=> { if (childContext instanceof EditorContext && childContext.paneRef == paneRef) { editorContext = childContext; return true; } else { return false; } }); return editorContext; }, _getCallbackObj: function ():CvEditorPaneCallback { let suppressEvents:boolean = false; return { clear: ():void => { this.editorContext().entityRec.propNames.forEach((propName:string)=>{ this._getCallbackObj().setPropValue(propName, null)}); }, getPropValueForDisplay: (propName:string):string => { const editorContext = this.editorContext(); const prop:Prop = editorContext.buffer.propAtName(propName); const propDef:PropDef = editorContext.entityRecDef.propDefAtName(propName); return PropFormatter.formatForRead(prop, propDef); }, getPropValueForEdit: (propName:string):string => { const editorContext = this.editorContext(); const prop:Prop = editorContext.buffer.propAtName(propName); const propDef:PropDef = editorContext.entityRecDef.propDefAtName(propName); return PropFormatter.formatForWrite(prop, propDef); }, openReadMode: (resultCallback?:CvResultCallback):void => { this.editorContext().changePaneMode(PaneMode.READ).onComplete((entityRecDefTry:Try)=> { if (entityRecDefTry.isSuccess) { if(!suppressEvents) { this._publishStateChange(CvStateChangeType.MODE_CHANGE_READ); this._checkDestroyed(); this.refresh(null, null, resultCallback); } else { resultCallback(entityRecDefTry.success); } } else { const event:CvEvent = { type: CvEventType.MESSAGE, eventObj: { message: 'Could not open read mode', messageObj: entityRecDefTry.failure, type: CvMessageType.ERROR } } if(!suppressEvents) this.eventRegistry().publish(event, false); resultCallback(null, entityRecDefTry.failure); } }); }, openWriteMode: (resultCallback?:CvResultCallback):void => { this.editorContext().changePaneMode(PaneMode.WRITE).onComplete((entityRecDefTry:Try)=> { if (entityRecDefTry.isSuccess) { if(!suppressEvents) { this.refresh(null, null, resultCallback); this._publishStateChange(CvStateChangeType.MODE_CHANGE_WRITE); this._checkDestroyed(); } else { resultCallback(entityRecDefTry.success); } } else { const event:CvEvent = { type: CvEventType.MESSAGE, eventObj: { message: 'Could not open edit mode', messageObj: entityRecDefTry.failure, type: CvMessageType.ERROR } } if(!suppressEvents) this.eventRegistry().publish(event, false); resultCallback(null, entityRecDefTry.failure); } }); }, refresh: ()=> { this.refresh(); }, saveChanges: (resultCallback?:CvResultCallback>, navTarget?:string):void => { const editorContext = this.editorContext(); editorContext.write().onComplete((writeTry:Try>)=> { if (writeTry.isFailure) { const event:CvEvent = { type: CvEventType.MESSAGE, eventObj: { message: 'Save changes failed', messageObj: writeTry.failure, type: CvMessageType.ERROR } } if(!suppressEvents) this.eventRegistry().publish(event, false); resultCallback(null, Error(writeTry.failure)); } else { const either:Either = writeTry.success; const result:CvEvent = either.isLeft ? this._handleNavigation(either.left, '#write', navTarget, editorContext.isDestroyed) : null; if(!suppressEvents) { this._publishStateChange(CvStateChangeType.DATA_CHANGE); this._checkDestroyed(); if (this.isMounted()) { this.forceUpdate(); } } resultCallback(result); } }); }, setBinaryPropWithDataUrl: (name:string, dataUrl:string) => { this.editorContext().setBinaryPropWithDataUrl(name, dataUrl); }, setBinaryPropWithEncodedData: (name:string, encodedData:string) => { this.editorContext().setBinaryPropWithEncodedData(name, encodedData); }, setPropValue: (name:string, value:any) => { Log.debug('setting: ' + name + ' : ' + value); const editorContext = this.editorContext(); const parsedValue = editorContext.setPropValue(name, value); const propDef:PropDef = editorContext.propDefAtName(name); if (propDef && propDef.canCauseSideEffects) { editorContext.processSideEffects(name, parsedValue).onComplete((propChangeTry:Try)=> { if (propChangeTry.isFailure) { const event:CvEvent = { type: CvEventType.MESSAGE, eventObj: { message: 'Process side effects failed', messageObj: propChangeTry.failure, type: CvMessageType.ERROR } } if(!suppressEvents) this.eventRegistry().publish(event, false); } else { if (this.isMounted()) { if(!suppressEvents) this.forceUpdate(); } } }); } }, suppressEvents: (val:boolean)=>{ suppressEvents = val; } } }, _handleAction: function (params:CvActionHandlerParams) { const event:CvEvent = params.event; const callback:CvResultCallback = params.callback; if (event.eventObj.clientAction) { const actionId = event.eventObj.actionId; if (actionId === '#refresh') { this.refresh(null, null, callback); } else { callback(null, `Action ${actionId} not yet implemented.`); } } }, _handleNavigation: function (navRequest:NavRequest, actionId:string, navTarget:string, sourceIsDestroyed:boolean):CvEvent { const resourceId = CvResourceManager.resourceIdForObject(navRequest, this.catavolt()); const event:CvEvent = { type: CvEventType.NAVIGATION, resourceId: resourceId, eventObj: { navRequest: navRequest, actionId: actionId, navTarget: navTarget, sourceIsDestroyed: sourceIsDestroyed, type: CvNavigationResultUtil.determineType(navRequest) } }; (this.eventRegistry() as CvEventRegistry).publish(event, CvResourceManager.shouldCacheResult(this.eventRegistry())); this.props.navigationListeners.forEach((listener)=> { listener(event) }); return event; }, _publishStateChange: function (type:CvStateChangeType) { const editorContext:EditorContext = this.editorContext(); if (editorContext) { const event:CvEvent = { type: CvEventType.STATE_CHANGE, resourceId: null, eventObj: {source: editorContext, type: type} }; (this.eventRegistry() as CvEventRegistry).publish(event, false) this.props.stateChangeListeners.forEach(listener=> { listener(event) }); } }, _render: function () { const editorContext:EditorContext = this.editorContext(); if (editorContext && !editorContext.isDestroyed) { if(editorContext.hasError) { return this.props.errorRenderer ? this.props.errorRenderer(this.getChildContext().cvContext, editorContext.error) : null; } else if (this.props.renderer) { return this.props.renderer(this.getChildContext().cvContext); } else if (this.props.detailsRenderer) { if (editorContext.buffer) { return this.props.detailsRenderer(this.getChildContext().cvContext, editorContext.entityRec, this._getCallbackObj()); } else { return null; } } else { return null; } } else { return null; } }, }