@syncfusion/ej2-richtexteditor
Version:
Essential JS 2 RichTextEditor component
1,094 lines • 62 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 } 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,