/** * Created by rburson on 1/14/16. */ import * as React from 'react' import {CvState, CvProps, CvBaseMixin, CvEvent, CvEventType, CvMessage, CvMessageType} from './catreact-core' import { Prop, PropDef, EntityRec, PropFormatter, PaneContext, Binary, Try, TimeValue, Log, ObjUtil, EditorContext } from 'catavolt-sdk' export interface CvPropState extends CvState { binary:Binary; } export interface CvPropProps extends CvProps { /** * If supplied, this is called when the property is boolean, to allow custom rendering */ booleanRenderer?:(boolVal:boolean) => {}; /** * If supplied, this is called when the property is a binary property, to allow custom rendering * The given url may be a 'data' url with image data inline, or it may be a 'remote' url * */ binaryRenderer?:(binaryUrl:string) => {} /** * className to apply to the property. wrapperElemProps will override this value */ className?:string, /** * style to apply to the property. wrapperElemProps will override this value */ style?:{} /** * Rendering override. Simple callback function to access the sdk {Prop} object */ , handler?:(o:Prop) => {}; /** * Function that allows for visibility control. Accepts sinlge param of sdk {Prop} object. Should return true * if the component should be rendered, false if the component should be hidden. */ isVisible?:(o:Prop) => boolean; /** * The name of this property. The given (or enclosing) sdk {EnityRec} will be searched for the sdk {Prop} of this name */ propName:string; /** * Default to be used if this sdk {Prop} value is null or undefined */ defaultValue?:string; /** * The sdk {EntityRec} that owns this sdk {Prop} */ entityRec?:EntityRec; /** * The containing PaneContext */ paneContext?:PaneContext; /** * The wrapper element name for this prop value (should be html) */ wrapperElemName?:any; /** * The wrapper element props */ wrapperElemProps?:any; /** * Force the value of this prop to be the given value */ overrideValue?:string; /** * Set the named properties' values to the property's final resolved value * i.e. 'title' on an html element for a tooltip */ dataPropNames?:Array; /** * Class name to be set on the image control, if this is for an image. */ imageClassName?:string; } /* *************************************************** * Render a Property *************************************************** */ export var CvProp = React.createClass({ mixins: [CvBaseMixin], componentWillUpdate: function(nextProps, nextState, nextContext) { //Log.debug("updating CvProps") }, componentDidMount: function () { //Log.debug("mounting CvProps"); this.refresh(); }, componentWillReceiveProps: function (nextProps, nextContext) { //reset the loaded state if this is a 'new' prop if(this.props.propName !== nextProps.propName || this.entityRec() !== this.entityRec(nextProps, nextContext)) { this.loaded = false; } this.refresh(nextProps, nextContext); }, entityRec: function (nextProps, nextContext) { if(nextProps && nextProps.entityRec) { return nextProps.entityRec; } else if(nextContext) { return this.findEntityRec(nextContext.cvContext.scopeCtx); } else { return this.findEntityRec(); } }, entityRecDef: function (nextProps, nextContext) { return this.paneContext(nextProps, nextContext).entityRecDef; }, getChildContext: function () { const ctx = this.getDefaultChildContext(); ctx.cvContext.scopeCtx.scopeObj = this.prop(); return ctx; }, getDefaultProps: function () { return { propName: null, defaultValue: null, booleanRenderer: null, binaryRenderer: null, handler: null, isVisible: null, entityRec: null, wrapperElemName: 'span', wrapperElemProps: null, overrideValue: null, style: null, className: null, renderer: null, dataPropNames: [] } }, getInitialState: function () { return {binary: null}; }, paneContext: function (nextProps, nextContext) { if(nextProps && nextProps.paneContext) { return nextProps.paneContext; }else if(nextContext) { return this.findPaneContext(nextContext.cvContext.scopeCtx); } else { return this.findPaneContext(); } }, prop: function (nextProps, nextContext) { const propName = nextProps ? nextProps.propName : this.props.propName; return this.entityRec(nextProps, nextContext).propAtName(propName) }, propDef: function (nextProps, nextContext) { const propName = nextProps ? nextProps.propName : this.props.propName; return this.entityRecDef(nextProps, nextContext).propDefAtName(propName); }, render: function () { const prop = this.prop(); if (!prop || (this.props.isVisible && !this.props.isVisible(prop))) { return null; } if (this.props.renderer) { return this.props.renderer(this.getChildContext().cvContext); } else if (this.props.handler) { return this.props.handler(prop); } else { if (React.Children.count(this.props.children) > 0) { return this.props.children } else { /* setup props for the wrapper element */ const propDef = this.propDef(); const nullRenderer = propDef && propDef.isBinaryType; let props = {style: this.props.style, className: this.props.className}; if(this.props.wrapperElemProps) { props = ObjUtil.addAllProps(this.props.wrapperElemProps, props); } /* overridden value */ if (this.props.overrideValue != null) { return React.createElement(this.props.wrapperElemName, props, this.props.overrideValue); /* missing value */ } else if (!nullRenderer && (prop.value === null || prop.value === undefined)) { return this.props.defaultValue !== null ? React.createElement(this.props.wrapperElemName, props, this.props.defaultValue) : null; } else { const propDef = this.propDef(); /* binary property */ if (propDef && propDef.isBinaryType) { if(this.state.binary) { var dataUrl = this.state.binary.toUrl(); } return this.props.binaryRenderer ? this.props.binaryRenderer(dataUrl) : React.createElement(this.props.wrapperElemName, props, '[binary]'); /* boolean property */ } else if (propDef && propDef.isBooleanType) { return this.props.booleanRenderer ? this.props.booleanRenderer(prop.value) : React.createElement(this.props.wrapperElemName, props, String(prop.value)); } else { /* text property */ const editorContext:EditorContext = this.paneContext(null, null) as EditorContext; const value = (editorContext && editorContext.isWriteMode) ? PropFormatter.formatForWrite(prop, propDef) : PropFormatter.formatForRead(prop, propDef); /* copy the props rendered value to any attributes that were specified */ if(this.props.dataPropNames && this.props.dataPropNames.length > 0) { this.props.dataPropNames.forEach(name=>props[name] = value); } return React.createElement(this.props.wrapperElemName, props, value); } } } } }, refresh: function (nextProps, nextContext) { const prop = this.prop(nextProps, nextContext); if(prop) { const propDef = this.propDef(nextProps, nextContext); if (propDef && propDef.isBinaryType) { if (prop.value) { //if this is an update and the propName hasn't changed, don't reload the binary if (this.loaded && nextProps && nextProps.propName === this.props.propName) { return; } const paneContext = this.paneContext(nextProps, nextContext); paneContext.binaryAt(this.props.propName, this.entityRec(nextProps, nextContext)).onComplete((binaryTry: Try) => { if (binaryTry.isSuccess) { if (this.isMounted()) { this.setState({binary: binaryTry.success}); } } else { const event: CvEvent = { type: CvEventType.MESSAGE, eventObj: { message: 'Could not load binary property: ' + prop.name, messageObj: binaryTry.failure, type: CvMessageType.ERROR } } this.eventRegistry().publish(event, false); } this.loaded = true; }); } else { // If there is no value, there is no data to read if (this.isMounted()) { this.setState({binary: null}); } } } } }, });