@@ -160,11 +160,7 @@ export class ViewerComponent
|
||||
afterGetColHeader: (col: number, th: any, headerLevel: number) => {
|
||||
// CRITICAL: Prevent "colToProp method cannot be called because this Handsontable instance has been destroyed" error
|
||||
// This callback can be triggered even after the instance is destroyed during rapid table switching
|
||||
if (
|
||||
!this.hotInstance ||
|
||||
this.hotInstance.isDestroyed ||
|
||||
this.isTableSwitching
|
||||
) {
|
||||
if (!this.hotInstance || this.hotInstance.isDestroyed) {
|
||||
// Graceful fallback: apply only dark mode styling when instance is unavailable
|
||||
th.classList.add(globals.handsontable.darkTableHeaderClass)
|
||||
return
|
||||
@@ -253,9 +249,6 @@ export class ViewerComponent
|
||||
private isTableSwitching: boolean = false
|
||||
private switchingTimeout: any = null
|
||||
|
||||
// Prevents duplicate setupHot() calls within short time windows
|
||||
private lastSetupTime: number = 0
|
||||
|
||||
public viewboxOpen: boolean = false
|
||||
|
||||
constructor(
|
||||
@@ -761,10 +754,6 @@ export class ViewerComponent
|
||||
// This prevents callbacks from accessing destroyed instances during table switching
|
||||
this.isTableSwitching = true
|
||||
|
||||
// CLEANUP: Ensure any existing Handsontable instance is properly destroyed
|
||||
// This prevents "instance destroyed" errors
|
||||
this.cleanupHotInstance()
|
||||
|
||||
this.loadingTableView = true
|
||||
|
||||
let libDataset: any
|
||||
@@ -1177,7 +1166,6 @@ export class ViewerComponent
|
||||
* Purpose: Prevents "instance destroyed" errors and memory leaks during table switching
|
||||
*
|
||||
* Called from:
|
||||
* - viewData() - before loading new table data
|
||||
* - setupHot() - before creating new instance
|
||||
* - ngOnDestroy() - component cleanup
|
||||
*
|
||||
@@ -1187,6 +1175,7 @@ export class ViewerComponent
|
||||
* - Sets instance to null to prevent stale references
|
||||
*/
|
||||
private cleanupHotInstance() {
|
||||
clearTimeout(this.setupHotTimer)
|
||||
if (this.hotInstance && !this.hotInstance.isDestroyed) {
|
||||
try {
|
||||
this.hotInstance.destroy()
|
||||
@@ -1195,39 +1184,32 @@ export class ViewerComponent
|
||||
}
|
||||
}
|
||||
this.hotInstance = null
|
||||
this.hooksAttached = false
|
||||
}
|
||||
|
||||
private setupHotTimer: any = null
|
||||
|
||||
/**
|
||||
* PERFORMANCE: Configures Handsontable with enhanced error handling (workaround needed for HOT version 16 and above)
|
||||
*
|
||||
* 1. Duplicate call prevention (500ms window)
|
||||
* 2. Reduced timeout delays (200ms + 50ms vs original 1000ms + 200ms)
|
||||
* 3. Multiple validation checks to prevent race conditions
|
||||
* 4. Forced render for immediate primary key styling
|
||||
*
|
||||
* Timeline: 50ms (viewData) + 200ms (main) + 50ms (component ready) = ~300ms total
|
||||
* Previous: 100ms + 600ms + 100ms = 800ms (plus render delays = ~2 seconds)
|
||||
* Configures Handsontable instance with settings, styling and hooks.
|
||||
* Instance lifecycle is managed by Angular's hot-table component via [data] and [settings] bindings.
|
||||
* This method only applies additional config that can't go through bindings (hooks, PK styling).
|
||||
* Debounced to avoid expensive render() calls on large tables.
|
||||
*/
|
||||
private setupHot() {
|
||||
// DUPLICATE PREVENTION: Avoid multiple setup calls during rapid table switching
|
||||
const now = Date.now()
|
||||
if (now - this.lastSetupTime < 500) {
|
||||
return
|
||||
}
|
||||
this.lastSetupTime = now
|
||||
clearTimeout(this.setupHotTimer)
|
||||
|
||||
setTimeout(() => {
|
||||
// VALIDATION: Don't setup if we're currently switching tables or still loading
|
||||
if (this.loadingTableView || !this.libDataset) {
|
||||
this.setupHotTimer = setTimeout(() => {
|
||||
if (!this.hotInstance || this.hotInstance.isDestroyed) {
|
||||
this.hotInstance = this.hotTableComponent?.hotInstance
|
||||
}
|
||||
|
||||
if (this.hotInstance && !this.hotInstance.isDestroyed) {
|
||||
this.configureHotInstance()
|
||||
return
|
||||
}
|
||||
|
||||
// CLEANUP: Ensure clean slate before new setup
|
||||
this.cleanupHotInstance()
|
||||
|
||||
// TIMING: Wait for Angular component to be ready (optimized from 100ms to 50ms)
|
||||
// Instance not ready yet — Angular may still be creating the component
|
||||
setTimeout(() => {
|
||||
// DOUBLE-CHECK: Ensure we're still in valid state after delays
|
||||
if (
|
||||
this.isTableSwitching ||
|
||||
this.loadingTableView ||
|
||||
@@ -1237,65 +1219,72 @@ export class ViewerComponent
|
||||
}
|
||||
|
||||
this.hotInstance = this.hotTableComponent?.hotInstance
|
||||
this.configureHotInstance()
|
||||
}, 250)
|
||||
}, 50)
|
||||
}
|
||||
|
||||
if (this.hotInstance && !this.hotInstance.isDestroyed) {
|
||||
this.hotInstance.updateSettings({
|
||||
height: this.hotTable.height,
|
||||
modifyColWidth: (width: any, col: any) => {
|
||||
if (width > 500) return 500
|
||||
else return width
|
||||
},
|
||||
afterGetColHeader: (col: number, th: any) => {
|
||||
// CRITICAL: Same safety checks as initial callback to prevent destroyed instance errors
|
||||
if (
|
||||
!this.hotInstance ||
|
||||
this.hotInstance.isDestroyed ||
|
||||
this.isTableSwitching
|
||||
) {
|
||||
th.classList.add(globals.handsontable.darkTableHeaderClass)
|
||||
return
|
||||
}
|
||||
private hooksAttached = false
|
||||
|
||||
try {
|
||||
const column = this.hotInstance.colToProp(col) as string
|
||||
/**
|
||||
* Applies settings that can't go through Angular [settings] binding:
|
||||
* - Primary key column header styling
|
||||
* - Column width cap
|
||||
* - ARIA accessibility hooks (attached once per instance)
|
||||
*/
|
||||
private configureHotInstance() {
|
||||
if (!this.hotInstance || this.hotInstance.isDestroyed) return
|
||||
|
||||
// PRIMARY KEY STYLING: Apply special styling to PK columns (populated from API response)
|
||||
const isPKCol = column && this.headerPks.indexOf(column) > -1
|
||||
if (isPKCol) th.classList.add('primaryKeyHeaderStyle')
|
||||
|
||||
// DARK MODE: Apply to all headers
|
||||
th.classList.add(globals.handsontable.darkTableHeaderClass)
|
||||
} catch (error) {
|
||||
// SAFETY NET: Ensure basic styling is always applied
|
||||
th.classList.add(globals.handsontable.darkTableHeaderClass)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
// Add hooks for accessibility fixes
|
||||
this.hotInstance.addHook('afterRender', () => {
|
||||
// Fix ARIA accessibility issues after each render
|
||||
this.fixAriaAccessibility()
|
||||
})
|
||||
|
||||
this.hotInstance.addHook('afterChange', () => {
|
||||
// Fix ARIA accessibility issues after any data change
|
||||
setTimeout(() => {
|
||||
this.fixAriaAccessibility()
|
||||
}, 50)
|
||||
})
|
||||
|
||||
// Force immediate render to apply primary key styling
|
||||
// Without this, styling would wait for ~2 seconds to be applied
|
||||
// With this, styling appears in ~300ms total (workaround needed for HOT version 16 and above)
|
||||
setTimeout(() => {
|
||||
if (this.hotInstance && !this.hotInstance.isDestroyed) {
|
||||
this.hotInstance.render()
|
||||
}
|
||||
}, 10)
|
||||
this.hotInstance.updateSettings({
|
||||
height: this.hotTable.height,
|
||||
modifyColWidth: (width: any, col: any) => {
|
||||
if (width > 500) return 500
|
||||
else return width
|
||||
},
|
||||
afterGetColHeader: (col: number, th: any) => {
|
||||
// CRITICAL: Same safety checks as initial callback to prevent destroyed instance errors
|
||||
if (!this.hotInstance || this.hotInstance.isDestroyed) {
|
||||
th.classList.add(globals.handsontable.darkTableHeaderClass)
|
||||
return
|
||||
}
|
||||
}, 50) // Optimized Angular component readiness delay
|
||||
}, 200) // Optimized main setup delay (was 600ms)
|
||||
|
||||
try {
|
||||
const column = this.hotInstance.colToProp(col) as string
|
||||
|
||||
// PRIMARY KEY STYLING: Apply special styling to PK columns (populated from API response)
|
||||
const isPKCol = column && this.headerPks.indexOf(column) > -1
|
||||
if (isPKCol) th.classList.add('primaryKeyHeaderStyle')
|
||||
|
||||
// DARK MODE: Apply to all headers
|
||||
th.classList.add(globals.handsontable.darkTableHeaderClass)
|
||||
} catch (error) {
|
||||
// SAFETY NET: Ensure basic styling is always applied
|
||||
th.classList.add(globals.handsontable.darkTableHeaderClass)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
// Add hooks for accessibility fixes
|
||||
// Hooks are attached once per instance to avoid accumulating duplicate listeners
|
||||
if (!this.hooksAttached) {
|
||||
this.hotInstance.addHook('afterRender', () => {
|
||||
// Fix ARIA accessibility issues after each render
|
||||
|
||||
this.fixAriaAccessibility()
|
||||
})
|
||||
|
||||
this.hotInstance.addHook('afterChange', () => {
|
||||
// Fix ARIA accessibility issues after any data change
|
||||
setTimeout(() => {
|
||||
this.fixAriaAccessibility()
|
||||
}, 50)
|
||||
})
|
||||
|
||||
this.hooksAttached = true
|
||||
}
|
||||
|
||||
// Force immediate render to apply primary key styling
|
||||
this.hotInstance.render()
|
||||
}
|
||||
|
||||
async loadWithParameters() {
|
||||
|
||||
Reference in New Issue
Block a user