We have a reasonable layout for the table-editor in the specialist. To be further refined.

This commit is contained in:
Josako
2025-05-30 05:05:13 +02:00
parent 25e169dbea
commit 73647e4795
7 changed files with 395 additions and 51 deletions

View File

@@ -33,21 +33,47 @@ window.EveAI.OrderedListEditors = {
// Create column definitions from list type
const columns = this._createColumnsFromListType(listTypeConfig);
// Debug log for data and columns
console.log('Data for Tabulator:', data);
console.log('Columns for Tabulator:', columns);
// Debug log for column titles
console.log('Column titles:', columns.map(col => col.title || ''));
// Initialize Tabulator
try {
console.log('Creating Tabulator for', containerId);
const table = new Tabulator(container, {
data: data || [],
columns: columns,
layout: "fitColumns",
layout: "fitColumns", // Changed to fitColumns to ensure columns display horizontally
movableRows: true,
height: "400px",
{#rowHeader: {headerSort:false, resizable: false, minWidth:30, width:30, rowHandle:true, formatter:"handle"},#}
maxHeight: "50%", // Auto height to display all rows
placeholder: "No Data Available",
autoResize: false,
resizableColumnFit: true,
responsiveLayout: false,
tooltips: true, // Enable tooltips
tooltipsHeader: true,
selectable: false, // Disable row selection to prevent jumping
selectableRangeMode: "click", // Only select on click, not on drag
selectableRollingSelection: false, // Disable rolling selection
scrollToRowIfVisible: false, // Don't scroll to row even if it's already visible
scrollToRowPosition: "nearest",
...options
});
console.log('Tabulator created for', containerId);
container.classList.add('tabulator-initialized');
// Debug: Log table structure
console.log('Table structure:', {
tableElement: container,
tableData: table.getData(),
tableColumns: table.getColumnDefinitions()
});
// Add row button
const addRowBtn = document.createElement('button');
addRowBtn.className = 'btn btn-sm btn-primary mt-2';
@@ -56,21 +82,65 @@ window.EveAI.OrderedListEditors = {
const newRow = {};
// Create empty row with default values
Object.entries(listTypeConfig).forEach(([key, field]) => {
newRow[key] = field.default || '';
if (field.type === 'boolean') {
newRow[key] = field.default === true;
} else {
newRow[key] = field.default !== undefined ? field.default : '';
}
});
table.addRow(newRow);
this._updateTextarea(containerId, table);
});
container.parentNode.insertBefore(addRowBtn, container.nextSibling);
// Add explode button for fullscreen mode
const explodeBtn = document.createElement('button');
explodeBtn.className = 'btn btn-sm btn-secondary mt-2 ms-2';
explodeBtn.innerHTML = '<i class="material-icons">fullscreen</i> Expand';
explodeBtn.addEventListener('click', () => {
container.classList.toggle('fullscreen-mode');
// Update button text based on current state
if (container.classList.contains('fullscreen-mode')) {
explodeBtn.innerHTML = '<i class="material-icons">fullscreen_exit</i> Collapse';
} else {
explodeBtn.innerHTML = '<i class="material-icons">fullscreen</i> Expand';
}
// Redraw table to adjust to new size
table.redraw(true);
});
container.parentNode.insertBefore(explodeBtn, addRowBtn.nextSibling);
// Store instance
this.instances[containerId] = {
table: table,
textarea: document.getElementById(containerId.replace('-editor', ''))
};
// Update textarea on data change
// Prevent scrolling when clicking on cells
container.addEventListener('click', function(e) {
// Prevent the default behavior which might cause scrolling
if (e.target.closest('.tabulator-cell')) {
e.preventDefault();
}
}, { passive: false });
// Update textarea on various events that change data
table.on("dataChanged", () => {
console.log("dataChanged event triggered");
this._updateTextarea(containerId, table);
});
// Listen for row movement
table.on("rowMoved", () => {
console.log("rowMoved event triggered");
this._updateTextarea(containerId, table);
});
// Listen for cell edits
table.on("cellEdited", () => {
console.log("cellEdited event triggered");
this._updateTextarea(containerId, table);
});
@@ -87,65 +157,147 @@ window.EveAI.OrderedListEditors = {
_updateTextarea: function(containerId, table) {
const instance = this.instances[containerId];
if (instance && instance.textarea) {
instance.textarea.value = JSON.stringify(table.getData());
const data = table.getData();
console.log('Updating textarea with data:', data);
instance.textarea.value = JSON.stringify(data);
console.log('Textarea value updated:', instance.textarea.value);
// Trigger change event on textarea to ensure form validation recognizes the change
const event = new Event('change', { bubbles: true });
instance.textarea.dispatchEvent(event);
// Also trigger input event for any listeners that might be using that
const inputEvent = new Event('input', { bubbles: true });
instance.textarea.dispatchEvent(inputEvent);
} else {
console.error('Cannot update textarea: instance or textarea not found for', containerId);
}
},
_getListTypeConfig: function(listType) {
// This would need to be implemented to fetch the list type configuration
// Could be from a global variable set in the template or via an API call
return window.listTypeConfigs && window.listTypeConfigs[listType];
// Try to get the list type configuration from window.listTypeConfigs
if (window.listTypeConfigs && window.listTypeConfigs[listType]) {
return window.listTypeConfigs[listType];
}
// If not found, log a warning and return a default configuration
console.warn(`List type configuration for ${listType} not found in window.listTypeConfigs. Using a default configuration.`);
return {
title: {
name: "Title",
description: "Title",
type: "str",
required: true
},
description: {
name: "Description",
description: "Description",
type: "text",
required: true
}
};
},
// Custom formatter for text columns to truncate text in normal mode
_truncateFormatter: function(cell, formatterParams, onRendered) {
const value = cell.getValue();
const maxLength = formatterParams.maxLength || 100;
if (value && value.length > maxLength) {
// Create a truncated version with "..." and show more indicator
const truncated = value.substring(0, maxLength) + "...";
// Return HTML with truncated text and a "show more" button
return `<div class="truncated-cell">
<div class="truncated-content">${truncated}</div>
<div class="show-more" title="Click to edit and see full text">
<i class="material-icons">more_horiz</i>
</div>
</div>`;
}
return value;
},
_createColumnsFromListType: function(listTypeConfig) {
const columns = [];
// Add drag handle column for row reordering
columns.push({
formatter: "handle",
headerSort: false,
frozen: true,
width: 30,
minWidth: 30
});
// Add columns for each field in the list type
Object.entries(listTypeConfig).forEach(([key, field]) => {
const column = {
title: field.name || key,
field: key,
tooltip: field.description
headerTooltip: field.description,
headerSort: false,
visible: true,
resizable: "header",
};
console.log("Column ", field.name, " type: ", field.type)
// Set width based on field type
if (field.type === 'boolean') {
column.minWidth = 50;
column.maxWidth = 80; // Limit maximum width
column.widthGrow = 0; // Don't allow boolean columns to grow
} else if (field.type === 'text') {
column.width = 400; // Much larger width for text columns (especially description)
column.minWidth = 300; // Ensure text columns have adequate minimum width
column.widthGrow = 2; // Allow text columns to grow significantly more
} else {
column.width = 150; // Default width for other columns
column.minWidth = 100;
column.widthGrow = 1; // Allow some growth
}
// Ensure consistent width calculation
column.widthShrink = 0; // Don't allow shrinking below minWidth
// Set editor based on field type
if (field.type === 'boolean') {
column.formatter = 'tickCross';
column.editor = 'tickCross';
column.hozAlign = 'center';
column.headerHozAlign = 'center';
column.formatterParams = {
allowEmpty: true,
allowTruthy: true,
tickElement: "<i class='material-icons'>check_circle</i>",
crossElement: "<i class='material-icons'>cancel</i>"
};
} else if (field.type === 'enum' && field.allowed_values) {
column.editor = 'select';
column.editorParams = {
values: field.allowed_values
};
column.hozAlign = 'left';
column.headerHozAlign = 'left';
} else if (field.type === 'text') {
column.editor = 'textarea';
column.formatter = this._truncateFormatter; // Use custom formatter to truncate text
column.variableHeight = true;
// Configure formatter parameters
column.formatterParams = {
maxLength: 50,
autoResize: true
};
// Prevent scrolling when editing text cells
column.editorParams = {
elementAttributes: {
preventScroll: true
}
};
column.hozAlign = 'left';
column.headerHozAlign = 'left';
} else {
column.editor = 'input';
column.hozAlign = 'left';
column.headerHozAlign = 'left';
}
columns.push(column);
});
// Add delete button column
columns.push({
formatter: function(cell, formatterParams, onRendered) {
return "<button class='btn btn-sm btn-danger'><i class='material-icons'>delete</i></button>";
},
width: 40,
hozAlign: "center",
headerSort: false,
cellClick: function(e, cell) {
cell.getRow().delete();
}
});
// We don't add a delete button column as per requirements
// to prevent users from deleting rows
return columns;
},
@@ -176,8 +328,17 @@ document.addEventListener('DOMContentLoaded', function() {
// Initialize ordered list editors
document.querySelectorAll('.ordered-list-field').forEach(function(textarea) {
const containerId = textarea.id + '-editor';
const container = document.getElementById(containerId);
if (!container) return;
console.log('Initializing ordered list editor for', containerId);
// Create container if it doesn't exist
let container = document.getElementById(containerId);
if (!container) {
container = document.createElement('div');
container.id = containerId;
container.className = 'ordered-list-editor';
textarea.parentNode.insertBefore(container, textarea.nextSibling);
textarea.classList.add('d-none'); // Hide the textarea
}
try {
const data = textarea.value ? JSON.parse(textarea.value) : [];
@@ -185,7 +346,7 @@ document.addEventListener('DOMContentLoaded', function() {
// Check if we have the list type configuration
if (listType && !window.listTypeConfigs[listType]) {
console.warn(`List type configuration for ${listType} not found. The editor may not work correctly.`);
console.warn(`List type configuration for ${listType} not found. Using default configuration.`);
}
window.EveAI.OrderedListEditors.initialize(containerId, data, listType);
@@ -203,19 +364,57 @@ document.addEventListener('DOMContentLoaded', function() {
/* Tabulator styling */
.ordered-list-editor {
margin-bottom: 1rem;
min-height: 200px; /* Minimum height, will expand as needed */
}
/* Make sure the Tabulator container has a proper height */
.ordered-list-editor .tabulator {
height: 400px;
height: auto; /* Auto height to display all rows */
min-height: 200px; /* Minimum height */
width: 100%;
border: 1px solid #dee2e6;
border-radius: 0.25rem;
margin-bottom: 0.5rem;
}
/* Ensure the table holder has a scrollbar */
.ordered-list-editor .tabulator-tableholder {
/* overflow-y: auto !important; - Removed to allow Tabulator to handle overflow */
/* max-height: calc(100% - 42px) !important; - Removed to allow Tabulator to handle height */
/* Consider using non-!important values if specific scrolling behavior is needed */
overflow-y: auto;
max-height: calc(100% - 42px);
}
/* Style for the table element */
.ordered-list-editor .tabulator-table {
display: table !important; /* Force display as table */
width: 100% !important;
table-layout: fixed !important; /* Use fixed table layout for consistent column widths */
}
/* Style for the handle column */
.ordered-list-editor .tabulator-row-handle {
cursor: move;
background-color: #f8f9fa;
border-right: 1px solid #dee2e6;
}
/* Style for the handle bars to make them more visible */
.ordered-list-editor .tabulator-row-handle-box {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 100%;
}
.ordered-list-editor .tabulator-row-handle-bar {
background: #666;
display: inline-block;
width: 10px;
height: 2px;
margin: 1px 0;
}
/* Style for the delete button */
@@ -228,4 +427,133 @@ document.addEventListener('DOMContentLoaded', function() {
.ordered-list-editor .tabulator-cell[data-type="boolean"] {
text-align: center;
}
/* Style for the table header */
.ordered-list-editor .tabulator-header {
background-color: #f8f9fa;
border-bottom: 2px solid #dee2e6;
}
/* Style for the headers container */
.ordered-list-editor .tabulator-headers {
display: table-row !important; /* Force display as table row */
}
/* Style for the header cells */
.ordered-list-editor .tabulator-col {
background-color: #f8f9fa;
padding: 8px;
font-weight: bold;
text-align: center;
display: table-cell !important; /* Force display as table cell */
box-sizing: border-box !important; /* Include padding in width calculation */
position: relative !important; /* Ensure proper positioning */
}
/* Override any inline styles that might hide column headers */
.ordered-list-editor .tabulator-col[style*="display: none"] {
display: table-cell !important; /* Force display as table cell */
}
/* Ensure header cells have the same width as their corresponding data cells */
.ordered-list-editor .tabulator-col,
.ordered-list-editor .tabulator-cell {
{#width: auto !important; /* Let the table-layout: fixed handle the width */#}
}
/* Style for the header cell content */
.ordered-list-editor .tabulator-col-title {
white-space: normal; /* Allow header text to wrap */
word-break: break-word; /* Break words to prevent horizontal overflow */
font-weight: bold;
color: #333;
}
/* Style for the table rows */
.ordered-list-editor .tabulator-row {
border-bottom: 1px solid #dee2e6;
display: table-row !important; /* Force display as table row */
}
/* Style for the table cells */
.ordered-list-editor .tabulator-cell {
padding: 8px;
white-space: normal; /* Allow text to wrap */
overflow: visible; /* Show overflowing content */
height: auto !important; /* Allow cell to grow as needed */
word-break: break-word; /* Break words to prevent horizontal overflow */
display: table-cell !important; /* Force display as table cell */
scroll-margin-top: 100px; /* Prevent unwanted scrolling when focusing */
scroll-behavior: auto; /* Disable smooth scrolling which might cause jumping */
font-size: 0.85rem; /* Smaller font size */
}
/* Style for truncated cells */
.ordered-list-editor .truncated-cell {
position: relative;
padding-right: 20px;
}
.ordered-list-editor .truncated-content {
white-space: normal;
word-break: break-word;
}
.ordered-list-editor .show-more {
position: absolute;
right: 0;
top: 50%;
transform: translateY(-50%);
color: #007bff;
cursor: pointer;
}
/* Style for the visible cells */
.ordered-list-editor .tabulator-cell-visible {
display: table-cell !important; /* Force display as table cell */
}
/* Override any inline styles that might hide cells */
.ordered-list-editor .tabulator-cell[style*="display: none"] {
display: table-cell !important; /* Force display as table cell */
}
/* Style for the textarea editor */
.ordered-list-editor .tabulator-cell textarea {
min-height: 60px;
resize: vertical;
width: 100%; /* Ensure textarea fills the cell */
}
/* Style for the placeholder */
.ordered-list-editor .tabulator-placeholder {
padding: 20px;
text-align: center;
color: #6c757d;
}
/* Style for the Add Row button */
.ordered-list-editor + .btn-primary {
margin-top: 0.5rem;
}
/* Fullscreen mode styles */
.ordered-list-editor.fullscreen-mode {
position: fixed;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
z-index: 9999;
background: white;
padding: 20px;
margin: 0;
overflow: auto;
box-sizing: border-box;
}
.ordered-list-editor.fullscreen-mode .tabulator {
height: calc(100vh - 100px) !important;
width: 100% !important;
}
</style>

View File

@@ -69,8 +69,9 @@
<div id="{{ field.id }}-editor" class="json-editor-container"></div>
{{ field(class="form-control d-none " + class, disabled=disabled, readonly=readonly) }}
{% elif field.type == 'OrderedListField' or 'ordered-list-field' in field_class %}
{# Behoud ordered-list-field class en voeg form-control toe #}
{{ field(class="form-control " + field_class|trim, disabled=disabled, readonly=readonly) }}
{# Create container for ordered list editor and hide the textarea #}
<div id="{{ field.id }}-editor" class="ordered-list-editor"></div>
{{ field(class="form-control d-none " + field_class|trim, disabled=disabled, readonly=readonly) }}
{% elif field.type == 'SelectField' %}
{{ field(class="form-control form-select " + class, disabled=disabled, readonly=readonly) }}
{% else %}
@@ -449,4 +450,3 @@
</div>
</div>
{% endmacro %}

View File

@@ -13,11 +13,15 @@
<script>
document.addEventListener('DOMContentLoaded', function() {
// Initialize tooltips
var tooltipTriggerList = [].slice.call(document.querySelectorAll('[data-bs-toggle="tooltip"]'))
var tooltipList = tooltipTriggerList.map(function (tooltipTriggerEl) {
return new bootstrap.Tooltip(tooltipTriggerEl)
});
// Initialize tooltips if bootstrap is available
if (typeof bootstrap !== 'undefined') {
var tooltipTriggerList = [].slice.call(document.querySelectorAll('[data-bs-toggle="tooltip"]'))
var tooltipList = tooltipTriggerList.map(function (tooltipTriggerEl) {
return new bootstrap.Tooltip(tooltipTriggerEl)
});
} else {
console.warn('Bootstrap is not defined. Tooltips will not be initialized.');
}
// De JSON editor initialisatie is hierboven al samengevoegd.
// Deze dubbele DOMContentLoaded listener en .json-editor initialisatie kan verwijderd worden.
@@ -47,9 +51,12 @@ document.addEventListener('DOMContentLoaded', function() {
// Find and click the corresponding tab button
const tabButton = document.querySelector(`[data-bs-toggle="tab"][data-bs-target="#${tabId}"]`);
if (tabButton) {
if (tabButton && typeof bootstrap !== 'undefined') {
const tab = new bootstrap.Tab(tabButton);
tab.show();
} else if (tabButton) {
// Fallback if bootstrap is not available
tabButton.click();
}
// Scroll the invalid field into view and focus it