UNPKG

@syncfusion/ej2-richtexteditor

Version:
1,094 lines 62 kB
/* * table-module.ts - Provides table management functionality for the Rich Text Editor */ // Core EJ2 base utilities import { detach, closest, Browser, isNullOrUndefined as isNOU, getComponent, isNullOrUndefined, EventHandler, addClass } from '@syncfusion/ej2-base'; // UI components import { Popup } from '@syncfusion/ej2-popups'; import { Button } from '@syncfusion/ej2-buttons'; import { NumericTextBox } from '@syncfusion/ej2-inputs'; // Internal modules import * as events from '../base/constant'; import * as classes from '../base/classes'; import * as EVENTS from '../../common/constant'; import { RenderType } from '../base/enum'; import { TableCommand } from '../../editor-manager/plugin/table'; // Utility functions import { dispatchEvent, parseHtml, hasClass } from '../base/util'; import { removeClassWithAttr } from '../../common/util'; import { ToolbarType } from '../../common/enum'; /* * `Table` module is used to handle table actions. */ var Table = /** @class */ (function () { function Table(parent, serviceLocator) { this.parent = parent; this.rteID = parent.element.id; this.l10n = serviceLocator.getService('rteLocale'); this.rendererFactory = serviceLocator.getService('rendererFactory'); this.dialogRenderObj = serviceLocator.getService('dialogRenderObject'); this.addEventListener(); this.isDestroyed = false; } /* * Registers all event listeners for table-related operations * * This method wires up all the necessary event handlers that the Table module * needs to function properly, including table creation, editing, navigation, * and keyboard interactions. */ Table.prototype.addEventListener = function () { // Early exit if parent component is already destroyed if (this.parent.isDestroyed) { return; } // Table creation and dialog events this.parent.on(events.createTable, this.renderDlgContent, this); this.parent.on(events.initialEnd, this.afterRender, this); this.parent.on(events.dynamicModule, this.afterRender, this); this.parent.on(events.showTableDialog, this.showDialog, this); this.parent.on(events.closeTableDialog, this.closeDialog, this); this.parent.on(events.clearDialogObj, this.clearDialogObj, this); this.parent.on(events.bindOnEnd, this.bindOnEnd, this); this.parent.on(events.updateProperty, this.updateTableProperty, this); // Mouse interaction events this.parent.on(events.docClick, this.docClick, this); this.parent.on(events.iframeMouseDown, this.onIframeMouseDown, this); this.parent.on(events.editAreaClick, this.editAreaClickHandler, this); // Toolbar and dropdown events this.parent.on(events.tableToolbarAction, this.onToolbarAction, this); this.parent.on(events.dropDownSelect, this.dropdownSelect, this); // Keyboard and input events this.parent.on(events.keyDown, this.keyDown, this); this.parent.on(events.tableModulekeyUp, this.tableModulekeyUp, this); this.parent.on(events.afterKeyDown, this.afterKeyDown, this); // UI and styling events this.parent.on(events.bindCssClass, this.setCssClass, this); // Lifecycle events this.parent.on(events.destroy, this.destroy, this); }; /* * Removes all event listeners previously attached by addEventListener method * * This method cleans up all event handlers attached to the parent component * to prevent memory leaks when the Table module is destroyed or needs to * be detached from the DOM. */ Table.prototype.removeEventListener = function () { // Table creation and dialog events this.parent.off(events.createTable, this.renderDlgContent); this.parent.off(events.initialEnd, this.afterRender); this.parent.off(events.dynamicModule, this.afterRender); this.parent.off(events.showTableDialog, this.showDialog); this.parent.off(events.closeTableDialog, this.closeDialog); this.parent.off(events.clearDialogObj, this.clearDialogObj); this.parent.off(events.bindOnEnd, this.bindOnEnd); this.parent.off(events.updateProperty, this.updateTableProperty); // Mouse interaction events this.parent.off(events.docClick, this.docClick); this.parent.off(events.iframeMouseDown, this.onIframeMouseDown); this.parent.off(events.editAreaClick, this.editAreaClickHandler); if (this.tableObj) { this.parent.off(events.mouseDown, this.tableObj.cellSelect); } // Toolbar and dropdown events this.parent.off(events.tableToolbarAction, this.onToolbarAction); this.parent.off(events.dropDownSelect, this.dropdownSelect); // Keyboard and input events this.parent.off(events.keyDown, this.keyDown); this.parent.off(events.tableModulekeyUp, this.tableModulekeyUp); this.parent.off(events.afterKeyDown, this.afterKeyDown); // UI and styling events this.parent.off(events.bindCssClass, this.setCssClass); // Lifecycle events this.parent.off(events.destroy, this.destroy); // Table quick toolbar events if (this.parent.editorMode !== 'Markdown' && this.parent.formatter) { this.parent.formatter.editorManager.observer.off(EVENTS.hideTableQuickToolbar, this.hideTableQuickToolbar); } }; /* * Initializes the TableCommand object in the editor manager after editor initialization is complete. * This method binds the table module to the editor's formatter for handling table-related operations. */ Table.prototype.bindOnEnd = function () { if (this.parent.editorMode === 'HTML' && this.parent.formatter && this.parent.formatter.editorManager && this.contentModule) { var tableModel = this.getTableModelProperty(); this.parent.formatter.editorManager.tableObj = this.tableObj = new TableCommand(this.parent.formatter.editorManager, tableModel, this.parent.iframeSettings); if (this.tableObj) { if (this.parent.tableSettings.resize) { this.tableObj.addResizeEventHandlers(); } // First remove any existing mouseDown event handler to prevent duplicates this.parent.off(events.mouseDown, this.tableObj.cellSelect); // Then add the event handler this.parent.on(events.mouseDown, this.tableObj.cellSelect, this.tableObj); } if (this.parent.formatter) { this.parent.formatter.editorManager.observer.on(EVENTS.hideTableQuickToolbar, this.hideTableQuickToolbar, this); } } }; /* Creates and returns a table model with editor configuration and callback methods for table operations */ Table.prototype.getTableModelProperty = function () { var _this = this; // Create TableCommand with table model containing required methods var tableModel = { tableSettings: this.parent.tableSettings, rteElement: this.parent.element, readonly: this.parent.readonly, enableRtl: this.parent.enableRtl, enterKey: this.parent.enterKey, editorMode: this.parent.editorMode, quickToolbarSettings: this.parent.quickToolbarSettings, // Method for retrieving CSS class name getCssClass: function (isSpace) { return _this.parent.getCssClass(isSpace); }, // Method for preventing default resize behavior preventDefaultResize: function (e) { _this.parent.preventDefaultResize(e); }, // Method for retrieving the document object of the content module getDocument: function () { if (!_this.contentModule) { return _this.parent.contentModule.getDocument(); } return _this.contentModule.getDocument(); }, // Method for retrieving the editable element object of the content module getEditPanel: function () { if (!_this.contentModule) { return _this.parent.contentModule.getEditPanel(); } return _this.contentModule.getEditPanel(); }, // Table resize event handlers resizeStart: function (args) { _this.resizeStart(args); }, resizing: function (args) { _this.resizing(args); }, resizeEnd: function (args) { _this.resizeEnd(args); }, // Table manipulation methods addRow: function (selectCell, e, tabkey) { _this.addRow(selectCell, e, tabkey); }, hideTableQuickToolbar: function () { _this.hideTableQuickToolbar(); }, removeTable: function (selection, args, delKey) { _this.removeTable(selection, args, delKey); }, // Method for Checks if the table quick toolbar is currently visible in the document. isTableQuickToolbarVisible: function () { return _this.isTableQuickToolbarVisible(); } }; return tableModel; }; /* Updates the table object with the latest editor configuration settings */ Table.prototype.updateTableProperty = function () { var tableModel = this.getTableModelProperty(); if (!isNullOrUndefined(this.tableObj)) { this.tableObj.updateTableModel(tableModel); } }; /* * Handles the resize start event by triggering an event and processing the result. */ Table.prototype.resizeStart = function (args) { var _this = this; if (this.parent.contentModule.getDocument().activeElement !== this.parent.inputElement) { this.parent.focusIn(); } this.parent.trigger(events.resizeStart, args, function (resizeStartArgs) { if (resizeStartArgs.cancel && _this.tableObj) { _this.tableObj.cancelResizeAction(); } }); }; /* * Handles the resizing event by triggering an event and processing the result. */ Table.prototype.resizing = function (args) { var _this = this; this.parent.trigger(events.onResize, args, function (resizingArgs) { if (_this.tableObj) { if (resizingArgs.cancel) { _this.tableObj.cancelResizeAction(); } else { _this.tableObj.perfomResizing(args.event); } } }); }; /* * Checks if the table quick toolbar is currently visible in the document. */ Table.prototype.isTableQuickToolbarVisible = function () { return this.quickToolObj && this.quickToolObj.tableQTBar && document.body.contains(this.quickToolObj.tableQTBar.element); }; /* * Handles the resize end event by triggering an event and processing the result. */ Table.prototype.resizeEnd = function (args) { this.parent.trigger(events.resizeStop, args); }; Table.prototype.afterRender = function () { if (isNullOrUndefined(this.contentModule)) { this.contentModule = this.rendererFactory.getRenderer(RenderType.Content); this.bindOnEnd(); } }; /* * Updates CSS classes for UI components like Buttons, Dialog, and NumericTextBox * * This method safely adds or replaces CSS classes on component instances while * preserving existing classes. It handles class addition, replacement, and proper spacing. */ Table.prototype.updateCss = function (currentObj, e) { if (currentObj && e.cssClass) { if (isNullOrUndefined(e.oldCssClass)) { currentObj.setProperties({ cssClass: (currentObj.cssClass + ' ' + e.cssClass).trim() }); } else { currentObj.setProperties({ cssClass: (currentObj.cssClass.replace(e.oldCssClass, '').trim() + ' ' + e.cssClass).trim() }); } } }; /* * Sets CSS classes for the table module's UI elements * * Applies CSS classes to popups and various form controls, ensuring consistent * styling throughout the table module. Handles both initial class application * and subsequent class updates. */ Table.prototype.setCssClass = function (e) { if (this.popupObj && e.cssClass) { if (isNullOrUndefined(e.oldCssClass)) { addClass([this.popupObj.element], e.cssClass); } else { removeClassWithAttr([this.popupObj.element], e.oldCssClass); addClass([this.popupObj.element], e.cssClass); } } this.updateCss(this.createTableButton, e); this.updateCss(this.editdlgObj, e); var numericTextBoxObj = [ this.columnTextBox, this.rowTextBox, this.tableWidthNum, this.tableCellPadding, this.tableCellSpacing ]; for (var i = 0; i < numericTextBoxObj.length; i++) { if (numericTextBoxObj[i]) { this.updateCss(numericTextBoxObj[i], e); } } }; /* * Handles dropdown selection events for table-related operations * * This method processes dropdown command selections for table operations like * inserting/deleting rows or columns, merging/splitting cells, and applying * table styles. It routes the command to the appropriate handler method based * on the selected subCommand. */ Table.prototype.dropdownSelect = function (e) { var item = e.item; if (!document.body.contains(document.body.querySelector('.e-rte-quick-toolbar')) || item.command !== 'Table') { return; } var range = this.parent.formatter.editorManager.nodeSelection.getRange(this.parent.contentModule.getDocument()); var args = { args: e, selection: this.parent.formatter.editorManager.nodeSelection.save(range, this.contentModule.getDocument()), selectParent: this.parent.formatter.editorManager.nodeSelection.getParentNodeCollection(range) }; switch (item.subCommand) { case 'InsertRowBefore': case 'InsertRowAfter': this.addRow(args.selection, e); break; case 'InsertColumnLeft': case 'InsertColumnRight': this.addColumn(args.selection, e); break; case 'DeleteColumn': case 'DeleteRow': this.removeRowColumn(args.selection, e); break; case 'AlignTop': case 'AlignMiddle': case 'AlignBottom': this.verticalAlign(args, e); break; case 'Dashed': case 'Alternate': case 'Custom': this.parent.formatter.process(this.parent, e, args, { command: item.command, subCommand: e.item.subCommand }); break; case 'Merge': case 'VerticalSplit': case 'HorizontalSplit': this.UpdateCells(args.selection, e); break; } }; /* * Handles merging or splitting of table cells * * This method processes cell operations such as merging multiple selected cells or * splitting cells vertically or horizontally. After processing the operation, * it hides the quick toolbar to provide a clean UI experience. */ Table.prototype.UpdateCells = function (selectCell, e) { this.parent.formatter.process(this.parent, e, e, { selection: selectCell, subCommand: e.item.subCommand }); this.hideTableQuickToolbar(); }; /* * Handles keyboard events for table operations in the editor * * This method processes key press events and manages table-related functionality * including navigation, selection, deletion, and insertion operations. */ Table.prototype.keyDown = function (e) { var event = e.args; this.handleSpecialActions(event, e); if (this.tableObj) { if (this.tableObj.isTableInteractionPossible(event)) { this.tableObj.handleTableKeyboardInteractions(event); } if (this.parent.editorMode === 'HTML') { this.tableObj.handleShiftKeyTableSelection(event); } this.tableObj.handleGlobalKeyboardShortcuts(event); this.tableObj.handleTableDeletion(event); this.tableObj.handleDeselectionOnTyping(event); } }; /* * Handles special action keys like Escape and Insert Table */ Table.prototype.handleSpecialActions = function (event, e) { switch (event.action) { case 'escape': break; case 'insert-table': if (this.parent.editorMode === 'HTML') { this.openDialog(true, e); } else if (this.parent.editorMode === 'Markdown') { this.parent.formatter.process(this.parent, null, event); } event.preventDefault(); break; } }; /* * Handles keyboard events after key up in tables. * This method identifies the current table cell element based on selection, * applies appropriate CSS classes, and manages selection state transitions * when navigating between cells. */ Table.prototype.tableModulekeyUp = function (e) { if (this.tableObj) { this.tableObj.tableModulekeyUp(e); } }; /* * Opens the insert table dialog. * This method prepares and opens the dialog for inserting a new table, * handling both toolbar-initiated and keyboard shortcut-initiated cases. */ Table.prototype.openDialog = function (isInternal, e) { if (!isInternal) { this.parent.contentModule.getEditPanel().focus(); } if (this.parent.editorMode === 'HTML') { var docElement = this.parent.contentModule.getDocument(); var range = this.parent.formatter.editorManager.nodeSelection.getRange(docElement); var selection = this.parent.formatter.editorManager.nodeSelection .save(range, docElement); var args = { originalEvent: e ? e.args : { action: 'insert-table' }, item: { command: 'Table', subCommand: 'CreateTable' }, name: !isInternal ? 'showDialog' : null }; this.insertTableDialog({ self: this, args: args, selection: selection }); } }; /* * Shows the table dialog from toolbar action * This method is the entry point for displaying the table dialog * when triggered from the toolbar. */ Table.prototype.showDialog = function () { this.openDialog(false); this.setCssClass({ cssClass: this.parent.getCssClass() }); }; /* * Closes the table dialog * This method hides the table editing dialog if it's currently open. */ Table.prototype.closeDialog = function () { if (this.editdlgObj) { this.editdlgObj.hide({ returnValue: true }); } }; /* * Processes table-related toolbar actions * This method handles toolbar button clicks for table operations such as * adding headers, removing tables, or editing table properties. */ Table.prototype.onToolbarAction = function (args) { var item = args.args.item; switch (item.subCommand) { case 'TableHeader': this.tableHeader(args.selection, args.args); break; case 'TableRemove': this.removeTable(args.selection, args.args); break; case 'TableEditProperties': this.editTable(args); break; } }; /* * Applies vertical alignment to table cells. * This method processes vertical alignment commands (top, middle, bottom) * for the selected table cell. */ Table.prototype.verticalAlign = function (args, e) { var tdEle = closest(args.selectParent[0], 'th') || closest(args.selectParent[0], 'td'); if (tdEle) { this.parent.formatter.process(this.parent, e, e, { tableCell: tdEle, subCommand: e.item.subCommand }); } }; /* * Hides the quick toolbar for table editing if it exists * * This method safely checks for the existence of the toolbar and its properties * before attempting to hide it, preventing potential null reference errors. */ Table.prototype.hideTableQuickToolbar = function () { if (this.quickToolObj && typeof this.quickToolObj.tableQTBar !== 'undefined' && this.quickToolObj.tableQTBar && typeof this.quickToolObj.tableQTBar.element !== 'undefined' && document.body.contains(this.quickToolObj.tableQTBar.element)) { this.quickToolObj.tableQTBar.hidePopup(); } }; /* * Processes table header commands * Delegates the processing of table header related commands to the formatter service * with proper parameters and type safety. */ Table.prototype.tableHeader = function (selection, e) { if (!e || typeof e !== 'object') { return; } this.parent.formatter.process(this.parent, e, e.originalEvent, { selection: selection, subCommand: typeof e.item !== 'undefined' ? e.item.subCommand : undefined }); }; /* * Gets the anchor node for an element * Finds the closest anchor element that contains the input element, * or returns the original element if no anchor is found. */ Table.prototype.getAnchorNode = function (element) { if (!element || typeof element !== 'object') { return element; } var selectParent = closest(element, 'a'); return selectParent ? selectParent : element; }; /* * Handles click event inside editable area to show or hide the table quick toolbar. */ Table.prototype.editAreaClickHandler = function (e) { if (this.shouldSkipQuickToolbar(e)) { return; } if (this.parent.editorMode === 'HTML' && this.parent.quickToolbarModule && this.parent.quickToolbarModule.tableQTBar) { this.quickToolObj = this.parent.quickToolbarModule; var args = e.args; var target = args.target; this.contentModule = this.rendererFactory.getRenderer(RenderType.Content); var range = this.parent.formatter.editorManager.nodeSelection.getRange(this.contentModule.getDocument()); if (this.shouldShowQuickToolbar(args, target, range)) { this.showTableQuickToolbar(e, args, target, range); } else { this.hideTableQuickToolbar(); } } }; /* * Determines whether to skip showing the quick toolbar. */ Table.prototype.shouldSkipQuickToolbar = function (e) { if (this.parent.readonly) { return true; } var target = e.args.target; if (!isNOU(closest(target, '.e-img-caption'))) { return true; } var args = e.args; var showOnRightClick = this.parent.quickToolbarSettings.showOnRightClick; // Right-click / left-click logic return (args.which === 2 || (showOnRightClick && args.which === 1) || (!showOnRightClick && args.which === 3)); }; /* * Determines whether to show the quick toolbar based on current selection and target. */ Table.prototype.shouldShowQuickToolbar = function (args, target, range) { var closestTable = closest(target, 'table'); var startNode = this.parent.getRange().startContainer.parentElement; var endNode = this.parent.getRange().endContainer.parentElement; var isAnchorEle = this.getAnchorNode(target); var ismacRightClick = this.parent.userAgentData.getPlatform() === 'macOS' && args.which === 3; var rangeAtPointer = range.startContainer === range.endContainer && range.startOffset === range.endOffset; var currentTime = new Date().getTime(); return target && target.nodeName !== 'A' && isAnchorEle.nodeName !== 'A' && target.nodeName !== 'IMG' && target.nodeName !== 'VIDEO' && target.nodeName !== 'AUDIO' && !target.classList.contains(classes.CLS_CLICKELEM) && (startNode === endNode || ismacRightClick) && (target.nodeName === 'TD' || target.nodeName === 'TH' || target.nodeName === 'TABLE' || (closestTable && this.parent.contentModule.getEditPanel().contains(closestTable))) && !(range.startContainer.nodeType === 3 && !(range.collapsed || ismacRightClick)) && (this.tableObj && (currentTime - this.tableObj.resizeEndTime > 100)) && (rangeAtPointer || closestTable.querySelectorAll('.e-multi-cells-select').length > 0); }; /* * Shows the table quick toolbar popup at the given position. */ Table.prototype.showTableQuickToolbar = function (e, args, target, range) { this.parent.formatter.editorManager.nodeSelection.save(range, this.contentModule.getDocument()); this.parent.formatter.editorManager.nodeSelection.Clear(this.contentModule.getDocument()); this.parent.formatter.editorManager.nodeSelection.restore(); this.quickToolObj.tableQTBar.showPopup(target, e.args); }; /* * Handles the click event on a table cell to trigger table insertion. */ Table.prototype.tableCellClick = function (e) { var target = e.target; if (!target) { return; } var parentRow = target.parentElement; var tableRow = parentRow ? parentRow.parentElement : null; if (!parentRow || !tableRow) { return; } var row = Array.prototype.slice.call(tableRow.children).indexOf(parentRow) + 1; var col = Array.prototype.slice.call(parentRow.children).indexOf(target) + 1; var selfObj = this.self; if (typeof selfObj.tableInsert === 'function') { selfObj.tableInsert(row, col, e, this); } }; /* * Handles table insertion operation in the rich text editor. */ Table.prototype.tableInsert = function (row, col, e, selectionObj) { var proxy = selectionObj && selectionObj.self ? selectionObj.self : this; var scrollX = window.scrollX; var scrollY = window.scrollY; this.prepareSelectionForTableInsert(selectionObj, proxy); this.cleanupTableCreationEvents(e, proxy); var tableConfig = this.createTableConfiguration(row, col, proxy, selectionObj); this.insertTableIntoEditor(tableConfig, selectionObj, proxy); // Restore scroll position and set up cell selection window.scrollTo(scrollX, scrollY); this.setupTableCellSelection(proxy); }; /* * Prepares the selection for table insertion. */ Table.prototype.prepareSelectionForTableInsert = function (selectionObj, proxy) { if (!selectionObj || !selectionObj.selection || !selectionObj.selection.range) { return; } var startContainer = selectionObj.selection.range.startContainer; // Handle empty paragraph if (startContainer.nodeName === 'P' && startContainer.textContent.trim() === '' && !(startContainer.childNodes.length > 0)) { startContainer.innerHTML = '<br>'; } var parentNode = startContainer.parentNode; // Handle HTML editor mode if (this.isHtmlEditorOutsideEditableRegion(proxy, parentNode)) { proxy.contentModule.getEditPanel().focus(); var range = proxy.parent.formatter.editorManager.nodeSelection.getRange(proxy.contentModule.getDocument()); selectionObj.selection = proxy.parent.formatter.editorManager.nodeSelection.save(range, proxy.contentModule.getDocument()); } }; /* * Checks if the current selection is outside the editable region in HTML mode. */ Table.prototype.isHtmlEditorOutsideEditableRegion = function (proxy, parentNode) { if (!proxy.parent || proxy.parent.editorMode !== 'HTML') { return false; } var inIframeOutsideEditor = proxy.parent.iframeSettings.enable && parentNode.ownerDocument && parentNode.ownerDocument.querySelector('body') && !hasClass(parentNode.ownerDocument.querySelector('body'), 'e-lib'); var panelId = proxy.contentModule.getPanel().id; var outsideEditPanel = !proxy.parent.iframeSettings.enable && isNOU(closest(parentNode, "[id=\"" + panelId + "\"]")); return inIframeOutsideEditor || outsideEditPanel; }; /* * Cleans up event handlers after table creation. */ Table.prototype.cleanupTableCreationEvents = function (e, proxy) { if (proxy.popupObj) { var target = e.target; if (target && target.parentElement && target.parentElement.parentElement) { var rows = Array.prototype.slice.call(target.parentElement.parentElement.children); for (var i = 0; i < rows.length; i++) { EventHandler.remove(rows[i], 'mouseleave', this.tableObj.tableCellLeave); var cells = Array.prototype.slice.call(rows[i].children); for (var j = 0; j < cells.length; j++) { EventHandler.remove(cells[j], 'mousemove', this.tableObj.tableCellSelect); EventHandler.remove(cells[j], 'mouseup', this.tableCellClick); if (this.parent.toolbarSettings.type === ToolbarType.Popup) { EventHandler.remove(cells[j], 'click', this.tableCellClick); } } } } proxy.popupObj.hide(); } if (proxy.editdlgObj) { proxy.editdlgObj.hide(); } if (!isNullOrUndefined(proxy.parent)) { if (proxy.parent.element.querySelector('.e-content')) { //focusing the content editable div proxy.parent.element.querySelector('.e-content').focus(); } } }; /* * Creates the table configuration for insertion. */ Table.prototype.createTableConfiguration = function (row, col, proxy, selectionObj) { return { rows: row, columns: col, width: { minWidth: proxy.parent.tableSettings.minWidth, maxWidth: proxy.parent.tableSettings.maxWidth, width: proxy.parent.tableSettings.width }, selection: selectionObj.selection }; }; /* * Inserts the table into the editor. */ Table.prototype.insertTableIntoEditor = function (tableConfig, selectionObj, proxy) { proxy.parent.formatter.process(proxy.parent, selectionObj.args, selectionObj.args.originalEvent, tableConfig); proxy.contentModule.getEditPanel().focus(); }; /* * Sets up table cell selection handlers after table insertion. */ Table.prototype.setupTableCellSelection = function (proxy) { if (!proxy.tableObj) { return; } proxy.parent.on(events.mouseDown, proxy.tableObj.cellSelect, proxy.tableObj); var selection = proxy.parent.formatter.editorManager.nodeSelection.get(proxy.contentModule.getDocument()); if (!isNullOrUndefined(selection) && !isNullOrUndefined(selection.anchorNode) && selection.anchorNode.nodeType === Node.ELEMENT_NODE) { var anchorElement = selection.anchorNode; var isTableCell = anchorElement.tagName === 'TD' || anchorElement.tagName === 'TH'; if (isTableCell) { proxy.tableObj.curTable = closest(selection.anchorNode, 'table'); proxy.tableObj.activeCell = selection.anchorNode; } } }; /* * Inserts a new row in the table at the selected cell position. */ Table.prototype.addRow = function (selectCell, e, tabkey) { var cmd; if (tabkey) { cmd = { item: { command: 'Table', subCommand: 'InsertRowAfter' } }; } var value = { selection: selectCell, subCommand: (tabkey) ? cmd.item.subCommand : e.item.subCommand }; this.parent.formatter.process(this.parent, (tabkey) ? cmd : e, e, value); }; /* * Adds a new column to the table at the selected cell position. */ Table.prototype.addColumn = function (selectCell, e) { this.parent.formatter.process(this.parent, e, e, { selection: selectCell, width: this.parent.tableSettings.width, subCommand: e.item.subCommand }); }; /* * Removes a row or column from the table based on the selected cell. */ Table.prototype.removeRowColumn = function (selectCell, e) { this.parent.formatter.process(this.parent, e, e, { selection: selectCell, subCommand: e.item.subCommand }); this.hideTableQuickToolbar(); }; /* * Removes the entire table from the editor content. */ Table.prototype.removeTable = function (selection, args, delKey) { var cmd; if (delKey) { cmd = { item: { command: 'Table', subCommand: 'TableRemove' } }; } var value = { selection: selection, subCommand: (delKey) ? cmd.item.subCommand : args.item.subCommand }; this.parent.formatter.process(this.parent, (delKey) ? cmd : args, args.originalEvent, value); this.contentModule.getEditPanel().focus(); if (this.tableObj) { this.tableObj.setDefaultEmptyContent(); this.tableObj.removeResizeElement(); } this.hideTableQuickToolbar(); }; /* * Renders the table dialog content based on user interaction. */ Table.prototype.renderDlgContent = function (args) { var _this = this; var argsTarget = args.args.originalEvent.target; if (Browser.isDevice || this.parent.inlineMode.enable || !isNullOrUndefined(closest(argsTarget, '.e-text-quicktoolbar'))) { this.insertTableDialog(args); return; } if (this.popupObj) { this.popupObj.hide(); return; } this.hideTableQuickToolbar(); var header = '1X1'; var insertbtn = this.l10n.getConstant('inserttablebtn'); this.dlgDiv = this.parent.createElement('div', { className: 'e-rte-table-popup' + this.parent.getCssClass(true), id: this.rteID + '_table' }); this.createTablePopupBoundFn = this.createTablePopupKeyDown.bind(this); this.dlgDiv.addEventListener('keydown', this.createTablePopupBoundFn); this.tblHeader = this.parent.createElement('div', { className: 'e-rte-popup-header' + this.parent.getCssClass(true) }); this.tblHeader.innerHTML = header; if (this.tableObj) { this.tableObj.tblHeader = this.tblHeader; this.tableObj.dlgDiv = this.dlgDiv; } this.dlgDiv.appendChild(this.tblHeader); var tableDiv = this.parent.createElement('div', { className: 'e-rte-table-span' + this.parent.getCssClass(true) }); this.drawTable(tableDiv, args); this.dlgDiv.appendChild(tableDiv); this.dlgDiv.appendChild(this.parent.createElement('span', { className: 'e-span-border' + this.parent.getCssClass(true) })); var btnEle = this.parent.createElement('button', { className: 'e-insert-table-btn' + this.parent.getCssClass(true), id: this.rteID + '_insertTable', attrs: { type: 'button', tabindex: '0' } }); if (!isNOU(this.parent.getToolbarElement().querySelector('.e-expended-nav'))) { this.parent.getToolbarElement().querySelector('.e-expended-nav').setAttribute('tabindex', '1'); } this.dlgDiv.appendChild(btnEle); this.createTableButton = new Button({ iconCss: 'e-icons e-create-table', content: insertbtn, cssClass: 'e-flat' + this.parent.getCssClass(true), enableRtl: this.parent.enableRtl, locale: this.parent.locale }); this.createTableButton.isStringTemplate = true; this.createTableButton.appendTo(btnEle); EventHandler.add(btnEle, 'click', this.insertTableDialog, { self: this, args: args.args, selection: args.selection }); this.parent.getToolbar().parentElement.appendChild(this.dlgDiv); var target = args.args.originalEvent.target; target = target.classList.contains('e-toolbar-item') ? target.firstChild : target.parentElement; this.popupObj = new Popup(this.dlgDiv, { targetType: 'relative', relateTo: target, collision: { X: 'fit', Y: 'none' }, offsetY: 8, viewPortElement: this.parent.element, position: { X: 'left', Y: 'bottom' }, enableRtl: this.parent.enableRtl, zIndex: 10001, close: function (event) { EventHandler.remove(btnEle, 'click', _this.insertTableDialog); _this.dlgDiv.removeEventListener('keydown', _this.createTablePopupBoundFn); detach(btnEle); if (_this.createTableButton && !_this.createTableButton.isDestroyed) { _this.createTableButton.destroy(); _this.createTableButton.element = null; _this.createTableButton = null; } _this.parent.isBlur = false; _this.popupObj.element.parentElement.style.zIndex = ''; _this.popupObj.destroy(); detach(_this.popupObj.element); _this.popupObj = null; } }); addClass([this.popupObj.element], 'e-popup-open'); this.popupObj.element.parentElement.style.zIndex = '11'; if (!isNOU(this.parent.cssClass)) { addClass([this.popupObj.element], this.parent.getCssClass()); } this.popupObj.refreshPosition(target); this.positionDialogue(target); btnEle.focus(); }; /* * Adjusts the position of the table dialog popup based on available screen space * This method calculates whether there's enough space below the button to display * the popup. If not enough space is available, it repositions the popup above the * button. It also handles the correct positioning with expanded toolbars. */ Table.prototype.positionDialogue = function (target) { var windowHeight = window.innerHeight; var popupHeight = this.popupObj.element.getBoundingClientRect().height; var spaceBelow = windowHeight - target.getBoundingClientRect().bottom; var buttonRowHeight; var toolbarButton = target.closest('.e-toolbar-item'); var isPopup = this.parent.toolbarSettings.type === 'Popup'; var toolbarWrapper = this.parent.toolbarModule.getToolbarElement(); var expandedToolbar = toolbarWrapper ? toolbarWrapper.querySelector('.e-toolbar-extended') : toolbarWrapper; if (toolbarButton) { if (isPopup) { buttonRowHeight = toolbarButton.getBoundingClientRect().height; if (toolbarButton.parentElement.getBoundingClientRect().top < toolbarWrapper.getBoundingClientRect().top) { buttonRowHeight = toolbarWrapper.getBoundingClientRect().height + toolbarButton.parentElement.getBoundingClientRect().height; } } else { buttonRowHeight = toolbarButton.parentElement.getBoundingClientRect().height; } } else { var toolbarItem = this.parent.element.querySelector('.e-toolbar-item'); if (toolbarItem) { buttonRowHeight = toolbarItem.parentElement.getBoundingClientRect().height; } } if (expandedToolbar && toolbarButton.parentElement !== expandedToolbar) { buttonRowHeight += expandedToolbar.getBoundingClientRect().height; } if (spaceBelow < popupHeight) { this.popupObj.element.style.setProperty('top', 'auto'); this.popupObj.element.style.setProperty('bottom', buttonRowHeight + "px"); } }; /* * Handles iframe mouse down events by hiding popups and cleaning up resize elements. */ Table.prototype.onIframeMouseDown = function () { if (this.popupObj) { this.popupObj.hide(); } if (this.parent.inlineMode.enable && this.editdlgObj) { this.editdlgObj.hide(); } if (!isNOU(this.parent) && !isNOU(this.parent.contentModule) && !isNOU(this.parent.contentModule.getEditPanel()) && this.tableObj) { this.tableObj.removeResizeElement(); } }; /* * Manages document click events to control popup visibility. */ Table.prototype.docClick = function (e) { var target = e.args.target; if (target && target.classList && ((this.popupObj && !closest(target, "[id='" + this.popupObj.element.id + "']") || (this.editdlgObj && !closest(target, '#' + this.editdlgObj.element.id)))) && !target.classList.contains('e-create-table') && target.offsetParent && !target.offsetParent.classList.contains('e-rte-backgroundcolor-dropdown')) { if (this.popupObj) { this.popupObj.hide(); } if (this.editdlgObj) { this.parent.notify(events.documentClickClosedBy, { closedBy: 'outside click' }); this.editdlgObj.hide(); } this.parent.isBlur = true; dispatchEvent(this.parent.element, 'focusout'); } var closestEle = closest(target, 'td'); var isExist = closestEle && this.parent.contentModule.getEditPanel().contains(closestEle) ? true : false; if (target && target.tagName !== 'TD' && target.tagName !== 'TH' && !isExist && closest(target, '.e-rte-quick-popup') === null && target.offsetParent && !target.offsetParent.classList.contains('e-quick-dropdown') && !target.offsetParent.classList.contains('e-rte-backgroundcolor-dropdown') && !closest(target, '.e-rte-dropdown-popup') && !closest(target, '.e-rte-elements')) { var isToolbarClick = target.closest('.e-toolbar') || target.closest('.e-toolbar-wrapper') ? true : false; var isClickedOnPasteCleanupDialog = closest(target, '#' + this.parent.element.id + '_pasteCleanupDialog') ? true : false; if (this.tableObj) { if (!isToolbarClick && !isClickedOnPasteCleanupDialog) { this.tableObj.removeCellSelectClasses(); this.tableObj.removeTableSelection(); } } if (!Browser.isIE) { this.hideTableQuickToolbar(); } } if (this.tableObj && target && target.classList && !target.classList.contains(EVENTS.CLS_TB_COL_RES) && !target.classList.contains(EVENTS.CLS_TB_ROW_RES) && !target.classList.contains(EVENTS.CLS_TB_BOX_RES)) { this.tableObj.removeResizeElement(); } }; /* * Generates and configures a table UI within the provided container. */ Table.prototype.drawTable = function (tableDiv, args) { var rowDiv; var tableCell; for (var row = 0; row < 3; row++) { rowDiv = this.parent.createElement('div', { className: 'e-rte-table-row' + this.parent.getCssClass(true), attrs: { 'data-column': '' + row } }); EventHandler.add(rowDiv, 'mouseleave', this.tableObj.tableCellLeave, this.tableObj); for (var col = 0; col < 10; col++) { tableCell = this.parent.createElement('div', { className: 'e-rte-tablecell e-default' + this.parent.getCssClass(true), attrs: { 'data-cell': '' + col } }); rowDiv.appendChild(tableCell); tableCell.style.display = 'inline-block'; if (col === 0 && row === 0) { addClass([tableCell], 'e-active'); } EventHandler.add(tableCell, 'mousemove', this.tableObj.tableCellSelect, this.tableObj); if (this.parent.toolbarSettings.type === ToolbarType.Popup) { EventHandler.add(tableCell, 'click', this.tableCellClick, { self: this, args: args.args, selection: args.selection }); } else { EventHandler.add(tableCell, 'mouseup', this.tableCellClick, { self: this, args: args.args, selection: args.selection }); } } tableDiv.appendChild(rowDiv); } }; /* * Opens a dialog to edit properties of an existing table. */ Table.prototype.editTable = function (args) { var _this = this; this.createDialog(args); var editContent = this.tableDlgContent(args); var update = this.l10n.getConstant('dialogUpdate'); var cancel = this.l10n.getConstant('dialogCancel'); var editHeader = this.l10n.getConstant('tableEditHeader'); this.editdlgObj.setProperties({ height: 'initial', width: '290px', content: editContent, header: editHeader, buttons: [{ click: this.applyProperties.bind(this, args), buttonModel: { content: update, cssClass: 'e-flat e-size-update' + this.parent.getCssClass(true), isPrimary: true } }, { click: function (e) { _this.cancelDialog(e); }, buttonModel: { cssClass: 'e-flat e-cancel' + this.parent.getCssClass(true), content: cancel } }], cssClass: this.editdlgObj.cssClass + ' e-rte-edit-table-prop-dialog' }); this.editdlgObj.element.style.maxHeight = 'none'; this.editdlgObj.content.querySelector('input').focus(); this.hideTableQuickToolbar(); }; /* * Opens a dialog for inserting a new table into the document. */ Table.prototype.insertTableDialog = function (args) { var proxy = (this.self) ? this.self : this; if (proxy.popupObj) { proxy.popupObj.hide(); } proxy.createDialog(args); var dlgContent = proxy.tableCellDlgContent(); var insert = proxy.l10n.getConstant('dialogInsert'); var cancel = proxy.l10n.getConstant('dialogCancel'); if (isNullOrUndefined(proxy.editdlgObj)) { return; } proxy.editdlgObj.setProperties({ height: 'initial', width: '290px', content: dlgContent, buttons: [{ click: proxy.customTable.bind(this, args), buttonModel: { content: insert, cssClass: 'e-flat e-insert-table' + ' ' + proxy.parent.cssClass, isPrimary: true } }, { click: function (e) { proxy.cancelDialog(e); }, buttonModel: { cssClass: 'e-flat e-cancel' + ' ' + proxy.parent.cssClass, content: cancel } }] }); if (!isNOU(proxy.parent.cssClass)) { proxy.editdlgObj.setProperties({ cssClass: proxy.parent.cssClass }); } proxy.editdlgObj.element.style.maxHeight = 'none'; proxy.editdlgObj.content.querySelector('input').focus(); }; /* * Creates the content for the table cell dialog with row and column inputs. */ Table.prototype.tableCellDlgContent = function () { var tableColumn = this.l10n.getConstant('columns'); var tableRow = this.l10n.getConstant('rows'); var tableWrap = this.parent.createElement('div', { className: 'e-cell-wrap' + this.parent.getCssClass(true) }); var content = '<div class="e-rte-field' + this.parent.getCssClass(true) + '"><input type="text" ' + ' data-role ="none" id="tableColumn" class="e-table-column' + this.parent.getCssClass(true) + '"/></div>' + '<div class="e-rte-field' + this.parent.getCssClass(true) + '"><input type="text" data-role ="none" id="tableRow" class="e-table-row' + this.parent.getCssClass(true) + '" /></div>'; var contentElem = parseHtml(content); tableWrap.appendChild(contentElem); this.columnTextBox = new NumericTextBox({ format: 'n0', min: 1, value: 3,