fix(typeahead): bypass Bloodhound's AJAX to properly display typeahead suggestions
This commit is contained in:
parent
486bf9cbad
commit
fd0d4a5f38
1 changed files with 95 additions and 81 deletions
|
@ -132,87 +132,100 @@ extern "C" {
|
||||||
fn tt_adapter(this: &Bloodhound) -> JsValue;
|
fn tt_adapter(this: &Bloodhound) -> JsValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
fn initialize_bloodhound(fetch: Callback<String, Vec<WikidataSuggestion>>) -> JsValue {
|
fn initialize_bloodhound(fetch: Callback<String, Vec<WikidataSuggestion>>) -> JsValue {
|
||||||
let bloodhound_options = Object::new();
|
let bloodhound_options = Object::new();
|
||||||
|
|
||||||
let remote_fn = Closure::wrap(Box::new(move |query: JsValue, sync: Function| {
|
// Create a closure that will be called by Bloodhound to fetch suggestions
|
||||||
|
let remote_fn = Closure::wrap(Box::new(move |query: JsValue, sync: Function, async_fn: Function| {
|
||||||
let query_str = query.as_string().unwrap_or_default();
|
let query_str = query.as_string().unwrap_or_default();
|
||||||
log!("[BLOODHOUND] Fetching suggestions for: {}", query_str);
|
log!("[BLOODHOUND] Fetching suggestions for: {}", query_str);
|
||||||
|
|
||||||
|
// Get suggestions from the callback
|
||||||
let suggestions = fetch.call(query_str.clone());
|
let suggestions = fetch.call(query_str.clone());
|
||||||
|
|
||||||
let array = Array::new();
|
// Create a JavaScript array to hold the suggestions
|
||||||
for suggestion in &suggestions {
|
let js_suggestions = Array::new();
|
||||||
|
|
||||||
|
// Convert each suggestion to a JavaScript object
|
||||||
|
for suggestion in suggestions {
|
||||||
let obj = Object::new();
|
let obj = Object::new();
|
||||||
|
|
||||||
// Set flattened structure for Typeahead compatibility
|
// Store the original ID, label, and description
|
||||||
Reflect::set(&obj, &"id".into(), &suggestion.id.clone().into()).unwrap();
|
Reflect::set(&obj, &"id".into(), &JsValue::from_str(&suggestion.id)).unwrap();
|
||||||
Reflect::set(&obj, &"label".into(), &suggestion.label.clone().into()).unwrap();
|
Reflect::set(&obj, &"label".into(), &JsValue::from_str(&suggestion.label)).unwrap();
|
||||||
Reflect::set(&obj, &"description".into(), &suggestion.description.clone().into()).unwrap();
|
Reflect::set(&obj, &"description".into(), &JsValue::from_str(&suggestion.description)).unwrap();
|
||||||
|
|
||||||
// Flatten display values for direct access
|
// Store the display values directly on the object for easier access
|
||||||
Reflect::set(
|
Reflect::set(&obj, &"displayLabel".into(),
|
||||||
&obj,
|
&JsValue::from_str(&suggestion.display.label.value)).unwrap();
|
||||||
&"displayLabel".into(),
|
Reflect::set(&obj, &"displayDescription".into(),
|
||||||
&suggestion.display.label.value.clone().into()
|
&JsValue::from_str(&suggestion.display.description.value)).unwrap();
|
||||||
).unwrap();
|
|
||||||
|
|
||||||
Reflect::set(
|
// Store the full suggestion for later retrieval
|
||||||
&obj,
|
let full_suggestion = JsValue::from_serde(&suggestion).unwrap();
|
||||||
&"displayDescription".into(),
|
Reflect::set(&obj, &"fullSuggestion".into(), &full_suggestion).unwrap();
|
||||||
&suggestion.display.description.value.clone().into()
|
|
||||||
).unwrap();
|
|
||||||
|
|
||||||
array.push(&obj);
|
// Add the object to the array
|
||||||
|
js_suggestions.push(&obj);
|
||||||
}
|
}
|
||||||
|
|
||||||
log!("[BLOODHOUND] suggestions: {:?}", array);
|
log!("[BLOODHOUND] Processed suggestions: {:?}", js_suggestions);
|
||||||
|
|
||||||
let _ = sync.call1(&JsValue::NULL, &array);
|
// Call the sync function with the suggestions
|
||||||
}) as Box<dyn Fn(JsValue, Function)>);
|
let _ = sync.call1(&JsValue::NULL, &js_suggestions);
|
||||||
|
}) as Box<dyn Fn(JsValue, Function, Function)>);
|
||||||
|
|
||||||
|
// Configure the remote options
|
||||||
let remote_config = Object::new();
|
let remote_config = Object::new();
|
||||||
|
|
||||||
// Url function
|
// Set transport function to avoid AJAX requests
|
||||||
|
let transport_fn = js_sys::Function::new_with_args(
|
||||||
|
"query, syncResults, asyncResults",
|
||||||
|
r#"
|
||||||
|
// Call our custom prepare function directly
|
||||||
|
window.bloodhoundPrepare(query, syncResults, asyncResults);
|
||||||
|
"#
|
||||||
|
);
|
||||||
|
|
||||||
|
Reflect::set(
|
||||||
|
&remote_config,
|
||||||
|
&"transport".into(),
|
||||||
|
&transport_fn
|
||||||
|
).unwrap();
|
||||||
|
|
||||||
|
// Set a dummy URL (not actually used with custom transport)
|
||||||
Reflect::set(
|
Reflect::set(
|
||||||
&remote_config,
|
&remote_config,
|
||||||
&"url".into(),
|
&"url".into(),
|
||||||
&JsValue::from_str("/dummy?query=%QUERY")
|
&JsValue::from_str("/dummy?query=%QUERY")
|
||||||
).unwrap();
|
).unwrap();
|
||||||
|
|
||||||
// Prepare function
|
// Store our prepare function globally
|
||||||
Reflect::set(
|
js_sys::Reflect::set(
|
||||||
&remote_config,
|
&js_sys::global(),
|
||||||
&"prepare".into(),
|
&"bloodhoundPrepare".into(),
|
||||||
remote_fn.as_ref().unchecked_ref()
|
remote_fn.as_ref().unchecked_ref()
|
||||||
).unwrap();
|
).unwrap();
|
||||||
|
|
||||||
// Rate limiting
|
// Set rate limiting to prevent too many requests
|
||||||
Reflect::set(
|
Reflect::set(
|
||||||
&remote_config,
|
&remote_config,
|
||||||
&"rateLimitWait".into(),
|
&"rateLimitWait".into(),
|
||||||
&JsValue::from(300)
|
&JsValue::from(300)
|
||||||
).unwrap();
|
).unwrap();
|
||||||
|
|
||||||
// Response filter to prevent HTML parsing errors
|
// Set the wildcard for query replacement
|
||||||
let filter_fn = js_sys::Function::new_no_args(
|
|
||||||
"return function(response) { return response || []; }"
|
|
||||||
);
|
|
||||||
Reflect::set(
|
|
||||||
&remote_config,
|
|
||||||
&"filter".into(),
|
|
||||||
&filter_fn
|
|
||||||
).unwrap();
|
|
||||||
|
|
||||||
// Wildcard function
|
|
||||||
Reflect::set(
|
Reflect::set(
|
||||||
&remote_config,
|
&remote_config,
|
||||||
&"wildcard".into(),
|
&"wildcard".into(),
|
||||||
&JsValue::from_str("%QUERY")
|
&JsValue::from_str("%QUERY")
|
||||||
).unwrap();
|
).unwrap();
|
||||||
|
|
||||||
|
// Add the remote config to the options
|
||||||
Reflect::set(&bloodhound_options, &"remote".into(), &remote_config).unwrap();
|
Reflect::set(&bloodhound_options, &"remote".into(), &remote_config).unwrap();
|
||||||
|
|
||||||
// Tokenizer functions from Bloodhound
|
// Set the tokenizers
|
||||||
let tokenizer = js_sys::Function::new_no_args(
|
let tokenizer = js_sys::Function::new_no_args(
|
||||||
r#"
|
r#"
|
||||||
return function(query) {
|
return function(query) {
|
||||||
|
@ -233,21 +246,17 @@ fn initialize_bloodhound(fetch: Callback<String, Vec<WikidataSuggestion>>) -> Js
|
||||||
&tokenizer
|
&tokenizer
|
||||||
).unwrap();
|
).unwrap();
|
||||||
|
|
||||||
|
// Create and initialize the Bloodhound instance
|
||||||
let bloodhound = Bloodhound::new(&bloodhound_options.into());
|
let bloodhound = Bloodhound::new(&bloodhound_options.into());
|
||||||
bloodhound.initialize(true);
|
bloodhound.initialize(true);
|
||||||
|
|
||||||
|
// Prevent the closure from being garbage collected
|
||||||
remote_fn.forget();
|
remote_fn.forget();
|
||||||
|
|
||||||
// Explicit retention
|
// Return the Bloodhound instance
|
||||||
js_sys::Reflect::set(
|
|
||||||
&js_sys::global(),
|
|
||||||
&"bloodhoundInstance".into(),
|
|
||||||
&bloodhound
|
|
||||||
).unwrap();
|
|
||||||
|
|
||||||
bloodhound.into()
|
bloodhound.into()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
fn initialize_typeahead(
|
fn initialize_typeahead(
|
||||||
input: &HtmlInputElement,
|
input: &HtmlInputElement,
|
||||||
bloodhound: JsValue,
|
bloodhound: JsValue,
|
||||||
|
@ -261,8 +270,22 @@ fn initialize_typeahead(
|
||||||
// Create selection handler closure
|
// Create selection handler closure
|
||||||
let closure = Closure::wrap(Box::new(move |_event: web_sys::Event, suggestion: JsValue| {
|
let closure = Closure::wrap(Box::new(move |_event: web_sys::Event, suggestion: JsValue| {
|
||||||
log!("[TYPEAHEAD] Selection made");
|
log!("[TYPEAHEAD] Selection made");
|
||||||
|
|
||||||
|
// Try to get the full suggestion from the suggestion object
|
||||||
|
if let Some(full_suggestion) = js_sys::Reflect::get(&suggestion, &"fullSuggestion".into()).ok() {
|
||||||
|
if let Ok(data) = full_suggestion.into_serde::<WikidataSuggestion>() {
|
||||||
|
log!("[TYPEAHEAD] Selected suggestion: {:?}", data);
|
||||||
|
on_select.call(data.clone());
|
||||||
|
if let Some(input) = node_ref.get() {
|
||||||
|
input.set_value(&data.label);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback: try to deserialize the suggestion directly
|
||||||
if let Ok(data) = suggestion.into_serde::<WikidataSuggestion>() {
|
if let Ok(data) = suggestion.into_serde::<WikidataSuggestion>() {
|
||||||
log!("[TYPEAHEAD] Selected suggestion: {:?}", data);
|
log!("[TYPEAHEAD] Selected suggestion (fallback): {:?}", data);
|
||||||
on_select.call(data.clone());
|
on_select.call(data.clone());
|
||||||
if let Some(input) = node_ref.get() {
|
if let Some(input) = node_ref.get() {
|
||||||
input.set_value(&data.label);
|
input.set_value(&data.label);
|
||||||
|
@ -287,6 +310,18 @@ fn initialize_typeahead(
|
||||||
console.log('[JS] Starting Typeahead init for #{id}');
|
console.log('[JS] Starting Typeahead init for #{id}');
|
||||||
try {{
|
try {{
|
||||||
var bloodhound = window.bloodhoundInstance;
|
var bloodhound = window.bloodhoundInstance;
|
||||||
|
|
||||||
|
// Define a custom source function that directly uses our Rust callback
|
||||||
|
var customSource = function(query, syncResults, asyncResults) {{
|
||||||
|
console.log('[JS] Custom source called with query:', query);
|
||||||
|
|
||||||
|
// Call our global prepare function directly
|
||||||
|
window.bloodhoundPrepare(query, function(suggestions) {{
|
||||||
|
console.log('[JS] Suggestions from custom source:', suggestions);
|
||||||
|
syncResults(suggestions);
|
||||||
|
}}, asyncResults);
|
||||||
|
}};
|
||||||
|
|
||||||
$('#{id}').typeahead(
|
$('#{id}').typeahead(
|
||||||
{{
|
{{
|
||||||
hint: true,
|
hint: true,
|
||||||
|
@ -297,24 +332,12 @@ fn initialize_typeahead(
|
||||||
name: 'suggestions',
|
name: 'suggestions',
|
||||||
display: function(data) {{
|
display: function(data) {{
|
||||||
console.log('[JS] Display function called with data:', data);
|
console.log('[JS] Display function called with data:', data);
|
||||||
return data.display?.label?.value || data.label || '';
|
return data.displayLabel || data.label || '';
|
||||||
}},
|
|
||||||
source: function(query, syncResults) {{
|
|
||||||
console.log('[JS] Bloodhound source called with query:', query);
|
|
||||||
var bloodhound = window.bloodhoundInstance;
|
|
||||||
bloodhound.ttAdapter()(query, function(suggestions) {{
|
|
||||||
console.log('[JS] Suggestions from Bloodhound before syncResults:', suggestions);
|
|
||||||
if (Array.isArray(suggestions)) {{
|
|
||||||
console.log('[JS] Passing suggestions to syncResults:', suggestions);
|
|
||||||
syncResults(suggestions);
|
|
||||||
}} else {{
|
|
||||||
console.warn('[JS] Suggestions are not an array:', suggestions);
|
|
||||||
}}
|
|
||||||
}});
|
|
||||||
}},
|
}},
|
||||||
|
source: customSource,
|
||||||
templates: {{
|
templates: {{
|
||||||
suggestion: function(data) {{
|
suggestion: function(data) {{
|
||||||
console.log('[JS] Rendering suggestion:', data);
|
console.log('[JS] Rendering suggestion:', data);
|
||||||
return $('<div>')
|
return $('<div>')
|
||||||
.addClass('suggestion-item')
|
.addClass('suggestion-item')
|
||||||
.append($('<div>').addClass('label').text(data.displayLabel || data.label))
|
.append($('<div>').addClass('label').text(data.displayLabel || data.label))
|
||||||
|
@ -327,15 +350,6 @@ fn initialize_typeahead(
|
||||||
}}
|
}}
|
||||||
}}
|
}}
|
||||||
)
|
)
|
||||||
.on('typeahead:asyncreceive', function(ev, dataset, suggestions) {{
|
|
||||||
console.log('[JS] Received suggestions in typeahead:asyncreceive:', suggestions);
|
|
||||||
if (suggestions && suggestions.length > 0) {{
|
|
||||||
console.log('[JS] Suggestions passed to dropdown:', suggestions);
|
|
||||||
$(this).data('ttTypeahead').dropdown.open();
|
|
||||||
}} else {{
|
|
||||||
console.warn('[JS] No suggestions received or suggestions are empty.');
|
|
||||||
}}
|
|
||||||
}})
|
|
||||||
.on('typeahead:select', function(ev, suggestion) {{
|
.on('typeahead:select', function(ev, suggestion) {{
|
||||||
console.log('[JS] Selection event received with suggestion:', suggestion);
|
console.log('[JS] Selection event received with suggestion:', suggestion);
|
||||||
window['{handler}'](ev, suggestion);
|
window['{handler}'](ev, suggestion);
|
||||||
|
|
Loading…
Add table
Reference in a new issue