File Under: JavaScript, Programming

Advanced JavaScript Tutorial – Lesson 4

In the previous lesson, we looked at the many different things you can do with text and strings. Today we’ll focus on two different types of data:images and objects. By the end of this lesson, you’ll know how to:

  • Use image maps with JavaScript
  • Preload images to speed up your image swaps
  • Create your own objects to make big scripts comprehensible
  • Use associative arrays to rapidly access all the objects in your scripts

We’ll get the ball rolling with image maps.


Contents

  1. Image Maps and JavaScript
  2. Preloading Images – What’s It All About?
  3. Preloading Images – How’s It Done?
  4. Object Advantage
  5. Creating Your Own Objects
  6. Your Object-Oriented Virtual Pet
  7. Evaluating Strings
  8. Getting at Hard-to-Reach Objects
  9. Another Way to Get at Hard-to-Reach Objects
  10. Lesson 4 Review

Image Maps and JavaScript

An image map is made up of regions, and each region has an associated hyperlink. Clicking on one area takes you to the associated link. Here’s a basic example, swiped (and slightly modified) from Patrick Corcoran’s image-map tutorial.

Click here.

Though this is one single image, Patrick has designated areas of the image to link to different HREFs. You see image maps like this all over the place, most frequently as a navigational element. As a quick review, here’s the HTML for the image map above. (I should note that the image tag in this example is broken up into multiple lines so that it displays correctly on this page. The real link is one big line. OK? OK.)

 <img src="http://www.wired.com/wired/webmonkey/stuff/my_image_image_maps.gif" border=0

 WIDTH=160 HEIGHT=140 ismap usemap="#foo">



 <map name="foo">

 <area name="yellow" href="http://www.webmonkey.com/" coords="20,20,70,60">

 <area name="red" href="http://www.hits.org/" coords="90,20,140,60">



 <area name="blue" href="http://www.hotwired.com/surf/central/" coords="20,80,70,120">

 <area name="green" href="http://www.animationexpress.com" coords="90,80,140,120">

 </map>

Notice that the <img src> tag has an element that says usemap="#foo". This tells the image to look for a map tag named “foo” and apply that map to the image. The map can go anywhere on the page, but it’s generally a good idea to put the map near the image, just like you see here, so that it is easy for you to find and edit. The map has a “begin” and “end” tag, with a bunch of area tags in between. Each area tag defines a region using the coords element and assigns that region an HREF. The coords element is a string of numbers, each representing a pixel in the image. When defining a square, the pixels should be listed in this order:top left, top right, bottom left, bottom right. Adding JavaScript to these image maps is as easy as adding it to an HREF. In fact, JavaScript treats area tags almost exactly like it does HREFs. You can stick onClicks, onMouseOvers, and onMouseOuts in the area tag, and they will do what you expect.

Here’s the above example, with a little JavaScript added:

Click here.

The new JavaScript-enhanced map looks like this:


 <map name="javascript_foo">

 <area name="yellow" href="http://www.webmonkey.com/"

    onMouseOver="writeInForm('All Hail Webmonkey!');" coords="20,20,70,60">



 <area name="red" href="http://www.hits.org/"

    onMouseOver="writeInForm('Humboldt Institute of Technological Studies');" coords="90,20,140,60">



 <area name="blue" href="http://www.hotwired.com/surf/central/"

    onMouseOver="writeInForm('The good, the bad, and the utterly useless');" coords="20,80,70,120">



 <area name="green" href="http://www.animationexpress.com"

    onMouseOver="writeInForm('The real animaniacs');" coords="90,80,140,120">



 </map>

It’s pretty much the same as the original; the only difference is that inside each <area> tag there’s an onMouseOver that calls a function that I wrote, called writeInForm(), which looks like this:

 <script language="JavaScript">

 <!-- hide me



 function writeInForm(text_to_write)

 {

     window.document.the_form.the_text.value= text_to_write;

 }



 // show me -->

 </script>

Simple, eh? For something a little more complicated, let’s learn how to preload images using JavaScript.

Preloading Images – What’s It All About?

One of the main problems with image swaps is that JavaScript will only tell the browser to download an image when it needs it for a swap. If you have a big image, and you swap in another on a mouseover, the browser has to download the new image. That can take awhile and ruin the fluid rollover effect you’re shooting for. To see what I’m talking about, click here.

If you have a slow connection, you will have to wait for a fairly large image to load after you do the mouseover. Since some browsers won’t swap in an image if it’s not stored in the cache, you may not see any image swapping at all. In order to avoid these nasty problems, preload the images when the page first loads.

Preloading Images – How’s It Done?

It’s not terribly hard to preload your images. All you have to do is create a new Image object, then assign the image to preload to the src of that image Just like so:

 var an_image = new Image();

 an_image.src = "my_nice_image.gif";

Setting the .src of the new image automatically downloads the image to the user’s hard drive, assuming, of course, the cache is turned on. The image is read off the hard drive, instead of downloaded. Try mousing over the links below. The image swap should happen pretty quickly.

Click here.

The only thing left is to learn how to make the preload happen when the page first loads in the browser, before any rollover action. Happily, this is pretty straightforward. The body tag has an event handler called onLoad, which is called once a page finishes loading. If you have a body tag that looks like this:

 <body onLoad="doPreload();">

The doPreload() function will be called when the page is done loading. The code for the functions that do the loading looks like this:

 function doPreload()

 {



    var the_images = new Array('stuff3a/kwmatt.jpg','stuff3a/matbon.jpg',

    'stuff3a/lunchMat.jpg');

    preloadImages(the_images);

 }



 function preloadImages(the_images_array) {



    for(var loop = 0; loop < the_images_array.length; loop++)



    {

  	var an_image = new Image();

 	an_image.src = the_images_array[loop];

    }

 }

Because I put that onLoad event handler in the body tag, the function doPreload() gets called when the page finishes loading. doPreload() creates an array of the names of images to preload and passes that array to preloadImages(), which loops through the array. With each pass through the loop, it creates a new Image object and assigns an image to the src of that image object. Not too hard, hmm? That Image object is pretty useful, right? I’m glad you think so, because now it’s time to move on to some mind-blowing – and trickier – things. Take a break now, because on the next page we’re going to enter the wide world of object creation.

Object Advantage

We’ve seen plenty of examples of JavaScript’s built-in objects and how they’re used:Window, Image, Document, Form, and a bevy of other objects. Remember:An object is just a special kind of data, made up of properties and methods. Properties are the things that an object knows about itself, while methods are the things that the object knows how to do.

For example, the Window object in JavaScript has properties, like window.status, which can be read and set. By setting the status property of the Window object to a string, it makes that string appear in the window’s status bar. Window also has methods, like window.focus(), which brings a window forward. If this stuff is unfamiliar to you, you should probably read Part I, Day 5 for more details.

Objects provide a great way to organize information. First, we deal with real-life objects all the time. A monkey, for example, is an object. Its properties include height, weight, furriness, and sassiness. All monkeys have the same set of properties. It’s the values of those properties that makes one monkey different from another. Monkeys also have methods, or things they do, like play(), eat(), sleep().

Objects are also handy because they allow you to preserve the “sense” of words. Take the notion of focus() in JavaScript, for example. Focus(), appropriately enough, brings things into focus. When focus() is applied to a window, it brings the window forward. Blur does the reverse – try it.

Click here.

However, when you apply focus() to a text box, it puts a typing cursor inside the box.

Click here.

Spooky, huh? JavaScript knows that focus does one thing for a window and something else for a textbox. The idea of focus is the same in both cases, so it makes sense to call it the same thing. But it also makes sense that focus() does slightly different things for two different objects. This notion of having one method name do different things for different objects is called “polymorphism,” and it’s fundamental to object-oriented programming.

Just as you can have the same method do different things for different objects, you can also have the same property mean different things in different objects. The length property, for example, works for both strings and arrays. The string.length returns the length of a string, while array.length returns the number of things in the array. Both properties are called length, but they mean slightly different things in the context of different objects.

Objects also make it easy to cut and paste code together. If you have a good library of objects, you can just copy an entire object and plunk it into whatever script you need. This is a lot harder when you don’t have your code organized into clear objects.

Since objects are so handy, JavaScript gives you the ability to create your own.

Creating Your Own Objects

Objects help you organize information in an easily understandable way. Let’s start with the frivolous example of constructing an employee database, then use the lessons we learn to tackle something far more complex and relevant to our lives: writing a JavaScript virtual pet.

For the purposes of this exercise, let’s put aside all those issues concerning the objectification of the worker in modern capitalist society, and consider each employee as an object with a certain set of properties and methods. Each employee has a name, title, salary, birthday, address, and so on. Employees can be promoted, go on vacation, transferred, or put on kitchen duty. An object represents all of this information, and the employee object is like a template. Each employee has the same types of properties – an age, name, title – but the values of the properties differ from employee to employee. Joe is an employee with one age and title, Jane is an employee with another age and title. Both are employees, hence they have the same properties. It’s the values of those properties that differ between employees.

To write your own object, you need to start with the template. In object-oriented programming, the template is called the object’s constructor. Once you have the template, you can create new instances of the object, like this:

 var fred = new Employee("Fred Flintstone", 33, "Surface Miner", 20000);

 var barney = new Employee("Barney Rubble", 33, "Slacker", 40000);

 var boss = new Employee("Mr. Slate",50, "CEO", 1000000);

After you’ve created these instances of the Employee object, you can do stuff like this:

 barney.promotion("Chief Slacker","10");

 fred.fired();

 boss.vacation(365);

If you want to promote Barney to Chief Slacker and give him a 10 percent raise, fire Fred, and note that the CEO is taking the year off.

Of course you have to write the constructor and the method calls yourself. Start with just the properties. Here is the Employee constructor:

 function Employee(name, age, title, salary)

 {

 	this.name = name;

 	this.age = age;

 	this.title = title;

 	this.salary = salary;

 }

Notice that a constructor is just a function. Inside the function you need to assign things to this.property_name. We’re not just setting the age to 0, we’re setting this.age to 0. The same is true for this.name, this.title, etc.

You can pass parameters into constructors. When we call the Employee constructor …

 var barney = new Employee("Barney Rubble", 33, "Slacker", 40000);



… and then in the Employee function …

 this.name = name;

… we’re setting the name of this employee to whatever we passed into the Employee constructor function.

The reason for all the “this” stuff in the constructor is that you’re going to have more than one employee at a time. In order for your methods and constructor to work, which employee you’re dealing with must be clear. That’s what “this” is:the instance of the object at hand. Showing you methods might help make things more clear.

Methods are just functions attached to objects. First, define the function, then assign the function to the object (inside the object’s constructor function). Take the promotion() method as an example:

 function promotion(new_title,percent_raise)

 {

 	var salary_increase = this.salary * percent_raise;

 	this.salary = this.salary + salary_increase;

 	this.title = new_title;

 }



This function calculates what the employee’s new salary should be and assigns that, along with the new title, to the employee. JavaScript knows which employee you’re talking about by using “this.” So if you write:

 barney.promotion("Chief Slacker",10);

Then “this” is Barney. It’s definitely a little weird and takes some time to get used to it. But once you start thinking in terms of objects, it soon becomes habit.

The last step to building the object is to tie the method to the object. As I mentioned earlier, you do this inside the constructor. To tie the promotion method to the Employee object, you’d write the promotion() function, then add this to the constructor:

 this.promotion = promotion;



Here’s the Employee constructor with the promotion method added:

 function Employee(name, age, title, salary)

 {

 	this.name = name;

 	this.age = age;

 	this.title = title;

 	this.salary = salary;



 	this.promotion = promotion;

 }



 function promotion(new_title,percent_raise)

 {

 	var salary_increase = this.salary * percent_raise;

 	this.salary = this.salary + salary_increase;

 	this.title = new_title;

 }

To add other information, say, the office in which an employee works, make a property named “office.” And then if you wanted to record a change in the employee’s venue, you’d create a method named transfer().

Got that? Now on to more complicated and important object-oriented JavaScript, like creating the virtual pet.

Your Object-Oriented Virtual Pet

You are now the proud owner of not one, but two virtual pets. It’s your responsibility to keep them healthy. If their health drops below zero, they die. Let there be life by clicking here.

As you’ve probably guessed, I’m a tad bitter about my inadequacies as a virtual pet owner. My poor pets die young every time. Anyway, this entire thing was programmed using objects. Each pet is an instantiation of a Pet object. Each pet has these properties:

  • health
  • happiness
  • hunger
  • age
  • pet_name
  • form_number – the HTML form the pet lives in These properties are affected by the following methods:
  • play() – increases happiness
  • feed() – decreases hunger
  • medicate() – increases health a little bit
  • makeOlder() – makes the pet older
  • display() – displays information about the pet in the status box So, with this in mind, let’s create a Pet object. The first thing we have to do is make a constructor. Here is the basic constructor (which we’ll create without the method calls for now):
 function Pet(pet_name, form_number)

{

	this.pet_name = pet_name;

	this.form_number = form_number;

	this.happiness = Math.random() * 5; //random number between 0 and 4.99.

	this.health = Math.random() * 5 + 2; // random number between 2 and 6.99

	this.display = display;

	this.age = 0;

	this.hunger = Math.random() * 5;

}

This constructor takes two parameters:the name of the pet and the position of the form in which that pet should be displayed. To create two pets, we do this:

 pet1 = new Pet(window.document.form_1.name_textfield.value, 0);

 pet2 = new Pet(window.document.form_2.name_textfield.value, 1);

The constructor is written to make each virtual pet different. It uses the Math.random() method to generate random numbers between zero and one. The Math.random() method isn’t perfectly random, but for our purposes it’s good enough, since it does allow us to give each pet a different initial hunger, health, and happiness. The name of the pet is set to whatever name gets passed to the constructor, and the pet’s age starts at zero. The last two lines set the number of the pet’s form and puts the name of the new pet into the appropriate textbox. Remember that the first form on a page can be referred to as window.document.forms[0], the second form on a page is window.document.forms[1], and so on. We have the properties; let’s work on the methods. Rather than outline each method, I’ll just show you a few of them to get you started:


 function feed()

{

	var meal_quality = Math.random() * 2;

	this.hunger = this.hunger - meal_quality;

	if(this.hunger < 0)

	{

		this.hunger = 0;

	}

	this.display();

}

This is the method used to feed the pet. If you execute the comment pet1.feed(), this method gets called. First, it generates a random number between zero and two. Then it subtracts this number from the hunger of the appropriate pet. We make sure that the hunger isn’t less than zero, and then we call the display method. The display method looks like this:

 function display()

{

	var the_string = "";

	if(this.health == min_health)

	{

		the_string = this.pet_name + " is dead!";

	} else {

		the_string += "Health: " + parseInt(this.health);

		the_string += ", Age: " + this.age;

		the_string += ", Happiness: " + parseInt(this.happiness);

		the_string += ", Hunger: " + parseInt(this.hunger);

		the_string += ".";

	}

	window.document.forms[this.form_number].status_textfield.value = the_string;

}

This method constructs a string and displays it in the correct textbox. The form_number property drops the status information into the proper textbox. When we created each pet, we gave it a form_numberpet1 got the form number 0, and pet2 got the form number 1. Using this form_number, we know that the status of pet1 should go into window.document.forms[0].status_textfield.value and the status of pet2 should go into window.document.forms[1].status_textfield.value. The last line of this method determines which form to drop the status using this.form_number. The other interesting thing in this method is the parseInt function. Thanks to the way we generated the random numbers, the values for things like health and happiness look something like this:2.738993720. They’re real numbers, but they can get pretty long. So parseInt chops off everything after the decimal point; e.g., parseInt(2.738) ends up as simply 2.The third interesting method is makeOlder(). It ages the pet and makes it a little more hungry and a little less happy. This method gets called every second using a setTimeout, which I’ll show you next. I won’t go through the whole makeOlder() method, just enough to give you an idea about what it’s doing.

 function makeOlder()

{

	var good_happiness = 5;

	var max_hunger = 5;

	this.age += 1

	var happiness_change = Math.random() * 2;

	this.happiness = this.happiness - happiness_change;

	var hunger_change = Math.random() * 2;

	this.hunger += hunger_change;

	if(this.hunger > max_hunger)

	{

		this.happiness = this.happiness - Math.random() * 2;

		this.health = this.health - Math.random() * 2;

	}

}

As you can see, the method adds one to the age of the pet, decreases its happiness, and increases its hunger by some random amount. Then a set of rules, only one of which is shown here, says, “If the pet is really hungry, make it more unhappy and less healthy.” Now that we have these methods, we will add them to the constructor to make a complete virtual pet object:

function Pet(pet_name, form_number)

{

	this.pet_name = pet_name;

	this.form_number = form_number;

	this.happiness = Math.random() * 5; //random number between 0 and 4.99.

	this.health = Math.random() * 5 + 2; // random number between 2 and 6.99

	this.display = display;

	this.age = 0;

	this.hunger = Math.random() * 5;



	this.play = play;

	this.feed = feed;

	this.medicate = medicate;

	this.makeOlder = makeOlder;

}

The last piece of the picture is the code that sics the makeOlder() method on each pet every three seconds. This function is called when you hit the start button.

 function progress_time()

{

	pet1.makeOlder();

	pet2.makeOlder();

	timeout = setTimeout("progress_time();", 3000);

	pet1.display();

	pet2.display();

}



As you see, it calls makeOlder() on each pet and calls itself again one second later. After a second passes, the function gets called yet again. The pets age and the setTimeout is called. This continues until an overworked user calls clearTimeout(the_time_out). This example utilizes almost everything we’ve learned to date. If you understood what’s going on, you know a big heap of JavaScript. Take another break before you go on to today’s last topic:using JavaScript shortcuts to get at hard-to-reach objects.

Evaluating Strings

JavaScript has a bunch of built-in tricks to make coding easier. One of these tricks is the eval() function, which takes a string and executes it as if it were a JavaScript expression. I actually talked about eval a little bit in Part I, Day 5, but here’s a little example for you to review:

 var the_unevaled_answer = "2 + 3";

 var the_evaled_answer = eval("2 + 3");

 alert("the un-evaled answer is " + the_unevaled_answer + " and the evaled answer is " + the_evaled_answer);

If you run this silly eval code, you’ll see that it actually executes the string “2 + 3″ in JavaScript. So, when you set the_evaled_answer equal to eval("2 + 3"), JavaScript figures out and then returns the sum of two and three. This may seem like a silly thing to do, but it can get way more interesting. For example, you can use eval to create functions entirely on the fly, based on user input. This could be used to make programs that actually alter themselves over time. They could evolve, mutate, become sentient, maybe even take over the world. In practice, eval is rarely used, but you might see people using it to access hard-to-reach objects.

Getting at Hard-to-Reach Objects

One of the problems with the Document Object Model Hierarchy is that it’s sometimes a pain to get to the object of your desire. For example, here is the function to ask the user for an image to swap:

Click here.

You could use this function:

 function swapOne()

 {

 	var the_image = prompt("change parrot or cheese","");

 	var the_image_object;



 	if (the_image == "parrot")

 	{

 		the_image_object = window.document.parrot;

 	} else {

 		the_image_object = window.document.cheese;

 	}



 	the_image_object.src = "ant.gif";

 }

With these image tags:

 <img src="http://www.wired.com/images/archivetuff3a/parrot.gif" name="parrot">

 <img src="http://www.wired.com/images/archivetuff3a/cheese.gif" name="cheese">

Note the lines that look like this:

 the_image_object = window.document.parrot;

This assigns the image object parrot to a variable. Although it might look strange, it’s perfectly legal. But what happens if you have 100 images instead of just two? You’d have to have a huge if-then-else statement. It would be nice if you could just do this:

 function swapTwo()

 {

 	var the_image = prompt("change parrot or cheese","");

 	window.document.the_image.src = "ant.gif";

 }

Unfortunately, JavaScript will look for an image named the_image rather than simply know you want the_image to be “cheese,” if the user typed “cheese” into the prompt box, or “parrot,” if he or she typed “parrot.” And thus you get an error saying something like, “Never heard of the object called the_image.”

Luckily, eval can help you get at the image object you want.

 function simpleSwap()

 {

 	var the_image = prompt("change parrot or cheese","");

 	var the_image_name = "window.document." + the_image;

 	var the_image_object = eval(the_image_name);

 	the_image_object.src = "ant.gif";

 }

If the user types “parrot” into the prompt box, the second line creates a string that looks like window.document.parrot. Then the eval on the third line says, “Give me the object window.document.parrot” – which is the image object you want. And once you have that image object, you can change its source to ant.gif. Funky, no? It’s actually pretty useful, and people do it a lot.

If you don’t like eval(), there is another way to access hard-to-reach objects.

If you don’t like eval(), there is another way to access hard-to-reach objects.

Another Way to Get at Hard-to-Reach Objects

Here’s what we’ve learned so far about getting at an image object:

function simpleSwap()

{

var the_image = prompt("change parrot or cheese","");

var the_image_name = "window.document." + the_image;

var the_image_object = eval(the_image_name);

the_image_object.src = "ant.gif";

}

As it turns out, you can refer to images by their name in the images-associative array, like this window.document.images["parrot"].src. This is just like referring to images by their index number in the forms array, as in window.document.images[0].src. Therefore, the above code can be rewritten as

function simpleSwap()

{

var the_image = prompt("change parrot or cheese","");

window.document.images[the_image].src = "ant.gif";

}

You can use this trick to locate all sorts of objects. If you have a textbox in a form, like this:

<form name="the_form">

<input type="text" name="the_text_box">

</form>

You could change the text in the textbox like this:

window.document.forms["the_form"].elements["the_text_box"].value = "hello!";

Now you know several ways to access and change information in objects. Using the form example above, we can access the textbox in four different ways:

var the_form_name = “the_form”; var the_element_name = “the_text_box”;

1. window.document.forms[0].elements[0].value = “hello!”;

2. window.document.forms[the_form_name].elements[the_element_name].value = “hello!”;

3. window.document.the_form.the_text_box.value = “hello!”;

4. var the_element_string = “window.document.” + the_form_name + “.” + the_element_name;

var the_element = eval(the_element_string);

the_element_string.value = “hello!”;

Whichever method you use depends on your mood, your comfort level, and what information you have available to you at the time. And that’s it for today’s lesson. Let us review.

Lesson 4 Review

Today we covered many topics, vast and deep. We started with a brief discussion of client-side image maps, and we learned how to add JavaScript just like you to links. After that we learned how to preload images through a clever use of the Image object. Creating a new Image object and assigning an image to that object’s src automatically loads the image into cache. Because Image and other objects are so great, we took a long sally into the world of objects and object-oriented programming. This journey ended in the creation of a virtual pet, complete with its own properties and methods. Finally, we learned how to use eval() and associative arrays to manipulate objects that might otherwise be hard to access. Object-oriented programming is a relatively new and funky idea. By understanding how it works, we take a huge step toward becoming a programmer for the next millennium. And as we come to the end of today’s lesson, we are also done with JavaScript. We’ve covered the length and breadth of the language. Sure, a few scattered things remain — some basic debugging and optimization techniques, for instance, which will be very useful now that you’re able to write big, complex programs. But, essentially speaking, you are now a JavaScript Master! Before you tackle the final Lesson, go ahead and test your mastery on today’s homework assignment.