CSSE 290 Web Programming

Lecture 16: Unobtrusive JavaScript, Timer Events, Prototype Brief Intro

Reading: 10.1

Attribution:Except where otherwise noted, the contents of this document are Copyright 2012 Marty Stepp, Jessica Miller, and Victoria Kirst. All rights reserved. Any redistribution, reproduction, transmission, or storage of part or all of the contents in any form is prohibited without the author's expressed written permission.

Otherwise noted: Claude Anderson was given permission to modify the slides for CSSE 290 at Rose-Hulman by author Jessica Miller. The authors' original slides, based on Web Programming Step by Step, can be seen at http://webstepbook.com.
Some of the examples in some days' slides are from David Fisher at Rose-Hulman, who was kind enough to allow me to use them. My intention is to mark these examples with [DSF].

Valid HTML Valid CSS!

JSLint

JSLint

Unobtrusive JavaScript

Obtrusive Event Handlers (bad)

<button onclick="okayClick();">OK</button>
// called when OK button is clicked
function okayClick() {
	alert("booyah");
}

Attaching Event Handler in JavaScript

// where element is a DOM element object
element.onevent = function;
<button id="ok">OK</button>
var okButton = document.getElementById("ok");
okButton.onclick = okayClick;

When does my code run?

<html>
	<head>
		<script src="myfile.js" type="text/javascript"></script>
	</head>
	<body> ... </body> </html>
// global code
var x = 3;
function f(n) { return n + 1; }
function g(n) { return n - 1; }
x = f(x);

A Failed Attempt at Being Unobtrusive

<html>
	<head>
		<script src="myfile.js" type="text/javascript"></script>
	</head>
	<body>
		<div><button id="ok">OK</button></div>
// global code
document.getElementById("ok").onclick = okayClick;   // error: null

The window.onload event

// this will run once the page has finished loading
function functionName() {
	element.event = functionName;
	element.event = functionName;
	...
}

window.onload = functionName;   // global code

An unobtrusive event handler

<button id="ok">OK</button>   <!-- look Ma, no JavaScript! -->
// called when page loads; sets up event handlers
function pageLoad() {
	document.getElementById("ok").onclick = okayClick;
}

function okayClick() {
	alert("booyah");
}

window.onload = pageLoad;  // global code

Common unobtrusive JS errors

Popup boxes

alert("message");     // message
confirm("message");   // returns true or false
prompt("message");    // returns user input string
alert confirm prompt

Events and Timers

JavaScript events

abort blur change click dblclick error focus
keydown keypress keyup load mousedown mousemove mouseout
mouseover mouseup reset resize select submit unload

Timer events

timer
method description
setTimeout(functiondelayMS); arranges to call given function after given delay in ms
setInterval(functiondelayMS); arranges to call function repeatedly every delayMS ms
clearTimeout(timerID);
clearInterval(timerID);
stops the given timer so it will not call its function

setTimeout example

<button onclick="delayMsg();">Click me!</button>
<span id="output"></span>
function delayMsg() {
	setTimeout(booyah, 5000);
	document.getElementById("output").innerHTML = "Wait for it...";
}

function booyah() {   // called when the timer goes off
	document.getElementById("output").innerHTML = "BOOYAH!";
}

setInterval example

var timer = null;  // stores ID of interval timer

function delayMsg2() {
	if (timer == null) {
		timer = setInterval(rudy, 1000);
	} else {
		clearInterval(timer);
		timer = null;
	}
}

function rudy() {   // called each time the timer goes off
	document.getElementById("output").innerHTML += " Rudy!";
}

Passing parameters to timers

function delayedMultiply() {
	// 6 and 7 are passed to multiply when timer goes off
	setTimeout(multiply, 2000, 6, 7);
}
function multiply(a, b) {
	alert(a * b);
}

Common timer errors

10.1: The Prototype Library

Problems with JavaScript

JavaScript is a powerful language, but it has many flaws:

Prototype framework

<script src="../scripts/prototype.js" type="text/javascript"></script>

The $ function

$("id")

Recap: The DOM Tree

DOM tree

Prototype's DOM Element Methods

absolutize addClassName classNames cleanWhitespace clonePosition
cumulativeOffset cumulativeScrollOffset empty extend firstDescendant
getDimensions getHeight getOffsetParent getStyle getWidth
hasClassName hide identify insert inspect
makeClipping makePositioned match positionedOffset readAttribute
recursivelyCollect relativize remove removeClassName replace
scrollTo select setOpacity setStyle show
toggle toggleClassName undoClipping undoPositioned update
viewportOffset visible wrap writeAttribute

Prototype's DOM tree traversal methods

method(s) description
ancestors, up elements above this one
childElements, descendants, down elements below this one (not text nodes)
siblings, next, nextSiblings,
previous, previousSiblings, adjacent
elements with same parent
as this one (not text nodes)
DOM element
// alter siblings of "main" that do not contain "Sun"
var sibs = $("main").siblings();
for (var i = 0; i < sibs.length; i++) {
	if (sibs[i].innerHTML.indexOf("Sun") < 0) {
		sibs[i].innerHTML += " Sunshine";
	}
}

Prototype's Methods for Selecting Elements

Prototype adds methods to document (and all DOM elements) for selecting groups of elements:

getElementsByClassName array of elements that use given class attribute
select array of descendants that match given CSS selector, such as "div#sidebar ul.news > li" (identical to querySelectorAll on the element)
$$ Equivalent to document.querySelectorAll
var gameButtons = $("game").select("button.control");
for (var i = 0; i < gameButtons.length; i++) {
	gameButtons[i].style.color = "yellow";
}

The $$ function

var arrayName = $$("CSS selector");
// hide all "announcement" paragraphs in the "news" section
var paragraphs = $$("div#news p.announcement");
for (var i = 0; i < paragraphs.length; i++) {
	paragraphs[i].hide();
}

Common $$ Issues

Recap: JavaScript - Remove a Node from the Page

function slideClick() {
	var bullets = document.getElementsByTagName("li");
	for (var i = 0; i < bullets.length; i++) {
		if (bullets[i].innerHTML.indexOf("children") >= 0) {
			bullets[i].parentNode.removeChild(bullets[i]);
		}
	}
}

Prototype: Remove a Node from the Page

function slideClick() {
	var bullets = $$("li");
	for (var i = 0; i < bullets.length; i++) {
		if (bullets[i].innerHTML.indexOf("children") >= 0) {
			bullets[i].remove();
		}
	}
}

in-class exercise (today and next week)

Pair-program this with your partner (do it on one computer)

Click a word, and all occurrences of that word get highlighted

highlighted words

I have provided a PHP file that delives the text. Read and discuss it together, but you should not have to change it.

DOM versus innerHTML hacking

Why not just code the previous example this way?

function slideClick() {
	$("thisslide").innerHTML += "<p>A paragraph!</p>";
}
  • Imagine that the new node is more complex:
    • ugly: bad style on many levels (e.g. JS code embedded within HTML)
    • error-prone: must carefully distinguish " and '
    • can only add at beginning or end, not in middle of child list
function slideClick() {
	$("main").innerHTML += "<p style='color: red; " +
			"margin-left: 50px;' " +
			"onclick='myOnClick();'>" +
			"A paragraph!</p>";
}

Benefits of DOM over innerHTML

function slideClick() {
	var p = document.createElement("p");
	p.className = "special";
	p.onclick = myOnClick;
	p.innerHTML = "A paragraph!";
	$("thisslide").appendChild(p);
}
.special {
	color: red;
	margin-left: 50px;
}

Problems with reading/changing styles

<button id="clickme">Click Me</button>
window.onload = function() {
	$("clickme").onclick = biggerFont;
};
function biggerFont() {
	var size = parseInt($("clickme").style.fontSize);
	size += 4;
	$("clickMe").style.fontSize = size + "pt";
}

Accessing styles in Prototype

function biggerFont() {
	// make the text bigger
	var size = parseInt($("clickme").getStyle("font-size"));
	$("clickme").style.fontSize = (size + 4) + "pt";
}

Common bug: incorrect usage of existing styles

$("main").style.top = $("main").getStyle("top") + 100 + "px";            // bad!

JavaScript: Getting/setting CSS classes

function highlightField() {
	// turn text yellow and make it bigger
	if (!$("text").className) {
		$("text").className = "highlight";
	} else if ($("text").className.indexOf("invalid") < 0) {
		$("text").className += " highlight";
	}
}

Getting/setting CSS classes in Prototype

function highlightField() {
	// turn text yellow and make it bigger
	if (!$("text").hasClassName("invalid")) {
		$("text").addClassName("highlight");
	}
}