import { injectable, inject } from 'inversify'; import { guid, IInital, LinkedTree, LinkedTreeNode } from 'ts-toolset'; import { assignDeep, deepClone, getObjectNest } from 'ts-toolset/dist/common/object'; import { MapStore, EventBus } from '../../../services'; import { ServicesIoc, WorkbenchIoc } from '../../../ioc'; import { MapStoreKeys, globalStoreKeys, UndoManagerKeys } from './common/signature'; import { IHtmlComponentEntity, IHtmlSchemaNode, IStyleRuleEntity, IHtmlTemplateEntity } from '../../../models/html'; import { LoopSchemaNodeParams, LoopSchemaNodeScope, HtmlSchemaNodeTypeEnum, HtmlSchemaNode, HtmlSlotDataType, HtmlSlotSchemaNode, HtmlStageAttachment, SyntaxKeyEnum, VariableScopeEnum, GenerateCustomSlotParams, CustomSlotHandleTypeEnum, InterfaceEvents, EditEvents, HtmlConstraintFields, LoopHtmlStageProxyParams, RenderHtmlStageProxyParams, DropPositionEnum, DragOverParams, HtmlComponentKeys, NodeTriggerTypeEnum, TriggerTypeEnum } from './common'; import { isShallowChild } from './common/utils'; import { UndoRedo } from '../../../common/undoRedo'; import { HtmlStage } from '../../parts/htmlStage'; import { Functional } from '../../parts/functional'; import { StyleAccess } from '../../parts/styleAccess'; import update from 'ts-toolset/dist/common/immutability'; import { tryJSONParse } from 'ts-toolset/dist/common/json'; import { HtmlSchema } from '../../../workbench/parts/htmlSchema'; import { isEmptyObject, isFunction, isString, isUndefined } from 'ts-toolset/dist/common/types'; import { OperatePositionEnum, OperateTypeEnum, TreeDndDatasetHandleKeys } from '../../../common/tree'; /** 视图层 部件集合 */ export type InitHtmlInterfaceIntegratorParams = Omit; export interface IHtmlInterfaceIntegrator extends IInital { /** 加载协议数据 */ //loadSchema(): void; } @injectable() export class HtmlInterfaceIntegrator implements IHtmlInterfaceIntegrator { private readonly _schemaTree: LinkedTree; private readonly _cssRuleStore: Map; private readonly _componentStore: Map; private readonly _templateSchemaStore: Map; private readonly _schemaUndoManager: UndoRedo; constructor( // services @inject(ServicesIoc.Identifier.MapStore) private _mapStore: MapStore, @inject(ServicesIoc.Identifier.EventBus) private _eventBus: EventBus, // parts @inject(WorkbenchIoc.PartsIdentifier.HtmlSchema) private _htmlSchema: HtmlSchema, @inject(WorkbenchIoc.PartsIdentifier.HtmlStage) private _htmlStage: HtmlStage, @inject(WorkbenchIoc.PartsIdentifier.Functional) private _functional: Functional, @inject(WorkbenchIoc.PartsIdentifier.StyleAccess) private _styleAccess: StyleAccess ) { const globalStore = this._mapStore.get(MapStoreKeys.globalStore)!; this._schemaTree = globalStore.get(globalStoreKeys.schemaTree); this._cssRuleStore = this._mapStore.get(MapStoreKeys.cssRuleStore)!; this._componentStore = this._mapStore.get(MapStoreKeys.componentStore)!; this._templateSchemaStore = this._mapStore.get(MapStoreKeys.templateSchemaStore)!; const undoManagerStore = this._mapStore.get(MapStoreKeys.undoManagerStore)!; this._schemaUndoManager = undoManagerStore.get(UndoManagerKeys.htmlSchemaUndoManager)!; this._eventBus .register(InterfaceEvents.Stage.renderStage, this._renderHtmlStage, { scope: this }) .register(InterfaceEvents.Stage.onOverProxy, this._overHtmlStageProxy, { scope: this }) .register(InterfaceEvents.Stage.onClickProxy, this._clickHtmlStageProxy, { scope: this }) .register(InterfaceEvents.Stage.onDragStart, this._htmlStageProxyDragStart, { scope: this }) .register(InterfaceEvents.Stage.onDragOver, this._htmlStageProxyDragOver, { scope: this }) .register(InterfaceEvents.Stage.onDrop, this._htmlStageProxyDrop, { scope: this }) .register(InterfaceEvents.Stage.onDragEnd, this._htmlStageProxyDragEnd, { scope: this }) .register(InterfaceEvents.Stage.onOverRootProxy, this._clearOverHtmlStageProxy, { scope: this }) .register(InterfaceEvents.Stage.onContextMenuProxy, this._contextMenuHtmlStageProxy, { scope: this }) .register(InterfaceEvents.Stage.onLeaveRootProxy, this._clearOverHtmlStageProxy, { scope: this }) .register(InterfaceEvents.Stage.onDragOverRootProxy, this._clearDropHtmlStageRootProxy, { scope: this }) .register(InterfaceEvents.Stage.onDropRootProxy, this._dropHtmlStageRootProxy, { scope: this }) .register(InterfaceEvents.Stage.onClickRootProxy, this._onClickHtmlStageRootProxy, { scope: this }) .register(InterfaceEvents.Stage.renderStage, this._renderHtmlStage, { scope: this }) .register(InterfaceEvents.Stage.renderProxy, this._renderHtmlStageProxy, { scope: this }) .register(InterfaceEvents.DndSource.onAddDndSource, this._addComponentDndSource, { scope: this }) .register(InterfaceEvents.HtmlSchema.operateSchemaNode, this._htmlSchemaOperate, { scope: this }) .register(InterfaceEvents.HtmlSchema.undo, this._htmlSchemaUndo, { scope: this }) .register(InterfaceEvents.HtmlSchema.redo, this._htmlSchemaRedo, { scope: this }) .register(InterfaceEvents.HtmlSchema.updateData, this._htmlSchemaUpdate, { scope: this }) } /** 加载部件需要的内容 */ initial({ stageDoc, stageProxyLayer }: InitHtmlInterfaceIntegratorParams) { this._htmlStage.setStageAttachment({ stageDoc, stageProxyLayer, schemaTree: this._schemaTree }); this._styleAccess.initStyleSheet(stageDoc); } /** 协议节点的操作方式 */ private _mapSchemaTreeHandle: any = { [HtmlSchemaNodeTypeEnum.syntax]: (node: HtmlSchemaNode, scope: LoopSchemaNodeScope, children: any) => { let result: any = undefined, slots = node.data.slotData as HtmlSlotSchemaNode | undefined, loopReturn = scope.global.loopReturn && scope.global.loopReturn.syntax; if (!isUndefined(slots)) { if (!isUndefined(node.data.syntaxData)) { if (node.data.syntaxData.variable) { let variable = this._functional.getVariable(node.data.syntaxData.variable.key); if (!isUndefined(variable)) { if (node.data.syntaxData.key === SyntaxKeyEnum.for && Array.isArray(variable.value)) { result = []; variable.value.forEach((v, i) => { let newScope = { // 处理for参数 current: { ...scope.current, keyPad: '_' + i }, global: { ...scope.global, variableValues: { ...scope.global.variableValues, ...v } } }; let newChildren = this.mapSchemaTree(slots!.children!, { global: newScope.global }); Array.isArray(newChildren) && newChildren.push(children); let temp = this._mapSchemaTreeHandle[HtmlSchemaNodeTypeEnum.component](node, newScope, newChildren); result.push(temp); }); } else if (node.data.syntaxData.key === SyntaxKeyEnum.switch && isString(variable.value)) { let newChildren = this.mapSchemaTree(slots!.slots[variable.value], { global: scope.global }); Array.isArray(newChildren) && newChildren.push(children); result = this._mapSchemaTreeHandle[HtmlSchemaNodeTypeEnum.component](node, scope, newChildren); // 默认有子节点,放置时替换子节点 } else if (node.data.syntaxData.key === SyntaxKeyEnum.if) { if (variable.value !== false) { let newChildren = this.mapSchemaTree(slots!.children!, { global: scope.global }); Array.isArray(newChildren) && newChildren.push(children); result = this._mapSchemaTreeHandle[HtmlSchemaNodeTypeEnum.component](node, scope, newChildren); } } } } if (node.data.syntaxData.key === SyntaxKeyEnum.slot) { if (scope.current && scope.current.slotData) { if (slots.slots && slots.slots._slot) { let newChildren = scope.current.slotData.slots[slots.slots._slot]; Array.isArray(newChildren) && newChildren.push(children); result = this._mapSchemaTreeHandle[HtmlSchemaNodeTypeEnum.component](node, scope, newChildren); } else if (slots.customs && slots.customs._slot) { if (scope.current.slotData.customs && !isEmptyObject(scope.current.slotData.customs)) { let fn = scope.current.slotData.customs![slots.customs._slot._slot!]._default_fn; if (isFunction(fn)) { let newChildren = fn(); Array.isArray(newChildren) && newChildren.push(children); result = this._mapSchemaTreeHandle[HtmlSchemaNodeTypeEnum.component](node, scope, newChildren); } } } } } } } return loopReturn ? loopReturn(node, result) : result; }, [HtmlSchemaNodeTypeEnum.template]: (node: HtmlSchemaNode, scope: LoopSchemaNodeScope) => { let result: any = undefined, slots = node.data.slotData as HtmlSlotSchemaNode | undefined, loopReturn = scope.global.loopReturn && scope.global.loopReturn.template; if (slots && slots.template) { let htmlSlotData: HtmlSlotDataType = { slots: {} }; this._mapSlotSchemaTree(node, scope, htmlSlotData, node.data.slotData && node.data.slotData.customs); // 第三个参数生成的函数转换出来 scope.current = scope.current || {}; scope.current.templateId = node.data.id; scope.current.slotData = htmlSlotData; htmlSlotData.template = this.mapSchemaTree(slots!.template, scope); result = loopReturn ? loopReturn(node, { htmlSlotData, scope }) : htmlSlotData.template; } return result; }, [HtmlSchemaNodeTypeEnum.component]: (node: HtmlSchemaNode, scope: LoopSchemaNodeScope, children: any) => { let result: any = undefined, loopReturn = scope.global.loopReturn && scope.global.loopReturn.component; const entity = this._componentStore.get(node.data.key); if (entity) { let params = { ...scope.global, ...scope.current }; !isShallowChild(node) && params.keyPad && Reflect.deleteProperty(params, HtmlConstraintFields.keyPad); const componentProps = this.getComponentProps(node.data, entity, params), htmlSlotData: HtmlSlotDataType = { slots: {}, children: node.data.isLeaf ? null : children }; node.data.type !== HtmlSchemaNodeTypeEnum.syntax && this._mapSlotSchemaTree(node, { global: scope.global }, htmlSlotData, componentProps); result = loopReturn && loopReturn(node, { componentProps, entity, htmlSlotData, scope }); } return result; } } private _generateCustomSlotProps({ node, sourceProps, targetProps, scope, htmlSlot }: GenerateCustomSlotParams) { const fn = (...args: any[]) => { const argsObj: any = {}; Array.isArray(htmlSlot.params) && htmlSlot.params.forEach((k, i) => argsObj[k] = args[i]); scope.global.variableValues = isUndefined(htmlSlot.paramWrapKey) ? { ...scope.global.variableValues, ...argsObj } : { ...scope.global.variableValues, [htmlSlot.paramWrapKey]: argsObj }; return this.mapSchemaTree(node, scope); }; const spec = tryJSONParse(htmlSlot.path, { reviver: (k, v) => { let value: any = v; if (k === CustomSlotHandleTypeEnum.moia && Array.isArray(v) && v.length === 4 && !isUndefined(targetProps)) { value[1] = targetProps; value[3] = fn; } if (k === CustomSlotHandleTypeEnum.update) { value[1] = fn; } return value; } }); update(sourceProps, spec); } /** 遍历插槽协议节点 */ private _mapSlotSchemaTree(node: HtmlSchemaNode, scope: LoopSchemaNodeScope, htmlSlotData: HtmlSlotDataType, sourceProps: any = {}) { let slots = node.data.slotData as HtmlSlotSchemaNode | undefined; if (!isUndefined(slots)) { // 处理子节点 if (!node.data.isLeaf && !isUndefined(slots.children)) { let result = this.mapSchemaTree(slots.children, { global: scope.global }); let loopReturn = scope.global.loopReturn && scope.global.loopReturn.children; htmlSlotData.children = loopReturn ? loopReturn(node, result) : result; } // 处理插槽节点 if (slots.slots && !isEmptyObject(slots.slots)) { let loopReturn = scope.global.loopReturn && scope.global.loopReturn.slots; Object.keys(slots.slots).forEach(key => { let result = this.mapSchemaTree(slots!.slots[key], { global: scope.global }); htmlSlotData.slots[key] = loopReturn ? loopReturn(node, { result, scope }) : result; }); } // 处理自定义插槽节点 if (slots.customs && !isEmptyObject(slots.customs)) { if (scope.global.isUpdateLoopParams) { const entity = node.data.type === HtmlSchemaNodeTypeEnum.template ? this._templateSchemaStore.get(node.data.key) : this._componentStore.get(node.data.key); if (!isUndefined(entity)) { Object.keys(slots.customs).forEach(key => { let htmlSlot = entity.slots!.find((so) => key === so.key)!; if (!isUndefined(htmlSlot)) { Object.keys(slots!.customs![key]).forEach(cus_key => cus_key !== HtmlConstraintFields._default_fn && this._generateCustomSlotProps({ scope, htmlSlot, sourceProps, targetProps: cus_key, node: slots!.customs![key][cus_key] }) ); } }); } } htmlSlotData.customs = slots!.customs; } } } /** 遍历协议节点 */ mapSchemaTree(scope: LoopSchemaNodeScope): any mapSchemaTree(node: HtmlSchemaNode, scope: LoopSchemaNodeScope): any mapSchemaTree(node: any, scope?: any): any { if (arguments.length === 1) { scope = node; node = this._schemaTree.root; } return this._schemaTree.map(node, (current, children) => { if (current.data.isSchemaRootNode) return children; const handle = this._mapSchemaTreeHandle[current.data.type]; return handle && handle(current, scope, children); }); } //#region -- HtmlComponent public getComponentProps(data: IHtmlSchemaNode, entity: IHtmlComponentEntity, params: LoopSchemaNodeParams) { let componentProps: any = { [params.key!]: data.id }; if (params.keyPad) { componentProps[params.key!] = componentProps[params.key!] + params.keyPad; } entity.props.forEach(prop => componentProps[prop.key] = prop.defaultValue); const cssData = (data.cssData || {}); componentProps = { ...componentProps, ...data.propData, ...cssData.styles, className: (cssData.classNames || []).join(' ') }; data.variableData && data.variableData.forEach(v => { let value: any = undefined; // 根据配置的作用域,获取要绑定的变量 if (v.scope === VariableScopeEnum.current) { params.variableValues && (value = params.variableValues[v.key]); } else { let variable = this._functional.getVariable(v.key, params.templateId); variable && (value = variable.value); } // 根据配置取值的路径 获取值 v.readPath && (value = getObjectNest(value, v.readPath)); assignDeep(v.bindPath, componentProps, value); }); // data.id 作为组件的唯一标识,用来获取 dom 元素,各类库组件 对 class 的属性支持最好,所以放到这个属性上,data-*, id 之类的属性可能会被吃掉 componentProps.className = (data.id + ' ' + componentProps.className).trim(); return componentProps; } //#endregion //#region -- HtmlStage /** 渲染视图层,触发渲染事件 */ private async _renderHtmlStage() { await this._eventBus.emitAsync(InterfaceEvents.Stage.onRenderStage); this._renderHtmlStageProxy(); } private _mapHtmlStageProxyHandle: any = { [HtmlSchemaNodeTypeEnum.syntax]: (node: HtmlSchemaNode, loopParams: LoopHtmlStageProxyParams) => { let slots = node.data.slotData as HtmlSlotSchemaNode | undefined; if (!isUndefined(slots)) { if (!isUndefined(node.data.syntaxData)) { if (node.data.syntaxData.variable) { let variable = this._functional.getVariable(node.data.syntaxData.variable.key); if (!isUndefined(variable)) { if (node.data.syntaxData.key === SyntaxKeyEnum.for && Array.isArray(variable.value)) { this._parseNestHtmlStageProxy(slots!.children!, loopParams); } else if (node.data.syntaxData.key === SyntaxKeyEnum.switch && isString(variable.value)) { this._parseNestHtmlStageProxy(slots!.slots[variable.value], loopParams); } else if (node.data.syntaxData.key === SyntaxKeyEnum.if) { variable.value !== false && this._parseNestHtmlStageProxy(slots!.children!, loopParams); } } } } } }, common: (node: HtmlSchemaNode, loopParams: LoopHtmlStageProxyParams) => { let slots = node.data.slotData as HtmlSlotSchemaNode | undefined; if (!isUndefined(slots)) { // 处理子节点 if (!node.data.isLeaf && !isUndefined(slots.children)) { this._parseNestHtmlStageProxy(slots.children, loopParams); } // 处理插槽节点 if (slots.slots && !isEmptyObject(slots.slots)) { Object.keys(slots.slots).forEach(key => { this._parseNestHtmlStageProxy(slots!.slots[key], loopParams); }); } // 处理自定义插槽节点 if (slots.customs && !isEmptyObject(slots.customs)) { Object.keys(slots.customs).forEach(key => { Object.keys(slots!.customs![key]).forEach(cus_key => cus_key !== HtmlConstraintFields._default_fn && this._parseNestHtmlStageProxy(slots!.customs![key][cus_key], loopParams) ); }); } } } } private _parseNestHtmlStageProxy(node: HtmlSchemaNode, loopParams: LoopHtmlStageProxyParams = {}) { this._htmlStage.renderProxy(node, loopParams); this._schemaTree.map(node, (current, children) => { if (current.data.isSchemaRootNode) return children; const handle = this._mapHtmlStageProxyHandle[current.data.type] || this._mapHtmlStageProxyHandle.common; return handle && handle(current, { slotWrapParentNode: loopParams.slotWrapNode, slotWrapNode: current }); }); } /** 渲染代理层,触发渲染事件 */ private async _renderHtmlStageProxy({ parseProxy = true, changeStates = true }: RenderHtmlStageProxyParams = {}) { if (parseProxy) { this._parseNestHtmlStageProxy(this._schemaTree.root); await this._eventBus.emitAsync(InterfaceEvents.Stage.onRenderProxy); } // 操作层 依赖代理层计算后的布局 做计算 changeStates && this._eventBus.emit(InterfaceEvents.Stage.onChangeProxyState, this._htmlStage.operateStates, this._htmlStage.operateStateNodes); } /** 生成协议节点 */ private _generateHtmlSchemaNode(keys: HtmlComponentKeys) { let key = `${keys.libraryKey}.${keys.key}`; let entity = this._componentStore.get(key); let node: HtmlSchemaNode | undefined; if (entity) { node = new LinkedTreeNode({ id: guid(), trigger: NodeTriggerTypeEnum.default, key, type: HtmlSchemaNodeTypeEnum.component, isLeaf: entity.isLeaf, visible: true, propData: entity.defaultProps && deepClone(entity.defaultProps) }); } return node; } /** 判断是否可以拖拽 */ public proxyLayerCanDrag(node?: HtmlSchemaNode) { return this._htmlStage.canDrag(node); } private _addComponentDndSource = (keys: HtmlComponentKeys) => { let schemaNode = this._generateHtmlSchemaNode(keys); if (schemaNode) { this._htmlSchemaOperate( OperateTypeEnum.add, OperatePositionEnum.children, schemaNode, this._htmlStage.selectedNode ?? this._schemaTree.root ) this._renderHtmlStage(); } } // private _dragComponentDndSource = (keys: HtmlComponentKeys) => { // let schemaNode = getHtmlSchemaNode(this._componentStore, keys); // schemaNode && this._htmlStage.dragStart(schemaNode); // } /** 经过视图层的 代理层 */ private _overHtmlStageProxy(node: HtmlSchemaNode) { this._htmlStage.onOverNode(node); this._renderHtmlStageProxy(); } /** 点击视图层的 代理层 */ private _clickHtmlStageProxy(node: HtmlSchemaNode) { this._htmlStage.onSelectNode(node); this._renderHtmlStageProxy(); if (node.data.trigger !== TriggerTypeEnum.slot) { this._eventBus.emit(EditEvents.onInitEditData, node.data); } } /** 右击菜单 的代理层 */ private _contextMenuHtmlStageProxy(node: HtmlSchemaNode) { this._htmlStage.onContextMenu(node); this._renderHtmlStageProxy(); } /** 视图层的 代理层 开始拖拽 */ private _htmlStageProxyDragStart(node: HtmlSchemaNode) { if (this._htmlStage.canDrag(node)) { this._htmlStage.dragStart(node); this._renderHtmlStageProxy(); } } /** 拖拽经过视图层的 代理层 */ private _htmlStageProxyDragOver(params: DragOverParams) { if (this._htmlStage.canDrag()) { // 拖拽经过,判断是否渲染代理层(根据浏览器 经过事件 的机制,这里会有频繁的调用,做防抖处理) if (!this._htmlStage.dropNode || params.overNode.data.id !== this._htmlStage.dropNode.data.id) { this._htmlStage.dragOver(params.overNode); // 渲染代理层,加载 拖拽经过 的状态 this._renderHtmlStageProxy(); } params.coord ? this._htmlStage.onChangeDropPosition(params.coord) : this._htmlStage.onChangeDropPosition(DropPositionEnum.center); this._eventBus.emit(InterfaceEvents.Stage.onChangeDropPosition, this._htmlStage.dropPosition); } } /** 放置节点 到视图层的 代理层 */ private _htmlStageProxyDrop(node: HtmlSchemaNode) { // 判断是否可以放置 if (this._htmlStage.canDrop(node)) { this._htmlStage.drop(node); this._htmlSchemaOperate( this._htmlStage.operateType!, this._htmlStage.operatePosition!, this._htmlStage.dragNode!, this._htmlStage.dropNode! ); } } /** 视图层的 代理层 拖拽结束 */ private _htmlStageProxyDragEnd() { this._htmlStage.dragEnd(); this._renderHtmlStageProxy(); this._eventBus.emit(InterfaceEvents.Stage.onChangeDropPosition); } /** 放置 根节点 */ private _dropHtmlStageRootProxy() { if (this._htmlStage.dragNode) { this._htmlStage.dropRootNode(this._schemaTree.root); this._htmlSchemaOperate( this._htmlStage.operateType!, this._htmlStage.operatePosition!, this._htmlStage.dragNode!, this._htmlStage.dropNode! ); this._clearDropHtmlStageRootProxy(); } } /** 清理 放置视图层的 代理层 */ private _clearDropHtmlStageRootProxy() { if (this._htmlStage.dropNode) { this._htmlStage.onClearDrop(); this._renderHtmlStageProxy(); } } /** 清理 经过视图层的 代理层 */ private _clearOverHtmlStageProxy() { if (this._htmlStage.overNode) { this._htmlStage.onClearOver(); this._renderHtmlStageProxy(); } } /** 清理 选中视图层的 代理层 */ private _onClickHtmlStageRootProxy() { if (this._htmlStage.selectedNode) { this._htmlStage.onClearSelected(); this._renderHtmlStageProxy(); } } //#endregion //#region -- HtmlSchema private _isUndoReodoSelectedNode() { let record = this._schemaUndoManager.getRecord()!; return this._htmlStage.selectedNode && record.restore.action === TreeDndDatasetHandleKeys.updateData && ((this._schemaUndoManager.isMin ? record.restore.params[0] : record.set.params[0]) || {}).id === this._htmlStage.selectedNode.data.id; } private _htmlSchemaUndo() { if (!this._schemaUndoManager.isMin) { let reset = this._isUndoReodoSelectedNode(); this._schemaUndoManager.undo(); // 重新渲染布局 this._renderHtmlStage(); // 重置编辑器的值 reset && this._eventBus.emit(EditEvents.onInitEditData, this._htmlStage.selectedNode!.data, false); } } private _htmlSchemaRedo() { if (!this._schemaUndoManager.isMax) { let reset = this._isUndoReodoSelectedNode(); this._schemaUndoManager.redo(); this._renderHtmlStage(); // 重置编辑器的值 reset && this._eventBus.emit(EditEvents.onInitEditData, this._htmlStage.selectedNode!.data, false); } } private _htmlSchemaOperate( type: OperateTypeEnum, position: OperatePositionEnum, operateTarget: HtmlSchemaNode, effectTarget?: HtmlSchemaNode ) { let record = this._htmlSchema.getOperateUndoReodoRecord(type, position, operateTarget, effectTarget); this._schemaUndoManager.setAndStoreRecord(record); this._renderHtmlStage(); } private _htmlSchemaUpdate(tree: HtmlSchemaNode, changes: any) { let record = this._htmlSchema.getUpdateDataRedoRecord(tree, changes); this._schemaUndoManager.setAndStoreRecord(record); this._renderHtmlStage(); } //#endregion }