Problem with mouse events

I wrote something like Captcha test, but with a few levels of difficulty. These levels are controlling by mouse events and not everything are working.

Task is complex, so I decide to divide it into 2 parts.

  1. Choosing the box (level of the game) Users have a choice of taking the level of the game. After clicking (mouseclick) any box on the screen (for example ‘box 3’), boxes from 1 to 3 should be yellow (active), but ‘box 4’ and ‘box 5’ are not.

Near the boxes I have made a blue colored place where I need to put the ‘alt’ parameter about clicked (chosen) box.

The problem is that AFTER clicking the box, colors (from ‘box 1’ to clicked number, for example I choose ‘box 3’ and ‘box 1’, ‘box 2’, ‘box 3’ are yellow) and single number of chosen box is not “blocked” permanently, but they could be changed freely by moving mouse pointer over the any other box. I need some kind of cooperations (“connections”) between mouse events.

HTML:

<div class="stars">
    <div class="game-level">
      <input type="image" class="star yellow" id="star1" 
src="" width="60" alt="box 1">
      <input type="image" class="star" id="star2" 
src="" width="60" alt="box 2">
      <input type="image" class="star" id="star3" 
src="" width="60" alt="box 3">
      <input type="image" class="star" id="star4"  
src="" width="60" alt="box 4">
      <input type="image" class="star" id="star5"  
src="" width="60" alt="box 5">
    </div>
    <div class="level-block">
      <span id="level-text">LEVEL NAME</span>
    </div>
</div>

CSS:

.stars {
  display: flex;
  margin: 10px auto;
  width: 500px;
}

.captcha {  
  background-color: white;
  height: 100px;
}

.game-level {
  display: flex;
  width: 300px;
  height: 100%;
}

.yellow {
  background-color: yellow;
}

input[type='image'] {
  width: 60px;
  height: 60px;
  border: thin solid black;
}

.level-block {
  display: flex;
  width: 200px;
  margin-left: 10px;
  justify-content: center;
  align-items: center;
  border: 1px solid hsl(217, 86%, 50%);
  border-radius: 25px;
  background-color: hsl(212, 29%, 80%);
}

.level-block > span {
  font-size: 18px;
}

Javascript:

window.addEventListener('DOMContentLoaded', changeStars, false);

/* Change level of the game depending on user choice */

function changeStars() {
let defaultIndex = 0; //default level, first box is always yellow
var star = document.querySelectorAll('.game-level')[defaultIndex];

let currentElement = undefined;

/* Get index of element from NodeList */
function findIndex() {
    let child = document.getElementById(`${currentElement}`);
    let parent = child.parentNode;
    let starNumber = Array.prototype.indexOf.call(parent.children, child);

    return starNumber;
}

let getStars = document.querySelectorAll('.star');
let levelText = document.querySelector('#level-text');

function starEvents(e) {
    currentElement = e.target.id;
    let starIndex = findIndex(currentElement);

    levelText.textContent = document.querySelector(`#${currentElement}`).alt;

    return starIndex;
}

let numberClicked = 0;

function starClicked(eventType) {
    let numberClicked = starEvents(eventType);
      for (let i = 0; i <= numberClicked; i++) {
          getStars[i].classList.add('yellow');
      }
};


star.addEventListener('mouseover', function(e) {
    let starNumber = starEvents(e);
    for (let i = 0; i <= starNumber; i++) {
        document.querySelector(`#star${i+1}`).style.backgroundColor = 'yellow';
    }
});

star.addEventListener('mouseout', function(e) {
    let starNumber = starEvents(e);
    for (let i = 1; i <= starNumber; i++) {
        document.querySelector(`#star${i+1}`).style.backgroundColor = 'white';
    }
});

star.addEventListener('click', starClicked, {once: true});

}

Please let me know if something is misunderstood.
Thanks for every advice in my subject!

1 Like

Hi @KrzysztofSobota!

I suggest you host this in Codepen and post the link, otherwise nobody will see your project :frowning:

Ok. Thanks. I’ve got it there :wink:

Codepen code is here:
https://codepen.io/Krzysiek1981/pen/VJLVwg

1 Like

Something like this is enough to get started? :wink:

@KrzysztofSobota Couple of questions for you:

  1. Since you already highlight level 1 star at the beginning, why not have the box text show “box 1”?

  2. If a user selects a box, can I assume you want that box and the boxes to the left of it to remain yellow?

  3. If a user selects box 3 (and assuming your answer to my previous question is “Yes”) then can I assume you want boxes 4 and 5 to still highlight yellow when moused-over?

  1. It’s probably my mistake - I check this out now.
  2. Yes - this is something like rating “stars” when you buying things online.
  3. Mouse over could change any box color only when users don’t click any box before. After the clicking box mouseover can change color with the higher number temporary (when mouse pointer is on the box). Making ‘click’ decision is permanent - only once (in addeventlistener is special callback for it replacing true/false bubbling)

There is one more things which don’t work. When you get mouse over the box and then get out, level in blue area wion’t be default level (box 1), but stay the last box level.

One more clarification on your response to no 3 above:

Let’s say the user selects box 3. This means box 1, 2 and 3 are now permanently yellow and no other clicks on boxes should do anything. I understand that. The question is after a box has been clicked, what do you want to happen with regards to highlighting if the user happens to mouseover box 4 or 5. Do you want the highlighting to not work anymore or is it ok for box 4 and 5 to still highlight on mouseover or do you not care what happens?

After clicking the box no other box behind can be see in yellow. Clicking the box also will be block level text in blue area permanent (for ‘box 3’ info in blue area will stay ‘box 3’).
Click is the last decision - this box is level of game which I will give probably today (if this problem will be solved)

So clicking box4 will give yellow boxes 1, 2, 3, 4 and text info ‘box 4’.
Any mouse events like -over, -out can’t change anything after click.

Not so easy as some people think? :wink:

It was fairly easy. I refactored your original code into smaller functions and made it a bit more DRY. I renamed many of your functions to make the code more readable and easier to understand what each function is really doing.

/* Change level of the game depending on user choice */
const changeStars = () => {
  const updateAltText = currentLevelIndex => {
     let levelText = document.querySelector("#level-text");
     levelText.textContent = document.querySelector(`#star${currentLevelIndex + 1}`).alt;
  };

  const getNumber = str =>Number(str.match(/\d+/)[0]) || 1;
  
  const getStarIndex = event => getNumber(event.target.id) - 1;

  const handleStarClick = event => {
    gameLevel.removeEventListener('mouseover', highlightStars);
    gameLevel.removeEventListener('mouseout', highlightStars);
    const stars = document.querySelectorAll('star');
    for (let i = 0; i <= getStarIndex(event); i++) {
      stars[i].classList.add('yellow');
    }
  };

  const highlightStars = event => {
    const starIndex = getStarIndex(event);
    updateAltText(starIndex);
    for (let i = 1; i <= starIndex; i++) {
      const box = document.querySelector(`#star${i + 1}`);
      box.classList.toggle('yellow')
    }
  };
  
  updateAltText(0); // update current level text
  const gameLevel = document.querySelector(".game-level");
  gameLevel.addEventListener("mouseover", highlightStars);
  gameLevel.addEventListener("mouseout", highlightStars);
  gameLevel.addEventListener("click", handleStarClick, { once: true });
};

window.addEventListener("DOMContentLoaded", changeStars);

The one thing you may want to change is I made the text in the blue box reflect one star above. If you would rather no stars be selected and show showing like “SELECT LEVEL” as the default text, then use the code below and remove “yellow” from the class of the first input in your HTML.

/* Change level of the game depending on user choice */
const changeStars = () => {
  const updateAltText = currentLevelIndex => {
     let levelText = document.querySelector("#level-text");
     levelText.textContent = document.querySelector(`#star${currentLevelIndex + 1}`).alt;
  };
 
  const getNumber = str =>Number(str.match(/\d+/)[0]) || 1;
  
  const getStarIndex = event => getNumber(event.target.id) - 1;

  const handleStarClick = event => {
    gameLevel.removeEventListener('mouseover', highlightStars);
    gameLevel.removeEventListener('mouseout', highlightStars);
    const stars = document.querySelectorAll('star');
    for (let i = 0; i <= getStarIndex(event); i++) {
      stars[i].classList.add('yellow');
    }
  };

  const highlightStars = event => {
    const starIndex = getStarIndex(event);
    updateAltText(starIndex);
    for (let i = 0; i <= starIndex; i++) {
      const box = document.querySelector(`#star${i + 1}`);
      box.classList.toggle('yellow')
    }
  };
  
  const gameLevel = document.querySelector(".game-level");
  gameLevel.addEventListener("mouseover", highlightStars);
  gameLevel.addEventListener("mouseout", highlightStars);
  gameLevel.addEventListener("click", handleStarClick, { once: true });
};

window.addEventListener("DOMContentLoaded", changeStars);

OK. I will try this - are you ready for the rest of the game code?

I will let you take care of that part.

1 Like

OK. Thanks a lot for help.