@mindfiredigital/page-builder
Version:
502 lines (501 loc) • 19.4 kB
JavaScript
import { Canvas } from '../canvas/Canvas.js';
import { ModalComponent } from './ModalManager.js';
export class TableComponent {
constructor() {
this.modalComponent = null;
this.modalComponent = new ModalComponent() || null;
}
create(rowCount, columnCount, isPreview = false, tableAttributeConfig) {
TableComponent.tableAttributeConfig = tableAttributeConfig || [];
const container = document.createElement('div');
container.classList.add('table-component');
const tableId = Canvas.generateUniqueClass('table');
container.id = tableId;
container.style.minWidth = '250px';
container.style.border = '1px solid #2F3132';
container.style.borderRadius = '8px';
container.style.display = 'flex';
container.style.flexDirection = 'column';
const tableWrapper = document.createElement('div');
tableWrapper.style.display = 'flex';
tableWrapper.style.flexDirection = 'column';
tableWrapper.classList.add('table-wrapper');
for (let i = 0; i < rowCount; i++) {
const row = this.createTableRow(i, columnCount, tableId);
tableWrapper.appendChild(row);
}
container.appendChild(tableWrapper);
if (!isPreview) {
// Create container for buttons
const buttonContainer = document.createElement('div');
buttonContainer.classList.add('table-btn-container');
buttonContainer.style.display = 'flex';
buttonContainer.style.gap = '10px';
buttonContainer.style.justifyContent = 'center';
buttonContainer.style.marginTop = '10px';
buttonContainer.style.marginBottom = '10px';
// Multiple rows container
const multiRowContainer = document.createElement('div');
multiRowContainer.style.display = 'flex';
multiRowContainer.style.alignItems = 'center';
multiRowContainer.style.gap = '5px';
// Number input for row count
const rowCountInput = document.createElement('input');
rowCountInput.className = 'row-count-input';
rowCountInput.type = 'number';
rowCountInput.min = '1';
rowCountInput.max = '20';
rowCountInput.value = '1';
rowCountInput.style.width = '60px';
rowCountInput.style.padding = '4px 8px';
rowCountInput.style.border = '1px solid #d1d5db';
rowCountInput.style.borderRadius = '4px';
rowCountInput.style.fontSize = '14px';
// Multiple rows button
const addMultipleRowsButton = document.createElement('button');
addMultipleRowsButton.textContent = 'Add Row';
addMultipleRowsButton.className = 'add-multiple-rows-button';
addMultipleRowsButton.contentEditable = 'false';
this.styleButton(addMultipleRowsButton, '#10b981', '#059669');
addMultipleRowsButton.addEventListener('click', () => {
const count = parseInt(rowCountInput.value) || 1;
this.addRows(tableWrapper, tableId, Math.min(Math.max(count, 1), 20));
});
multiRowContainer.appendChild(rowCountInput);
multiRowContainer.appendChild(addMultipleRowsButton);
// buttonContainer.appendChild(addRowButton);
buttonContainer.appendChild(multiRowContainer);
container.appendChild(buttonContainer);
}
return container;
}
evaluateRowVisibility(inputValues, table) {
let allRows;
if (table) {
allRows = table.querySelectorAll('.table-row');
} else {
allRows = document.querySelectorAll('.table-row');
}
allRows.forEach(row => {
const rulesAttribute = row.getAttribute('data-visibility-rules');
if (!rulesAttribute) {
row.style.display = 'grid';
return;
}
try {
const rules = JSON.parse(rulesAttribute);
if (rules.length === 0) {
row.style.display = 'grid';
return;
}
let isVisible = true;
rules.forEach(rule => {
const inputValue = inputValues[rule.inputKey];
if (inputValue) {
const isConditionMet = this.evaluateRule(
inputValue,
rule.operator,
rule.value
);
if (isConditionMet) {
if (rule.action === 'hide') {
isVisible = false;
} else if (rule.action === 'show') {
isVisible = true;
}
} else {
if (rule.action === 'show') {
isVisible = false;
}
}
}
});
if (isVisible) {
row.style.display = 'grid';
} else {
row.style.display = 'none';
}
} catch (e) {
console.error('Failed to parse or evaluate visibility rules:', e);
}
});
}
evaluateRule(inputValue, operator, ruleValue) {
const numInputValue = parseFloat(inputValue);
const numRuleValue = parseFloat(ruleValue);
const lowerCaseInputValue = inputValue.toLowerCase();
const lowerCaseRuleValue = ruleValue.toLowerCase();
switch (operator) {
case 'equals':
return lowerCaseInputValue === lowerCaseRuleValue;
case 'not_equals':
return lowerCaseInputValue !== lowerCaseRuleValue;
case 'greater_than':
return (
!isNaN(numInputValue) &&
!isNaN(numRuleValue) &&
numInputValue > numRuleValue
);
case 'less_than':
return (
!isNaN(numInputValue) &&
!isNaN(numRuleValue) &&
numInputValue < numRuleValue
);
case 'contains':
return lowerCaseInputValue.includes(lowerCaseRuleValue);
default:
return false;
}
}
createTableRow(rowIndex, cellCount, tableId) {
const rowDiv = document.createElement('div');
rowDiv.style.display = 'grid';
rowDiv.style.gridTemplateColumns = `repeat(${cellCount}, 1fr)`;
rowDiv.className = 'table-row';
rowDiv.id = `table-row-T-${tableId}-R${rowIndex}`;
rowDiv.style.position = 'relative';
rowDiv.style.cursor = 'pointer';
for (let j = 0; j < cellCount; j++) {
const cell = this.createTableCell(rowIndex, j, tableId);
rowDiv.appendChild(cell);
}
return rowDiv;
}
createTableCell(rowIndex, cellIndex, tableId) {
const cell = document.createElement('div');
cell.className = 'table-cell';
cell.style.border = '1px solid #2F3132';
cell.style.minHeight = '45px';
cell.style.position = 'relative';
cell.style.cursor = 'pointer';
cell.style.transition = 'background-color 0.2s ease';
cell.style.display = 'flex';
cell.style.alignItems = 'center';
cell.style.justifyContent = 'flex-start';
// Create control buttons container
const controlsContainer = document.createElement('div');
controlsContainer.className = 'cell-controls';
controlsContainer.style.position = 'absolute';
controlsContainer.style.bottom = '5px';
controlsContainer.style.right = '5px';
controlsContainer.style.display = 'flex';
controlsContainer.style.gap = '4px';
controlsContainer.style.alignItems = 'center';
controlsContainer.style.justifyContent = 'center';
controlsContainer.contentEditable = 'false';
const contentElement = document.createElement('span');
contentElement.textContent = `R${rowIndex}C${cellIndex}`;
contentElement.contentEditable = 'true';
contentElement.classList.add('table-cell-content');
contentElement.id = `table-cell-T-${tableId}-R${rowIndex}-C${cellIndex}`;
// Add Cell button
const addCellButton = document.createElement('button');
addCellButton.textContent = '+';
addCellButton.className = 'add-cell-button';
addCellButton.style.width = '15px';
addCellButton.style.height = '15px';
addCellButton.style.border = 'none';
addCellButton.style.borderRadius = '3px';
addCellButton.style.backgroundColor = '#10b981';
addCellButton.style.color = 'white';
addCellButton.style.fontSize = '12px';
addCellButton.style.cursor = 'pointer';
addCellButton.style.display = 'flex';
addCellButton.style.alignItems = 'center';
addCellButton.style.justifyContent = 'center';
addCellButton.style.fontWeight = 'bold';
addCellButton.addEventListener('mouseenter', () => {
addCellButton.style.backgroundColor = '#059669';
});
addCellButton.addEventListener('mouseleave', () => {
addCellButton.style.backgroundColor = '#10b981';
});
addCellButton.addEventListener('click', e => {
e.stopPropagation();
this.addCellToRow(cell, tableId);
});
// Delete Cell button
const deleteCellButton = document.createElement('button');
deleteCellButton.innerHTML = '×';
deleteCellButton.className = 'delete-cell-button';
deleteCellButton.style.width = '15px';
deleteCellButton.style.height = '15px';
deleteCellButton.style.border = 'none';
deleteCellButton.style.borderRadius = '3px';
deleteCellButton.style.backgroundColor = '#ef4444';
deleteCellButton.style.color = 'white';
deleteCellButton.style.fontSize = '14px';
deleteCellButton.style.cursor = 'pointer';
deleteCellButton.style.display = 'flex';
deleteCellButton.style.alignItems = 'center';
deleteCellButton.style.justifyContent = 'center';
deleteCellButton.style.fontWeight = 'bold';
deleteCellButton.addEventListener('mouseenter', () => {
deleteCellButton.style.backgroundColor = '#dc2626';
});
deleteCellButton.addEventListener('mouseleave', () => {
deleteCellButton.style.backgroundColor = '#ef4444';
});
deleteCellButton.addEventListener('click', e => {
e.stopPropagation();
this.deleteCell(cell);
});
controlsContainer.appendChild(addCellButton);
controlsContainer.appendChild(deleteCellButton);
cell.appendChild(contentElement);
cell.appendChild(controlsContainer);
return cell;
}
addCellToRow(referenceCell, tableId) {
const row = referenceCell.parentElement;
if (!row) return;
const rowIndex = Array.from(row.parentElement.children).indexOf(row);
const currentCellCount = row.children.length;
// Find the highest existing cell index to avoid duplicates after deletions
let maxCellIndex = -1;
Array.from(row.querySelectorAll('.table-cell-content')).forEach(el => {
const match = el.id.match(/-C(\d+)$/);
if (match) {
maxCellIndex = Math.max(maxCellIndex, parseInt(match[1], 10));
}
});
const newCellIndex = maxCellIndex + 1;
const newCell = this.createTableCell(rowIndex, newCellIndex, tableId);
row.appendChild(newCell);
row.style.gridTemplateColumns = `repeat(${currentCellCount + 1}, 1fr)`;
}
deleteCell(cellToDelete) {
const row = cellToDelete.parentElement;
if (!row) return;
const cellCount = row.children.length;
row.removeChild(cellToDelete);
if (cellCount === 1) {
const tableWrapper = row.parentElement;
if (tableWrapper && tableWrapper.children.length > 1) {
tableWrapper.removeChild(row);
}
} else {
row.style.gridTemplateColumns = `repeat(${cellCount - 1}, 1fr)`;
}
}
styleButton(button, bgColor, hoverColor) {
button.style.padding = '8px 16px';
button.style.backgroundColor = bgColor;
button.style.color = 'white';
button.style.border = 'none';
button.style.borderRadius = '6px';
button.style.fontSize = '14px';
button.style.fontWeight = '500';
button.style.cursor = 'pointer';
button.style.transition = 'background-color 0.2s ease';
button.addEventListener('mouseenter', () => {
button.style.backgroundColor = hoverColor;
});
button.addEventListener('mouseleave', () => {
button.style.backgroundColor = bgColor;
});
}
seedFormulaValues(values) {
const allTables = document.querySelectorAll('.table-component');
allTables.forEach(table => {
const cells = table.querySelectorAll('div[data-attribute-key]');
cells.forEach(cell => {
const controlsElement = cell.querySelector('.cell-controls');
const key = cell.getAttribute('data-attribute-key');
const textContentCell = cell.querySelector('.table-cell-content');
if (textContentCell && key && values.hasOwnProperty(key)) {
textContentCell.textContent = values[key];
cell.style.color = '#000000';
cell.style.fontSize = '16px';
}
if (controlsElement) {
cell.appendChild(controlsElement);
}
});
});
Canvas.dispatchDesignChange();
}
updateInputValues(values) {
const allTables = document.querySelectorAll('.table-component');
allTables.forEach(table => {
const cells = table.querySelectorAll('div[data-attribute-key]');
cells.forEach(cell => {
const key = cell.getAttribute('data-attribute-key');
const type = cell.getAttribute('data-attribute-type');
const textContentOfCell = cell.querySelector('.table-cell-content');
if (
textContentOfCell &&
key &&
values.hasOwnProperty(key) &&
type === 'Input'
) {
textContentOfCell.textContent = values[key];
}
});
});
Canvas.dispatchDesignChange();
}
updateCellContent(cell, attribute) {
cell.setAttribute('data-attribute-key', attribute.key);
cell.setAttribute('data-attribute-type', attribute.type);
const controlsElement = cell.querySelector('.cell-controls');
const textContentOfCell = cell.querySelector('.table-cell-content');
if (attribute.type === 'Formula' && textContentOfCell) {
textContentOfCell.textContent = `${attribute.title}`;
cell.style.fontSize = '10px';
cell.style.color = 'rgb(188 191 198)';
cell.style.fontWeight = '500';
} else if (attribute.type === 'Constant' && textContentOfCell) {
textContentOfCell.textContent = `${attribute.value}`;
} else if (attribute.type === 'Input' && textContentOfCell) {
textContentOfCell.textContent = `${attribute.value}`;
}
if (controlsElement) {
cell.appendChild(controlsElement);
}
Canvas === null || Canvas === void 0
? void 0
: Canvas.dispatchDesignChange();
}
setModalComponent(modalComponent) {
this.modalComponent = modalComponent;
}
addRows(tableWrapper, tableId, count = 1) {
const tableRows = tableWrapper.children;
const existingRowCount = tableRows.length;
// Determine the number of columns from the first existing row, or default to 1
let cellCount = 1;
if (existingRowCount > 0) {
cellCount = tableRows[0].children.length;
}
for (let i = 0; i < count; i++) {
// The rowIndex needs to be unique and sequential
const newRowIndex = existingRowCount + i;
const row = this.createTableRow(newRowIndex, cellCount, tableId);
tableWrapper.appendChild(row);
}
Canvas.historyManager.captureState();
}
static getDefaultValuesOfInput() {
const defaults = {};
TableComponent.tableAttributeConfig.forEach(attr => {
if (
attr.type === 'Input' &&
attr.default_value !== undefined &&
attr.default_value !== null
) {
defaults[attr.key] = attr.default_value;
}
});
return defaults;
}
static restore(container, editable) {
const instance = new TableComponent();
const tableWrapper = container.querySelector('.table-wrapper');
const closestTable =
tableWrapper === null || tableWrapper === void 0
? void 0
: tableWrapper.closest('.table-component');
const tableId =
closestTable === null || closestTable === void 0
? void 0
: closestTable.id;
if (!tableWrapper) {
console.error('No table wrapper found in container');
return;
}
const cells = tableWrapper.querySelectorAll('.table-cell');
const rows = tableWrapper.querySelectorAll('.table-row');
rows.forEach(row => {
const rowElement = row;
if (rowElement.classList.contains('selected')) {
rowElement.classList.remove('selected');
}
});
cells.forEach(cell => {
const cellElement = cell;
const attributeKey = cellElement.getAttribute('data-attribute-key');
const attributeType = cellElement.getAttribute('data-attribute-type');
const textContentOfCell = cell.querySelector('.table-cell-content');
if (
textContentOfCell === null || textContentOfCell === void 0
? void 0
: textContentOfCell.classList.contains('selected')
) {
textContentOfCell.classList.remove('selected');
}
if (attributeKey && textContentOfCell) {
const attribute = TableComponent.tableAttributeConfig.find(
attr => attr.key === attributeKey
);
if (attribute) {
const controlsElement = cell.querySelector('.cell-controls');
if (
attribute.default_value &&
(attributeType === 'Formula' || attributeType === 'Input')
) {
textContentOfCell.textContent = `${attribute.default_value}`;
cellElement.style.fontSize = '14px';
cellElement.style.color = '#000000';
} else if (attributeType === 'Formula') {
// Restore the title and styling for formula cells
textContentOfCell.textContent = `${attribute.title}`;
cellElement.style.fontSize = '10px';
cellElement.style.color = 'rgb(188 191 198)';
cellElement.style.fontWeight = '500';
}
if (controlsElement) {
cell.appendChild(controlsElement);
}
}
}
const controls = cellElement.querySelector('.cell-controls');
if (editable === false) {
controls === null || controls === void 0 ? void 0 : controls.remove();
textContentOfCell === null || textContentOfCell === void 0
? void 0
: textContentOfCell.removeAttribute('contenteditable');
return;
}
if (controls) {
const addButton = controls.querySelector('.add-cell-button');
const deleteButton = controls.querySelector('.delete-cell-button');
if (addButton) {
addButton.addEventListener('click', e => {
e.stopPropagation();
instance.addCellToRow(cellElement, tableId);
});
}
if (deleteButton) {
deleteButton.addEventListener('click', e => {
e.stopPropagation();
instance.deleteCell(cellElement);
});
}
}
});
const addMultipleRowsButton = container.querySelector(
'.add-multiple-rows-button'
);
const btnContainer = container.querySelector('.table-btn-container');
const rowCountInput = container.querySelector('.row-count-input');
if (addMultipleRowsButton && editable !== false) {
rowCountInput.value = '1';
addMultipleRowsButton.addEventListener('click', () => {
const count = parseInt(rowCountInput.value) || 1;
instance.addRows(
tableWrapper,
tableId,
Math.min(Math.max(count, 1), 20)
);
});
} else if (editable === false && btnContainer) {
btnContainer === null || btnContainer === void 0
? void 0
: btnContainer.remove();
}
const defaultValues = TableComponent.getDefaultValuesOfInput();
instance.evaluateRowVisibility(defaultValues, container);
}
}