feat(typeahead): Enhance Bloodhound and Typeahead initialization logic
- Improved Bloodhound initialization by adding explicit global storage for the instance. - Enhanced `initialize_bloodhound` to include proper rate limiting, wildcard configuration, and tokenizer setup. - Refactored `initialize_typeahead` to include a more robust dataset configuration with templates for rendering suggestions. - Fixed DOM element access in the `on:input` handler to ensure proper interaction with the input element. - Simplified the `remote_fn` logic in `initialize_bloodhound` for fetching and syncing suggestions. - Added error handling and logging for better debugging during Typeahead initialization. - Ensured closures are properly registered in the global scope to handle Typeahead events. - Updated the JavaScript initialization script to use bracket notation for safer handler invocation. - Removed the additional inclusion of `corejs-typeahead` script from the `<head>` section.
This commit is contained in:
parent
5ca277ee80
commit
07405db017
2 changed files with 107 additions and 52 deletions
|
@ -32,7 +32,6 @@ pub fn App() -> impl IntoView {
|
|||
<head>
|
||||
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
|
||||
<script src="https://twitter.github.io/typeahead.js/releases/latest/typeahead.bundle.min.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/corejs-typeahead/1.3.1/typeahead.bundle.min.js"></script>
|
||||
</head>
|
||||
<Router>
|
||||
<Routes>
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
use leptos::*;
|
||||
use wasm_bindgen::prelude::*;
|
||||
use crate::models::item::WikidataSuggestion;
|
||||
use js_sys::{Object, Array, Function, JSON, Reflect};
|
||||
use js_sys::{Object, Array, Function, Reflect};
|
||||
use leptos::html::Input;
|
||||
use gloo_utils::format::JsValueSerdeExt;
|
||||
use wasm_bindgen::JsCast;
|
||||
|
@ -26,6 +26,14 @@ pub fn TypeaheadInput(
|
|||
if let Some(input) = node_ref.get() {
|
||||
log!("[INIT] Input element found");
|
||||
let bloodhound = initialize_bloodhound(fetch_suggestions.clone());
|
||||
|
||||
// Store bloodhound globally
|
||||
js_sys::Reflect::set(
|
||||
&js_sys::global(),
|
||||
&"bloodhoundInstance".into(),
|
||||
&bloodhound
|
||||
).unwrap();
|
||||
|
||||
initialize_typeahead(&input, bloodhound, on_select.clone(), node_ref.clone());
|
||||
set_initialized.set(true);
|
||||
break;
|
||||
|
@ -46,12 +54,8 @@ pub fn TypeaheadInput(
|
|||
on:input=move |ev| {
|
||||
let value = event_target_value(&ev);
|
||||
log!("[INPUT] Value changed: {}", value);
|
||||
if let Some(input) = node_ref.get() {
|
||||
// Correct DOM element access using JsCast
|
||||
let dom_input: &web_sys::HtmlInputElement = &*input;
|
||||
let id = dom_input.id();
|
||||
let _ = js_sys::eval(&format!("console.log('JS Value:', $('#{}').val())", id));
|
||||
}
|
||||
let _ = js_sys::eval("console.log('jQuery version:', $.fn.jquery)");
|
||||
let _ = js_sys::eval("console.log('Typeahead version:', $.fn.typeahead ? 'loaded' : 'missing')");
|
||||
}
|
||||
/>
|
||||
}
|
||||
|
@ -75,35 +79,46 @@ extern "C" {
|
|||
fn initialize_bloodhound(fetch: Callback<String, Vec<WikidataSuggestion>>) -> JsValue {
|
||||
let bloodhound_options = Object::new();
|
||||
|
||||
// Configure Bloodhound remote with proper parameters
|
||||
let remote_fn = Closure::wrap(Box::new(move |query: String, sync: Function| {
|
||||
log!("[BLOODHOUND] Fetching suggestions for: {}", query);
|
||||
let suggestions = fetch.call(query.clone());
|
||||
let remote_fn = Closure::wrap(Box::new(move |query: JsValue, sync: Function| {
|
||||
let query_str = query.as_string().unwrap_or_default();
|
||||
log!("[BLOODHOUND] Fetching suggestions for: {}", query_str);
|
||||
let suggestions = fetch.call(query_str.clone());
|
||||
log!("[BLOODHOUND] Received {} suggestions", suggestions.len());
|
||||
|
||||
let array = Array::new();
|
||||
for suggestion in &suggestions {
|
||||
let obj = Object::new();
|
||||
Reflect::set(&obj, &"label".into(), &suggestion.label.clone().into()).unwrap();
|
||||
Reflect::set(&obj, &"value".into(), &suggestion.id.clone().into()).unwrap();
|
||||
Reflect::set(&obj, &"label".into(), &suggestion.label.clone().into()).unwrap_or_default();
|
||||
Reflect::set(&obj, &"value".into(), &suggestion.id.clone().into()).unwrap_or_default();
|
||||
array.push(&obj);
|
||||
}
|
||||
|
||||
sync.call1(&JsValue::NULL, &array).unwrap();
|
||||
}) as Box<dyn Fn(String, Function)>);
|
||||
let _ = sync.call1(&JsValue::NULL, &array);
|
||||
}) as Box<dyn Fn(JsValue, Function)>);
|
||||
|
||||
let remote_config = Object::new();
|
||||
|
||||
// Url function
|
||||
Reflect::set(
|
||||
&remote_config,
|
||||
&"url".into(),
|
||||
&JsValue::from_str("/dummy?query=%QUERY")
|
||||
).unwrap();
|
||||
|
||||
// Prepare function
|
||||
Reflect::set(
|
||||
&remote_config,
|
||||
&"prepare".into(),
|
||||
&js_sys::eval(&format!(
|
||||
"function(query, callback) {{
|
||||
return {}(query, callback);
|
||||
}}",
|
||||
remote_fn.as_ref().unchecked_ref::<js_sys::Function>().to_string()
|
||||
)).unwrap()
|
||||
remote_fn.as_ref().unchecked_ref()
|
||||
).unwrap();
|
||||
|
||||
// Rate limiting
|
||||
Reflect::set(
|
||||
&remote_config,
|
||||
&"rateLimitWait".into(),
|
||||
&JsValue::from(300)
|
||||
).unwrap();
|
||||
|
||||
// Wildcard function
|
||||
Reflect::set(
|
||||
&remote_config,
|
||||
&"wildcard".into(),
|
||||
|
@ -111,16 +126,38 @@ fn initialize_bloodhound(fetch: Callback<String, Vec<WikidataSuggestion>>) -> Js
|
|||
).unwrap();
|
||||
|
||||
Reflect::set(&bloodhound_options, &"remote".into(), &remote_config).unwrap();
|
||||
Reflect::set(&bloodhound_options, &"datumTokenizer".into(), &JsValue::from_str("whitespace")).unwrap();
|
||||
Reflect::set(&bloodhound_options, &"queryTokenizer".into(), &JsValue::from_str("whitespace")).unwrap();
|
||||
|
||||
// Tokenizer functions from Bloodhound
|
||||
let tokenizer = js_sys::eval(r#"Bloodhound.tokenizers.whitespace"#)
|
||||
.expect("Should get whitespace tokenizer");
|
||||
|
||||
Reflect::set(
|
||||
&bloodhound_options,
|
||||
&"datumTokenizer".into(),
|
||||
&tokenizer
|
||||
).unwrap();
|
||||
|
||||
Reflect::set(
|
||||
&bloodhound_options,
|
||||
&"queryTokenizer".into(),
|
||||
&tokenizer
|
||||
).unwrap();
|
||||
|
||||
let bloodhound = Bloodhound::new(&bloodhound_options.into());
|
||||
bloodhound.initialize(true);
|
||||
remote_fn.forget();
|
||||
|
||||
// Explicit retention
|
||||
js_sys::Reflect::set(
|
||||
&js_sys::global(),
|
||||
&"bloodhoundInstance".into(),
|
||||
&bloodhound
|
||||
).unwrap();
|
||||
|
||||
bloodhound.into()
|
||||
}
|
||||
|
||||
|
||||
fn initialize_typeahead(
|
||||
input: &HtmlInputElement,
|
||||
bloodhound: JsValue,
|
||||
|
@ -138,52 +175,71 @@ fn initialize_typeahead(
|
|||
Reflect::set(&dataset, &"display".into(), &"label".into()).unwrap();
|
||||
Reflect::set(&dataset, &"limit".into(), &JsValue::from(10)).unwrap();
|
||||
|
||||
// Create and register the closure
|
||||
let templates = Object::new();
|
||||
let suggestion_fn = js_sys::Function::new_no_args(
|
||||
"return '<div class=\"suggestion-item\">' + data.label + '</div>';"
|
||||
);
|
||||
Reflect::set(&templates, &"suggestion".into(), &suggestion_fn.into()).unwrap();
|
||||
Reflect::set(&dataset, &"templates".into(), &templates).unwrap();
|
||||
|
||||
let closure = Closure::wrap(Box::new(move |_event: web_sys::Event, suggestion: JsValue| {
|
||||
log!("[TYPEAHEAD] Selection made");
|
||||
let data: WikidataSuggestion = suggestion.into_serde().unwrap();
|
||||
on_select.call(data.clone());
|
||||
|
||||
if let Some(input) = node_ref.get() {
|
||||
input.set_value(&data.label);
|
||||
if let Ok(data) = suggestion.into_serde::<WikidataSuggestion>() {
|
||||
on_select.call(data.clone());
|
||||
if let Some(input) = node_ref.get() {
|
||||
input.set_value(&data.label);
|
||||
}
|
||||
} else {
|
||||
log!("[ERROR] Failed to deserialize suggestion");
|
||||
}
|
||||
}) as Box<dyn FnMut(web_sys::Event, JsValue)>);
|
||||
|
||||
// Register the closure in the JS global scope
|
||||
|
||||
let handler_name = format!("handler_{}", input_id);
|
||||
let handler_name_global = handler_name.clone();
|
||||
let global = js_sys::global();
|
||||
Reflect::set(
|
||||
&global,
|
||||
&handler_name_global.into(),
|
||||
&closure.as_ref()
|
||||
js_sys::Reflect::set(
|
||||
&js_sys::global(),
|
||||
&handler_name.clone().into(),
|
||||
closure.as_ref(),
|
||||
).unwrap();
|
||||
closure.forget();
|
||||
|
||||
// Typeahead initialization using jQuery
|
||||
// Corrected initialization script using bracket notation for handler
|
||||
let init_script = format!(
|
||||
r#"
|
||||
(function() {{
|
||||
console.log('[TYPEAHEAD] Initializing for #{id}');
|
||||
console.log('[JS] Starting Typeahead init for #{id}');
|
||||
try {{
|
||||
var bloodhound = window.bloodhoundInstance;
|
||||
$('#{id}').typeahead(
|
||||
{{
|
||||
hint: true,
|
||||
highlight: true,
|
||||
minLength: 1
|
||||
}},
|
||||
{dataset}
|
||||
{{
|
||||
name: 'suggestions',
|
||||
source: bloodhound.ttAdapter(),
|
||||
display: 'label',
|
||||
templates: {{
|
||||
suggestion: function(data) {{
|
||||
console.log('[JS] Rendering suggestion', data);
|
||||
return $('<div>').text(data.label);
|
||||
}}
|
||||
}}
|
||||
}}
|
||||
).on('typeahead:select', function(ev, suggestion) {{
|
||||
console.log('[TYPEAHEAD] Select event triggered');
|
||||
{handler}(ev, suggestion);
|
||||
console.log('[JS] Selection event received');
|
||||
window['{handler}'](ev, suggestion);
|
||||
}});
|
||||
console.log('[TYPEAHEAD] Initialization complete for #{id}');
|
||||
}})();
|
||||
console.log('[JS] Typeahead initialized successfully');
|
||||
}} catch (e) {{
|
||||
console.error('[JS] Typeahead init error:', e);
|
||||
}}
|
||||
"#,
|
||||
id = input_id,
|
||||
dataset = JSON::stringify(&dataset).unwrap(),
|
||||
handler = handler_name
|
||||
handler = handler_name.replace('-', "_") // Replace hyphens to avoid JS issues
|
||||
);
|
||||
|
||||
log!("[TYPEAHEAD] Init script: {}", init_script);
|
||||
let _ = js_sys::eval(&init_script).unwrap();
|
||||
closure.forget();
|
||||
log!("[RUST] Initialization script: {}", init_script);
|
||||
if let Err(e) = js_sys::eval(&init_script) {
|
||||
log!("[RUST] Eval error: {:?}", e);
|
||||
}
|
||||
}
|
Loading…
Add table
Reference in a new issue