@schneideress/dashboardframework 20.0.26 → 20.0.28
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -4980,8 +4980,291 @@ class RADashboardArea {
|
|
|
4980
4980
|
setTimeout(() => {
|
|
4981
4981
|
this.currentLoadingCount--;
|
|
4982
4982
|
}, 100);
|
|
4983
|
+
// For expanded view, override widget dimensions after data loads
|
|
4984
|
+
const isExpandedView = new URLSearchParams(window.location.search).get('expanded') === 'true';
|
|
4985
|
+
if (isExpandedView && widgetInfo && widgetInfo.widgetInstanceId) {
|
|
4986
|
+
this.overrideWidgetDimensions(widgetInfo);
|
|
4987
|
+
}
|
|
4983
4988
|
// this.loadData()
|
|
4984
4989
|
}
|
|
4990
|
+
/**
|
|
4991
|
+
* Override widget height and width for expanded view based on actual data content
|
|
4992
|
+
* Reinitializes gridster after updating dimensions
|
|
4993
|
+
*/
|
|
4994
|
+
overrideWidgetDimensions(widgetInfo) {
|
|
4995
|
+
// Find the gridster item for this widget
|
|
4996
|
+
const gridsterItem = this.widgetList.find((item) => item.widgetInfo && item.widgetInfo.widgetInstanceId === widgetInfo.widgetInstanceId);
|
|
4997
|
+
if (gridsterItem && gridsterItem.widgetElement) {
|
|
4998
|
+
// Wait for DOM to update after data load - use multiple attempts to catch async rendering
|
|
4999
|
+
this.measureAndUpdateWidgetDimensions(gridsterItem, 0);
|
|
5000
|
+
}
|
|
5001
|
+
}
|
|
5002
|
+
/**
|
|
5003
|
+
* Measure widget content and update dimensions, with retry logic for async data loading
|
|
5004
|
+
*/
|
|
5005
|
+
measureAndUpdateWidgetDimensions(gridsterItem, attempt = 0, maxAttempts = 8) {
|
|
5006
|
+
setTimeout(() => {
|
|
5007
|
+
// Get the actual rendered height of the widget content
|
|
5008
|
+
const widgetElement = gridsterItem.widgetElement;
|
|
5009
|
+
let element = null;
|
|
5010
|
+
// widgetElement is the ElementRef from widgetLoaded event (wcWrapper.nativeElement)
|
|
5011
|
+
if (widgetElement) {
|
|
5012
|
+
if (widgetElement.nativeElement) {
|
|
5013
|
+
element = widgetElement.nativeElement;
|
|
5014
|
+
}
|
|
5015
|
+
else if (widgetElement instanceof HTMLElement) {
|
|
5016
|
+
element = widgetElement;
|
|
5017
|
+
}
|
|
5018
|
+
}
|
|
5019
|
+
if (!element || this.gridcellHeight <= 0) {
|
|
5020
|
+
if (attempt < maxAttempts) {
|
|
5021
|
+
this.measureAndUpdateWidgetDimensions(gridsterItem, attempt + 1, maxAttempts);
|
|
5022
|
+
}
|
|
5023
|
+
return;
|
|
5024
|
+
}
|
|
5025
|
+
// Find the table body or grid content that contains the actual data rows
|
|
5026
|
+
const tbody = element.querySelector('tbody');
|
|
5027
|
+
const table = element.querySelector('table');
|
|
5028
|
+
const wcBody = element.querySelector('.wcBody');
|
|
5029
|
+
const wc = element.querySelector('.wc');
|
|
5030
|
+
const kendoGridContent = element.querySelector('.k-grid-content');
|
|
5031
|
+
const kendoGridTable = element.querySelector('.k-grid-table');
|
|
5032
|
+
// Measure the actual content height - try multiple strategies to get accurate measurement
|
|
5033
|
+
let contentHeight = 0;
|
|
5034
|
+
let measuredElement = null;
|
|
5035
|
+
// Strategy 1: Measure table body (most accurate for row count)
|
|
5036
|
+
if (tbody) {
|
|
5037
|
+
measuredElement = tbody;
|
|
5038
|
+
// Count actual rows and calculate height
|
|
5039
|
+
const rows = tbody.querySelectorAll('tr');
|
|
5040
|
+
if (rows.length > 0) {
|
|
5041
|
+
// Get height of first row to estimate
|
|
5042
|
+
const firstRow = rows[0];
|
|
5043
|
+
const rowHeight = firstRow.offsetHeight || 30; // Default row height if not available
|
|
5044
|
+
// Calculate total height based on row count
|
|
5045
|
+
const estimatedHeight = rows.length * rowHeight;
|
|
5046
|
+
// Use scrollHeight if available, otherwise use estimated
|
|
5047
|
+
contentHeight = Math.max(tbody.scrollHeight, estimatedHeight);
|
|
5048
|
+
}
|
|
5049
|
+
else {
|
|
5050
|
+
contentHeight = tbody.scrollHeight;
|
|
5051
|
+
}
|
|
5052
|
+
}
|
|
5053
|
+
// Strategy 2: Measure Kendo grid content
|
|
5054
|
+
else if (kendoGridContent) {
|
|
5055
|
+
measuredElement = kendoGridContent;
|
|
5056
|
+
contentHeight = kendoGridContent.scrollHeight;
|
|
5057
|
+
// Also check the table inside
|
|
5058
|
+
if (kendoGridTable) {
|
|
5059
|
+
const tableHeight = kendoGridTable.scrollHeight;
|
|
5060
|
+
contentHeight = Math.max(contentHeight, tableHeight);
|
|
5061
|
+
}
|
|
5062
|
+
}
|
|
5063
|
+
// Strategy 3: Measure table
|
|
5064
|
+
else if (table) {
|
|
5065
|
+
measuredElement = table;
|
|
5066
|
+
contentHeight = table.scrollHeight;
|
|
5067
|
+
}
|
|
5068
|
+
// Strategy 4: Measure widget content
|
|
5069
|
+
else if (wc) {
|
|
5070
|
+
measuredElement = wc;
|
|
5071
|
+
contentHeight = wc.scrollHeight;
|
|
5072
|
+
}
|
|
5073
|
+
// Strategy 5: Measure widget body
|
|
5074
|
+
else if (wcBody) {
|
|
5075
|
+
measuredElement = wcBody;
|
|
5076
|
+
contentHeight = wcBody.scrollHeight;
|
|
5077
|
+
}
|
|
5078
|
+
// Fallback: measure the wrapper
|
|
5079
|
+
else {
|
|
5080
|
+
measuredElement = element;
|
|
5081
|
+
contentHeight = element.scrollHeight;
|
|
5082
|
+
}
|
|
5083
|
+
// If scrollHeight seems constrained, try alternative measurements
|
|
5084
|
+
if (measuredElement) {
|
|
5085
|
+
// Check if scrollHeight is accurate (should be > clientHeight if content overflows)
|
|
5086
|
+
const scrollH = measuredElement.scrollHeight;
|
|
5087
|
+
const clientH = measuredElement.clientHeight;
|
|
5088
|
+
const offsetH = measuredElement.offsetHeight;
|
|
5089
|
+
// If scrollHeight equals clientHeight and seems too small, content might be constrained
|
|
5090
|
+
if (scrollH === clientH && scrollH < 200) {
|
|
5091
|
+
// Try offsetHeight
|
|
5092
|
+
contentHeight = Math.max(contentHeight, offsetH);
|
|
5093
|
+
}
|
|
5094
|
+
// For tables, also try measuring all rows directly
|
|
5095
|
+
if (tbody || table) {
|
|
5096
|
+
const allRows = (tbody || table)?.querySelectorAll('tr') || [];
|
|
5097
|
+
if (allRows.length > 0) {
|
|
5098
|
+
let totalRowHeight = 0;
|
|
5099
|
+
allRows.forEach((row) => {
|
|
5100
|
+
totalRowHeight += row.offsetHeight || 30;
|
|
5101
|
+
});
|
|
5102
|
+
// Use the maximum of scrollHeight and calculated row heights
|
|
5103
|
+
contentHeight = Math.max(contentHeight, totalRowHeight);
|
|
5104
|
+
}
|
|
5105
|
+
}
|
|
5106
|
+
}
|
|
5107
|
+
// Get header height
|
|
5108
|
+
const header = element.querySelector('.wcheader');
|
|
5109
|
+
const headerHeight = header ? header.offsetHeight : 46;
|
|
5110
|
+
// Calculate total height needed (content + header + padding)
|
|
5111
|
+
// Add sufficient padding to ensure no scrolling
|
|
5112
|
+
const padding = 40; // Padding for margins and spacing
|
|
5113
|
+
const totalHeight = contentHeight + headerHeight + padding;
|
|
5114
|
+
// Check if content is loaded (reasonable height)
|
|
5115
|
+
// For expanded view, we expect significant content
|
|
5116
|
+
const hasValidContent = contentHeight > 100 || (attempt >= 3 && contentHeight > 50);
|
|
5117
|
+
if (!hasValidContent && attempt < maxAttempts) {
|
|
5118
|
+
// Content not fully loaded, retry with longer delay
|
|
5119
|
+
this.measureAndUpdateWidgetDimensions(gridsterItem, attempt + 1, maxAttempts);
|
|
5120
|
+
return;
|
|
5121
|
+
}
|
|
5122
|
+
// Calculate rows needed based on grid cell height
|
|
5123
|
+
// Use Math.ceil to ensure we have enough space (round up)
|
|
5124
|
+
const calculatedRows = Math.ceil(totalHeight / this.gridcellHeight);
|
|
5125
|
+
// Add a small buffer (0.5 rows) to ensure no scrolling, then round up
|
|
5126
|
+
// This accounts for any rounding errors or minor spacing issues
|
|
5127
|
+
const bufferedRows = calculatedRows + 0.5;
|
|
5128
|
+
const newRows = Math.max(Math.ceil(bufferedRows), 4);
|
|
5129
|
+
// For expanded view, use full available width (no spacing needed since widgets stack vertically)
|
|
5130
|
+
const maxCols = this.responsiveService.maxColsDesktop || 12;
|
|
5131
|
+
const newCols = maxCols; // Full width
|
|
5132
|
+
// Update gridster item dimensions if changed
|
|
5133
|
+
const needsUpdate = gridsterItem.rows !== newRows || gridsterItem.cols !== newCols || gridsterItem.x !== 0;
|
|
5134
|
+
if (needsUpdate) {
|
|
5135
|
+
gridsterItem.rows = newRows;
|
|
5136
|
+
gridsterItem.cols = newCols;
|
|
5137
|
+
gridsterItem.x = 0; // Always start at the beginning of the row (full width)
|
|
5138
|
+
// Update widget info
|
|
5139
|
+
if (gridsterItem.widgetInfo) {
|
|
5140
|
+
gridsterItem.widgetInfo.height = newRows;
|
|
5141
|
+
gridsterItem.widgetInfo.width = newCols;
|
|
5142
|
+
gridsterItem.widgetInfo.position_x = 0; // Start at position 0 for full width
|
|
5143
|
+
}
|
|
5144
|
+
// Reposition widgets to prevent overlapping after dimension change
|
|
5145
|
+
this.repositionWidgetsAfterDimensionChange();
|
|
5146
|
+
// Force gridster to recalculate layout
|
|
5147
|
+
this.ngZone.run(() => {
|
|
5148
|
+
// Trigger change detection by updating the array reference
|
|
5149
|
+
this.widgetList = [...this.widgetList];
|
|
5150
|
+
// Call optionsChanged if API is available
|
|
5151
|
+
if (this.options && this.options.api) {
|
|
5152
|
+
this.options.api?.optionsChanged();
|
|
5153
|
+
}
|
|
5154
|
+
// Update area height after dimensions change
|
|
5155
|
+
setTimeout(() => {
|
|
5156
|
+
this.setAreaHeight();
|
|
5157
|
+
// Re-verify height after layout update to ensure accuracy
|
|
5158
|
+
// Check if there's still scrolling and adjust if needed
|
|
5159
|
+
if (attempt < maxAttempts - 1) {
|
|
5160
|
+
setTimeout(() => {
|
|
5161
|
+
// Re-measure to verify the height is correct
|
|
5162
|
+
const widgetEl = gridsterItem.widgetElement;
|
|
5163
|
+
let el = null;
|
|
5164
|
+
if (widgetEl) {
|
|
5165
|
+
el = widgetEl.nativeElement || widgetEl;
|
|
5166
|
+
}
|
|
5167
|
+
if (el) {
|
|
5168
|
+
const wcBody = el.querySelector('.wcBody');
|
|
5169
|
+
const tbody = el.querySelector('tbody');
|
|
5170
|
+
const contentEl = tbody || wcBody || el;
|
|
5171
|
+
// Check if there's scrolling (scrollHeight > clientHeight)
|
|
5172
|
+
if (contentEl && contentEl.scrollHeight > contentEl.clientHeight + 10) {
|
|
5173
|
+
// Still has scrolling, need more height
|
|
5174
|
+
const additionalHeight = contentEl.scrollHeight - contentEl.clientHeight;
|
|
5175
|
+
const additionalRows = Math.ceil(additionalHeight / this.gridcellHeight);
|
|
5176
|
+
if (additionalRows > 0) {
|
|
5177
|
+
gridsterItem.rows = gridsterItem.rows + additionalRows;
|
|
5178
|
+
if (gridsterItem.widgetInfo) {
|
|
5179
|
+
gridsterItem.widgetInfo.height = gridsterItem.rows;
|
|
5180
|
+
}
|
|
5181
|
+
this.widgetList = [...this.widgetList];
|
|
5182
|
+
if (this.options && this.options.api) {
|
|
5183
|
+
this.options.api?.optionsChanged();
|
|
5184
|
+
}
|
|
5185
|
+
setTimeout(() => this.setAreaHeight(), 100);
|
|
5186
|
+
}
|
|
5187
|
+
}
|
|
5188
|
+
}
|
|
5189
|
+
// Continue with normal retry logic
|
|
5190
|
+
this.measureAndUpdateWidgetDimensions(gridsterItem, attempt + 1, maxAttempts);
|
|
5191
|
+
}, 400);
|
|
5192
|
+
}
|
|
5193
|
+
}, 200);
|
|
5194
|
+
});
|
|
5195
|
+
}
|
|
5196
|
+
else if (attempt < maxAttempts - 1) {
|
|
5197
|
+
// Dimensions didn't change, but verify one more time that content is fully loaded
|
|
5198
|
+
this.measureAndUpdateWidgetDimensions(gridsterItem, attempt + 1, maxAttempts);
|
|
5199
|
+
}
|
|
5200
|
+
}, attempt === 0 ? 1500 : attempt < 3 ? 800 : 500); // Longer initial wait for data to fully render
|
|
5201
|
+
}
|
|
5202
|
+
/**
|
|
5203
|
+
* Reposition widgets to prevent overlapping after dimension changes in expanded view
|
|
5204
|
+
* When 2 widgets are in the same row, the 2nd widget moves to next row after the 1st widget's height
|
|
5205
|
+
*/
|
|
5206
|
+
repositionWidgetsAfterDimensionChange() {
|
|
5207
|
+
if (this.widgetList.length === 0)
|
|
5208
|
+
return;
|
|
5209
|
+
// Sort widgets by their original Y position (row), then by X position (left to right)
|
|
5210
|
+
const sortedWidgets = [...this.widgetList].sort((a, b) => {
|
|
5211
|
+
const aY = a.y || 0;
|
|
5212
|
+
const bY = b.y || 0;
|
|
5213
|
+
if (aY !== bY) {
|
|
5214
|
+
return aY - bY;
|
|
5215
|
+
}
|
|
5216
|
+
// If same row, sort by X position (left to right)
|
|
5217
|
+
return (a.x || 0) - (b.x || 0);
|
|
5218
|
+
});
|
|
5219
|
+
// Group widgets by their original row (Y position) before repositioning
|
|
5220
|
+
const rowGroups = new Map();
|
|
5221
|
+
sortedWidgets.forEach(widget => {
|
|
5222
|
+
const originalY = widget.y || 0;
|
|
5223
|
+
if (!rowGroups.has(originalY)) {
|
|
5224
|
+
rowGroups.set(originalY, []);
|
|
5225
|
+
}
|
|
5226
|
+
rowGroups.get(originalY).push(widget);
|
|
5227
|
+
});
|
|
5228
|
+
// Sort rows by Y position
|
|
5229
|
+
const sortedRows = Array.from(rowGroups.entries()).sort((a, b) => a[0] - b[0]);
|
|
5230
|
+
let currentY = 0;
|
|
5231
|
+
// Process each original row
|
|
5232
|
+
sortedRows.forEach(([originalRowY, widgetsInRow]) => {
|
|
5233
|
+
// Sort widgets in this row by X position (left to right)
|
|
5234
|
+
widgetsInRow.sort((a, b) => (a.x || 0) - (b.x || 0));
|
|
5235
|
+
// Process widgets sequentially: first widget at currentY, subsequent widgets after previous widget's height
|
|
5236
|
+
// All widgets get x=0 since they take full width and stack vertically
|
|
5237
|
+
widgetsInRow.forEach((widget, index) => {
|
|
5238
|
+
if (index === 0) {
|
|
5239
|
+
// First widget in row: place at currentY
|
|
5240
|
+
widget.y = currentY;
|
|
5241
|
+
}
|
|
5242
|
+
else {
|
|
5243
|
+
// Subsequent widgets: place after the previous widget's bottom edge
|
|
5244
|
+
const previousWidget = widgetsInRow[index - 1];
|
|
5245
|
+
const previousWidgetHeight = previousWidget.rows || 0;
|
|
5246
|
+
const previousWidgetY = previousWidget.y || 0;
|
|
5247
|
+
widget.y = previousWidgetY + previousWidgetHeight;
|
|
5248
|
+
}
|
|
5249
|
+
// Set x to 0 for all widgets (full width, no horizontal spacing)
|
|
5250
|
+
widget.x = 0;
|
|
5251
|
+
// Update widget info
|
|
5252
|
+
if (widget.widgetInfo) {
|
|
5253
|
+
widget.widgetInfo.position_y = widget.y;
|
|
5254
|
+
widget.widgetInfo.position_x = 0; // Full width, start at position 0
|
|
5255
|
+
}
|
|
5256
|
+
});
|
|
5257
|
+
// Calculate where the next row should start
|
|
5258
|
+
// If multiple widgets in row, use the last widget's bottom edge
|
|
5259
|
+
// Otherwise, use the first widget's bottom edge
|
|
5260
|
+
if (widgetsInRow.length > 0) {
|
|
5261
|
+
const lastWidget = widgetsInRow[widgetsInRow.length - 1];
|
|
5262
|
+
const lastWidgetY = lastWidget.y || 0;
|
|
5263
|
+
const lastWidgetHeight = lastWidget.rows || 0;
|
|
5264
|
+
currentY = lastWidgetY + lastWidgetHeight;
|
|
5265
|
+
}
|
|
5266
|
+
});
|
|
5267
|
+
}
|
|
4985
5268
|
isElementLoaded(el) {
|
|
4986
5269
|
if (el) {
|
|
4987
5270
|
let rect = el.getBoundingClientRect();
|
|
@@ -5122,6 +5405,8 @@ class RADashboardArea {
|
|
|
5122
5405
|
};
|
|
5123
5406
|
let _the = this;
|
|
5124
5407
|
this.isSiteDetailDashboard = this.dashboardInfo?.templateName === 'Site Detail';
|
|
5408
|
+
// Check if expanded view is requested
|
|
5409
|
+
//const isExpandedView = new URLSearchParams(window.location.search).get('expanded') === 'true';
|
|
5125
5410
|
this.dashboardService.getWidgetsByDashboardArea(widgetInfo, this.appConfig).subscribe(widgets => {
|
|
5126
5411
|
if (this.isSiteDetailDashboard) {
|
|
5127
5412
|
widgets = this.arrangeWidgetsInTwoColumnGrid(widgets);
|