typesense/app.js

114 lines
4.5 KiB
JavaScript

// --- Configuration ---
// IMPORTANT: For production, use a SEARCH-ONLY API Key.
// NEVER expose your Admin API Key in client-side code.
// Best practice is to proxy requests through your backend
// or use Typesense Scoped Search Keys.
const TYPESENSE_CONFIG = {
nodes: [{
host: '192.168.2.203', // Or your Typesense host
port: '8108',
protocol: 'http'
}],
apiKey: 'imdiowenxodjioqwenlj', // Generate a search-only API key in Typesense
connectionTimeoutSeconds: 2
};
const typesenseClient = new Typesense.Client(TYPESENSE_CONFIG);
// --- DOM Elements ---
const searchInput = document.getElementById('user-search-input');
const suggestionsList = document.getElementById('suggestions-list');
const selectedUserIdEl = document.getElementById('selected-user-id');
const selectedUserNameEl = document.getElementById('selected-user-name');
// --- Debounce Function (to limit API calls) ---
let debounceTimer;
function debounce(func, delay) {
return function(...args) {
clearTimeout(debounceTimer);
debounceTimer = setTimeout(() => func.apply(this, args), delay);
};
}
// --- Fetch and Display Suggestions ---
async function fetchSuggestions(query) {
if (!query.trim()) {
suggestionsList.innerHTML = '';
suggestionsList.style.display = 'none';
return;
}
const searchParameters = {
'q': query,
'query_by': 'user_name', // Field to search in
'per_page': 5, // Number of suggestions to show
// 'sort_by': '_text_match:desc', // Default, or 'user_name_lowercase_sort:asc' if you use that field
'infix': 'always', // Ensure infix search is active if not default for the field
// 'filter_by': 'is_active:=true' // Example: if you have an is_active boolean field
};
try {
const searchResults = await typesenseClient.collections('users').documents().search(searchParameters);
displaySuggestions(searchResults.hits || []);
} catch (error) {
console.error('Typesense search error:', error);
suggestionsList.innerHTML = '<li>Error fetching suggestions</li>';
suggestionsList.style.display = 'block';
}
}
function displaySuggestions(hits) {
suggestionsList.innerHTML = ''; // Clear previous suggestions
if (hits.length === 0) {
suggestionsList.style.display = 'none';
return;
}
hits.forEach(hit => {
const user = hit.document; // The actual document data
const li = document.createElement('li');
// Create a highlighted version of the name if available
let displayName = user.user_name;
if (hit.highlights && hit.highlights.length > 0) {
const nameHighlight = hit.highlights.find(h => h.field === 'user_name');
if (nameHighlight && nameHighlight.snippet) {
displayName = nameHighlight.snippet; // Use Typesense's highlighted version
}
}
li.innerHTML = displayName; // Display name (can be highlighted)
// Store full user data (or just ID) for selection
li.dataset.userId = user.user_id;
li.dataset.userName = user.user_name; // Store the original, non-highlighted name
li.addEventListener('click', () => {
searchInput.value = user.user_name; // Fill input with selected name
selectedUserIdEl.textContent = user.user_id;
selectedUserNameEl.textContent = user.user_name;
suggestionsList.innerHTML = ''; // Clear suggestions
suggestionsList.style.display = 'none';
// You now have user.user_id and user.user_name
console.log('Selected:', { id: user.user_id, name: user.user_name });
// Here, you might want to submit a form, make another API call, etc.
});
suggestionsList.appendChild(li);
});
suggestionsList.style.display = 'block';
}
// --- Event Listeners ---
searchInput.addEventListener('input', debounce((event) => {
fetchSuggestions(event.target.value);
}, 300)); // 300ms debounce
// Optional: Hide suggestions when clicking outside
document.addEventListener('click', (event) => {
if (!searchInput.contains(event.target) && !suggestionsList.contains(event.target)) {
suggestionsList.style.display = 'none';
}
});
console.log("Autocomplete App Initialized. Make sure your Typesense server is running and accessible.");
console.log("And ensure you've replaced 'YOUR_SEARCH_ONLY_API_KEY' with a valid key.");