Jump to content
  • Add an autocomplete search bar to Spotfire® using Text Areas


    This script implements an autocomplete feature for an input field using a list of CSV values.

    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.

    autocomplete.thumb.gif.b1b42bfbfa65385047ba69fc611f9e6c.gif

     

    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.

    1. It creates a new input field and a popup for suggestions.
    2. It watches for changes in a CSV values element and updates the suggestions accordingly.
    3. It adds event listeners to handle user interactions like typing, navigating suggestions with arrow keys, selecting a suggestion, and losing focus.
    4. It toggles between the original and new input fields based on user interaction.
    5. 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.

    • Like 1

    User Feedback

    Recommended Comments

    There are no comments to display.


×
×
  • Create New...