Skip to content
4thex edited this page Dec 19, 2014 · 46 revisions
* [What is it?](#what-is-it) * [Why should I care?](#why-should-i-care) * [Details](#details) + [Select Elements](#select-elements) + [Add elements](#add-elements) + [Changing Style](#changing-style) + [Reacting to events](#reacting-to-events) + [The canvas element](#the-canvas-element) + [Custom Tags](#custom-tags) + [Testing](#testing) + [Slow operations](#slow-operations) * [Where can I find more information?](#where-can-i-find-more-information)

What is it?

We did this, not because we do not have the right to such help, but in order to offer ourselves as a model for you to imitate. 2 Thessalonians 3:9 NIV

DOM is an abbreviation for Document Object Model. An HTML5 web page or document has elements. Each of these elements map to a particular JavaScript object. Each of these objects have properties and methods.

top

Why should I care?

This model of the HTML document makes it possible to react to user actions like mouse clicks, and key strokes. We can change the model based on decisions the program makes, and change element content, attribute values, and CSS styles.
top

Details

Select Elements

To find a specific element in the document, use the querySelector method. This method exists on the Element object, which means that you can call it on any element in the document or on the document object itself.

When you create a new JavaScript wep app in CDE there is an example of this in the main.js file:

var element = document.querySelector("#greeting");
element.innerText = "Hello, world!";

It calls the querySelector method on document with the argument "#greeting". This will return the first element that is a child element of document with an id attribute with the value greeting. Next the innerText property of this element is set to the value "Hello, world!".

The index.html file looks like this:

<!DOCTYPE html>

<html>
<head>
  <title>MyFirstProject</title>

  <link rel="stylesheet" href="styles.css">
</head>

<body>
  <div id="greeting"></div>

  <script src="main.js"></script> 
</body>
</html>

Notice that there is a div element with the id attribute set to "greeting".

When you run this example, you will see that the content of the div element which was empty to begin with now contains Hello, world!:
Hello, world!

Right-click on the div element, and select Inspect element from the menu:
Inspect element

Notice that the actual content of the div element is different from the content of the index.html file:
Inspect element

The argument to the querySelector method is a CSS Selector, so anything that can be a CSS Selector can be used as argument. Here are some examples:

Element with id attribute value of "name": "#name"
Element with class attribute value of "hidden": ".hidden"
An input element with type attribute value of "text": "input[type='text']" An element that is a child of a div element: "div *"

To find all elements that match rather than only the first one, use the querySelectorAll method. It returns a collection of matching elements.

top

Add elements

To add elements, you first need to create them in the document with the createElement method. The argument to the createElement method is the name of the element.
To create text content, use the createTextNode method of document. Use the appendChild method of the element to add something as the last node in the list of child nodes.

Here is an example that will put a p element in the body element:

(function(){
  var p = document.createElement("p");
  var text = document.createTextNode("Here is some text");
  p.appendChild(text);
  var body = document.querySelector("body");
  body.appendChild(p);
}());

Creating a paragraph

You could also have set the innerText property of the p element instead, like this:

(function(){
  var p = document.createElement("p");
  p.innerText = "Here is some text";
  var body = document.querySelector("body");
  body.appendChild(p);
}());

Perhaps you don't want to append the new element, but rather put it as the first child. You can do that by first locating the first child using the firstChild property of the element, and then use the insertBefore method like this:

(function(){
  var p = document.createElement("p");
  p.innerText = "Here is some text";
  var body = document.querySelector("body");
  if(body.hasChildNodes()) {
    var first = body.firstChild;
    body.insertBefore(p, first);
  } else {
    body.appendChild(p);
  }
}());

Creating a paragraph as the first body child

Notice how the code first checks to see if there are any children using the hasChildNodes. We could also have written it this way:

(function(){
  var p = document.createElement("p");
  p.innerText = "Here is some text";
  var body = document.querySelector("body");
  var first = body.firstChild;
  if(first) {
    body.insertBefore(p, first);
  } else {
    body.appendChild(p);
  }
}());

top

###Changing Style It is possible to change the style of any element using the style property. The style property is an object that has a property for each possible CSS style that you can set on an HTML element in the web page.

Since it is not convenient to have a dash (-) in a property name in JavaScript, each CSS property is changed from it's dash-separated format into camelCase.

The format camelCase means that each word starts with an upper-case character except for the first one.

The CSS property background-color becomes the style property backgroundColor, and border-left-width becomes borderLeftWidth and so on.

If I wanted to change the background-color of an element, I would do this:

var element = document.querySelector("#some-element");
element.style.backgroundColor = "red";

Setting the style property is the same as setting the style attribute of the element, and that is evaluated last, so whatever you set in JavaScript will be in effect regardless of what was set in the CSS files. If you didn't set a particular property in JavaScript, it will be the value from the CSS files that take effect.

top

Reacting to events

Events in the DOM are incidents. Something happened to an element in the web page, and that element emits an event to tell whoever cares about it. These events can be caused by user interaction, like a user clicking on a button element, or an input element with the type attribute set to button. They can also be about the loading state of the page or elements in it.

Imagine that you write a Christmas card. You put it in the mailbox. You can think of the card as an event. If the mailman never came to pick up the card, no one would know that it was written. Now the mailman comes along and empties the mailbox and delivers the card to the recipient. In JavaScript the recipient of an event is called an event listener. The addEventListener method on the DOM object tells it that some one cares about the event. The method takes two arguments. The first one is the type of the event, and the second is the function to execute. The method can take more optional arguments, but we will not explore those just yet. The event object itself is passed as argument to the function in the second argument when the event occurs.

Example:
Call the addEventListener method on any element object. When the event occurs, the function you specify as argument will be called, like this:

(function(){
  window.addEventListener("load", function() {
    var p = document.createElement("p");
    var text = document.createTextNode("Here is some text");
    p.appendChild(text);
    var body = document.querySelector("body");
    body.appendChild(p);
  });
}());

In this example, we added a function to be called when the Window object is done loading the document. The addEventListener method can be called multiple times, and every function that is added will be called in turn when the event occurs, so we could have done this:

(function(){
  var load = function() {
    var p = document.createElement("p");
    var text = document.createTextNode("Here is some text");
    p.appendChild(text);
    var body = document.querySelector("body");
    body.appendChild(p);
  }
  var loadMore = function() {
    var p = document.createElement("p");
    var text = document.createTextNode("Here is some more text");
    p.appendChild(text);
    var body = document.querySelector("body");
    body.appendChild(p);
  }
  window.addEventListener("load", load);
  window.addEventListener("load", loadMore);
}());

The resulting page will look like this in the browser:
Multiple event listeners

When you implement your event listener, you must be careful not to take too much time to handle the event. No other event can be handled in the web page until you return from the event listener. It is almost like the mailman having to wait for the recipient to read the card before he can deliver the next card. It would be more effective if you could tell the mailman: This is going to take a while for me to read, so why don't you come back and check if I read it in a while. The way to do this in JavaScript is my calling the setTimeout method on the window object. We will get back to this when we talk about Long Running Processes later.

You can add an event listener to the click event on a button like this:

index.html

<!DOCTYPE html>
<html>
  <head>
    <title>Events</title>
  </head>
  <body>
    <input id="button" type="button" value="Click me!" />
    <div id="status">Not clicked yet</div>
    <script src="main.js"></script> 
  </body>
</html>

main.js

(function(){
  var button = document.querySelector("#button");
  var status = document.querySelector("#status");
  button.addEventListener("click", function(event) {
    status.innerText = "Clicked";
  });
}());

There are many different types of events, and the event object has different properties. A list of events can be seen here: [https://developer.mozilla.org/en-US/docs/Web/Events].

You can add multiple handlers to the same event. The browser will call each handler in the order they were added. If you want to prevent other handlers from getting the event, you can call the stopImmediatePropagation method on the event like this:

(function(){
  var button = document.querySelector("#button");
  var status = document.querySelector("#status");
  button.addEventListener("click", function(event) {
    event.stopImmediatePropagation();
    status.innerText = "Clicked";
  });
  button.addEventListener("click", function(event) {
    status.innerText = "Clicked, second handler";
  });
}());

The second handler is not called.

You can also add the same handler to multiple events. In that case it is useful to know where the event came from. The event has a property called target. In this example we will use one handler to handle click events from two buttons, and hide the button after it has been clicked.

index.html

<!DOCTYPE html>
<html>
  <head>
    <title>Events</title>
  </head>
  <body>
    <input id="button1" type="button" value="button1" />
    <input id="button2" type="button" value="button2" />
    <div id="status">Not clicked yet</div>
    <script src="main.js"></script> 
  </body>
</html>

main.js

(function(){
  var button1 = document.querySelector("#button1");
  var button2 = document.querySelector("#button2");
  var status = document.querySelector("#status");
  var handler = function(event) {
    var target = event.target;
    target.style.display = "none";
    status.innerText = target.getAttribute("id") + " was clicked";
  };
  button1.addEventListener("click", handler);
  button2.addEventListener("click", handler);
}());

top

The canvas element

The canvas element can be used to draw text and images. We will use it in this example to draw a playing card. The web page will look like this:
Playing card

The HTML for the page is here:

<!DOCTYPE html>

<html>
  <head>
    <title>Cards</title>

    <link rel="stylesheet" href="styles.css">
    <script src="main.js"></script>
  </head>

  <body>
  </body>
</html>

In the styles.css file, we define how we want the canvas elements to look:

canvas {
  border-color: black;
  border-style: solid;
  border-width: thin;
}

In the main.js, we first define a namespace to hold our code:

if(!NSMscsbend) {
  var NSMscsbend = {};
}

We will only be using one global variable NSMscsbend. The code simply says that if it is not already defined, we will create an empty object.

Next we will defined an event listener for the load event:

(function() {
  window.addEventListener("load", function() {
    var card = NSMscsbend.card({id: "A", suit: "S"});
    var body = document.querySelector("body");
    body.appendChild(card.element);
  });
}());

We call the card function in NSMscsbend with an argument that is an object with two properties id, and suit. The id property will be the card number such as A for an ace, 2 through 10, J for a jack, Q for a queen, and K for a king. The suit property will be S for spade, H for heart, D for diamond, and C for club.

The card function will return a canvas element that shows the playing card. We then find the body element using the querySelector method on the Document object, and finally we append the canvas element as the last element in the body element using the appendChild method of Element.

In the card function, we first create the canvas element like this:

NSMscsbend.card = function(spec) {
  var that = {};
  var canvas = document.createElement("canvas");
  canvas.width = "150";
  canvas.height = "250";

The that object is created as an empty object first. We then use the createElement method to create a new canvas element. We set the width, and height of the canvas element in pixels.

Next we will determine what color to use for drawing the card number:

  var color;
  if(spec.suit === "S" || spec.suit === "C") {
    color = "black";
  } else {
    color = "red";
  }

If the suit property is either S for spade, or C for club, we choose black as the color, otherwise red.

We draw the card number like this:

  var context = canvas.getContext("2d");
  context.font = "50px Arial";
  context.fillStyle = color;
  context.fillText(spec.id, 5, 50);

The getContext method of the Canvas object returns a CanvasRenderingContext2D object when we give the value "2d" as argument. We set the font by assigning "50p Arial" to the font property of the context object. The fillStyle property is set to the color we chose before; black or red, and finally we draw the card number using the fillText method of the context object. The first argument is the text to draw, then the X-offset; 5 and the Y-offset; 50, so the text will be drawn 5 pixels from the left border, and 50 pixels from the top border.

Now we want to draw the suit of the card:

  var image = new Image();
  image.onload = function() {
    var x, y, w, h = 100;
    if(spec.suit === "S") {
      x = 0;
      y = 0;
      w = 80;
    } else if(spec.suit === "H") {
      x = 100;
      y = 0;
      w = 100;
    } else if(spec.suit === "D") {
      x = 0;
      y = 100;
      w = 80;
    } else if(spec.suit === "C") {
      x = 100;
      y = 100;
      w = 100;
    }
    var metrics = context.measureText(spec.id);
    context.drawImage(image, x, y, w, h, metrics.width + 5, 5, 50, 50);
  };
  image.src = "http://upload.wikimedia.org/wikipedia/commons/6/63/French_suits.svg";

First we create an Image object with the new keyword. Once the image has been loaded, the onload function is called, so we define that function to draw an image onto the context with the drawImage method.

Before we get to that, let's take a look at the measureText method of the context object. We call it with the text we drew before spec.id, and the width property of the returned value is the width of that text in pixels on the canvas. We do this to find out where to draw the suit symbol.

Now the drawImage method of the context object takes the image we want to draw as the first argument. The next 4 arguments indicate the part of the image we want to draw as x, and y of the top left corner, and then the width w, and height h. The last 4 arguments indicate where in the canvas we want this part of the image to be drawn. We place it 5 pixels to the right of the card number metrics.width + 5, and 5 pixels from the top, 50 pixels wide, and 50 pixels high.

The image address is specified in the src property of the Image object. The image looks like this:
The card suits

The last thing we need to do in the card function is to set the element property to the canvas and return the that object:

  that.element = canvas;
  return that;
};

top

Custom Tags

When the browser comes across a tag that it doesn't recognize, it just renders the text of the content as if it was HTML and moves on.

Example:

<!DOCTYPE html>
<html>
  <head>
    <title>CustomTags</title>
  </head>
  <body>
    <greeting name="Palle"><b>JavaScript is not enabled</b></greeting>
  </body>
</html>

It will look like this in the browser:
Custom tag rendered

Now let's add some JavaScript. First add this line as the last in the body element:

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

Then create the main.js file with the following content:

var element = document.querySelector("greeting");
var name = element.getAttribute("name");
element.outerHTML = "<div>Hello, my name is "+name+"</div>";

Now it will look like this in the browser:
Custom tag modified

We can still use the querySelector method of the document object to get to elements even though the browser doesn't know how to render them. We then get the name attribute using the getAttribute method of the element. Now we replace the element and all children in the document completely by setting the outerHTML property.

We can use this technique to template our content. Look at this example:

<!DOCTYPE html>
<html>
  <head>
    <title>CustomTags</title>
  </head>
  <body>
    <color>red</color>
    <color>green</color>
    <color>blue</color>
    <script src="main.js"></script>  
  </body>
</html>

main.js:

var elements = document.querySelectorAll("color");
for(var i=0; i<elements.length; i++) {
  var element = elements[i];
  var color = element.innerText;
  element.innerHTML = "<div>"+color+"</div>";
  var div = element.firstChild;
  div.style.backgroundColor = color;
  div.style.color = "white";
}

Using the querySelectorAll method of the document object, we first find all the color elements. We iterate over them and replace the content using the innerHTML method. We set the background-color and the color on the first child of the element, remember that we just added a child by setting the innerHTML property.

It now looks like this in the browser:
Custom tags colors

Do you see the idea? We can basically create our own HTML elements. It is very easy for a web designer to use our new elements. These custom tags are sometimes called JavaScript Widgets.

Using this same technique, it is possible to create much more complicated widgets such as date pickers, login forms, menus, or anything else you can imagine.

top

Testing

When you are writing a big application, something can go wrong. Maybe you made a mistake, and now something doesn't work right. You can now of course try to find your mistake using the Chrome Developer Tools that your start by pressing <Ctrl+Shift+I> in Chrome, but it is much better if we can make sure that each little piece of the application is working as we expect as we are writing it. This approach is called TDD (Test Driven Development).

There are many free or commercial frameworks available that will make it easier for you to write tests, but to get you started, let's write our very own tiny test framework.

Create a new JavaScript Web App in Chrome Dev Editor, and add the following files:

tinyTest.html

<!DOCTYPE html>
<html>
  <head>
    <script src="tinyTest.js"></script>
    <script src="tinyTestCases.js"></script>
  </head>
  <body>

  </body>
</html>

tinyTest.js

if(!NSTinyTest) {
  var NSTinyTest = {};
}

NSTinyTest.test = function(spec) {
  var bodyElement = document.querySelector("body");
  var resultElement = document.createElement("div");
  resultElement.style.color = "white";
  bodyElement.appendChild(resultElement);
  var result = "Executed " + spec.execute.name + "<br/>";
  if(spec.execute()) {
    result += "Succeeded";
    resultElement.style.backgroundColor = "green";
  } else {
    result += "Failed: "+spec.message;
    resultElement.style.backgroundColor = "red";
  }
  resultElement.innerHTML = result;
};

tinyTestCases.js

(function() {
  window.addEventListener("load", function(event) {
    NSTinyTest.test({
      execute: function oopsTest() {
        return false;
      },
      message: "Oops!"
    });
    NSTinyTest.test({
      execute: function successfulTest() {
        return true;
      },
      message: "Should have succeeded?"
    });
  });
}());

Writing tests

Now running the tinyTest.html file will result in this:
Test result

Now to write your own tests, you can just add them in the tinyTestCases.js file. Every call to NSTinyTest.test will execute one test. The argument is an object with two properties; execute and message. The execute property must be a function to execute that returns false if the test fails, and true if the test succeeds. The message property is a String to show if the test fails.

How it works

Let's take a closer look at what happens in the NSTinyTest.test function.

  var bodyElement = document.querySelector("body");
  var resultElement = document.createElement("div");
  resultElement.style.color = "white";
  bodyElement.appendChild(resultElement);

We find the body element using the querySelector method on object document. We then create a new div element, we set the text color to white by setting the color property of the style property of the new div element. Then we add that new element to the body using the appendChild method.

  var result = "Executed " + spec.execute.name + "<br/>";
  if(spec.execute()) {
    result += "Succeeded";
    resultElement.style.backgroundColor = "green";
  } else {
    result += "Failed: "+spec.message;
    resultElement.style.backgroundColor = "red";
  }
  resultElement.innerHTML = result;

Now we set the result variable to a text such as Executed oopsTest <br/>, where oopsTest is the name of the function we specified in the execute property. Since functions are really also objects, they too can have properties. Once such property is the name.

We then execute the execute function. If it returns true; the test succeeded, we set the backgroundColor of the resultElement to green. If it returns false; the test failed, and we set the backgroundColor to red, and we also add to the result text something similar to Failed: Oops! depending on the message property.

Finally we set the innerHTML of the resultElement to the result. That's it!

A practical test example

Let's pretend that we don't know for sure how the substr method of a String object works. Well, we can test it!

Replace the content of the tinyTestCases.js file with the following:

(function() {
  window.addEventListener("load", function(event) {
    NSTinyTest.test({
      execute: function substrTest() {
        var text = "dog";
        var last = text.substr(-1);
        return last === "d";
      },
      message: "I thought it would have been d, but it was " + "dog".substr(-1)
    });
  });
}());

Running the test gives this result:
Failed dog test

Ah! We made a mistake when we wrote the test. substr(-1) will give us the last character of the string, not the first.

Let's correct the test:

(function() {
  window.addEventListener("load", function(event) {
    NSTinyTest.test({
      execute: function substrTest() {
        var text = "dog";
        var last = text.substr(-1);
        return last === "g";
      },
      message: "I thought it would have been g, but it was " + "dog".substr(-1)
    });
  });
}());

Now the test succeeds is as expected:
Successful test

top

Slow operations

Sometimes you might want to do something that takes a long time, and you want to give the user the option to regret starting the operation by clicking a Cancel button.

Let's use the following HTML document as an example:

<!DOCTYPE html>

<html>
  <head>
    <title>Progress</title>
    <link rel="stylesheet" href="styles.css">
    <script src="deck.js"></script>
    <script src="shuffler.js"></script>
    <script src="progress.js"></script>
    <script src="main.js"></script>
  </head>

  <body>
    <div id="result"></div>
    <form>
      <input id="shuffle" type="button" value="Shuffle" />
    </form>
  </body>
</html>

It will look like this in the browser:
The Shuffler

Not very interesting yet. The idea for the example is that when the Shuffle button is pressed, a deck of cards will be shuffled while a progress bar is shown with a Cancel button. It will take several seconds to shuffle the deck. As you can see from the script elements in the HTML, we will break this up into 4 different pieces, that can be used individually; the deck, the shuffler, the progress, and the main.

The main

This is the file that ties the other pieces together.

(function(){

  window.addEventListener("load", function(event) {
    var button = document.querySelector("#shuffle");
    var resultElement = document.querySelector("#result");
    var deck = NSMscsbend.deck();
    button.addEventListener("click", function(event) {
      var argument = {
        sequence: deck.sequence,
        swaps: 1000000,
        complete: function(sequence) {
          resultElement.innerText = sequence.join();
        }
      };
      var shuffler = NSMscsbend.shuffler(argument);

      var progressBar = NSMscsbend.progressBar({
        element: button,
        cancel: shuffler.cancel
      });

      shuffler.addEventListener("progress", progressBar.onprogress);

      shuffler.execute();
    });
  });

}());

Everything in this file is inside an invocation statement. We mentioned that at the end of the JavaScript chapter. It is a way to isolate a function, so that no other code can call it.

(function(){
  // The code goes here
}());

We add an event handler for the load event on the Window object that gets fired when the document is fully loaded.

  window.addEventListener("load", function(event) {

In this event handler, we locate the #shuffle element and the #result element. Remember from the CSS chapter that this matches the elements that have an id attribute with the value shuffle, and result.

    var button = document.querySelector("#shuffle");
    var resultElement = document.querySelector("#result");

We use the querySelector method on the document object to find the elements.

We then create the deck by calling the deck function in the NSMscsbend namespace.

    var deck = NSMscsbend.deck();

Now we add an event handler for the click event of the #shuffle button.

    button.addEventListener("click", function(event) {

The event is fired when we click the button.

Here is the content of that event handler:

      var argument = {
        sequence: deck.sequence,
        swaps: 1000000,
        complete: function(sequence) {
          resultElement.innerText = sequence.join();
        }
      };
      var shuffler = NSMscsbend.shuffler(argument);

      var progressBar = NSMscsbend.progressBar({
        element: button,
        cancel: shuffler.cancel
      });

      shuffler.addEventListener("progress", progressBar.onprogress);

      shuffler.execute();

The argument object is the argument to the shuffler function. The shuffler will shuffle the sequence swaps times and call the complete function when it is done. The complete function takes the sequence as argument, so we can do something with the now shuffled sequence.

We just write the sequence into the #result element.

          resultElement.innerText = sequence.join();

You can experiment with doing something more interesting.

The progressBar is created by calling the progressBar function in the NSMscsbend namespace. The argument is an object with an element and a cancel property.

      var progressBar = NSMscsbend.progressBar({
        element: button,
        cancel: shuffler.cancel
      });

The element is the element in the HTML document that it will show up after. We set it to button, so it will show up right under the #shuffle button. It looks like this when it runs:
Progress bar

The cancel property is the function to call when the Cancel button is clicked. We set that to the cancel method on the shuffler to tell it to cancel the shuffling. Otherwise it would keep going until it is finished.

Now we will add an event listener to the shuffler object.

      shuffler.addEventListener("progress", progressBar.onprogress);

The progress event is fired whenever the shuffler has made significant progress that it wants to tell us about. We set the onprogress method of the progressBar object as the handler for the event.

The last step is to start shuffling.

      shuffler.execute();

We do that by calling the execute method on the shuffler object.

The deck

// Namespace
if(!NSMscsbend) {
  var NSMscsbend = {};
}

NSMscsbend.deck = function(spec) {
  var that = {
    sequence: [
      "AS", "2S", "3S", "4S", "5S", "6S", "7S", "8S", "9S", "10S", "JS", "QS", "KS",
      "AH", "2H", "3H", "4H", "5H", "6H", "7H", "8H", "9H", "10H", "JH", "QH", "KH",
      "AD", "2D", "3D", "4D", "5D", "6D", "7D", "8D", "9D", "10D", "JD", "QD", "KD",
      "AC", "2C", "3C", "4C", "5C", "6C", "7C", "8C", "9C", "10C", "JC", "QC", "KC",
    ]
  };
  that.parse = function(value) {
    var result = {};
    result.id = value.substr(0, value.length-1);
    result.suit = value.substr(-1);
    return result;
  };
  that.get = function(index) {
    return that.parse(that.sequence[index]);
  };
  return that;
};

We first define the NSMscsbend namespace. We then define a deck function, that returns an object with one property; sequence, which is the deck of 52 cards. Each card is represented as a String, where the first character(s) is the id of the card as A, 2, 3, 4, 5, 6, 7, 8, 9, 10, J, Q, or K. The last character is the suit as S for spade, H for heart, D for diamond, and C for club just like the previous example.

The parse method of the deck object uses the substr method of the String object to split the card into an id and a suit value and return them as one card object, as in the previous example.

  that.parse = function(value) {
    var result = {};
    result.id = value.substr(0, value.length-1);
    result.suit = value.substr(-1);
    return result;
  };

The method will return a part of the String is it called on. The first argument to substr is the index starting at 0 for the first character, so to get the id, we start with 0. The next argument is optional, and indicates the number of characters to include, so for id we specify value.length-1 because the last character is the suit. To get the suit, we use the argument -1. This means that we will start with the last character. We omit the second argument, so it is assumed that we meant to include all characters, but there in only one left.

The get method will return a specific card in the deck as a card object.

  that.get = function(index) {
    return that.parse(that.sequence[index]);
  };

It gets the String value at the indicated index in sequence and uses that as the argument to the parse method.

The shuffler

// Namespace
if(!NSMscsbend) {
  var NSMscsbend = {};
}

NSMscsbend.shuffler = function(spec) {
  var that = {
    spec: spec
  };

  var state = {
    running: false,
    totalExecuted: 0,
    swapIndex: 0,
    sequence: spec.sequence
  };

  var steps = spec.swaps * 10;

  var pick = function() {
    var result = Math.floor(Math.random() * state.sequence.length);
    return result;
  };

  var report = function(executed) {
    state.totalExecuted += executed;
    if(!(state.totalExecuted % Math.floor(steps/100))) {
      var event = new ProgressEvent("progress",
      {
        lengthComputable: true,
        loaded: state.totalExecuted,
        total: steps
      });
      that.onprogress(event);
      return true;
    }
    return false;
  };

  that.execute = function() {
    state.running = true;

    while(state.swapIndex < spec.swaps) {
      var firstIndex = pick(state.sequence.length);
      var secondIndex = pick(state.sequence.length);
      var first = state.sequence[firstIndex];
      var second = state.sequence[secondIndex];
      state.sequence[firstIndex] = second;
      state.sequence[secondIndex] = first;
      state.swapIndex++;
      if(report(10)) {
        state.timer = window.setTimeout(that.execute, 0);
        return;
      }
    }
    state.running = false;
    state.swapIndex = 0;
    state.totalExecuted = 0;
    that.spec.complete(state.sequence);
  };

  that.cancel = function() {
    if(!state.running) return;
    state.running = false;
    state.swapIndex = 0;
    state.totalExecuted = 0;
    if(state.timer) {
      window.clearTimeout(state.timer);
    }
  };

  var listeners = [];

  that.addEventListener = function(type, listener) {
    listeners.push({type: type, listener: listener});
  };

  that.removeEventListener = function(type, listener) {
    var remaining = listeners.filter(function(element) {
      return element.type === type && element.listener === listener;
    });
    return remaining;
  };

  that.onprogress = function(event) {
    listeners.forEach(function(element, index, array) {
      if(element.type === "progress") {
        element.listener(event);
      }
    });
  };

  return that;
};

Let's first take a look at the pick method.

  var pick = function() {
    var result = Math.floor(Math.random() * state.sequence.length);
    return result;
  };

It allows us to pick a random index in the sequence. Note how this method is not part of the that object, so no one can call this method from outside the shuffler object.

The progress

// Namespace
if(!NSMscsbend) {
  var NSMscsbend = {};
}

NSMscsbend.progressBar = function(spec) {
  var that = {};

  var cleanup = function() {
    container.remove();
    spec.element.disabled = false;
  };

  spec.element.disabled = true;
  var container = document.createElement("div");
  container.style.width = "100%";
  var bar = document.createElement("progress");
  bar.style.width = "100%";
  container.appendChild(bar);
  var button = document.createElement("input");
  button.style.width = "8em";
  button.setAttribute("type", "button");
  button.setAttribute("value", "Cancel");
  button.addEventListener("click", function(event) {
    cleanup();
    oncancel();
  });
  container.appendChild(button);
  var sibling;
  if(sibling = spec.element.nextElementSibling) {
    document.insertBefore(container, sibling);
  } else {
    spec.element.parentNode.appendChild(container);
  }

  that.onprogress = function(event) {
    bar.setAttribute("value", event.loaded);
    bar.setAttribute("max", event.total);
    if(event.loaded === event.total) {
      cleanup();
    }
  };

  var oncancel = function() {
    if(spec.cancel) {
      spec.cancel();
    }
  };

  return that;
};

top

Where can I find more information?

top

Introduction
[Install CDE](Install CDE)

Part 1 - The basics

HTML5
CSS
JavaScript
DOM

Part 2 - Projects

[A Ringing Bell](A Ringing Bell)
[Projectile Movement](Projectile Movement)
[Map with location](Map with location)
[Slide 15 Puzzle](Slide 15 Puzzle)
[A Running Man](A Running Man)
Sudoku

Part 3 - Getting it out there

[Package Chrome Application](Package Chrome Application)
[Uploading Your Application](Uploading Your Application)

Clone this wiki locally