Web Programming Step by Step, 2nd Edition

Chapter 10: Prototype and Scriptaculous

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.

Valid XHTML 1.1 Valid CSS!

10.1: Prototype

Problems with JavaScript

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

Prototype framework

<script src="http://ajax.googleapis.com/ajax/libs/prototype/1.7.0.0/prototype.js" 
 type="text/javascript"></script>

The $ function

$("id")

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";
	}
}

Selecting groups of DOM objects

name description
getElementsByTagName returns array of descendents with the given tag, such as "div"
getElementsByName returns array of descendents with the given name attribute (mostly useful for accessing form controls)

Prototype's methods for selecting elements

Prototype adds methods to the document object (and all DOM element objects) 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"
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

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() {
	// turn text yellow and make it bigger
	var size = parseInt($("clickme").getStyle("font-size"));
	$("clickme").style.fontSize = (size + 4) + "pt";
}

Common bug: incorrect usage of existing styles

this.style.top = this.getStyle("top") + 100 + "px";            // bad!
this.style.top = parseInt(this.getStyle("top")) + 100 + "px";  // correct

Setting CSS classes in Prototype

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

Prototype form shortcuts

$F("formID")["name"]
$F("controlID")

Stopping an event

<form id="exampleform" action="http://foo.com/foo.php">...</form>
window.onload = function() {
	$("exampleform").observe("submit", checkData);
};

function checkData(event) {
	if ($F("city") == "" || $F("state").length != 2) {
		alert("Error, invalid city/state.");  // show error message 
		event.stop();
		return false;
	}
}

Classes and prototypes

Creating a class

className = Class.create({
	// constructor
	initialize : function(parameters) {
		this.fieldName = value;
		...
	},

	methodName : function(parameters) {	
		statements;
	},
	...
});

Class.create example

Point = Class.create({
	// Constructs a new Point object at the given initial coordinates.
	initialize: function(initialX, initialY) {
		this.x = initialX;
		this.y = initialY;
	},

	// Computes the distance between this Point and the given Point p.	
	distance: function(p) {
		var dx = this.x - p.x;
		var dy = this.y - p.y;
		return Math.sqrt(dx * dx + dy * dy);
	},

	// Returns a text representation of this Point object.	
	toString: function() {
		return "(" + this.x + ", " + this.y + ")";
	}
});

Inheritance

className = Class.create(superclass, {
	...
});
// Points that use "Manhattan" (non-diagonal) distances.
ManhattanPoint = Class.create(Point, {
	// Computes the Manhattan distance between this Point and p.
	// Overrides the distance method from Point.
	distance: function(p) {
		var dx = Math.abs(this.x - p.x);
		var dy = Math.abs(this.y - p.y);
		return dx + dy;
	},
	
	// Computes this point's Manhattan Distance from the origin.
	distanceFromOrigin: function() {
		return this.x + this.y;
	}
});

Referring to superclass: $super

name: function($super, parameters) {
	statements;
}
ManhattanPoint3D = Class.create(ManhattanPoint, {
	initialize: function($super, initialX, initialY, initialZ) {
		$super(initialX, initialY);   // call Point constructor
		this.z = initialZ;
	},
	
	// Returns 3D "Manhattan Distance" from p.
	distance: function($super, p) {
		var dz = Math.abs(this.z - p.z);
		return $super(p) + dz;
	},
});

Practice problem: Fancy movies

movies page v2

10.2: Scriptaculous

Scriptaculous overview

Scriptaculous : a JavaScript library, built on top of Prototype, that adds:

Downloading and using Scriptaculous

<script src="http://ajax.googleapis.com/ajax/libs/prototype/1.7.0.0/prototype.js"
 type="text/javascript"></script>

<script src="http://ajax.googleapis.com/ajax/libs/scriptaculous/1.9.0/scriptaculous.js"
 type="text/javascript"></script>

Visual effects

(appearing)


(disappearing)


(Getting attention)

scriptaculous logo Click effects above

Adding effects to an element

element.effectName();   // for most effects

// some effects must be run the following way:
new Effect.name(element or id);
$("sidebar").shake();

var buttons = $$("results > button");
for (var i = 0; i < buttons.length; i++) {
	buttons[i].fade();
}

Effect options

element.effectName(
	{
		option: value,
		option: value,
		...
	}
);
$("my_element").pulsate({
	duration: 2.0, 
	pulses: 2
});

Effect events

$("my_element").fade({
	duration: 3.0, 
	afterFinish: displayMessage
});

function displayMessage(effect) {
	alert(effect.element + " is done fading now!");
}

Drag and drop

Scriptaculous provides several objects for supporting drag-and-drop functionality:

Draggable

new Draggable(element or id,
	{ options }
);

Draggable example

<div id="draggabledemo1">Draggable demo. Default options.</div>
<div id="draggabledemo2">Draggable demo.
	{snap: [40,40], revert: true}</div>
document.observe("dom:loaded", function() {
	new Draggable("draggabledemo1");
	new Draggable("draggabledemo2", {revert: true, snap: [40, 40]});
});
logo Draggable demo.
Default options.
Draggable demo.
{snap:[60, 60], revert:true}

Draggables

Droppables

Droppables.add(element or id,
	{ options }
);

Drag/drop shopping demo

<img id="product1" src="images/shirt.png" alt="shirt" />
<img id="product2" src="images/cup.png" alt="cup" />
<div id="droptarget"></div>
document.observe("dom:loaded", function() {
	new Draggable("product1");
	new Draggable("product2");
	Droppables.add("droptarget", {onDrop: productDrop});
});

function productDrop(drag, drop, event) {
	alert("You dropped " + drag.id);
}
shirt cup

Sortable

Sortable.create(element or id of list,
	{ options }
);

Sortable demo

<ol id="simpsons">
	<li id="simpsons_0">Homer</li>
	<li id="simpsons_1">Marge</li>
	<li id="simpsons_2">Bart</li>
	<li id="simpsons_3">Lisa</li>
	<li id="simpsons_4">Maggie</li>
</ol>
document.observe("dom:loaded", function() {
	Sortable.create("simpsons");
});
  1. Homer
  2. Marge
  3. Bart
  4. Lisa
  5. Maggie

Sortable list events

event description
onChange when any list item hovers over a new position while dragging
onUpdate when a list item is dropped into a new position (more useful)
document.observe("dom:loaded", function() {
	Sortable.create("simpsons", {
			onUpdate: listUpdate
	});
});

Sortable list events example

document.observe("dom:loaded", function() {
	Sortable.create("simpsons", {
			onUpdate: listUpdate
	});
});

function listUpdate(list) {
	// can do anything I want here; effects, an Ajax request, etc.
	list.shake();
}
  1. Homer
  2. Marge
  3. Bart
  4. Lisa
  5. Maggie

Subtleties of Sortable events

Auto-completing text fields

autocomplete

Scriptaculous offers ways to make a text box that auto-completes based on prefix strings:

Using Autocompleter.Local

new Autocompleter.Local(
	element or id of text box, 
	element or id of div to show completions,
	array of choices, 
	{ options }
);

Autocompleter.Local demo

<input id="bands70s" size="40" type="text" />
<div id="bandlistarea"></div>
document.observe("dom:loaded", function() {
	new Autocompleter.Local(
		"bands70s",
		"bandlistarea",
		["ABBA", "AC/DC", "Aerosmith", "America", "Bay City Rollers", ...], 
		{}
	);
});

Autocompleter styling

<input id="bands70s" size="40" type="text" />
<div id="bandlistarea"></div>
#bandlistarea {
	border: 2px solid gray;
}
/* 'selected' class is given to the autocomplete item currently chosen */
#bandlistarea .selected {
	background-color: pink;
}

Using Ajax.Autocompleter

new Ajax.Autocompleter(
	element or id of text box, 
	element or id of div to show completions,
	url, 
	{ options }
);

Ajax.InPlaceEditor

new Ajax.InPlaceEditor(element or id,
	url,
	{ options }
);

Ajax.InPlaceCollectionEditor

new Ajax.InPlaceCollectionEditor(element or id,
	url,
	{
		collection: array of choices,
		options
	}
);

Playing sounds (API)

method description
Sound.play("url"); plays a sound/music file
Sound.disable(); stops future sounds from playing (doesn't mute any sound in progress)
Sound.enable(); re-enables sounds to be playable after a call to Sound.disable()
Sound.play("music/java_rap.mp3");
Sound.play("music/wazzaaaaaap.wav");

Other neat features