/** * Created by rburson on 4/29/16. */ import * as React from 'react' import { CvState, CvProps, CvResultCallback, CvEvent, CvEventType, CvEventRegistry, CvStateChangeResult, CvStateChangeType, CvContext, CvMessage, CvMessageType, CvValueProvider, CvActionHandlerParams, CvActionFiredResult, CvActionBase } from './catreact-core' import {FormContext, EntityRec, Try, QueryContext, QueryMarkerOption, Log, ObjUtil, DialogException} from 'catavolt-sdk' export interface CvQueryPaneState extends CvState { } export enum CvQueryMode { NORMAL, DEFERRED, MANUAL } export interface CvQueryPaneProps extends CvProps { paneRef?:number; formContext?:FormContext; queryContext?:QueryContext; stateChangeListeners?:Array<(event:CvEvent)=>void>; actionListeners?:Array<(event:CvEvent)=>void> queryRenderer?:(cvContext:CvContext, callback:CvQueryPaneCallback)=>any; errorRenderer?:(cvContext:CvContext, dialogException?:DialogException)=>{}; actionProvider?:CvValueProvider; recordPageSize?:number; queryMode?:CvQueryMode; } export interface CvQueryPaneCallback { hasMoreBackward():boolean; hasMoreForward():boolean; isInProgress():boolean; pageBackward(resultCallback:CvResultCallback, replaceBuffer?:boolean):void; pageForward(resultCallback:CvResultCallback, replaceBuffer?:boolean):void; pageSize():number; refresh():void; suppressEvents(suppressEvents:boolean):void; } export var CvQueryBase = { 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.queryContext(); return ctx; }, reload: function (pageSize:number, nextProps, nextContext) { //we allow multiple refresh calls - the sdk chains Futures so they are safe this.refreshInProgress = this.refreshInProgress ? ++this.refreshInProgress : 1; const queryContext = this.queryContext(nextProps, nextContext); queryContext.setScroller(pageSize, null, [QueryMarkerOption.None]); CvActionBase._publishActionStarted('#refresh', queryContext, false, this.props.actionListeners, this.eventRegistry()); queryContext.scroller.refresh().onComplete(entityRecTry=> { this.refreshInProgress--; if (entityRecTry.isFailure) { const event:CvEvent = { type: CvEventType.MESSAGE, eventObj: { message: 'Failed to load query buffer', messageObj: entityRecTry.failure, type: CvMessageType.ERROR } } this.eventRegistry().publish(event, false); } else { //Log.debug(JSON.stringify(queryContext.scroller.buffer)); if (this.isMounted()) { this.forceUpdate(); } } CvActionBase._publishActionFinished('#refresh', queryContext, this.props.actionListeners, this.eventRegistry()); }); }, queryContext: function (nextProps, nextContext) { if(nextProps && nextProps.queryContext) { return nextProps.queryContext; } else if (this.props.queryContext) { return this.props.queryContext; } else { const paneRef = nextProps ? nextProps.paneRef : this.props.paneRef; const formContext:FormContext = this.formContext(nextProps, nextContext); let queryContext = null; formContext.childrenContexts.some((childContext)=> { if (childContext instanceof QueryContext && childContext.paneRef == paneRef) { queryContext = childContext; return true; } else { return false; } }); return queryContext; } }, isInProgress: function():boolean { return !!this.refreshInProgress; }, checkReloadNeeded: function (nextProps, nextContext) { const queryContext = this.queryContext(nextProps, nextContext); //update page size if it has changed - note: this resets the buffer //attempt to detect changes to the queryContext //is this an update to recordPageSize? if(nextProps && nextProps.recordPageSize && nextProps.recordPageSize != this.props.recordPageSize) { this.reload(nextProps.recordPageSize, nextProps, nextContext); //is this a 'new' queryContext object } else if (queryContext && this.props.queryContext && (queryContext != this.props.queryContext)) { this.reload(this.props.recordPageSize, nextProps, nextContext); // is the queryContext's buffer empty? @TODO - removing this for now, not sure it's necessary //} else if (queryContext.scroller.buffer && queryContext.scroller.buffer.length === 0) { //this.reload(this.props.recordPageSize, nextProps, nextContext); //is the pane out of date? }else if(queryContext.isRefreshNeeded) { this.reload(this.props.recordPageSize, nextProps, nextContext); } }, _checkDestroyed: function () { const queryContext = this.queryContext(); if (queryContext && queryContext.isDestroyed) { const event:CvEvent = { type: CvEventType.STATE_CHANGE, resourceId: null, eventObj: {source: queryContext, type: CvStateChangeType.DESTROYED} }; this.props.stateChangeListeners.forEach(listener=> { listener(event) }); } }, _componentWillMount: function () { if(this.props.queryMode === CvQueryMode.NORMAL) { this.reload(this.props.recordPageSize); } 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.queryMode === CvQueryMode.NORMAL) { this.checkReloadNeeded(nextProps, nextContext); } }, _componentWillUnmount: function () { (this.eventRegistry() as CvEventRegistry).unsubscribe(this._dataChangeListener); }, _dataChangeListener: function (dataChangeResult:CvEvent) { //reload if someone else ('source' !== me) published a state change if (dataChangeResult.eventObj.type === CvStateChangeType.DATA_CHANGE && dataChangeResult.eventObj.source !== this.queryContext()) { if(this.props.queryMode === CvQueryMode.NORMAL || this.props.queryMode === CvQueryMode.DEFERRED) { this.reload(this.props.recordPageSize); } } }, _getCallbackObj: function ():CvQueryPaneCallback { let inProgress:boolean = false; let suppressEvents:boolean = false; return { hasMoreBackward: ():boolean => { return this.queryContext().scroller.hasMoreBackward; }, hasMoreForward: ():boolean => { return this.queryContext().scroller.hasMoreForward; }, isInProgress: ():boolean => { return inProgress || this.isInProgress(); }, pageBackward: (resultCallback:CvResultCallback, replaceBuffer:boolean=false):void => { if (!inProgress) { const queryContext = this.queryContext(); const scroller = queryContext.scroller; if(scroller.hasMoreBackward) { inProgress = true; if(!suppressEvents) CvActionBase._publishActionStarted('#pageBackward', queryContext, false, this.props.actionListeners, this.eventRegistry()); scroller.pageBackward().onComplete((entityRecTry:Try>) => { inProgress = false; if (entityRecTry.isFailure) { resultCallback(null, Error(entityRecTry.failure)); } else { if(replaceBuffer) { scroller.trimLast(scroller.buffer.length - entityRecTry.success.length); } if(!suppressEvents) this.forceUpdate(); resultCallback(entityRecTry.success.length); } if(!suppressEvents) CvActionBase._publishActionFinished('#pageBackward', queryContext, this.props.actionListeners, this.eventRegistry()); }); } else { resultCallback(null, Error('There are no more previous records')); } } else { resultCallback(null, Error('paging already in progress')); } }, pageForward: (resultCallback:CvResultCallback, replaceBuffer:boolean=false):void => { if (!inProgress) { const queryContext = this.queryContext(); const scroller = queryContext.scroller; if(scroller.hasMoreForward) { inProgress = true; if(!suppressEvents) CvActionBase._publishActionStarted('#pageForward', queryContext, false, this.props.actionListeners, this.eventRegistry()); scroller.pageForward().onComplete((entityRecTry:Try>) => { inProgress = false; if (entityRecTry.isFailure) { resultCallback(null, Error(entityRecTry.failure)); } else { if(replaceBuffer) { scroller.trimFirst(scroller.buffer.length - entityRecTry.success.length); } if(!suppressEvents) this.forceUpdate(); resultCallback(entityRecTry.success.length); } if(!suppressEvents) CvActionBase._publishActionFinished('#pageForward', queryContext, this.props.actionListeners, this.eventRegistry()); }); } else { resultCallback(null, Error('There are no more records')); } } else { resultCallback(null, Error('paging already in progress')); } }, pageSize: ():number => { return this.queryContext().scroller.pageSize; }, refresh: ()=> { this.reload(this.props.recordPageSize); }, suppressEvents: (val:boolean)=>{ suppressEvents = val;} } }, _getDefaultProps: function () { return { paneRef: null, formContext: null, queryContext: null, queryRenderer: null, errorRenderer: null, stateChangeListeners: [], actionListeners: [], actionProvider: null, recordPageSize:50, queryMode: CvQueryMode.NORMAL } }, _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.reload(this.queryContext().scroller.buffer.length); callback(true); } else { callback(null, `Action ${actionId} not yet implemented.`); } } }, _render: function () { const queryContext:QueryContext = this.queryContext(); if (queryContext && !queryContext.isDestroyed) { if(queryContext.hasError) { return this.props.errorRenderer ? this.props.errorRenderer(this.getChildContext().cvContext, queryContext.error) : null; }else if (this.props.renderer) { return this.props.renderer(this.getChildContext().cvContext); } else if (this.props.queryRenderer) { return this.props.queryRenderer(this.getChildContext().cvContext, this._getCallbackObj()) } else if (React.Children.count(this.props.children) > 0) { return this.props.children } else { return null; } } else { return null; } } }