JavaScript in small bites, Part 4

JavaScript is a great langauge for learning programming: you don't need any new software, and you get instantaneous feedback. "In small bites" is a by-example philosophy to language-learning: in this article, you will find code snippets showing many different ways to accomplish the same task in JavaScript. "In small bites" contrasts with the "syntax and semantics" approach to language-learning, which emphasizes top-down learning of formal grammar and specification.

Part 1 covered creating a basic JavaScript program, "Hello, World!", literal values, basic functions and basic objects.

Part 2 covers interacting with forms, object constructors, methods, prototypes and the meaning of the this keyword. The exercise is to create a hand-held calculator simulator from partially completed code.

Part 3 covers arrays, DOM tree interaction and events. The exercise is to create a simulated board game from partially completed code.

Part 4 covers (more) event-handling and how to interact with CSS from JavaScript. The exercise is to create the game Minesweeper from partially completed code.

Advanced event handling

To handle events like mouse clicks and key presses in JavaScript, you attach event handling code to HTML elements. You can attach an event handling code in HTML, by adding an onevent attribute to an element; this code will be executed whenever that event happens.

[html-event.html]

<html>
 <body>
  <input type="button"  value="Click me!" onclick="alert('I got clicked!')" />
 </body>
</html>

produces:

In JavaScript, the most common events are onclick, onkeypress, onkeydown, and onkeyup. The special onload event applies to the body tag, and it runs after the page has finished loading.

It is also possible to add event handlers directly in JavaScript, by accessing HTML elements and using onevent fields:

[javascript-event.html]

<html>
 <body>
  <input type="button"  value="Click me!" id="my-button" />
  <script>
   document.getElementById("my-button").onclick = function () { alert('I got clicked!') ; }
  </script>
 </body>
</html>

produces:

Note that when event handlers are added in JavaScript, they are added functions rather than text. That is, element.onclick = "alert('Alert me!');" does not work.

When an event fires, the event handler function receives an event object describing information about the event. Browsers disagree on the naming of these fields, but for non-IE browsers, the target field contains the event which received the action:

[event-target.html]

<html>
 <body>
  <input type="button" id="button-1" value="Click me!" />

  <input type="button" id="button-2" value="Or me!" />

  <script>
   function HandleClick (event) {
    event.target.value = "Clicked at " + (new Date()).getTime() ;
   }
 
   document.getElementById("button-1").onclick = HandleClick ;
   document.getElementById("button-2").onclick = HandleClick ;
  </script>
 </body>
</html>

produces:

Interacting with CSS

CSS is a means for separating the content of a web page from the style of a web page. JavaScript can interact with the CSS styles associated with each element using the style field. In JavaScript, there are two ways to access a given style property--as a literal field, or as a an index. For example, one can render the following page:

[style-example-css.html]

<html>
 <body>
  <div style="border: 1px black solid; padding: 20px; color: red; background-color: grey ;">
   This is red text in a box.
  </div>
 </body>
</html>

produces:

by accessing all of the styles as fields:

[style-example-fields.html]

<html>
 <body>

  <div id="my-div">
   This is red text in a box.
  </div>

  <script>
  var d = document.getElementById("my-div") ;
  d.style.border = "1px black solid" ;
  d.style.padding = "20px" ;
  d.style.color = "red" ;
  d.style.backgroundColor = "grey" ;
  </script>
  
 </body>
</html>

produces:

or by accessing all of the styles as indexes:

[style-example-indexes.html]

<html>
 <body>

  <div id="my-div">
   This is red text in a box.
  </div>

  <script>
  var d = document.getElementById("my-div") ;
  d.style["border"] = "1px black solid" ;
  d.style["padding"] = "20px" ;
  d.style["color"] = "red" ;
  d.style["backgroundColor"] = "grey" ;
  </script>
  
 </body>
</html>

produces:

Note that for CSS styles which contain hyphens, their name in JavaScript changes to "camel case"; for example, background-color becomes backgroundColor.

Exercise: Minesweeper

Below, you'll find a partially constructed Minesweeper game. The code creates the board with DOM tree manipulation, and stores information about the game inside the DOM tree. It randomly initializes the board with mines. At the moment, it calls the procedure DisplayDebugInfo when it starts, which overlays information on the board. (If you want, have DisplayDebugInfo invoked when the user presses the character "C".)

You can decide on the precise controls, but it's recommend that a user should be able to highlight a cell, and click "F" to toggle the flag, or "B" to "clear" the cell.

For simplicity, you can use blue to mark a cell as flagged. If you're feeling adventurous, add some graphics!

[minesweeper-exercise.html]

<html>
 <body id="body">

  <style>

   table#board {
    background-color: grey ;
   }

   table#board td {
    text-align: center ;
    font-family: Helvetica ;
    width: 32px ;
    height: 32px ;
    border: 1px solid white ;
   }
  </style>

  <center>
  <table id="board">
  </table>
  </center>

  <script>

   /* Game parameters. */
   var Rows = 10 ;
   var Cols = 10 ;

   var MineProbability = 0.1 ;

   /* Game models. */
   var Board = document.getElementById("board") ;
   var Cells = [] ;

   var LastClickedCell = null ;

   /* Methods to attach to each cell. */
   function neighbors () {
    var n = [] ;
    for (var i = -1; i <= 1; ++i)
     for (var j = -1; j <= 1; ++j) {
      if (Cells[this.row+i] && Cells[this.row+i][this.col+j] && !(i == 0 && j == 0))
       n.push(Cells[this.row+i][this.col+j]) ;
     }
    return n; 
   }

   function neighborsMine() {
    var n = this.neighbors() ;
    for (var i = 0; i < n.length; ++i) {
     if (n[i].hasMine)
      return true ;
    }
    return false ;
   }


   function neighboringMines() {
    var n = this.neighbors() ;
    var count = 0 ;
    for (var i = 0; i < n.length; ++i) {
     if (n[i].hasMine)
      ++count ;
    }
    return count ;
   }   


   function HandleCellClick(event) {
    LastClickedCell = event.target ;

    // console.log(event.button) ;
     
    // For now, highlight the cell in green:
    event.target.style.backgroundColor = "green" ;
   }

   function HandleKeyPress(event) {
    // console.log(event); 
    switch (event.charCode) {
     case 114: // R
      // Reveal this cell.
     break ;

     case 102: // F
      // Toggle flag on/off.
     break ;
    }
   }

   document.onkeypress = HandleKeyPress ;
  
   /* Board generation. */
   for (var i = 0; i < Rows; ++i) {
    var row = document.createElement("tr") ;
    Cells[i] = [] ;
    Board.appendChild(row) ;
    for (var j = 0; j < Cols; ++j) {
     var cell = document.createElement("td") ;
     cell.innerHTML = "<span></span>" ;

     cell.row = i;
     cell.col = j ;
     cell.hasMine = Math.random() <= MineProbability ;
     cell.hasBeenRevealed = false ;
     cell.hasFlag = false ;
  
     cell.neighbors = neighbors ;
     cell.neighborsMine = neighborsMine ;
     cell.neighboringMines = neighboringMines ;
     cell.onclick = HandleCellClick ;

     if (cell.hasMine)
      cell.style.backgroundColor = "red" ;
  
     Cells[i][j] = cell ;
     row.appendChild(cell) ;
    }
   }

   function DisplayDebugInfo() {
    for (var i = 0; i < Rows; ++i) {
     for (var j = 0; j < Cols; ++j) {
      Cells[i][j].innerHTML = Cells[i][j].neighboringMines() ;
      if (Cells[i][j].hasMine) {
       var n = Cells[i][j].neighbors() ;
       for (var k = 0; k < n.length; ++k) {
        n[k].style.backgroundColor = "blue" ;
       }
      }
     }
    }
   }

   DisplayDebugInfo() ;
  </script>
 </body>
</html>

produces: