UNPKG

devexpress-reporting

Version:

DevExpress Reporting provides the capability to develop a reporting application to create and customize reports.

413 lines (412 loc) 19.6 kB
/** * DevExpress HTML/JS Reporting (designer\internal\scripting\_scriptsEditor.js) * Version: 25.2.3 * Build date: Dec 15, 2025 * Copyright (c) 2012 - 2025 Developer Express Inc. ALL RIGHTS RESERVED * License: https://www.devexpress.com/Support/EULAs/universal.xml */ import { ActionListsBase, extend, getControlFullName, getParentContainer, getUniqueName, replaceInvalidSymbols, ShowMessage } from '@devexpress/analytics-core/analytics-internal'; import { getLocalization } from '@devexpress/analytics-core/analytics-utils'; import { createViewModelGenerator } from '@devexpress/analytics-core/analytics-serializer-native'; import * as ko from 'knockout'; import { ReportScriptService } from '../../services/_reportScriptService'; import { eventArgsTypes } from './_eventArgsTypes'; import { LanguageHelper } from './_languageHelper'; import { ReportDummyCreator } from './_reportDummyCreator'; export class ScriptsEditor extends ActionListsBase { _changeSelection(textToSelect) { const editorContainer = this.editorContainer(); if (editorContainer) { editorContainer.find(textToSelect, { backwards: false, wrap: false, caseSensitive: false, wholeWord: true, regExp: false }, true); editorContainer.findNext(); editorContainer.findPrevious(); } } createActionViewModel(action, index) { const viewModel = super.createActionViewModel(action, index); if ('controls' in action) { const selectBoxActionViewModel = createViewModelGenerator(viewModel) .generateProperty('getPopupContainer', getParentContainer) .generateProperty('widget', createViewModelGenerator() .generateProperty('dataSource', ko.unwrap(action.controls)) .generateProperty('value', ko.unwrap(action['control'])) .generateProperty('width', '300px') .generateProperty('onValueChanged', (e) => { action['control'](e.value); }) .generateProperty('displayExpr', action.displayExpr) .generateProperty('placeholder', getLocalization('Select control...', 'ASPxReportsStringId.ReportDesigner_ScriptEditor_Toolbar_SelectControl')) .getViewModel()) .getViewModel(); this.subscribe(action, 'controls', () => selectBoxActionViewModel.widget.dataSource = ko.unwrap(action.controls)); this.subscribe(action, 'control', () => selectBoxActionViewModel.widget.value = ko.unwrap(action['control'])); return selectBoxActionViewModel; } if ('events' in action) { const selectBoxActionViewModel = createViewModelGenerator(viewModel) .generateProperty('getPopupContainer', getParentContainer) .generateProperty('widget', createViewModelGenerator() .generateProperty('dataSource', ko.unwrap(action.events)) .generateProperty('width', '200px') .generateProperty('value', ko.unwrap(action['event'])) .generateProperty('onValueChanged', (e) => { action['event'](e.value); }) .generateProperty('displayExpr', action.displayExpr) .generateProperty('placeholder', getLocalization('Select event...', 'ASPxReportsStringId.ReportDesigner_ScriptEditor_Toolbar_SelectEvent')) .getViewModel()) .getViewModel(); this.subscribe(action, 'events', () => selectBoxActionViewModel.widget.dataSource = ko.unwrap(action.events)); this.subscribe(action, 'event', () => selectBoxActionViewModel.widget.value = ko.unwrap(action['event'])); return selectBoxActionViewModel; } return viewModel; } _initializeToolbar() { const self = this, copyText = ko.observable(''); this.toolbarItems.push({ text: 'Cut', displayText: () => getLocalization('Cut', 'AnalyticsCoreStringId.EditCut'), imageClassName: 'dxrd-image-cut', imageTemplateName: 'dxrd-svg-toolbar-cut', disabled: ko.pureComputed(() => { return !self.report() || !self._selectionNotEmpty(); }), visible: true, clickAction: () => { copyText(self.editorContainer().getCopyText()); self.editorContainer().execCommand('cut'); }, hotKey: { ctrlKey: true, keyCode: 'X'.charCodeAt(0) } }); this.toolbarItems.push({ text: 'Copy', displayText: () => getLocalization('Copy', 'AnalyticsCoreStringId.Cmd_Copy'), imageClassName: 'dxrd-image-copy', imageTemplateName: 'dxrd-svg-toolbar-copy', disabled: ko.pureComputed(() => { return !self.report() || !self._selectionNotEmpty(); }), visible: true, clickAction: () => { copyText(self.editorContainer().getCopyText()); }, hotKey: { ctrlKey: true, keyCode: 'C'.charCodeAt(0) } }); this.toolbarItems.push({ text: 'Paste', displayText: () => getLocalization('Paste', 'AnalyticsCoreStringId.Cmd_Paste'), imageClassName: 'dxrd-image-paste', imageTemplateName: 'dxrd-svg-toolbar-paste', disabled: ko.pureComputed(() => { return !self.report() || !copyText(); }), visible: true, clickAction: () => { self.editorContainer().onPaste(copyText()); }, hotKey: { ctrlKey: true, keyCode: 'V'.charCodeAt(0) } }); this.toolbarItems.push({ text: 'Delete', displayText: () => getLocalization('Delete', 'AnalyticsCoreStringId.Cmd_Delete'), imageClassName: 'dxrd-image-delete', imageTemplateName: 'dxrd-svg-toolbar-delete', disabled: ko.pureComputed(() => { return !self.report() || !self._selectionNotEmpty(); }), visible: true, clickAction: () => { self.editorContainer().execCommand('del'); } }); this.toolbarItems.push({ text: 'Undo', displayText: () => getLocalization('Undo', 'AnalyticsCoreStringId.Undo'), imageClassName: 'dxrd-image-undo', imageTemplateName: 'dxrd-svg-toolbar-undo', disabled: ko.pureComputed(() => { return !self.report() || !self._canUndo(); }), visible: true, clickAction: () => { self.editorContainer().undo(false); self._updateEditorState(); }, hotKey: { ctrlKey: true, keyCode: 'Z'.charCodeAt(0) }, hasSeparator: true }); this.toolbarItems.push({ text: 'Redo', displayText: () => getLocalization('Redo', 'AnalyticsCoreStringId.Redo'), imageClassName: 'dxrd-image-redo', imageTemplateName: 'dxrd-svg-toolbar-redo', disabled: ko.pureComputed(() => { return !self.report() || !self._canRedo(); }), visible: true, clickAction: () => { self.editorContainer().redo(false); self._updateEditorState(); }, hotKey: { ctrlKey: true, keyCode: 'Y'.charCodeAt(0) }, }); this.toolbarItems.push({ text: 'Controls', disabled: ko.pureComputed(() => { return !this.report(); }), visible: this.editorVisible, control: self.selectedControl, controls: self.controls, displayExpr: (value) => getControlFullName(value), templateName: 'dxrd-scripting-selectboxaction', hasSeparator: true }); this.toolbarItems.push({ text: 'Events', disabled: ko.pureComputed(() => { return !this.report(); }), visible: this.editorVisible, events: self.eventsCollection, event: self.selectedEvent, templateName: 'dxrd-scripting-selectboxaction' }); this.toolbarItems.push({ text: 'Validate', displayText: () => getLocalization('Validate', 'ReportStringId.ScriptEditor_Validate'), imageClassName: 'dxrd-image-validate', imageTemplateName: 'dxrd-svg-toolbar-validate', disabled: ko.pureComputed(() => { return !self.report() || self.validateDisabled() || !self.editorContainer(); }), visible: this.editorVisible, hotKey: { altKey: true, ctrlKey: undefined, keyCode: 'L'.charCodeAt(0) }, clickAction: () => { self.validateDisabled(true); self._setScriptsText(); ReportScriptService.validateScripts(self.report()).done(function (result) { if (!self.editorContainer()) return; const errors = []; result.forEach((error) => { const linesCount = self.editorContainer().getSession().getLength(); errors.push({ row: self._getValidIndex(error.Line, linesCount), column: self._getValidIndex(error.Column, linesCount), text: error.ErrorNumber + ' - ' + error.ErrorText, type: error.IsWarning ? 'warning' : 'error' }); }); self.editorContainer().getSession().setAnnotations(errors); self.validateDisabled(false); if (errors.length > 0) { ShowMessage(getLocalization('The script contains error(s).', 'ASPxReportsStringId.ReportDesigner_ScriptValidation_Message_ContainsErrors')); } else { ShowMessage(getLocalization('The script is valid.', 'ASPxReportsStringId.ReportDesigner_ScriptValidation_Message_ValidScript'), 'success'); } }) .fail(function (result) { ShowMessage(getLocalization('Impossible to validate the script.', 'ASPxReportsStringId.ReportDesigner_ScriptValidation_Error')); self.validateDisabled(false); }); }, hasSeparator: true }); this.toolbarItems.push({ text: 'Design', displayText: () => getLocalization('Design', 'ReportStringId.RepTabCtl_Designer'), imageClassName: 'dxrd-image-design', imageTemplateName: 'dxrd-svg-preview-report_designer', disabled: ko.pureComputed(() => { return !this.report(); }), visible: this.editorVisible, hotKey: { ctrlKey: true, keyCode: 'P'.charCodeAt(0) }, clickAction: () => { self._setScriptsText(); self.editorVisible(false); }, hasSeparator: true }); } _getValidIndex(errorPosition, linesCount) { const position = errorPosition <= linesCount ? Math.max(1, errorPosition) : 1; return Math.max(0, position - 1); } _setScriptsText() { const editorContainer = this.editorContainer(); if (editorContainer) { this.scriptsText(editorContainer.getValue()); } } _getFunctionName(row) { let name = ''; const allLines = this.editorContainer().getSession().getDocument().getAllLines(); for (let rowIndex = row; rowIndex >= 0; rowIndex--) { if (name) { break; } for (let index = 0; index < this.allFunctionNames.length; index++) { if (allLines[rowIndex].indexOf(this.allFunctionNames[index]) !== -1) { name = this.allFunctionNames[index]; break; } } } return name; } _getEventByFunction(control, currentFunctionName) { let result = ''; Object.keys(control.scripts).some((propertyName) => { if (result) { return true; } const eventName = propertyName; if (ko.unwrap(control.scripts[eventName]) === currentFunctionName && eventName.indexOf('on') === 0) { result = eventName.substring(2); } return false; }); return result; } static generateFunctionName(control, eventName, functionName, allFunctionNames = []) { if (functionName || ko.unwrap(control.name)) { return replaceInvalidSymbols(functionName || (ko.unwrap(control.name) + '_' + eventName)); } else { return getUniqueName(allFunctionNames, eventName); } } static getEventArgsType(eventName) { return eventArgsTypes[eventName] || 'System.EventArgs'; } initialize() { const self = this; ReportScriptService.setCodeDom('', JSON.stringify({ 'XtraReportsLayoutSerializer': ReportDummyCreator._createDummy(this.report().serialize()) })).done((result) => { this.guid(result.Guid); }); } constructor(report, allControls) { super(); this._selectionNotEmpty = ko.observable(false); this._canUndo = ko.observable(false); this._canRedo = ko.observable(false); this._cursorPosition = ko.observable().extend({ throttle: 100 }); this.guid = ko.observable(null); this.editorContainer = ko.observable(); this.editorVisible = ko.observable(false); this.toolbarItems = []; this.controls = ko.observableArray([]); this.selectedControl = ko.observable(); this.eventsCollection = ko.observable([]); this.selectedEvent = ko.observable(); this.validateDisabled = ko.observable(false); this.aceOptions = { enableBasicAutocompletion: true, enableSnippets: true, enableLiveAutocompletion: true, showPrintMargin: false }; const self = this; let cursorPositionChanging = false; this.languageHelper = new LanguageHelper(report); this._updateEditorState = () => { if (this.editorContainer() && this.editorContainer().getSession()) { this._canUndo(this.editorContainer().getSession().getUndoManager().hasUndo()); this._canRedo(this.editorContainer().getSession().getUndoManager().hasRedo()); } }; this.selectionChanged = (editor) => { this._selectionNotEmpty(!editor.getSelection().isEmpty()); this._updateEditorState(); editor.focus(); }; this.report = report; this.scriptsText = ko.pureComputed({ read: () => { return this.report() && this.report().scriptsSource(); }, write: (newText) => { this.report() && this.report().scriptsSource(newText); } }); this._initializeToolbar(); this.editorContainer.subscribe(editor => { if (editor.getSession()) { editor.getSession().getSelection().on('changeSelection', () => { this.selectionChanged(editor); }); editor.getSession().getSelection().on('changeCursor', () => { self._cursorPosition(editor.getCursorPosition()); }); } }); this._cursorPosition.subscribe((currentCursorPosition) => { const currentFunctionName = this._getFunctionName(currentCursorPosition.row); let control, event; this.controls().some((ctrl) => { control = ctrl; event = this._getEventByFunction(ctrl, currentFunctionName); return !!event; }); if (control && event) { try { cursorPositionChanging = true; this.selectedControl(control); this.selectedEvent(event); } finally { cursorPositionChanging = false; } } }); this._disposables.push(ko.computed(() => { this.controls(allControls().filter((control) => { return !!control.scripts && !control.lockedInUserDesigner(); })); })); this.selectedControl.subscribe((newSelectedControl) => { let eventsList = []; if (newSelectedControl) { const info = newSelectedControl.getInfo(); const scripts = info.filter(x => x.propertyName === 'scripts')[0]; eventsList = scripts && scripts.info.filter((x) => ko.unwrap(x.visible) !== false && !ko.unwrap(x.disabled)).map((item) => { return item.propertyName.indexOf('on') === 0 ? item.propertyName.substring(2) : item.propertyName; }); } this.selectedEvent(''); this.eventsCollection(eventsList); }); this._ensureFunction = (functionName, eventArgsType) => { const editorContainer = this.editorContainer(); if (editorContainer) { const editorContent = editorContainer.getValue(); if (editorContent.indexOf(functionName) === -1) { const newEventHandler = this.languageHelper.createNewHandler(functionName, eventArgsType); const resultScripts = editorContent.concat(newEventHandler); this.report().scriptsSource(resultScripts); editorContainer.setValue(resultScripts); } } }; this.ensureEvent = (eventName, functionName, model) => { let selectedControl = model; if (!selectedControl) { selectedControl = this.selectedControl(); } functionName = ScriptsEditor.generateFunctionName(selectedControl, eventName, functionName, this.allFunctionNames); const eventArgsType = ScriptsEditor.getEventArgsType(eventName); this._ensureFunction(functionName, eventArgsType); this._changeSelection(functionName); selectedControl.scripts['on' + eventName](functionName); this.selectedControl(selectedControl); if (this.controls.indexOf(selectedControl) === -1) { this.controls.push(extend({ displayExpr: selectedControl.name }, selectedControl)); } this.selectedEvent(eventName); }; this.selectedEvent.subscribe((newEvent) => { if (!cursorPositionChanging && newEvent) { const selectedControl = this.selectedControl(); const selectedControlNewEvent = selectedControl && selectedControl.scripts['on' + newEvent]; const newEventFunction = selectedControlNewEvent && selectedControlNewEvent(); if (!newEventFunction || this.allFunctionNames.indexOf(newEventFunction) === -1) { this.ensureEvent(newEvent); } else { this._changeSelection(newEventFunction); } } }); } get allFunctionNames() { return this.scriptsText() ? this.languageHelper.getFunctionNamesFromScript(this.scriptsText()) : []; } }