skip to Main Content

I put together some code for a simple search bar with a predefined list of search terms to highlight words on the page that matches the search. Everything works fine, but after clicking on a search result to highlight the words on the page, the javascript completely stops working until the page is refreshed. Here’s a snippet of the code.

function ShowSearchResults() {
    FindSearchResults();
    resultListContainer.style.display = 'block';
    setTimeout(() => {
        resultListContainer.style.transitionDuration = '300ms'
        resultListContainer.style.opacity = '1';
      }, 1);
}

function HideSearchResults() {
    resultListContainer.style.transitionDuration = '500ms'
    resultListContainer.style.opacity = '0';
    setTimeout(() => {
        resultListContainer.style.display = 'block';
      }, 500);
}

function FindSearchResults(){
    let result = [];
    let input = searchInput.value;
    if (input.length > 0){
        result = searchTerms.filter((keyword) => {
            return keyword.toLowerCase().includes(input.toLowerCase());
        });
    }
    else{
        result = searchTerms
    }
    DisplaySearchResults(result);
}

function DisplaySearchResults(result) {
    const content = result.map((list) => {
        return "<li>" + list + "</li>";
    });
    resultList.innerHTML = "<ul>" + content.join('') + "</ul>";

    document.querySelectorAll('.result-list li').forEach((item) => {
        item.addEventListener('click', function () {
            const searchText = item.textContent;
            highlightMatches(searchText);
            scrollToFirstMatch(searchText);
        });
    });
}

function highlightMatches(searchText) {
    clearHighlights(); 
    const regex = new RegExp(searchText, 'gi');

    const highlightedText = bodyText.replace(regex, (match) => {
        return `<span class="highlight-span">${match}</span>`;
    });

    document.body.innerHTML = highlightedText;

    searchInput.focus();
}

function scrollToFirstMatch(searchText) {
    const firstMatch = document.querySelector('.highlight-span');
    if (firstMatch) {
        firstMatch.scrollIntoView({
            behavior: 'smooth',
            block: 'start'
        });
    }
}

function clearHighlights() {
    document.body.innerHTML = document.body.innerHTML.replace(/<span class="highlight-span">(.*?)</span>/g, '$1');
}

Let me know if any furthur code is required!

2

Answers


  1. Avoid modifying "document.body.innerHTML" completely.

    function highlightMatches(searchText) {
    clearHighlights();
    const regex = new RegExp(searchText, 'gi');
    
    // Recursive function to highlight text without replacing the entire 
    // innerHTML
    walkAndHighlight(document.body, regex);
    
    searchInput.focus();
    }
    
    function walkAndHighlight(node, regex) {
        if (node.nodeType === 3) { // Text node
            const matches = node.nodeValue.match(regex);
            if (matches) {
                const spanWrapper = document.createElement('span');
                spanWrapper.innerHTML = node.nodeValue.replace(regex, (match) => `<span class="highlight-span">${match}</span>`);
                node.replaceWith(spanWrapper);
            }
        } else if (node.nodeType === 1 && node.nodeName !== 'SCRIPT' && node.nodeName !== 'STYLE') {
            // Recursively search child nodes
            for (let i = 0; i < node.childNodes.length; i++) {
                walkAndHighlight(node.childNodes[i], regex);
            }
        }
    }
    

    The reason for this function is to ensure that the text, that was highlighted, returns back to its normal look without disturbing the rest of the DOM.

    function clearHighlights() {
        document.querySelectorAll('.highlight-span').forEach(span => {
            span.replaceWith(document.createTextNode(span.textContent));
        });
    }
    
    Login or Signup to reply.
  2. You are replacing the content of the document in your code. This means the elements after the replace are not the same as before. This also means any event-handlers attached to them are no longer working.

    To fix this, you need to run the code again which is able to attach the event-handlers. You already have this code. Move it to an extra function and call it twice.

    You can update your code like this to fix the issue:

    // This function adds event handlers
    function addEventhandlers() {
        document.querySelectorAll('.result-list li').forEach((item) => {
            item.addEventListener('click', function () {
                const searchText = item.textContent;
                highlightMatches(searchText);
                scrollToFirstMatch(searchText);
            });
        });
    }
    
    function DisplaySearchResults(result) {
        const content = result.map((list) => {
            return "<li>" + list + "</li>";
        });
        resultList.innerHTML = "<ul>" + content.join('') + "</ul>";
        addEventhandlers(); // This call replaces the old handlers
    }
    
    function highlightMatches(searchText) {
        clearHighlights(); 
        const regex = new RegExp(searchText, 'gi');
    
        const highlightedText = bodyText.replace(regex, (match) => {
            return `<span class="highlight-span">${match}</span>`;
        });
    
        document.body.innerHTML = highlightedText;
    
        searchInput.focus();
        addEventhandlers(); // This call is new
    }
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search