Tuesday, December 10, 2013

More looping constructs

JavaScript - Loops Part Two

In the last JavaScript class we were doing
something like this in Plunker:

<html>
<head>
  <script>
      document.getElementById("btn")
          .addEventListener("click", function() {
              console.log("Button clicked");
          });
  </script>
</head>
<body>
  <button id="btn">Push Me</button>
</body>
</html>

Actually, we were doing


  <script src="script.js">

And the code was in the script.js file, but I've simplified it by
just including the code itself, so it's all in one place.  Including
the code from the script.js file is equivalent.

Using the JavaScript console with Plunker

To debug JavaScript it's really useful to be able to type
or cut and paste code into the JavaScript console in the
Chrome Debugger.  But if we type or paste

document.getElementById("btn")

We get back null.  The reason is because of iframes.
The IFRAME tag in HTML is a way to include one
web page inside another web page.  So you say

<iframe src="http://google.com"></iframe>

and you've embedded the Google home page inside
your own web page.  And this is what Plunker is doing
when running your plunk.  It's creating an iframe to
run the code.  So when you bring up the JavaScript Console
you are looking at Plunker itself, and not your plunk.
Fortunately, the JavaScript debugger let's you select the
iframe that you want to debug.


Along the bottom after the clear button is a frame selector.
Just click on that and set the frame to plunkerPreviewTarget.

Now we can get our element ID



Okay, now let's back to our plunk above.

JavaScript Order of Operations

Our plunk above with the Push Me button isn't working.
The JavaScript Console (in the Chrome Debugger) reports:

Uncaught TypeError: Cannot call method 'addEventListener' of null    (index):5

So it's telling us that document.getElementById("btn") is returning null
so it's saying that button doesn't exist.    But it's right there in the code

  <button id="btn">Push Me</button>

And if we try to get it in the JavaScript console (using frame plunkerPreviewTarget)
we can get the button.  So why isn't it working.  For the same reason that
Jerry Seinfeld is a great comedian.  Timing.

Let's annotate our code with some timing info.

<html>
<head>
  <script>
      document.getElementById("btn")  // time t(0)
          .addEventListener("click", function() {
              console.log("Button clicked");
          });
  </script>
</head>
<body>
  <button id="btn">Push Me</button>  // time t(1)
</body>
</html>

So the code is looking for the button before the button is created!  So how
do we fix it?  Well, we could just put the script after the button creation:

<html>
<head>
</head>
<body>
  <button id="btn">Push Me</button>   // time t(0)
  <script>
      document.getElementById("btn")  // time t(1)
          .addEventListener("click", function() {
              console.log("Button clicked");
          });
  </script>
</body>
</html>

But we want to keep our code in the HEAD section?  In that case, we have
add an event listener for the "load" event on the window.

<html>
<head>
  <script>
    function onLoad(){                // time t(1)
      document.getElementById("btn")  // time t(4)
          .addEventListener("click", function() {
              console.log("Button clicked");
          });
    }
    window.addEventListener("load", onLoad); // time t(2)
  </script>
</head>
<body>
  <button id="btn">Push Me</button>   // time t(3)
</body>
</html>

So now we create our onLoad() function, then bind it to the "load" event
on the window object.  Now our getElementById() isn't called until after
the window loads, which is after the button has been created.

Note: For performance reasons many sites are putting their script at the end
of the body.  This allows the page to start loading before the script loads
which can make the site load faster.

For Loops

Now let's get to some loopy goodness.  We looked at the document
cookies last time and if we split that into individual cookies we have
something to iterate.

<html>
<head>
  <script>
    function onLoad(){
      document.getElementById("btn")
          .addEventListener("click", function() {
              console.log("Button clicked");
          });
    }
    window.addEventListener("load", onLoad);
    var cookies = document.cookie.split(';');
    for (var i = 0; i < cookies.length; i++) {
      console.log(i, cookies[i]);
    }
  </script>
</head>
<body>
  <button id="btn">Push Me</button>
</body>
</html>

Then we reviewed the simplified form:

    var cookies = document.cookie.split(';');
    for (var i in cookies) {
      console.log(i, cookies[i]);

We get the same result, but less typing and it's easier to read too!

But we don't want to have to look in the JavaScript console to
see the cookies.  Let's put them in our web page.

<html>
<head>
  <script>
    function onLoad(){
      document.getElementById("btn")
          .addEventListener("click", function() {
              console.log("Button clicked");
          });
        var ul = document.getElementById("cookielist");
        for (c in cookies) {
          // cookies[c] looks like: __ga:GA1.2.3....
          var nameval = cookies[c].split("=");
          // nameval[0] now was the name, __ga
          // nameval[1] now has the value, GA1.2.3...
          var li = document.createElement("li");
          li.innerHTML = '<b>' + nameval[0] + '</b>' + ':' + nameval[1]
          ul.appendChild(li);
        }
    }
    window.addEventListener("load", onLoad);
    var cookies = document.cookie.split(';');
    for (var i in cookies) {
      console.log(i, cookies[i]);
    }
  </script>
</head>
<body>
  <button id="btn">Push Me</button>
  <ul id="cookielist"></ul>
</body>
</html>

Finally we talked briefly about the do-while loop.  It's typically
used when you want the loop to happen at least once.  Such as
when prompt the user for something.  So let's prompt the user
for a cookie name and keep prompting until they enter a valid
cookie name.

First, we'll introduce an object to hold the cookies so we can
look them up by name.

<html>
<head>
  <script>
    var cookieObject = {};
    function onLoad(){
      document.getElementById("btn")
          .addEventListener("click", function() {
              console.log("Button clicked");
          });
        var ul = document.getElementById("cookielist");
        for (c in cookies) {
          // cookies[c] looks like: __ga:GA1.2.3....
          var nameval = cookies[c].split("=");
          var name = nameval[0]; // the name, __ga
          var value = nameval[1] // the value, GA1.2.3...
          var li = document.createElement("li");
          li.innerHTML = '<b>' + name + '</b>' + ':' + value
          ul.appendChild(li);
          cookieObject[name] = value;
        }
    }
    window.addEventListener("load", onLoad);
    var cookies = document.cookie.split(';');
    for (var i in cookies) {
      console.log(i, cookies[i]);
    }
  </script>
</head>

Now we can prompt the user for a cookie.

<html>
<head>
  <script>
    var cookieObject = {};
    function onLoad(){
      document.getElementById("btn")
          .addEventListener("click", function() {
              nameprompt();
          });
        var ul = document.getElementById("cookielist");
        for (c in cookies) {
          // cookies[c] looks like: __ga:GA1.2.3....
          var nameval = cookies[c].split("=");
          var name = nameval[0]; // the name, __ga
          var value = nameval[1] // the value, GA1.2.3...
          var li = document.createElement("li");
          li.innerHTML = '<b>' + name + '</b>' + ':' + value
          ul.appendChild(li);
          cookieObject[name.trim()] = value;
        }
    }
    function nameprompt() {
      var name;
      do {
        name = prompt("Name?");
        if (cookieObject[name]) {
          alert(cookieObject[name]);
        }
      } 
      while (!cookieObject[name])
    }
    window.addEventListener("load", onLoad);
    var cookies = document.cookie.split(';');
    for (var i in cookies) {
      console.log(i, cookies[i]);
    }
  </script>
</head>
<body>
  <button id="btn">Push Me</button>
  <ul id="cookielist"></ul>
</body>
</html>

So now our button calls a new JavaScript function, nameprompt, that
runs a do-while loop to prompt the user for a cookie name.  And it
keeps prompting until the user enters a valid cookie name.

One other change we had to make was to deal with spaces in the name.
When we split the name/value string on '=' we get spaces at the beginning
of some of the names.  So we fixed that by calling .trim() on the name.

Well, that was what we covered.  Hope it all makes sense!

Code on!

1 comment:

  1. This great reference, thanks for posting in such detail.

    ReplyDelete