01219245/javascript1/tutorial2

จาก Theory Wiki
ไปยังการนำทาง ไปยังการค้นหา
This is part of 01219245

While the sequential-style interaction as in our previous tutorial is easy to write, the pop-ups is not a nice way to interact with users. Most UI application uses another programming model, called event-driven programming. In this tutorial, we shall learn how to do that in JavaScript. While event-driven programming is easy to do in JavaScript, it will be more entertaining to use a famous jQuery library to simplify various tasks.

This time we will not use jsFiddle, but we will write an easy web-application in your laptop/notebook/pc. We will also use Git to keep track of our development.

To help you to get familiar with Git, we will commit a lot in this tutorial. In real project we will commit when we make some "unit" of changes and might not commit as often as in this tutorial.

The plan

In this tutorial, we will re-implement the number guessing game we did previously so that it works like a typical web application.

The game should look like this:

Guess-ui.png

From that, let's think a bit about what we have to do (or have to learn how to do).

Think

Don't read further until you have thought carefully about the above question.

Steps

What we have to do:

  • Read a guess from the user.
    • If we look more carefully into this step, we have to wait until the user press the button, then take the number in the text box.
  • Check the guess.
  • Update the result.

In GUI application, there are many "pieces" of the UI that the user can interact. For example, in the application above, if we want to add another button, called "Clear", that clear the text input, the user can either press the "Guess" button or the "Clear" button. It is hard for usual sequential program that waits for the user input to deal with many things at the same time. A better way to deal with these kinds of application is to think about the events that can occur and write programs that handle them. This is the idea of an event-driven programming.

First try at event-driven programming and jQuery

In this section, we shall experiment with the concepts and tools that we will use to re-write our number guessing game, namely, event-driven programming and jQuery.

Create a new directory called guessingprep. Create a new Git repository there.

Let's create an empty web page with a button. Create a file index.html with the following:

<!doctype html>
<html lang="en">
<body>
  <button>Click me!</button>
</body>
</html>

View the page on a browser. (Note: enter file:/// into the URL bar, and keep browsing.) You should see the button. Clicking at it won't make anything happens.

Right now, it's a nice place to commit our changes to the Git repository.

git add index.html
git commit -m "an empty page with a button"

Let's add a simple interaction. An onclick (from on click) attribute at a button element specify a JavaScript code that should be executed if the button is clicked. Change the button line to:

 <button onclick="alert('Hello!');">Click me!</button>

Reload the page, and try clicking at the button.

Let's commit again. Put your own commit message.

git commit -am "________________________________"

Later on, I'll signal you where you should commit, you must add the commit message by yourself.

Including JavaScript in a web page

If we want to do complex things when a button is clicked, placing all the code in the onclick attribute is a mess. We shall write a function to do the work, and just call the function inside the onclick attribute.

There are two ways to include the JavaScript code in a web page. We can write a JavaScript code in a file and place a link to that file in our html page. We can also put the code directly inside the html page; this is call inline JavaScript. We shall write an inline JavaScript code for now.

A JavaScript code is placed inside a script block. Change index.html to the following:

<!doctype html>
<html lang="en">
<body>
  <button onclick="update();">Click me!</button>

<script type="text/javascript">
var c = 0;
function update() {
  c++;
  alert(c);
}
</script>
</body>
</html>

Note a weird indentation. To help writing JavaScript code better, I remove one-step of indentation from the script block.

You should click to play with it, if it works, commit the change.

You can add onclick attributes to buttons, links, and many elements. If we have to do that for many elements in the web page, it can be very messy. jQuery has a nice way we can deal with this mess.

Using jQuery

jQuery is a wonderful library that facilitates many tasks in JavaScript.

Go to jQuery's homepage, go to its download page, and download the compressed, production jQuery. While the filename should look like jquery-1.X.X.min.js, save the jquery script as jquery.js.

To include the library into our web page, add the following line

 <script src="jquery.js"></script>

before our previous script block. This is how you include an external JavaScript to an html page.

With jQuery, instead of attaching the onclick attribute to the DOM elements, we will add the handler to the elements in the JavaScript source.

NOTES: We usually calls the elements in an HTML page a DOM element, where DOM stands for Document Object Model.

Selectors

What jQuery provides us is a function $. (You are right; the function name is just a dollar sign.) This function is used to query the DOM elements so that we can work on them. Let's see that by examples.

Let's remove the onclick attribute from the button, and add a line:

$("button").click(update);

to the end of the script block.

The complete index.html should look like this:

<!doctype html>
<html lang="en">
<body>
  <button>Click me!</button>

<script src="jquery.js"></script>
<script type="text/javascript">
var c = 0;
function update() {
  c++;
  alert(c);
}
$("button").click(update);
</script>
</body>
</html>

The weird statement we have just added falls into a common idiom for using jQuery:

$(selector).dosomething().dosomethingmore().dosomethingfurthermore();

The selector button selects all button elements in the page. The .click(update); part registers function update to the onclick event.

Try the code, and commit the current changes.

Passing functions

When registering a function for a click event in

$("button").click(update);

we pass function update to method click. This is different from what we wrote earlier in onclick attribute as

<button onclick="update();">Click me!</button>

(note the missing ().)

In JavaScript, you can use functions as ordinary data values. Take a look at the following example:

function add10( x ) {
    return x + 10;
}
function a( f, x ) {
    x += 1;
    return f( x );      // <--- add10 is actually called at this point
}
alert( a( add10, 100 ) );

Suppose that when a button is clicked, you want to call dosomething(abc), what happens if you write it like this:

$("#mybutton").click( dosomething( abc ) );

When passing arguments to a method, all arguments are evaluated (i.e., functions are called as well); therefore dosomething is called right away when you register it for the click event.

Actually, you don't want to call dosomething when registering the handler, but you want to pass a function that calls dosomething. To do so you can either define a new function for that

function clickHandler() { 
    dosomething( abc ); 
}
$("#mybutton").click( clickHandler );

or, you can use anonymous functions, like this:

$("#mybutton").click( function() { 
    dosomething( abc ); 
});

This is a common compressed writing for using anonymous function. But if you look carefully, it is the same as the definition of function clickHandler without the name

$("#mybutton").click( 
  function() { 
    dosomething( abc ); 
  }
);

If you are going to write a function that is called once, defining it an anonymous function sometimes save you from thinking about new names. It is more direct and very natural for JavaScript. You will see more usages of anonymous functions later on.

Two buttons: id and class

Let's add another button. Add the following line after the first button:

<button>Don't click me</button>

Before actually trying, what do you think will happen if you click this new button? Will the alert box appear? Why?

Now, let's try it. Do you know why?

If we don't want function update to run when the second button is clicked, we should selects only the first button when registering the click event. Right now the two buttons look pretty much the same; therefore, selecting with just button gives you both.

We can add more information to the DOM elements. Let's try that by example.

Let's add another button and change that first part of the page to:

  <button id="button1" class="left-buttons">Click me!</button>
  <button id="button2" class="left-buttons right-buttons">Don't click me</button>
  <button id="button3" class="right-buttons">Don't ever click me</button>

Then try changing the selector "button" in the jQuery call to the following, and for each selector, try clicking the buttons to see which buttons can handle the click.

  • "button1"
  • "#button1"
  • ".button1"
  • "left-buttons"
  • "#left-buttons"
  • ".left-buttons"

Try to answer this:

  • What selector should we use to select two buttons on the right?
  • What selector should we use to select the first and the last buttons (not the middle one)? If you can't find a suitable selector, what should you do to register the click event to both of these buttons?
  • What do . and # mean?

After you try the experiment and answer the above questions, you can see more explanation here.

id and class: A DOM element has two special attributes id and class. By definition, an ID should be unique for each element (i.e., you can't have two elements with the same ID). Many elements can share the same class; an element can also be in many classes. jQuery uses # to prefix the ID, and . to prefix the class in selectors. (This is a standard selector for CSS---a language that specifies page style.)

There are other ways to select elements, but this two selection should be enough for us for now.

Ready

As a common practice, we should work on the DOM elements after the web page is loaded. To do so, jQuery provides .ready function. It is a rather technical practice; right now, you can just remember how to do it.

Let's change the click registration of our code to

$(function(){
  $("#button1").click(update);
});

Note that we wrap our old code inside an anonymous function that is passed to function $.

As a quick summary, put all your jQuery registration and DOM-element modification in the wrapper like this:

$(function(){
  // your code here
});
Let's commit at this point.

Modifying texts

Let's remove alert from our previous example. (And also remove useless buttons.) Change our page to this:

<!doctype html>
<html lang="en">
<body>
  <span id="counterLabel">0</span><br>
  <button id="counterButton">Click me!</button>

<script src="jquery.js"></script>
<script type="text/javascript">
var c = 0;
function update() {
  c++;
  $("#counterLabel").text('Counter = ' + c);    // *****
}

$(function(){
  $("#counterButton").click(update);
});
</script>
</body>
</html>

The above page contains a SPAN element that groups other elements. Here, we use span to store text (i.e., the label counter).

Try the page to see how it works.

OK, let's commit at this point.

A few questions to think about:

  • Guess what .text(....) does?
  • What happens at line *****?
  • Do you see any automatic type conversion?

Reading the input

We will not use function prompt, however, we will create an input element so that we can read the user input. Add this two lines to index.html

  <input id="initValue">
  <button id="initButton">Reset</button>

Note the id's of both elements. Add the following JavaScript into the jQuery ready call so that it becomes:

$(function(){
  $("#counterButton").click(update);   // this is an old code
  $("#initButton").click(function(){
    var v = $("#initValue").val();
    alert(v);
  });
});

Try the page, put something into the input box and press Reset. Try that a few times.

What does function .val() do?

Change the code so that after you click reset, the value of the text box becomes the value of the counter.

OK, this is the last commit

Let's write our game

Let's create a new directory guess and create a new Git repository in that directory. We shall re-write our number guessing game here.

For a small game like this, if you are experienced, you can just write it in one go. However, we will try to be slow to practice incremental development (and also Git).

Before we move on, let's think about what are the small steps that we should do to complete this game. Think about these steps as a list of features of the game that you can continuously add to the game.

After you finish, let's compare what you think and mine.

We may want to keep adding features along with the interface, but sometimes it is easy to just get the interface right before implementing any feature. For this project, since we do not care much about the interface, I choose to complete the interface first. Here are my steps:

  • Write index.html with all interface elements (i.e., buttons, text boxes, texts).
  • Read the guess after the user click the "Guess" button.
  • Create a fake random function (as in the previous example). (I need this fake function because it helps me when I test the code.)
  • Compare and show the result using alert.
  • Instead of using alert, change the message on some span element.
  • Add the round counter.
  • Use the real random function.
  • Add reset button to let the user restart the game.

A screen prototype

Let's start with a non-working page:

<!doctype html>
<html lang="en">
<body>
  <p>
    Guess the number!
  </p>
  <p>
    Your guess: <input> <button>Guess</button>
  </p>
  <p>
    Number of guesses: 0
  </p>
</body>
</html>
If it looks OK, we should commit.

Adding jQuery and JavaScript block

Copy jquery.js from the previous project to the project's directory. We shall load jQuery as an external library and add an empty JavaScript block. Add these line at the end of the body element.

<script src="jquery.js"></script>
<script type="text/javascript">
</script>
Try to reload the page and see if there's any error on the JavaScript console. If every works, just commit the change.

Click

Let's start by reading the user's guess and compare with some number as in our previous tutorial.

We need a way to talk with the input text box; therefore, let's add an id to it. We also add an id to the button.

    Your guess: <input id="guess"> <button id="guessButton">Guess</button>

To handle the button click, let's define a function checkGuess and register it as a click handler.

<script type="text/javascript">
function checkGuess() {
}

$(function() {
  $("#guessButton").click(checkGuess);
});
</script>

To test that our function runs when the user click the button, we can add alert('hello'); inside checkGuess and try to reload the page and click the button.

If that works, we will read the input from the text box. The following code for checkGuess reads the input and shows an alert.

function checkGuess() {
  var guess = $( "#guess" ).val();
  alert( guess );
}

Try to see if this function works. Let's add some solution; put this before function checkGuess:

var solution = 55;

Now, let's take a look at our code from the last tutorial and implement function checkGuess so that it says if the guess is too high, too low, or correct.

At this point, the program should just alert the message. Try guessing with 30, 60, and 55 to see if the check works properly.

Let's commit our work

Remove alerts

We will use the first line (which currently says 'Guess the number!') to shows the message. Let's add an id. It should look like:

Guess the number!

Now, we can just change all calls to alert to set the text of #gameMessage directly like this

    $("#gameMessage").text( "Too low" );

However, we will have 3 lines that look like this. We will practice a simple principle called Don't Repeat Yourself principle (or DRY) by introducing a new function setMessage

function setMessage( msg ) {
  $( "#gameMessage" ).text( msg );
}

We can now replace all calls to alert with setMessage to remove all alerts.

Let's commit our work.

Exercises

Ex.1: Pull the code for randomizing the solution from previous tutorial. Incorporate it to our current code.

Ex.2: Add the counter. Make sure that the number of guesses (last line) changes as we keep making guesses.

Ex.3: Right now after we guess correctly, we can keep on playing. Clearly, this is not fun because the solution is the same over rounds, and you have to reload the page to get a new solution to try. Add another button called reset and if you click this button, all the memories are all gone.