javascriptnextsibling

how to change ClassName on lastElementSibling?


Project summary : I have a div with id wordsContainer and inside there are multiple divs each having a word inside and that word is further split into span tags (each span tags carrying a letter) . The purpose behind it is to find out if typed letter is correct or incorrect (a simple typing game).

Problem : in the addEventListner function depending on the input I am trying to change classes on every keydown event . if for example we have a word "hello" it acts accordingly till it goes to letter "o" it doesn't change its class and on space-key it moves to the next word and does nothing with letters(span tags) in the next word. After the first word only thing works is pressing space-key to move to next word .

I am doing all this with nextSibling method.I tried using querySelector(classname) but it couldn't figure out the end of a word (i created all the letter with "letter" class and after keypress modified that class so that it could go to the next letter).

I created a codpen for visual presentation here :https://codepen.io/Marcian/pen/oNVBrQB

here is the code i am trying right now

function addClass(element, name){            // adds Class
    element.className = ''+name;
}
function removeClass(element , name){                     // Removes class
  element.className= element.className.replace(name, ' ');
}

  let  wordSibling = wordsContainer.firstElementChild;
  let  letterSibling = wordSibling.firstChild;


      addClass(wordSibling, "currentWord")
      addClass(letterSibling, "currentLetter" )




document.addEventListener("keydown", function(e){
    const typedKey = e.key;
   
    const expected = document.querySelector(".currentLetter").innerHTML;
    console.log({expected , typedKey});
  
    if ( letterSibling.nextSibling){
         
        
        addClass(letterSibling, "currentLetter")
        letterSibling = letterSibling.nextSibling;
        addClass(letterSibling, "currentLetter")
        removeClass(letterSibling.previousSibling, "currentLetter")
        addClass(letterSibling.previousSibling, expected===typedKey? "correct": "incorrect")
    }else {
        if (typedKey === " "){
        wordSibling= wordSibling.nextSibling;
        wordSibling.previousSibling.lastChild.className = expected===typedKey? "correct": "incorrect";
        letterSibling = wordSibling.firstChild
        addClass(letterSibling, "currentletter")
        addClass(wordSibling, "currentWord")
        removeClass(wordSibling.previousSibling, "currentWord")}
    }
})

Solution

  • The way you intend to work with the space looks mixed up: the HTML/CSS you use does not have a way to distinctively identify a correctly typed or incorrectly typed space.

    I would suggest to include also span elements for the spaces between words. That way you can also style those spaces to indicate whether they are current, correct or incorrect.

    I adapted a bit the styling (CSS) relying more on background colors than foreground colors, so that it also works well with spaces. I removed the height: 100px as it makes the spaces tall. Also fixed a typo you had (#wordsConatainer??).

    But the essence in this solution is to add spaces also as span elements in the HTML, and to style them. As nextSibling would not work to find them, I have chosen to rely on querySelectorAll instead -- during initialisation -- and use an iterator over that collection.

    const text = "hello how are you home home office germany america  france Hopes Hopes and dreams were dashed that day It should have been expected, but it still came as a shock. The warning signs had been ignored in favor of the possibility however remote, that it could actually happen That possibility had grown from hope to an undeniable belief it must be destiny. That was until it wasn't and the hopes and dreams came crashing down She sat deep in thought. The next word that came out o her mouth would likely be the most important word of her life It had to be exact with no possibility of being misinterpreted She was ready She looked deeply into his eyes and said";
    const wordsContainer = document.getElementById("wordsContainer");
    const wordsArray = text.split(" ");
    const wordsCount = wordsArray.length;
    
    function spanifyWords(word, parent) {
        for (const ch of word) {
            const newSpan = document.createElement("span");
            parent.append(newSpan);
            newSpan.textContent = ch;
        }
    }
    
    function renderDivs(n) {
        for (let i = 0; i < n; i++) {
            const randomIndex = Math.floor(Math.random()*wordsCount)
            const randomWord = wordsArray[randomIndex];
            if (i) { // Add a span for a space
                const newSpan = document.createElement("span");
                wordsContainer.append(newSpan);
                newSpan.innerHTML = "&nbsp;";
            }
            const newDiv = document.createElement("div"); 
            wordsContainer.append(newDiv);
            spanifyWords(randomWord, newDiv);
        }
    }
    
    renderDivs(20);
    // Get an iterator over all spans, including spans that represent spaces between words
    const letters = [...wordsContainer.querySelectorAll("span")].values();
    let letterSibling = letters.next().value;
    letterSibling.classList.add("currentLetter");
    letterSibling.parentNode.classList.add("currentWord");
    
    document.addEventListener("keydown", function(e){
        if (!letterSibling) return; // All done!
        const typedKey = e.key;
        if (typedKey.length !== 1) return; // Not a display character
        // 1. Use textContent, not innerHTML
        // 2. Use the variable, not a CSS-selector
        const expected = letterSibling.textContent.trim() || " "; // Replace non-breaking space with space
        //console.log({expected , typedKey});
    
        // Apply the relevant class to the current span:
        letterSibling.classList.add(expected === typedKey ? "correct" : "incorrect");
        // Remove the "current" indications
        letterSibling.classList.remove("currentLetter");
        letterSibling.parentElement.classList.remove("currentWord");
        // Go to next span
        letterSibling = letters.next().value;
        if (!letterSibling) return; // All done!
        // And apply the "current" indications
        letterSibling.classList.add("currentLetter");
        letterSibling.parentElement.classList.add("currentWord");
    });
    body{
        height:100vh;
        display: flex;
        flex-direction: column;
        align-items: center; 
        justify-content: flex-start;
        font-family: 'Roboto Mono', monospace;
        background-color: rgba(75, 75, 75, 0.822);
        overflow-x: hidden;
    }
    #wordsContainer div span{
        color: rgb(160, 160, 160);
        font-size: 25px;
    }
    #wordsContainer .correct {  /* Typo corrected */
        background: lightgreen;
    }
    #wordsContainer .incorrect {
        background: red;
    }
    #wordsContainer .currentLetter {
        background: yellow;
    }
    #wordsContainer div .currentWord {
        color: darkblue;
    }
    #wordsContainer {
        letter-spacing: 1px;
        display: flex;
        flex-wrap: wrap;
        overflow: hidden;
    }
    <div id = "wordsContainer"></div>