skip to Main Content

I have stored some names in h3 tags, and when I press a button, I want them to be highlighted just one at a time, and stop at one random name.

 <div class="all-names">
            <h3 class="name-one"><span class="line">Name One</span></h3>
            <h3 class="name-two">Name2</h3>
            <h3 class="name-three">Name3</h3>
            <h3 class="name-four">Name4</h3>
            <h3 class="name-five">Name5</h3>
            <h3 class="name-six">Name6</h3>
            <h3 class="name-seven">Name7</h3>
            <h3 class="name-eight">Name8</h3>
            <h3 class="name-nine">Name9 <span class="arrow"></span></h3>
            
        </div>

Name one must be crossed off if it is not a Monday, and it will not be highlighted either if it’s not a Monday.

const nameOne = document.querySelector(".name-one")
const goBtn = document.querySelector(".btn")
let allNames = document.querySelector(".all-names")
const isOne = document.querySelector(".answer")
const date=new Date();
const isMonday = date.getDay();

if(isMonday === 1) {
    setTimeout(() => {
        isOne.textContent = 'yes'   
    }, 600);
} else {
    setTimeout(() => {
        isOne.textContent = 'no'        
    }, 600);

let nameArray = []

goBtn.onclick = function() {
    const elements = document.getElementsByTagName("h3")
    for (i=0; i < elements.length; i++) {
        teamArray.push(elements[i])
        // let noNameOne = nameArray.shift()  //i would like to remove nameOne whenever it is not a monday;  
        let randomName = Math.floor(Math.random() * nameArray.length)
            nameArray[randomName].style.backgroundColor= "white"
        
        nameArray.forEach(randomName => {
            nameArray[randomName].style.backgroundColor= "none"
        });
    }
}

The code as it is now, it will generate console error:

TypeError: Cannot read properties of undefined (reading 'style')
    at app.js:44:35
    at Array.forEach (<anonymous>)
    at goBtn.onclick (app.js:43:19)
(

If i remove the forEach bit, when I press the button it will highlight several random names. So the problems are these :

  1. On button press, I should get a single h3 element be highlighted, then the highlight removed and the next element should be highlighted. I should add that every element can be highlighted multiple times, not just once;
    -> this should be a rather speedy iteration, until it randomly stops at one element that will stay highlighted.
    Then, I should be able to press the button again and the whole process to run again.

3

Answers


  1. In this first part:

    let randomName = Math.floor(Math.random() * nameArray.length)
    nameArray[randomName].style.backgroundColor= "white"
    

    randomName is a number, the index of a random element in the array nameArray.

    In this second part:

    nameArray.forEach(randomName => {
        nameArray[randomName].style.backgroundColor= "none"
    });
    

    randomName is an element from the array nameArray. Please see documentation of forEach for details.

    So there is no need to look up the element in the array nameArray, you already have that element in randomName. Just use it directly:

    nameArray.forEach(randomName => {
        randomName.style.backgroundColor= "none"
    });
    

    That should fix the TypeError you got.
    It however does not implement the desired functionality.

    Login or Signup to reply.
  2. In addition to the answer by @KompjoeFriek, I took the liberty to modify your code a bit to achieve what I believe to be the expected functionality, with some comments added in.

    const nameOne = document.querySelector(".name-one")
    const goBtn = document.querySelector(".btn")
    let allNames = document.querySelector(".all-names")
    const isOne = document.querySelector(".answer")
    const date=new Date();
    const isMonday = date.getDay() === 1;
    // the array below contains the list of all h3 elements including mainOne
    // this ensures that on monday mainOne is in the array
    let nameArray = Array.from(document.getElementsByTagName("h3"));
    
    if(isMonday) {
        setTimeout(() => {
            isOne.textContent = 'yes'   
        }, 600);
      //   If it is monday text formatting for nameOne should be normal
      //   should be the same as others
      nameOne.style.textDecoration = "none";
      nameOne.style.color = "black";
    } else {
        setTimeout(() => {
            isOne.textContent = 'no';     
        }, 600);
    
      //  if it is not monday the text for nameOne should be crossed off
      // the text color for nameOne should also be changed to gray
      nameOne.style.textDecoration = "line-through";
      nameOne.style.color = "gray";
      
      // if the day is not a monday filter out the nameOne element from the list of elements
      nameArray = nameArray.filter(elem => {
        return isMonday ? true: !elem.classList.contains("name-one");
      } );
    }
      
    function handleClick () {
      for (let i = 0; i < nameArray.length; i++) {
        nameArray[i].style.backgroundColor = "white";
      }
    
      let randomIndex = Math.floor(Math.random() * nameArray.length);
      nameArray[randomIndex].style.backgroundColor= "yellow";
    }
    
    goBtn.addEventListener("click", handleClick);
     <div class="all-names">
                <h3 class="name-one"><span class="line">Name One</span></h3>
                <h3 class="name-two">Name2</h3>
                <h3 class="name-three">Name3</h3>
                <h3 class="name-four">Name4</h3>
                <h3 class="name-five">Name5</h3>
                <h3 class="name-six">Name6</h3>
                <h3 class="name-seven">Name7</h3>
                <h3 class="name-eight">Name8</h3>
                <h3 class="name-nine">Name9 <span class="arrow"></span></h3>
                
            </div>
    <button class="btn">click me</button>
    <div class="answer"></div>
    Login or Signup to reply.
  3. The way I’d approach it is as follows, with explanatory comments in the code:

    // simple utilities to minimise typing and simplify code:
    const D = document,
      create = (tag, props) => Object.assign(D.createElement(tag), props),
      get = (selector, context = D) => context.querySelector(selector),
      getAll = (selector, context = D) => [...context.querySelectorAll(selector)],
    
    // this function takes two arguments, 'min' and 'max' (max, by default, is set
    // the value of 'min' plus 30; adjust to taste; in the function body:
      randomInRange = (min, max = min + 30) => {
        // we return the floored value returned from the inner functions:
        return Math.floor(
          // here we multiply the value returned by Math.random() by the
          // value of max minus min (to give the range), and then add the
          // min value back so the returned number is equal to, or greater
          // than, the min:
          Math.random() * (max - min) + min
        )
      };
    
    // a named event-handler function:
    const handler = () => {
      // an Array of the days of the week:
      const days = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'],
        // the number that represents today's day of the week, running
        // as in the Array above, starting at 0 (Sunday) through to 6 (Saturday):
        today = new Date().getDay(),
        // all the <h3> elements, gathered using the get() function (above),
        // and returned as an Array:
        elements = getAll('h3'),
        // here we filter the elements we've retrieved to keep only those
        // that are 'eligible':
        eligible = elements.filter(
          // we use Array.prototype.filter() to filter the Array of elements,
          // passing a reference to the current element ('el') into the
          // function body:
          (el) => {
            // retrieving the attribute-value of the custom data-* attribute
            let r = el.dataset.onlyOn;
            
            // if there is no such attribute-value we return true (in order
            // to keep the days without 'restrictions,' or if there is an
            // attribute value we move the next assessment, to see if
            // the returned attribute-value (the name of a day, in English
            // so you may wish to adapt for your own circumstances) is the
            // same as the day of the week in the Array of days (using 'today'
            // as the index):
            return !r || r === days[today];
          });
    
      // here we filter the elements to retain only those that are not
      // in the eligible Array:
      elements.filter(
        // passing the current ('h') element to the function body,
        // and checking to see if the eligible Array includes that
        // current element, we then invert that result using the not
        // operator (because we only want the ineligible elements):
        (h) => !eligible.includes(h)
      // we then iterate over the retained ineligible elements:
      ).forEach(
        (el) => {
          // adding a line-through text-decoration:
          el.style.textDecoration = 'line-through'
        });
        
      // here we determine the chosen element, using the randomInRange()
      // function to generate a random number between 0 and the length
      // of the eligible Array, to serve as the index, and we then
      // retrieve the element at that index from the Array:
      const chosen = eligible[randomInRange(0, eligible.length)],
        // we retrieve the element to hold the log (remove this if
        // you don't want the log to be used):
        log = get('.log');
        
      // we retrieve an Array of elements with the class of 'selected',
      // using the getAll() function and then we iterate over that
      // Array:
      getAll('.selected').forEach(
        // removing the 'selected' class-name from each
        // element in the Array:
        (el) => el.classList.remove('selected')
      );
      
      // we then add the 'selected' class-name to the element
      // chosen earlier:
      chosen.classList.add('selected');
      
      // prepend a created <li> element, with its text set
      // to that of the chosen <h3> element:
      log.prepend(create('li', {
        textContent: chosen.textContent
      }));
      
      // iterating over the log element's child elements:
      [...log.children].forEach((child, index) => {
        // if the index of the current element is
        // greater than 19, it's removed (just to limit
        // the number of logged elements; adjust to taste):
        if (index > 19) {
          child.remove();
        }
      })
    
    };
    
    // we retrieve the <button> element, using the get() function,
    // and bind the handler() function as the event-handler for
    // the 'click' event:
    get('button').addEventListener('click', handler);
    .all-names {
      display: flex;
      flex-flow: row wrap;
      gap: 1rem;
      justify-content: start;
    }
    
    .all-names>* {
      border: 2px solid hsl(300deg 90% 80%);
      border-radius: 40rem;
      display: flex;
      justify-content: space-between;
      padding-block: 0.5rem;
      padding-inline: 1rem 3rem;
    }
    
    .selected {
      background-image: linear-gradient( 90deg, hsl(300deg 90% 80% / 0.7) 0 calc(100% - 2rem), hsl(300deg 90% 80% / 0.2) calc(100% - 2rem));
      position: relative;
    }
    
    .selected::after {
      content: '2962';
      position: absolute;
      right: 0.6rem;
      flex-basis: 2rem;
      text-align: center;
    }
    
    .log {
      display: flex;
      flex-flow: column wrap;
      block-size: 20rem;
      gap: 0.25rem;
      inline-size: 80%;
      margin-inline: auto;
    }
    
    .log li {
      justify-content: space-between;
      padding-block: 0.25rem;
      padding-inline: 1rem;
    }
    
    .log li:first-child {
      background-image: linear-gradient( 90deg, transparent 0 0.5rem, hsl(300deg 90% 80% / 0.7) 0.5rem 9rem, hsl(300deg 90% 80% / 0.2) 9rem 12rem, transparent 12rem);
      inline-size: 10rem;
    }
    
    .log li:first-child::after {
      content: '2962';
      float: right;
    }
    <!-- I removed the <span> elements, since they served no purpose in this approach,
         but if they're required for some reason adding them back won't cause problems: --> 
    <button type="button">Select a name at random</button>
    <div class="all-names">
      <!-- I added a custom data-* attribute to provide a reference
           to the day on which this element may be shown: -->
      <h3 class="name-one" data-only-on="Monday">Name One</h3>
      <h3 class="name-two">James</h3>
      <h3 class="name-three">Sanjay</h3>
      <h3 class="name-four">Neema</h3>
      <h3 class="name-five">Erika</h3>
      <h3 class="name-six">Ralph</h3>
      <h3 class="name-seven">Marwen</h3>
      <h3 class="name-eight">Rhys</h3>
      <h3 class="name-nine">Noémi</h3>
    </div>
    
    <!-- this has been added just so that you can see which names are being selected;
         occasionally the same name will be selected more than once consecutively
         which can - without a log - look like the code failing silently; remove this
         and the relevant JavaScript, if it's not required: -->
    <ol class="log" reversed></ol>

    JS Fiddle demo.

    References:

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search