@syncfusion/ej2-richtexteditor
Version:
Essential JS 2 RichTextEditor component
1,085 lines • 137 kB
JavaScript
/*
* 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, Tooltip } 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';
import { ColorPickerInput } from '../actions/color-picker';
import { DropDownButtons } from '../actions';
import { DropDownButton } from '@syncfusion/ej2-splitbuttons';
// Utility functions
import { dispatchEvent, parseHtml, hasClass } from '../base/util';
import { convertPixelToPercentage, insertColGroupWithSizes, removeClassWithAttr } from '../../common/util';
import { ToolbarType } from '../../common/enum';
import { DOMMethods } from '../../editor-manager/plugin/dom-tree';
/*
* `Table` module is used to handle table actions.
*/
var Table = /** @class */ (function () {
function Table(parent, serviceLocator) {
this.isMultiSelection = false;
this.tableCellPaddingValue = '';
this.tableCellVerticalAlignValue = '';
this.tableCellHorizontalAlignValue = '';
this.tableCellHeightValue = '';
this.tableStyles = {};
this.multipleSelectionCellStyles = [];
this.colElementsInitialWidths = new Map();
this.createdButtons = [];
this.createdDropdownButtons = [];
this.alignmentButtonHandlers = [];
this.heightValue = '';
this.widthValue = '';
this.savedSelectionForDialog = null;
/**
* @private
*/
this.selectionStage = 0;
this.parent = parent;
this.rteID = parent.element.id;
this.l10n = serviceLocator.getService('rteLocale');
this.tableBorderStyle = new DropDownButtons(this.parent, serviceLocator);
this.tableBorderColor = new ColorPickerInput(this.parent, serviceLocator);
this.tableBackgroundColor = new ColorPickerInput(this.parent, serviceLocator);
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);
this.parent.on(events.colorPickerChanged, this.tableColorHandler, 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);
// Model events
this.parent.on(events.modelChanged, this.onPropertyChanged, 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);
this.parent.off(events.colorPickerChanged, this.tableColorHandler);
// 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);
}
// Model events
this.parent.off(events.modelChanged, this.onPropertyChanged);
};
/*
* 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) {
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);
}
}
};
Table.prototype.onPropertyChanged = function (e) {
for (var _i = 0, _a = Object.keys(e.newProp); _i < _a.length; _i++) {
var prop = _a[_i];
if (prop === 'tableSettings') {
switch (Object.keys(e.newProp.tableSettings)[0]) {
case 'resize':
if (this.parent.tableSettings.resize === false) {
this.tableObj.removeResizeEventHandlers();
}
else {
this.tableObj.addResizeEventHandlers();
}
break;
}
}
}
};
/* 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,
tableSelectionFeature: true,
// 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();
},
//Method for enableUndo
enableUndo: function () {
_this.enableUndo();
}
};
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.inputElement.focus({ preventScroll: true });
}
this.parent.trigger(events.resizeStart, args, function (resizeStartArgs) {
if (resizeStartArgs.cancel && _this.tableObj) {
_this.tableObj.cancelResizeAction();
}
});
};
/*
* enableUndo method
*/
Table.prototype.enableUndo = function () {
if (this.parent.formatter) {
this.parent.formatter.enableUndo(this.parent);
}
};
/*
* 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.tableHeightNum,
this.tableCellPadding,
this.tableCellSpacing,
this.tableBorderWidth
];
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 _this = this;
var item = e.item;
if (item.command === 'BorderStyle') {
var actionBeginArgs = { cancel: false, requestType: 'BorderStyle' };
this.parent.trigger(events.actionBegin, actionBeginArgs, function (actionBeginArgs) {
if (!actionBeginArgs.cancel) {
var isTable = _this.selectedItem.nodeName === 'TABLE';
var borderDropDown = _this.parent.contentModule.getPanel().ownerDocument.getElementById(_this.parent.getID() + (isTable ? '_borderStyle' : '_cellborderStyle'));
borderDropDown.firstChild.innerHTML = '<span class="e-rte-dropdown-btn-text" >' + item.text + '</span>';
if (_this.isMultiSelection) {
for (var i = 0; i < _this.multiSelectedItems.length; i++) {
_this.multiSelectedItems[i].style.cssText += "border-style: " + item.subCommand.toLowerCase() + ";";
}
}
else {
_this.selectedItem.style.cssText += "border-style: " + item.subCommand.toLowerCase() + ";";
}
_this.applyBorderStyleAndWidth();
_this.parent.trigger(events.actionComplete, { requestType: 'BorderStyle' });
}
});
}
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;
var selectedCell = this.contentModule.getDocument().querySelector('.e-cell-select');
if ((event.ctrlKey || event.metaKey) && event.code === 'KeyA' && selectedCell) {
if (this.selectionStage === 0) {
event.preventDefault();
addClass([selectedCell], 'e-multi-cells-select');
this.selectionStage = 1;
}
else if (this.selectionStage === 1) {
// Stage 2: select the row
var row = selectedCell.closest('tr');
if (row) {
event.preventDefault();
this.tableObj.selectTableRow(row);
this.selectionStage = 2;
}
}
else if (this.selectionStage === 2) {
event.preventDefault();
var table = selectedCell.closest('table');
this.tableObj.selectEntireTable(table);
this.selectionStage = 3;
}
else {
this.selectionStage = 0;
}
}
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);
}
if (this.selectionStage === 0) {
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;
}
};
/*
* Invokes the OnSelectionChange Event
*/
Table.prototype.selectionEventTriggers = function () {
var selection = this.parent.contentModule.getDocument().getSelection();
var range = selection && selection.rangeCount !== 0 && selection.getRangeAt(0);
if (range) {
var rangeStartElement = range.startContainer.nodeName === '#text' ? range.startContainer.parentElement : range.startContainer;
if (rangeStartElement.closest('table')) {
var selectedCell = rangeStartElement.closest('.e-multi-cells-select');
if (!isNullOrUndefined(selectedCell)) {
var targetTable = rangeStartElement.closest('table');
var extractedTable = this.tableObj.extractSelectedTable(targetTable, false);
var selectionArgs = {
selectedContent: extractedTable ? extractedTable.outerHTML : null,
selection: selection,
editorMode: this.parent.editorMode
};
this.parent.trigger(events.selectionChanged, selectionArgs);
}
}
}
};
/*
* 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) {
var _this = this;
if (this.tableObj) {
this.tableObj.tableModulekeyUp(e);
}
if (this.selectionTimeout) {
clearTimeout(this.selectionTimeout);
this.selectionTimeout = null;
}
var isRteUnitTesting = (this.parent.element && this.parent.element.dataset && this.parent.element.dataset.rteUnitTesting === 'true');
if (isRteUnitTesting) {
this.selectionEventTriggers();
}
else {
this.selectionTimeout = window.setTimeout(function () {
_this.selectionEventTriggers();
}, 600);
}
};
/*
* 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':
case 'TableCellProperties':
if (args.args.item.subCommand === 'TableEditProperties') {
this.selectedItem = closest(args.selectParent[0], 'table');
}
else {
this.widthValue = 'px';
this.heightValue = 'px';
this.selectedItem = closest(args.selectParent[0], 'td') || closest(args.selectParent[0], 'th');
this.multiSelectedItems = this.parent.inputElement.querySelectorAll('.e-cell-select');
if (this.multiSelectedItems.length > 1) {
this.isMultiSelection = true;
}
}
if (this.selectedItem && !this.isMultiSelection) {
var style = this.selectedItem.style;
if (this.selectedItem.tagName === 'TABLE') {
this.tableCellPaddingValue = this.selectedItem.querySelector('td') ?
this.selectedItem.querySelector('td').style.padding : '';
}
else if (this.selectedItem.tagName === 'TD' || this.selectedItem.tagName === 'TH') {
this.tableCellPaddingValue = this.selectedItem.style.padding;
this.tableCellVerticalAlignValue = this.selectedItem.style.verticalAlign;
this.tableCellHorizontalAlignValue = this.selectedItem.style.textAlign;
this.tableCellHeightValue = this.selectedItem.style.height;
}
this.tableStyles = {
borderStyle: style.borderStyle,
borderColor: style.borderColor,
backgroundColor: style.backgroundColor,
borderWidth: style.borderWidth,
width: style.width,
borderSpacing: style.borderSpacing,
borderCollapse: style.borderCollapse
};
if (!this.selectedItem.style.borderWidth && this.selectedItem.tagName === 'TABLE') {
this.selectedItem.style.cssText += 'border-width: 1px;';
}
if (!this.selectedItem.style.borderStyle && this.selectedItem.tagName === 'TABLE') {
this.selectedItem.style.cssText += 'border-style: double;';
}
}
else if (this.isMultiSelection) {
for (var i = 0; i < this.multiSelectedItems.length; i++) {
var style = this.multiSelectedItems[i].style;
this.multipleSelectionCellStyles.push({
borderStyle: style.borderStyle,
borderColor: style.borderColor,
backgroundColor: style.backgroundColor,
borderWidth: style.borderWidth,
width: style.width,
height: style.height,
tableCellPaddingValue: this.multiSelectedItems[i].style.padding,
tableCellVerticalAlignValue: this.multiSelectedItems[i].style.verticalAlign,
tableCellHorizontalAlignValue: this.multiSelectedItems[i].style.textAlign
});
if (!this.multiSelectedItems[i].style.borderWidth && this.selectedItem.tagName === 'TABLE') {
this.multiSelectedItems[i].style.cssText += 'border-width: 1px;';
}
if (!this.multiSelectedItems[i].style.borderStyle && this.selectedItem.tagName === 'TABLE') {
this.multiSelectedItems[i].style.cssText += 'border-style: double;';
}
}
}
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) {
this.selectionStage = 0;
if (this.parent.quickToolbarModule && this.parent.quickToolbarModule.tableQTBar) {
this.quickToolObj = this.parent.quickToolbarModule;
}
if (this.shouldSkipQuickToolbar(e)) {
return;
}
if (this.parent.editorMode === 'HTML' && this.parent.quickToolbarModule && this.parent.quickToolbarModule.tableQTBar) {
var args_1 = e.args;
var target = args_1.target;
this.contentModule = this.rendererFactory.getRenderer(RenderType.Content);
var range = this.parent.formatter.editorManager.nodeSelection.getRange(this.contentModule.getDocument());
var gripper = target.classList.length > 0 && (target.className.includes('e-icons e-drag-and-drop e-active')
|| target.className.includes('e-icons e-move e-active'));
if (gripper) {
target = this.contentModule.getDocument().querySelector('.e-cell-select-end');
var domMethods = new DOMMethods(this.parent.inputElement);
var lastNode = domMethods.getLastTextNode(target);
lastNode = !isNOU(lastNode) ? lastNode : target;
var offset = !isNOU(lastNode) ? lastNode.textContent.length : 0;
this.parent.formatter.editorManager.nodeSelection.setCursorPoint(this.contentModule.getDocument(), lastNode, offset);
range = this.parent.formatter.editorManager.nodeSelection.getRange(this.contentModule.getDocument());
}
if (this.shouldShowQuickToolbar(args_1, target, range)) {
this.showTableQuickToolbar(e, args_1, target, range);
}
else {
this.hideTableQuickToolbar();
}
}
var args = e.args;
this.selectionEventTriggers();
};
/*
* 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, '.' + classes.CLS_IMG_CAPTION_CONTAINER))) {
return true;
}
var args = e.args;
var showOnRightClick = this.parent.quickToolbarSettings.showOnRightClick;
// Right-click / left-click logic
// Custom condition: only skip on right-click if a certain class exists in DOM
if (e.args.target.nodeType === Node.ELEMENT_NODE) {
var gripper = target.classList.length > 0 && (target.className.includes('e-icons e-drag-and-drop e-active')
|| target.className.includes('e-icons e-move e-active'));
if (showOnRightClick && gripper) {
return false;
}
}
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();
}
};
/*
* 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.clas