fix(typeahead_input): add explicit alive flag and defensive js checks for component lifecycle management
This commit is contained in:
parent
e528f9e684
commit
24c138b866
1 changed files with 119 additions and 13 deletions
|
@ -135,8 +135,16 @@ pub fn TypeaheadInput(
|
||||||
initialized: false,
|
initialized: false,
|
||||||
bloodhound: null,
|
bloodhound: null,
|
||||||
handlers: {{}},
|
handlers: {{}},
|
||||||
|
// EXPLICIT ALIVE FLAG
|
||||||
|
alive: true,
|
||||||
// Method to safely call handlers
|
// Method to safely call handlers
|
||||||
callHandler: function(handlerName, ...args) {{
|
callHandler: function(handlerName, ...args) {{
|
||||||
|
// DEFENSIVE: Check alive flag before calling any handler
|
||||||
|
if (!this.alive) {{
|
||||||
|
console.warn('[JS] Component {component_id} is no longer alive, ignoring handler call: ' + handlerName);
|
||||||
|
return null;
|
||||||
|
}}
|
||||||
|
|
||||||
try {{
|
try {{
|
||||||
const handler = this.handlers[handlerName];
|
const handler = this.handlers[handlerName];
|
||||||
if (handler && typeof handler === 'function') {{
|
if (handler && typeof handler === 'function') {{
|
||||||
|
@ -149,6 +157,12 @@ pub fn TypeaheadInput(
|
||||||
}},
|
}},
|
||||||
// Method to register a handler
|
// Method to register a handler
|
||||||
registerHandler: function(name, fn) {{
|
registerHandler: function(name, fn) {{
|
||||||
|
// DEFENSIVE: Don't register handlers if component is not alive
|
||||||
|
if (!this.alive) {{
|
||||||
|
console.warn('[JS] Component {component_id} is no longer alive, ignoring handler registration: ' + name);
|
||||||
|
return false;
|
||||||
|
}}
|
||||||
|
|
||||||
this.handlers[name] = fn;
|
this.handlers[name] = fn;
|
||||||
return true;
|
return true;
|
||||||
}},
|
}},
|
||||||
|
@ -163,11 +177,43 @@ pub fn TypeaheadInput(
|
||||||
// Method to clean up all resources
|
// Method to clean up all resources
|
||||||
cleanup: function() {{
|
cleanup: function() {{
|
||||||
try {{
|
try {{
|
||||||
|
// IMPORTANT: Set alive to false FIRST to prevent any new calls
|
||||||
|
this.alive = false;
|
||||||
|
console.log('[JS] Component {component_id} marked as not alive');
|
||||||
|
|
||||||
// Clean up typeahead
|
// Clean up typeahead
|
||||||
const inputId = 'typeahead-input-{component_id}';
|
const inputId = 'typeahead-input-{component_id}';
|
||||||
const $input = $('#' + inputId);
|
const $input = $('#' + inputId);
|
||||||
if ($input.length > 0) {{
|
if ($input.length > 0) {{
|
||||||
|
// Remove all event handlers first
|
||||||
|
$input.off('typeahead:select');
|
||||||
|
$input.off('typeahead:active');
|
||||||
|
$input.off('typeahead:idle');
|
||||||
|
$input.off('typeahead:open');
|
||||||
|
$input.off('typeahead:close');
|
||||||
|
$input.off('typeahead:change');
|
||||||
|
$input.off('typeahead:render');
|
||||||
|
$input.off('typeahead:autocomplete');
|
||||||
|
$input.off('typeahead:cursorchange');
|
||||||
|
$input.off('typeahead:asyncrequest');
|
||||||
|
$input.off('typeahead:asynccancel');
|
||||||
|
$input.off('typeahead:asyncreceive');
|
||||||
|
console.log('[JS] Removed all typeahead event handlers for {component_id}');
|
||||||
|
|
||||||
|
// Now destroy the typeahead
|
||||||
$input.typeahead('destroy');
|
$input.typeahead('destroy');
|
||||||
|
console.log('[JS] Destroyed typeahead for {component_id}');
|
||||||
|
}}
|
||||||
|
|
||||||
|
// Explicitly null out the global handler references
|
||||||
|
if (window.rustSelectHandler_{component_id_safe}) {{
|
||||||
|
window.rustSelectHandler_{component_id_safe} = null;
|
||||||
|
console.log('[JS] Nulled rustSelectHandler_{component_id_safe}');
|
||||||
|
}}
|
||||||
|
|
||||||
|
if (window.rustFetchHandler_{component_id_safe}) {{
|
||||||
|
window.rustFetchHandler_{component_id_safe} = null;
|
||||||
|
console.log('[JS] Nulled rustFetchHandler_{component_id_safe}');
|
||||||
}}
|
}}
|
||||||
|
|
||||||
// Clear all handlers
|
// Clear all handlers
|
||||||
|
@ -181,6 +227,8 @@ pub fn TypeaheadInput(
|
||||||
return true;
|
return true;
|
||||||
}} catch (e) {{
|
}} catch (e) {{
|
||||||
console.error('[JS] Error during cleanup:', e);
|
console.error('[JS] Error during cleanup:', e);
|
||||||
|
// Still mark as not alive even if cleanup fails
|
||||||
|
this.alive = false;
|
||||||
return false;
|
return false;
|
||||||
}}
|
}}
|
||||||
}}
|
}}
|
||||||
|
@ -189,7 +237,8 @@ pub fn TypeaheadInput(
|
||||||
console.log('[JS] Registered component {component_id}');
|
console.log('[JS] Registered component {component_id}');
|
||||||
true
|
true
|
||||||
"#,
|
"#,
|
||||||
component_id = component_id
|
component_id = component_id,
|
||||||
|
component_id_safe = component_id.replace("-", "_")
|
||||||
);
|
);
|
||||||
|
|
||||||
// Execute the registration script
|
// Execute the registration script
|
||||||
|
@ -342,20 +391,34 @@ pub fn TypeaheadInput(
|
||||||
r#"
|
r#"
|
||||||
// Perform cleanup in JavaScript first
|
// Perform cleanup in JavaScript first
|
||||||
if (window.typeaheadRegistry && window.typeaheadRegistry['{component_id}']) {{
|
if (window.typeaheadRegistry && window.typeaheadRegistry['{component_id}']) {{
|
||||||
|
console.log('[JS] Starting cleanup for component {component_id}');
|
||||||
|
|
||||||
// Clean up the component
|
// Clean up the component
|
||||||
const result = window.typeaheadRegistry['{component_id}'].cleanup();
|
const result = window.typeaheadRegistry['{component_id}'].cleanup();
|
||||||
|
|
||||||
|
// DEFENSIVE: Explicitly null out global handlers even if cleanup fails
|
||||||
|
if (window.rustSelectHandler_{component_id_safe}) {{
|
||||||
|
window.rustSelectHandler_{component_id_safe} = null;
|
||||||
|
console.log('[JS] Nulled rustSelectHandler_{component_id_safe} during cleanup');
|
||||||
|
}}
|
||||||
|
|
||||||
|
if (window.rustFetchHandler_{component_id_safe}) {{
|
||||||
|
window.rustFetchHandler_{component_id_safe} = null;
|
||||||
|
console.log('[JS] Nulled rustFetchHandler_{component_id_safe} during cleanup');
|
||||||
|
}}
|
||||||
|
|
||||||
// Remove from registry
|
// Remove from registry
|
||||||
delete window.typeaheadRegistry['{component_id}'];
|
delete window.typeaheadRegistry['{component_id}'];
|
||||||
|
|
||||||
console.log('[JS] Component {component_id} removed from registry');
|
console.log('[JS] Component {component_id} removed from registry');
|
||||||
result
|
result
|
||||||
}} else {{
|
}} else {{
|
||||||
console.warn('[JS] Component {component_id} not found in registry');
|
console.warn('[JS] Component {component_id} not found in registry during cleanup');
|
||||||
false
|
false
|
||||||
}}
|
}}
|
||||||
"#,
|
"#,
|
||||||
component_id = component_id_for_cleanup
|
component_id = component_id_for_cleanup,
|
||||||
|
component_id_safe = component_id_for_cleanup.replace("-", "_")
|
||||||
);
|
);
|
||||||
|
|
||||||
// Execute JavaScript cleanup
|
// Execute JavaScript cleanup
|
||||||
|
@ -643,16 +706,25 @@ fn initialize_bloodhound(
|
||||||
let transport_fn = js_sys::Function::new_with_args(
|
let transport_fn = js_sys::Function::new_with_args(
|
||||||
"query, syncResults, asyncResults",
|
"query, syncResults, asyncResults",
|
||||||
&format!(r#"
|
&format!(r#"
|
||||||
|
// DEFENSIVE: Check if registry exists and component is alive
|
||||||
|
if (!window.typeaheadRegistry || !window.typeaheadRegistry['{component_id}']) {{
|
||||||
|
console.warn('[JS] Component registry not found for {component_id}, returning empty results');
|
||||||
|
syncResults([]);
|
||||||
|
return;
|
||||||
|
}}
|
||||||
|
|
||||||
|
// DEFENSIVE: Check alive flag explicitly
|
||||||
|
if (!window.typeaheadRegistry['{component_id}'].alive) {{
|
||||||
|
console.warn('[JS] Component {component_id} is no longer alive, returning empty results');
|
||||||
|
syncResults([]);
|
||||||
|
return;
|
||||||
|
}}
|
||||||
|
|
||||||
// Call our handler through the registry
|
// Call our handler through the registry
|
||||||
if (window.typeaheadRegistry && window.typeaheadRegistry['{component_id}']) {{
|
try {{
|
||||||
try {{
|
window.typeaheadRegistry['{component_id}'].callHandler('fetch', query, syncResults, asyncResults);
|
||||||
window.typeaheadRegistry['{component_id}'].callHandler('fetch', query, syncResults, asyncResults);
|
}} catch (e) {{
|
||||||
}} catch (e) {{
|
console.error('[JS] Error calling fetch handler through registry:', e);
|
||||||
console.error('[JS] Error calling fetch handler through registry:', e);
|
|
||||||
syncResults([]);
|
|
||||||
}}
|
|
||||||
}} else {{
|
|
||||||
console.error('[JS] Component registry not found for {component_id}');
|
|
||||||
syncResults([]);
|
syncResults([]);
|
||||||
}}
|
}}
|
||||||
"#, component_id = component_id)
|
"#, component_id = component_id)
|
||||||
|
@ -908,11 +980,16 @@ fn initialize_typeahead(
|
||||||
r#"
|
r#"
|
||||||
console.log('[JS] Starting Typeahead init for #{input_id}');
|
console.log('[JS] Starting Typeahead init for #{input_id}');
|
||||||
try {{
|
try {{
|
||||||
// Get the component from registry
|
// DEFENSIVE: Check if registry exists and component is alive
|
||||||
if (!window.typeaheadRegistry || !window.typeaheadRegistry['{component_id}']) {{
|
if (!window.typeaheadRegistry || !window.typeaheadRegistry['{component_id}']) {{
|
||||||
throw new Error('Component not found in registry: {component_id}');
|
throw new Error('Component not found in registry: {component_id}');
|
||||||
}}
|
}}
|
||||||
|
|
||||||
|
// DEFENSIVE: Check alive flag explicitly
|
||||||
|
if (!window.typeaheadRegistry['{component_id}'].alive) {{
|
||||||
|
throw new Error('Component {component_id} is no longer alive');
|
||||||
|
}}
|
||||||
|
|
||||||
// Get the bloodhound instance from the registry
|
// Get the bloodhound instance from the registry
|
||||||
var bloodhound = window.typeaheadRegistry['{component_id}'].bloodhound;
|
var bloodhound = window.typeaheadRegistry['{component_id}'].bloodhound;
|
||||||
if (!bloodhound) {{
|
if (!bloodhound) {{
|
||||||
|
@ -925,6 +1002,9 @@ fn initialize_typeahead(
|
||||||
throw new Error('Input element not found: #{input_id}');
|
throw new Error('Input element not found: #{input_id}');
|
||||||
}}
|
}}
|
||||||
|
|
||||||
|
// DEFENSIVE: Remove any existing typeahead to prevent duplicate handlers
|
||||||
|
$input.typeahead('destroy');
|
||||||
|
|
||||||
$input.typeahead(
|
$input.typeahead(
|
||||||
{{
|
{{
|
||||||
hint: true,
|
hint: true,
|
||||||
|
@ -938,6 +1018,20 @@ fn initialize_typeahead(
|
||||||
return data.displayLabel || data.label || '';
|
return data.displayLabel || data.label || '';
|
||||||
}},
|
}},
|
||||||
source: function(query, syncResults, asyncResults) {{
|
source: function(query, syncResults, asyncResults) {{
|
||||||
|
// DEFENSIVE: Check if registry exists and component is alive
|
||||||
|
if (!window.typeaheadRegistry || !window.typeaheadRegistry['{component_id}']) {{
|
||||||
|
console.warn('[JS] Component registry not found for {component_id}, returning empty results');
|
||||||
|
syncResults([]);
|
||||||
|
return;
|
||||||
|
}}
|
||||||
|
|
||||||
|
// DEFENSIVE: Check alive flag explicitly
|
||||||
|
if (!window.typeaheadRegistry['{component_id}'].alive) {{
|
||||||
|
console.warn('[JS] Component {component_id} is no longer alive, returning empty results');
|
||||||
|
syncResults([]);
|
||||||
|
return;
|
||||||
|
}}
|
||||||
|
|
||||||
// Call the fetch handler through the registry
|
// Call the fetch handler through the registry
|
||||||
window.typeaheadRegistry['{component_id}'].callHandler('fetch', query, syncResults, asyncResults);
|
window.typeaheadRegistry['{component_id}'].callHandler('fetch', query, syncResults, asyncResults);
|
||||||
}},
|
}},
|
||||||
|
@ -957,6 +1051,18 @@ fn initialize_typeahead(
|
||||||
}}
|
}}
|
||||||
)
|
)
|
||||||
.on('typeahead:select', function(ev, suggestion) {{
|
.on('typeahead:select', function(ev, suggestion) {{
|
||||||
|
// DEFENSIVE: Check if registry exists and component is alive
|
||||||
|
if (!window.typeaheadRegistry || !window.typeaheadRegistry['{component_id}']) {{
|
||||||
|
console.warn('[JS] Component registry not found for {component_id}, ignoring selection event');
|
||||||
|
return;
|
||||||
|
}}
|
||||||
|
|
||||||
|
// DEFENSIVE: Check alive flag explicitly
|
||||||
|
if (!window.typeaheadRegistry['{component_id}'].alive) {{
|
||||||
|
console.warn('[JS] Component {component_id} is no longer alive, ignoring selection event');
|
||||||
|
return;
|
||||||
|
}}
|
||||||
|
|
||||||
if (!suggestion) {{
|
if (!suggestion) {{
|
||||||
console.error('[JS] Selection event received with null suggestion');
|
console.error('[JS] Selection event received with null suggestion');
|
||||||
return;
|
return;
|
||||||
|
|
Loading…
Add table
Reference in a new issue