Introduction
While list box and other text filters are quick and easy ways to add the ability to apply text-based filters for users, an user-friendly method that is more familiar for users is to have an autocomplete search box. This is a common technique used on most websites such as Google and Amazon allowing suggestions to appear as the user types in search terms. This is especially useful if you have a large number of text options for the user to filter by. This can be achieved in Spotfire also by following this guide.
Autocomplete is a feature within software programs that predicts the rest of a word a user is typing. In programming, it's often used in IDEs to speed up coding by reducing typos and other common mistakes. Autocomplete can also be seen in web development, where it's used in input fields to help users fill in data. It works by using a pre-populated list of values to match the user's input, and offering suggestions in real time.
Define your Text Area
The steps to implement the autocomplete are shown below:
1) Add a Text Area to your page which will hold the autocomplete search box
2) Right click, selected Edit HTML on your Text Area and copy the following html code. Replace the first SpotfireControl tag with an Input field Property Control
<span id="autocomplete"> <SpotfireControl id="83380581581c4f8d8a1370dbf07e96c7" /> </span> <span id="autocomplete-csv-values" style="position:fixed;left:111111px"> John Doe,Jane Smith,Robert Johnson,Michael Brown,Emily Davis,Sarah Miller,James Wilson,Patricia Moore,Richard Taylor,Linda Anderson </span>
3) Now insert a new JavaScript script (from the Edit Text Area top right icon) with the following code:
function setupAutocomplete(id){ let dataElement= document.getElementById(id+"-csv-values"); let currentFocus = -1; let isScriptTriggered = false; const autoCompleteDiv = document.getElementById(id); autoCompleteDiv.position = "relative"; const spotfireInput = autoCompleteDiv.firstElementChild; const autocompleteInput = document.createElement('input'); autocompleteInput.id = id + "Input"; autocompleteInput.style.display = 'none'; // Copy style from SpotfireControl to input for (let style in spotfireInput.style) { if (spotfireInput.style.hasOwnProperty(style) && spotfireInput.style[style]) { autocompleteInput.style[style] = spotfireInput.style[style]; } } autoCompleteDiv.appendChild(autocompleteInput); //popup for autocomplete items const popup = document.createElement('div'); popup.style.width = `${spotfireInput.offsetWidth}px`; popup.style.position = 'absolute'; popup.style.zIndex = '100'; popup.className = 'autocomplete-items'; popup.style.display = 'none'; autoCompleteDiv.appendChild(popup); //observer for csv values as it comes from a calculated value const csvObserver = new MutationObserver(() => { csvValues = dataElement.innerText.split(','); }); csvObserver.observe(dataElement, { childList: true, characterData: true, subtree: true }); let csvValues = dataElement.innerText.split(','); function toggleInputDisplay() { spotfireInput.style.display = ''; autocompleteInput.style.display = 'none'; spotfireInput.value = autocompleteInput.value.trim(); isScriptTriggered = true; spotfireInput.focus(); spotfireInput.blur(); isScriptTriggered = false; } autocompleteInput.addEventListener('click', () => { autocompleteInput.value = ""; }); autocompleteInput.addEventListener('input', () => { const inputValue = autocompleteInput.value; const filteredValues = csvValues.filter(name => name.toLowerCase().includes(inputValue.toLowerCase())); popup.innerHTML = ''; filteredValues.forEach(value => { const div = document.createElement('div'); div.innerHTML = value.replace(new RegExp(inputValue, 'gi'), match => "<span class='highlight'>" + match + "</span>"); // Copy style from src input to div for (var style in autocompleteInput.style) { if (autocompleteInput.style.hasOwnProperty(style) && autocompleteInput.style[style]) { div.style[style] = autocompleteInput.style[style]; } } div.addEventListener('click', () => { autocompleteInput.value = value.trim(); popup.style.display = 'none'; spotfireInput.value = value.trim(); currentFocus = -1; toggleInputDisplay(); }); popup.appendChild(div); }); popup.style.display = filteredValues.length ? 'block' : 'none'; }); autocompleteInput.addEventListener('keydown', (e) => { const divs = popup.getElementsByTagName('div'); if (e.keyCode == 40) { // Down arrow currentFocus++; if (currentFocus >= divs.length) currentFocus = 0; } else if (e.keyCode == 38) { // Up arrow currentFocus--; if (currentFocus < 0) currentFocus = divs.length - 1; } else if (e.keyCode == 13) { // Enter if (currentFocus > -1) { divs[currentFocus].click(); } else { spotfireInput.value = autocompleteInput.value.trim(); isScriptTriggered = true; toggleInputDisplay(); isScriptTriggered = false; } } else if (e.keyCode == 27) { // Escape key isScriptTriggered = true; toggleInputDisplay(); isScriptTriggered = false; } for (let i = 0; i < divs.length; i++) { divs[i].classList.remove('over'); } if (currentFocus > -1) { divs[currentFocus].classList.add('over'); } }); spotfireInput.addEventListener('focus', () => { if (isScriptTriggered) return; // When spotfireInput gets focus, hide it and show input spotfireInput.style.display = 'none'; autocompleteInput.style.display = ''; autocompleteInput.value = ""; autocompleteInput.focus(); }); autocompleteInput.addEventListener('blur', () => { setTimeout(() => { popup.style.display = 'none'; toggleInputDisplay(); }, 200); }); //styles const styleTag = ` <style> #autocompleteInput{ outline: none; } .autocomplete-items{ background-color: #fff; color:unset !important; border:1px solid #c6c8cc; } .autocomplete-items div:hover, .autocomplete-items div.over { color: #FFFFFF !important; background-color: #7289F9; cursor:default; } .highlight { font-weight: bold; text-decoration: underline; } </style>` autoCompleteDiv.insertAdjacentHTML('afterEnd', styleTag) dataElement.hidden = true; } setupAutocomplete("autocomplete")
This JavaScript code is an implementation of an autocomplete feature for an input field. It's wrapped in an immediately invoked function expression (IIFE) to avoid polluting the global scope.
- It creates a new input field and a popup for suggestions.
- It watches for changes in a CSV values element and updates the suggestions accordingly.
- It adds event listeners to handle user interactions like typing, navigating suggestions with arrow keys, selecting a suggestion, and losing focus.
- It toggles between the original and new input fields based on user interaction.
- It adds some CSS styles for the autocomplete feature.
Adding data for your autocomplete
To choose your own values for the autocomplete items, replace the names inside the autocomplete-csv-values with a Calculated Value Dynamic item Spotfire Control with a custom expression
UniqueConcatenate([yourColumn])
Additional Notes
- If you have a very large amount of data you wish to expose to the autocomplete, it may be better to automate writing out the JavaScript array to a file instead for the autocomplete to read, or even use a web service to perform the autocomplete live.
Back to the JavaScript Component Hub for Spotfire
Disclaimer
This method makes assumptions about the inner workings of Spotfire version 12.0.0 LTS Any analyses created using this hack may cause instability and performance issues and are likely to break when Spotfire is upgraded or hotfixed in the future. Any issues resulting from applying this hack are not covered by Spotfire maintenance agreements and Support will not assist in troubleshooting problems with analysis files where this approach is used.
- 1
Recommended Comments
There are no comments to display.