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,
|
||||
bloodhound: null,
|
||||
handlers: {{}},
|
||||
// EXPLICIT ALIVE FLAG
|
||||
alive: true,
|
||||
// Method to safely call handlers
|
||||
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 {{
|
||||
const handler = this.handlers[handlerName];
|
||||
if (handler && typeof handler === 'function') {{
|
||||
|
@ -149,6 +157,12 @@ pub fn TypeaheadInput(
|
|||
}},
|
||||
// Method to register a handler
|
||||
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;
|
||||
return true;
|
||||
}},
|
||||
|
@ -163,11 +177,43 @@ pub fn TypeaheadInput(
|
|||
// Method to clean up all resources
|
||||
cleanup: function() {{
|
||||
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
|
||||
const inputId = 'typeahead-input-{component_id}';
|
||||
const $input = $('#' + inputId);
|
||||
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');
|
||||
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
|
||||
|
@ -181,6 +227,8 @@ pub fn TypeaheadInput(
|
|||
return true;
|
||||
}} catch (e) {{
|
||||
console.error('[JS] Error during cleanup:', e);
|
||||
// Still mark as not alive even if cleanup fails
|
||||
this.alive = false;
|
||||
return false;
|
||||
}}
|
||||
}}
|
||||
|
@ -189,7 +237,8 @@ pub fn TypeaheadInput(
|
|||
console.log('[JS] Registered component {component_id}');
|
||||
true
|
||||
"#,
|
||||
component_id = component_id
|
||||
component_id = component_id,
|
||||
component_id_safe = component_id.replace("-", "_")
|
||||
);
|
||||
|
||||
// Execute the registration script
|
||||
|
@ -342,20 +391,34 @@ pub fn TypeaheadInput(
|
|||
r#"
|
||||
// Perform cleanup in JavaScript first
|
||||
if (window.typeaheadRegistry && window.typeaheadRegistry['{component_id}']) {{
|
||||
console.log('[JS] Starting cleanup for component {component_id}');
|
||||
|
||||
// Clean up the component
|
||||
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
|
||||
delete window.typeaheadRegistry['{component_id}'];
|
||||
|
||||
console.log('[JS] Component {component_id} removed from registry');
|
||||
result
|
||||
}} else {{
|
||||
console.warn('[JS] Component {component_id} not found in registry');
|
||||
console.warn('[JS] Component {component_id} not found in registry during cleanup');
|
||||
false
|
||||
}}
|
||||
"#,
|
||||
component_id = component_id_for_cleanup
|
||||
component_id = component_id_for_cleanup,
|
||||
component_id_safe = component_id_for_cleanup.replace("-", "_")
|
||||
);
|
||||
|
||||
// Execute JavaScript cleanup
|
||||
|
@ -643,16 +706,25 @@ fn initialize_bloodhound(
|
|||
let transport_fn = js_sys::Function::new_with_args(
|
||||
"query, syncResults, asyncResults",
|
||||
&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
|
||||
if (window.typeaheadRegistry && window.typeaheadRegistry['{component_id}']) {{
|
||||
try {{
|
||||
window.typeaheadRegistry['{component_id}'].callHandler('fetch', query, syncResults, asyncResults);
|
||||
}} catch (e) {{
|
||||
console.error('[JS] Error calling fetch handler through registry:', e);
|
||||
syncResults([]);
|
||||
}}
|
||||
}} else {{
|
||||
console.error('[JS] Component registry not found for {component_id}');
|
||||
try {{
|
||||
window.typeaheadRegistry['{component_id}'].callHandler('fetch', query, syncResults, asyncResults);
|
||||
}} catch (e) {{
|
||||
console.error('[JS] Error calling fetch handler through registry:', e);
|
||||
syncResults([]);
|
||||
}}
|
||||
"#, component_id = component_id)
|
||||
|
@ -908,11 +980,16 @@ fn initialize_typeahead(
|
|||
r#"
|
||||
console.log('[JS] Starting Typeahead init for #{input_id}');
|
||||
try {{
|
||||
// Get the component from registry
|
||||
// DEFENSIVE: Check if registry exists and component is alive
|
||||
if (!window.typeaheadRegistry || !window.typeaheadRegistry['{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
|
||||
var bloodhound = window.typeaheadRegistry['{component_id}'].bloodhound;
|
||||
if (!bloodhound) {{
|
||||
|
@ -925,6 +1002,9 @@ fn initialize_typeahead(
|
|||
throw new Error('Input element not found: #{input_id}');
|
||||
}}
|
||||
|
||||
// DEFENSIVE: Remove any existing typeahead to prevent duplicate handlers
|
||||
$input.typeahead('destroy');
|
||||
|
||||
$input.typeahead(
|
||||
{{
|
||||
hint: true,
|
||||
|
@ -938,6 +1018,20 @@ fn initialize_typeahead(
|
|||
return data.displayLabel || data.label || '';
|
||||
}},
|
||||
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
|
||||
window.typeaheadRegistry['{component_id}'].callHandler('fetch', query, syncResults, asyncResults);
|
||||
}},
|
||||
|
@ -957,6 +1051,18 @@ fn initialize_typeahead(
|
|||
}}
|
||||
)
|
||||
.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) {{
|
||||
console.error('[JS] Selection event received with null suggestion');
|
||||
return;
|
||||
|
|
Loading…
Add table
Reference in a new issue