User-defined Functions


We will initially complete some of the activities from the previous lecture

Also, we apologize for the readings for today. It wasn't what we intended, and we didn't discover the trouble until too late. We'll rewrite them and re-post.


Goal

By the end of today, you'll have had some experience with using loops to process simple lists of numbers. You'll see how functions and event handlers can make for pages with interactive sound playing. We'll also have a fun egg-hunting game.

Reading Recap

Let's first recap the new concepts and techniques that we saw in the reading:

  • document.querySelectorAll() is a companion to the document.querySelector() that we are already familiar with.
    • The differences are that (1) the selector is allowed to match more than one element of the document and (2) the function returns an array of all the elements that match.
    • Since we now understand how to loop over an array, we can easily do something with every element that matches. (Think of how this might have been useful in the Madlib assignment.)
    • In the reading, the code loops over all the elements matching #words img and adds an event handler to each one.
  • Google hosts a bunch of MP3 files that are the pronunciations of different dictionary words
  • Assigning a src to an audio element (that has autoplay set) will cause the MP3 to be loaded and played.

The example in the reading used these techniques to add click-handlers to pronounce the words that were next to the audio elements.

The Sound Playing Example

First, let's see that example again, here in a slightly different form. example that uses a Javascript function to add an audio element on the fly to a page.

Observations:

  • The page initially has four paragraphs, each with a word, an icon and an audio element, in that order.
  • The code at the end of the .js file sets up the page, adding an onclick event handler to each icon
  • The event handler looks to the siblings of the image that was clicked on for information (the word to pronounce) and abilities (the audio element).

Alternative playSound

The playSound function is like this:

  
function playSound() {

  var word = this.previousSibling.innerHTML;
  console.log("word is: "+word);    // here's the interesting stuff
  var audio = this.nextSibling;
  var url = "https://ssl.gstatic.com/dictionary/static/sounds/de/0/" + word + ".mp3";
  audio.setAttribute("src",url);
}

Quiz Question Nr. 1

Below is the structure of each paragraph in the page:

<p><span>apple</span><img src="icons/play.png" alt="sound icon"><audio></audio></p>

What would happen if we rearranged some of the elements in each paragraph, such as putting the audio element before the icon?

  1. The code would be fine
  2. The code would break because this would have the wrong value
  3. The code would break because playSound would not get invoked.
  4. The code would break because the siblings of this would be different elements.

Let's consider alternative ways to do a page like this

Quiz Question Nr. 2

Is it possible to make the words clickable instead of the icons?

  1. yes, if we can get the right selector to get a list of the span elements
  2. yes, if we can get event handler for them
  3. no, only the image icons can be clickable
  4. no, because it needs to be next to the audio element.

Quiz Question Nr. 3

Do we need four audio elements?

  1. yes, we need a different one for each word
  2. yes, because the audio element needs to be in the same paragraph as the word
  3. no, because we're setting the src to a URL that depends on the word
  4. no, but we do need more than one audio element.

Easter Egg Hunt

For fun, Scott and Eni have built a very cool egg hunt game.

Let's look at the materials of the game, starting with the game images

Next, a quick look at the important CSS:

    div {float: left; 
         width: 100px;
         height: 100px;
      border: 1px red solid;
      margin: 2px;
    }
    div img {width: 100px; visibility: hidden;}
    div.shown img { visibility: visible; }

So, any img inside a div is hidden, but we can make it visible by adding the shown class to the div.

Egg Hunt Code

Now it's time to understand the code.

Let's look at some of the functions, in turn. The first prepares the game board by hiding some images in randomly selected cells:

function prepare_game() {
    var cellsCount = create_grid();
    
    // randomly select the number of total images
    var filesCount = fileNames.length;
    var imageLimit = Math.min(filesCount,cellsCount);
    var totalImages = Math.floor(Math.random()*imageLimit);
    document.querySelector("#total").innerHTML = totalImages;
    document.querySelector("#found").innerHTML = foundByUser;
    // randomly select the image indices
    var imageIdxArr = getArrayRandomNumbers(totalImages, filesCount);
    // randomly select the div indices to hold the images
    var divIdxArr = getArrayRandomNumbers(totalImages, cellsCount);
    
    setImages(imageIdxArr, divIdxArr);
}

That code is pretty complicated, so we'll walk through it step by step.

(Note that the create_grid function creates all the cells (as many as it can, depending on how big our window is), and tells us (by returning a value) how many cells there are. We won't look at the details of that.)

  • We find out how many cells we have to hide the eggs in
  • We find out how many eggs we have (filesCount)
  • The min of those is the most eggs we could hide
  • Choose a random number of eggs, up to that max
  • insert onto the page the total and number found so far\
  • get a list of random image indexes (eggs)
  • get a list of random cell indexes (hiding places)
  • Hide the eggs.

Suppose that totalImages is a number like 3. The last two lines pick 3 images out of the set of egg images, and they also pick 3 cells out of the grid (however many there turn out to be), to put the 3 images into.

Quiz Question Nr. 4

Which of the following is okay?

  1. swapping lines 5 and 6
  2. swapping lines 6 and 7
  3. swapping lines 7 and 8
  4. swapping lines 8 and 9

Let's look at how the eggs are hidden:

function setImages(imgArr, divArr) {
    console.log("inside setImages");
    if( imgArr.length != divArr.length ) {
        console.log("something has gone wrong: array lengths don't match");
        return;
    }
    for (var i=0; i < imgArr.length; i++) {
        var imgIdx = imgArr[i];
        var divId = divArr[i];
        setOneImage(imgIdx, divId);
    }
}
  

This is pretty much our standard loop, being applied in a slightly different way. All the interesting work is in the setOneImage function:

  function setOneImage(imageIndex, divId) {
    console.log("image "+imageIndex+" will go in cell "+divId);
    var div = document.querySelector("#i_"+divId);
    var imgEl = document.createElement("img");
    var filename = fileNames[imageIndex];
    imgEl.setAttribute("src", "eggs/"+filename+".png");
    imgEl.setAttribute("alt", filename);
    div.appendChild(imgEl);
    div.onclick = showImage;
}

This function creates an image element, sets the src depending on the imageIndex and puts the image into the div with the id given by divId.

By having this as a separate function, we can test it by hand. Let's do that! Open up a console and reload the page a few times until you get a small number of images, so there are lots of empty cells. The JS console will tell you which cells are in use, so you can play a perfect game if you want. But suppose cells 0, 1 and 2 are empty. Do this:

setOneImage(0,0);
setOneImage(0,1);
setOneImage(0,2);

That puts the same image (the first image) in all three cells. Now, click on those cells.

Now, let's look at what happens when you click on a cell:

function showImage() {
    // find div that was clicked to show in console
    var divId = this.getAttribute("id");
    console.log("Found "+divId);
    // alert("Found you!");
    this.classList.add("shown"); // makes the image visible
    foundByUser += 1;
    document.querySelector("#found").innerHTML = foundByUser;
}

Here's where we use the CSS trick to make the hidden image visible.

Thought Question

How could we count the number of clicks the person took?

Quiz Question Nr. 5

How could we cheat and get the page to say "Found 10 out of 9"

  1.     document.querySelector("#found").innerHTML = "10 of 9";
          
  2.     document.querySelector("#total").innerHTML = "10 of 9";
          
  3.     document.querySelector("#total").innerHTML = 9;
        document.querySelector("#found").innerHTML = 10;
          
  4.     document.querySelector("#total").innerHTML = 10;
        document.querySelector("#found").innerHTML = 9;
          

Summary

We hope that after these activities you have a good understanding of:

  • connecting the JavaScript ideas and techniques to the making interactive web pages.
  • how to use the JavaScript console to test pieces of code
  • how to use your knowledge of JavaScript to cheat at games