This section is about using JavaScript's Document Object Model (DOM).

## 1. Bouncing Ball

Create a page which contains an animated bouncing ball. You can view an example of this page here. You are given `ball.html` and `ball.css`, and you write `ball.js`.

`ball.html` contains the following HTML:

``````<!DOCTYPE html>
<html>
<title>Bouncing Ball</title>
type="text/javascript"></script>
<script src="ball.js" type="text/javascript"></script>

<body>
<div>
<img src="redball.png" alt="red ball" id="ball" />
</div>
</body>
</html>``````

`ball.css` contains the following styles:

``````#ball {
position: fixed;
top: 0px;
}``````

This problem involves using the `setInterval` JavaScript function to update the ball's position every `20` milliseconds. You can position the ball using the CSS properties `top` and `left`. The ball has `position: fixed`, so the ball will be positioned from the top left corner of the page.

You will need to store two pieces of information about the ball: it's current y position and its current y velocity. Every 20 milliseconds, the ball's position should be updated by adding the current velocity into it:

```    ballY += ballVelocity;
```

Every 20 milliseconds, the ball's velocity should also be updated by a certain value representing gravity:

```    ballVelocity += gravity;
```

You can choose a value for gravity that feels right. Finally, to make the ball bounce, you must test to see if the ball's Y position is greater than the height of the window, and then flip the sign of the ball's velocity. You can access the width and height of the window by using `window.innerWidth` and `window.innerHeight`.

### Solution

#### ball.js

```var ballY = 0;
var ballVelocity = 0;

\$("ball").style.top = ballY + "px";
\$("ball").style.left = (window.innerWidth / 2) + "px";
setInterval(update, 20);
};

function update() {
\$("ball").style.top = ballY + "px";
ballY += ballVelocity;
ballVelocity += 1;

if (ballY > window.innerHeight) {
ballVelocity *= -.9;
}
}```

Given the following HTML file, write the JavaScript code cheerleader.js to echo typed characters to the screen as in this working example.

``````<!DOCTYPE html>
<html>
type="text/javascript"></script>

<body>

<ul id="cheers"></ul>

<p>Type a letter to cheer!</p>
</body>
</html>
``````

Inject the pressed keys as `li` elements inside `#cheers`. Each character should be in upper case, followed by an exclamation point. The lecture slide on key events will probably be useful to help you determine which key was pressed and convert it from a code to a character.

After you have made the pressed keys appear, modify your code so that each cheer removes itself from the page after two seconds (i.e., 2000 milliseconds).

### Solution

```window.onload = function() {
document.onkeypress = cheer;
};

function cheer(e) {
var cheer = document.createElement("li");
cheer.innerHTML = String.fromCharCode(e.charCode).toUpperCase() + "!";
\$("cheers").appendChild(cheer);
setTimeout(removeCheer, 2000);
}

function removeCheer() {
var letters = \$\$("#cheers li");
if (letters.length > 0) {
letters[0].remove();
}
}
```

## 3. Turtles All the Way Down

A well-known scientist (some say it was Bertrand Russell) once gave a public lecture on astronomy. He described how the earth orbits around the sun and how the sun, in turn, orbits around the center of a vast collection of stars called our galaxy. At the end of the lecture, a little old lady at the back of the room got up and said: What you have told us is rubbish. The world is really a flat plate supported on the back of a giant tortoise. The scientist gave a superior smile before replying, What is the tortoise standing on? You're very clever, young man, very clever, said the old lady. But it's turtles all the way down!

Stephen Hawking, A Brief History of Time

We will implement a version of the "turtles all the way down" model (commonly but falsely attributed to Hindu mythology) in which the earth rests on the back of an elephant, which in turn rests on the back of infinite tortoises.

Given the following HTML file (containing the earth, elephant, and a single tortoise), write the necessary JavaScript code turtles.js to give the page infinitely-scrolling turtles as in this working example.

#### turtles.html:

``````<!DOCTYPE html>
<html>
<title>turtles all the way down</title>
type="text/javascript"></script>
<script type="text/javascript" src="turtles.js"></script>

<body>
<div id="earth"></div>
<div id="elephant"></div>
<div class="turtle"></div>
</body>
</html>
``````

To solve this problem you will need to add a turtle to the bottom of the page every time the user scrolls to the bottom. Adding a single tortoise to the bottom of the page will result in having to scroll the new turtle into view — which in turn will cause a new tortoise to be created, and so on. In this way the page can scroll indefinitely.

You can attach an `onscroll` event handler to the `document`, so that every time the page is scrolled that event handler will be executed. In order to determine when the user has scrolled to the bottom of the page there are three useful page measurements we can use:

`window.scrollY`
The portion of the document that has scrolled off-screen above the currently-visible portion.
`window.innerHeight`
The height of the visible area of the document on-screen.
`document.body.getHeight()`
The height of the `body` tag (i.e., the height of the entire page, on- and off-screen). This is a Prototype function.

When the user has scrolled to the very bottom of the page, the sum of scrollY (the off-screen portion above) and innerHeight (the on-screen portion) will be equal to the height of the body. That's when you'll want to add another turtle — which will grow the body and result in more scrolling.

To add a turtle, simply append to `document.body` a new `div` with the class of `turtle`.

Once you've made the page grow indefinitely, you can fix one small special case: what if the height of the window is greater than the initial height of the animals? (You can test this by zooming out a lot and then refreshing.) In this case the initial body height will be less than the window height, and the off-screen portion will be zero. How would you fix this?

problem adapted from original implementation by Kevin Wallace

### Solution

#### turtles.js

```window.onload = function() {
document.onscroll = turtles;
turtles(); // in case window height is initially taller than animals
};

function turtles() {
while (window.scrollY + window.innerHeight >= document.body.getHeight()) {
var div = document.createElement("div");
div.className = "turtle";
document.body.appendChild(div);
}
}
```

## 4. Randy the Raptor (long)

(Click the image below to run the sample solution.) (solution JS code `attack.js`)

A raptor is on the loose. Rawr! He wants to stomp the townspeople. Write JavaScript code to allow the raptor to eat them. The HTML and CSS are already completely written; start from this skeleton of attack.html.

#### attack.html:

``````<!DOCTYPE html>
<html>
<!-- CSE 190M, Lab 6: Raptor (Rarrrr) -->

<title>Raptor</title>

<!-- you write this -->
<script src="attack.js" type="text/javascript"></script>

<body>
<h1 class="normal">RANDY THE RAPTOR IS HUNGRY</h1>
<div id="city">
<img src="http://www.cs.washington.edu/education/courses/cse190m/09sp/labs/section6-raptor/solution/raptor.png" id="raptor" alt="Raptor" />
<div id="people">
<!-- 5 initial people to play with -->
<div class="person"></div>
<div class="person"></div>
<div class="person"></div>
<div class="person"></div>
<div class="person"></div>
</div>
</div>

<fieldset>
<legend>Attack!</legend>

<p>
<button id="kill">Kill!</button>
<label><input id="boys" type="radio" name="gender" /> Boys</label>
<label><input id="girls" type="radio" name="gender" checked="checked" /> Girls</label>
</p>

<p>
<button id="cleanup">Clean up!</button>
<button id="stomp">Stomp!</button>
<button id="enrage">Enrage!</button>
<button id="patrol">Patrol!</button>
</p>
</fieldset>

<fieldset id="legend">
<legend>Legend</legend>
<div><div class="person boy"></div> boy</div>
<div><div class="person girl"></div> girl</div>
<div><div class="person splat"></div> raptor splat!</div>
</fieldset>
</body>
</html>
``````

Here are the behaviors to add:

• Make it so that when the page first appears, 5 boys are visible in the town. There are already 5 persons in the HTML, but they have no gender. These are stored in the `div` with `id` of `people` as `div`s with the `class` of `person`. Assign them the additional class `boy` when the page loads (while retaining the class `person`).
```<div id="people">
<div class="person"></div>  <!-- give these 5 divs the class 'boy' -->
<div class="person"></div>
<div class="person"></div>
<div class="person"></div>
<div class="person"></div>
</div>
```
• HINT 1 (hover)
• HINT 2 (hover)
• HINT 3: Add/remove CSS classes from an element with Prototype's `addClassName` function.
• Add! Adds 5 more people of the currently selected gender to the page. A person is a `div` with the classes of `person` and either `boy` or `girl`.
```<button id="add">Add!</button>
```
• HINT 1
• HINT 2
• Kill! Randomly "kills" 1/5 of the people of the currently selected gender. Kill them by giving them a class of `splat` (in addition to their existing `person` class, but in place of their gender class such as `boy` or `girl`).
```<button id="kill">Kill!</button>
```
• Boys / Girls: Selects which gender to add or kill.
```<label><input id="boys" type="radio" name="gender" /> Boys</label>
<label><input id="girls" type="radio" name="gender" checked="checked" /> Girls</label>
```
• Clean Up! Removes any dead splatted people from the page (any `div`s with class `splat`).
```<button id="cleanup">Clean up!</button>
```
• HINT: You can remove an element from the page by calling its DOM object's `remove` method.
• Stomp! Makes the raptor move up or down by 75px and also kills 1/5 of both genders. The raptor is an `img` tag with an `id` of `raptor`.
```<button id="stomp">Stomp!</button>
```
• HINT 1: Move the raptor by setting his `top` style attribute to be either `10px` or `85px`.
• HINT 2: You can find out an object's existing style properties by calling Prototype's `getStyle` method.
• Enrage! Applies the CSS class of `enrage` to the raptor and the page's top `h1` heading. In addition, the raptor should be made to be 50px wider than his current width. Clicking the button again removes the class from both elements and returns the width to its previous value. The `h1` has an existing CSS class that should not be removed. You are guaranteed that there is exactly one `h1` element on the page.
```<button id="enrage">Enrage!</button>
```
• Patrol! (advanced) Makes the raptor animate. He should move right by 4px every 20ms until his `left` position style is at least `300px`, he should change directions and start patrolling to the left until his `left` position is `10px` or less, at which point he stops patrolling.
```<button id="patrol">Patrol!</button>
```

problem by Stefanie Hatcher

### Solution

#### attack.js

```window.onload = function() {
\$("kill").onclick = kill;
\$("stomp").onclick = stomp;
\$("patrol").onclick = patrol;
\$("enrage").onclick = enrageRaptor;

// Make it a city of N Men...Initially
var people = \$\$("#people .person");
for (var i = 0; i < people.length; i++) {
}
};

// Helper function to get which gender is currently selected.
function getGender() {
if (\$("boys").checked) {
return "boy";
} else {
return "girl";
}
}

function populate() {
var gender = getGender();
for (var i = 0; i < 5; i++) {
var newPerson = document.createElement("div");
\$("people").appendChild(newPerson);
}
}

// Kill! button event handler
// tells raptor to randomly kill 1/5 of selected gender
function kill() {
var gender = getGender();
splat(gender);
}

// Get all guys or girls and splat one fifth of them
// Random targets, could over lap splats,
// allowing for more randomness double death or not!
function splat(gender) {
var peeps = \$\$("#people ." + gender);
for (var i = 0; i < peeps.length / 5; i++) {
var randomIndex = Math.floor(Math.random() * peeps.length);
peeps[randomIndex].removeClassName(gender);
peeps[randomIndex].addClassName("splat");   // so future kills won't choose splat victims
}
}

// Clean up the dead! button event handler
for (var i = 0; i < dead.length; i++) {
}
}

// Stomp! button event handler
function stomp() {
var pxtop = parseInt(\$("raptor").getStyle("top"));
\$("raptor").style.top = ((pxtop + 75) % 150) + "px";
splat("boy");
splat("girl");
}

// Enrage! button event handler
function enrageRaptor() {
// If enraged -- go back to normal, else get ENRAGED
if (\$("raptor").hasClassName("enrage")) {
\$("raptor").removeClassName("enrage");
\$\$("h1")[0].removeClassName("enrage");
\$("raptor").style.width = parseInt(\$("raptor").getStyle("width")) - 50 + "px";
} else {
\$("raptor").style.width = parseInt(\$("raptor").getStyle("width")) + 50 + "px";
}
}

// Patrol! event handler code (advanced)
var timer;

function patrol() {
clearInterval(timer);
timer = setInterval(patrolRight, 20);
}

function patrolRight() {
var pxleft = parseInt(\$("raptor").getStyle("left"));
pxleft += 4;
\$("raptor").style.left = pxleft + "px";
if (pxleft >= 300) {
clearInterval(timer);
timer = setInterval(patrolLeft, 20);
}
}

function patrolLeft() {
var pxleft = parseInt(\$("raptor").getStyle("left"));
pxleft -= 4;
\$("raptor").style.left = pxleft + "px";
if (pxleft <= 10) {
clearInterval(timer);
\$("raptor").style.top = "5px";  // Reset the Raptor
\$("raptor").style.left = "10px";
}
}
```

# Solutions

## 1. Bouncing Ball

### ball.js

```var ballY = 0;
var ballVelocity = 0;

\$("ball").style.top = ballY + "px";
\$("ball").style.left = (window.innerWidth / 2) + "px";
setInterval(update, 20);
};

function update() {
\$("ball").style.top = ballY + "px";
ballY += ballVelocity;
ballVelocity += 1;
if (ballY > window.innerHeight) {
ballVelocity *= -.9;
}
}
```

```window.onload = function() {
document.onkeypress = cheer;
};

function cheer(e) {
var cheer = document.createElement("li");
cheer.innerHTML = String.fromCharCode(e.charCode).toUpperCase() + "!";
\$("cheers").appendChild(cheer);
setTimeout(removeCheer, 2000);
}

function removeCheer() {
var letters = \$\$("#cheers li");
if (letters.length > 0) {
letters[0].remove();
}
}
```

## 3. Turtles All the Way Down

### turtles.js

```window.onload = function() {
document.onscroll = turtles;
turtles(); // in case window height is initially taller than animals
};

function turtles() {
while (window.scrollY + window.innerHeight >= document.body.getHeight()) {
var div = document.createElement("div");
div.className = "turtle";
document.body.appendChild(div);
}
}
```

## 4. Randy the Raptor

### attack.js

```window.onload = function() {
\$("kill").onclick = kill;
\$("stomp").onclick = stomp;
\$("patrol").onclick = patrol;
\$("enrage").onclick = enrageRaptor;

// Make it a city of N Men...Initially
var people = \$\$("#people .person");
for (var i = 0; i < people.length; i++) {
}
};

// Helper function to get which gender is currently selected.
function getGender() {
if (\$("boys").checked) {
return "boy";
} else {
return "girl";
}
}

function populate() {
var gender = getGender();
for (var i = 0; i < 5; i++) {
var newPerson = document.createElement("div");
\$("people").appendChild(newPerson);
}
}

// Kill! button event handler
// tells raptor to randomly kill 1/5 of selected gender
function kill() {
var gender = getGender();
splat(gender);
}

// Get all guys or girls and splat one fifth of them
// Random targets, could over lap splats,
// allowing for more randomness double death or not!
function splat(gender) {
var peeps = \$\$("#people ." + gender);
for (var i = 0; i < peeps.length / 5; i++) {
var randomIndex = Math.floor(Math.random() * peeps.length);
peeps[randomIndex].removeClassName(gender);
peeps[randomIndex].addClassName("splat");   // so future kills won't choose splat victims
}
}

// Clean up the dead! button event handler
for (var i = 0; i < dead.length; i++) {
}
}

// Stomp! button event handler
function stomp() {
var pxtop = parseInt(\$("raptor").getStyle("top"));
\$("raptor").style.top = ((pxtop + 75) % 150) + "px";
splat("boy");
splat("girl");
}

// Enrage! button event handler
function enrageRaptor() {
// If enraged -- go back to normal, else get ENRAGED
if (\$("raptor").hasClassName("enrage")) {
\$("raptor").removeClassName("enrage");
\$\$("h1")[0].removeClassName("enrage");
\$("raptor").style.width = parseInt(\$("raptor").getStyle("width")) - 50 + "px";
} else {
\$("raptor").style.width = parseInt(\$("raptor").getStyle("width")) + 50 + "px";
}
}

// Patrol! event handler code (advanced)
var timer;

function patrol() {
clearInterval(timer);
timer = setInterval(patrolRight, 20);
}

function patrolRight() {
var pxleft = parseInt(\$("raptor").getStyle("left"));
pxleft += 4;
\$("raptor").style.left = pxleft + "px";
if (pxleft >= 300) {
clearInterval(timer);
timer = setInterval(patrolLeft, 20);
}
}

function patrolLeft() {
var pxleft = parseInt(\$("raptor").getStyle("left"));
pxleft -= 4;
\$("raptor").style.left = pxleft + "px";
if (pxleft <= 10) {
clearInterval(timer);
\$("raptor").style.top = "5px";  // Reset the Raptor
\$("raptor").style.left = "10px";
}
}
```