This section is about using Scriptaculous for visual effects, and creating PHP web services to supply data to Ajax requests.
Write a PHP web service, factors.php, which will return (in text/plain
) a comma-separated list of the prime factors of a provided number. For example, the request:
factors.php?n=264
Would return:
2 * 2 * 2 * 3 * 11
You can make queries to the following working solution to test:
One simple algorithm for prime factorization of num
is as follows:
for fact from 2 to num: while num % fact = 0: num = num / fact add fact to the list of prime factors of the original num
Make your web service return a 400 Bad Request
HTTP error code with an instructive error message if the parameter n
is not provided.
problem idea by Eli White; revised by Morgan Doocy
<?php
header('Content-type: text/plain');
if (!isset($_GET['n'])) {
header('HTTP/1.1 400 Bad Request');
print "Please provide a parameter n.";
} else {
$factors = factorize($_GET["n"]);
print implode(" * ", $factors);
}
function factorize($num) {
$factors = array();
for ($factor = 2; $factor <= $num; $factor++) {
while ($num % $factor == 0) {
$num /= $factor;
array_push($factors, $factor);
}
}
return $factors;
}
?>
Modify your PHP web service, factors.php to return the prime factors of a
provided number in JSON format. For example, the request factors.php?n=264
would return:
{"number": 264, "factors": [2, 2, 2, 3, 11]}
You can try out the service here.
problem idea by Eli White; revised by Roy McElmurry
<?php
header('Content-type: application/json');
if (!isset($_GET['n'])) {
header('HTTP/1.1 400 Bad Request');
print "Please provide a parameter n.";
} else {
$n = $_GET["n"];
$ret = array(
"number" => $n,
"factors" => factorize($n),
);
print json_encode($ret);
}
function factorize($num) {
$factors = array();
for ($factor = 2; $factor <= $num; $factor++) {
while ($num % $factor == 0) {
$num /= $factor;
array_push($factors, $factor);
}
}
return $factors;
}
?>
You are given the following files for an address book web application:
The javascript file sends requests to a web service that reads and saves address data. You can view the working application here. Write the PHP file, addressbook.php
, that provides the following behavior:
If a GET
request is sent to addressbook.php
, then print out, in plain text, a comma separated list of the names of people in the address book currently, like so:
Morgan,Alex,Marty,Tyler
If the GET
request has a name
parameter set, then print out the address associated with that name. For example, if name=Alex
:
1234 65th Ave
If a POST
request is sent to addressbook.php
, then you must add a name/address pair to the address book's data. You may assume that the user is passing a name
and address
parameter. You will need to save the data in the address book in some way. You may choose the specific method. It will most likely involve saving each name/address pair as a line in a plain text file.
Extra: Edit addressbook.js
to add Scriptaculous effects to the page.
problem by Alex Miller
<?php
header("Content-type: text/plain");
$file_text = file_get_contents("addresses.txt");
if ($_SERVER['REQUEST_METHOD'] == "GET") {
$lines = explode("\n", $file_text);
if (isset($_GET["name"])) {
$name = $_REQUEST["name"];
foreach ($lines as $line) {
$info = explode(",", $line);
if ($info[0] == $name) {
print $info[1];
}
}
} else {
$names = array();
foreach ($lines as $line) {
$info = explode(",", $line);
array_push($names, $info[0]);
}
print(implode($names, ","));
}
} else {
$name = $_REQUEST["name"];
$address = $_REQUEST["address"];
$file_text = file_get_contents("addresses.txt");
$file_text .= "\n" . $name . "," . $address;
file_put_contents("addresses.txt", $file_text);
}
?>
Modify your addressbook.php
web service to return data in JSON format.
You should use these HTML and
JS files for this version.
For example, if a GET request is made without any parameters, you should return
{"names": ["name1", "name2", ...]}
And if a GET request is made with the name
parameter, you should return
{"name": "some_name", "address": "some_address"}
You can try out the service here.
problem by Alex Miller, revised by Roy McElmurry
<?php
header("Content-type: application/json");
$file_text = file_get_contents("addresses.txt");
if ($_SERVER['REQUEST_METHOD'] == "GET") {
$lines = explode("\n", $file_text);
if (isset($_GET["name"])) {
$name = $_REQUEST["name"];
$ret = array(
"name" => $name,
"address" => "",
);
foreach ($lines as $line) {
$info = explode(",", $line);
if ($info[0] == $name) {
$ret["address"] = $info[1];
}
}
print json_encode($ret);
} else {
$names = array();
foreach ($lines as $line) {
$info = explode(",", $line);
array_push($names, $info[0]);
}
$ret = array(
"names" => $names,
);
print json_encode($ret);
}
} else {
$name = $_REQUEST["name"];
$address = $_REQUEST["address"];
$file_text = file_get_contents("addresses.txt");
$file_text .= "\n" . $name . "," . $address;
file_put_contents("addresses.txt", $file_text);
}
?>
Write the necessary JavaScript / Scriptaculous code to complete the following page, which removes each icon with an Effect.Puff
when it is dragged and dropped onto the trash can as in this working solution. Start from these skeletons:
trashcan.html
(complete HTML/CSS code)trashcan.js
(skeleton of JS code)<!DOCTYPE html>
<html>
<head>
<title>Garbage Collector</title>
<link rel="stylesheet" type="text/css" href="http://webster.cs.washington.edu/cse190m/sections/8/trashcan/trashcan.css" />
<script src="http://ajax.googleapis.com/ajax/libs/prototype/1.6.1.0/prototype.js" type="text/javascript"></script>
<script src="http://ajax.googleapis.com/ajax/libs/scriptaculous/1.8.3/scriptaculous.js" type="text/javascript"></script>
<script src="trashcan.js" type="text/javascript"></script>
</head>
<body>
<h1>CSE 190 M Garbage Collector</h1>
<p>Drag the IE6 browser icons to the trash to destroy them.</p>
<div id="browsers"></div>
<div id="trashcan"></div>
</body>
</html>
document.observe('dom:loaded', function() {
createBrowsers();
// finish me!
});
function createBrowsers() {
// finish me!
}
function positionRandomly(elem) {
var x = parseInt(Math.random() * 472);
var y = parseInt(Math.random() * 172);
elem.style.left = x + "px";
elem.style.top = y + "px";
elem.style.position = "absolute";
}
You will first need to inject 20 IE6 icons (imgs of class .browser
, with a src
of http://webster.cs.washington.edu/cse190m/sections/9/trashcan/ie6.png) into the #browsers div. Use the provided positionRandomly
function to give each img that you inject a random location.
Then refer to the Scriptaculous documentation for Draggables, Droppables, and Core Effects to figure out how to configure your objects so that:
.browser
..browser
icon to any part of the page except the trash can results in the icon returning to its original position..browser
element over the trash icon will cause its class to change to .full
.Effect.Puff
when they are dropped onto the trash icon.problem by Morgan Doocy
document.observe('dom:loaded', function() {
createBrowsers();
Droppables.add('trashcan', {
accept: 'browser',
hoverclass: 'full',
onDrop: removeBrowser
});
});
function createBrowsers() {
for (var i = 0; i < 10; i++) {
var img = $(document.createElement('img'));
img.addClassName('browser');
img.src = 'http://webster.cs.washington.edu/cse190m/sections/9/trashcan/ie6.png';
positionRandomly(img);
new Draggable(img, {
revert: 'failure'
});
$('browsers').appendChild(img);
}
}
function positionRandomly(elem) {
var x = parseInt(Math.random() * 472);
var y = parseInt(Math.random() * 172);
elem.style.left = x + "px";
elem.style.top = y + "px";
elem.style.position = "absolute";
}
function removeBrowser(elem) {
new Effect.Puff(elem, {
duration: .5,
afterFinish: function() {
elem.remove();
}
});
}
Today we will write an animated photo gallery using Scriptaculous. Start with this skeleton:
gallery.html
(complete HTML/CSS code)gallery.js
(skeleton of JS code)<!DOCTYPE html>
<html>
<!--
CSE 190M, Spring 2012
Section 9 (Photo Gallery)
original code by TA Sylvia Tashev
-->
<head>
<meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
<title>Photo Gallery</title>
<link href="http://webster.cs.washington.edu/cse190m/sections/9/photogallery/gallery.css" type="text/css" rel="stylesheet" />
<script src="http://ajax.googleapis.com/ajax/libs/prototype/1.6.1.0/prototype.js" type="text/javascript"></script>
<script src="http://ajax.googleapis.com/ajax/libs/scriptaculous/1.8.3/scriptaculous.js" type="text/javascript"></script>
<script src="gallery.js" type="text/javascript"></script>
</head>
<body>
<h1>Picture-It Gallery</h1>
<div id="main">
<img id="mainimage" src="http://webster.cs.washington.edu/cse190m/sections/9/photogallery/images/picture1.jpg" alt="picture" />
</div>
<p>
<img id="left" src="http://webster.cs.washington.edu/cse190m/sections/9/photogallery/images/left.jpg" alt="Left" />
Double-click or drag a thumbnail into the viewing area or use the arrow keys to go in sequence.
<img id="right" src="http://webster.cs.washington.edu/cse190m/sections/9/photogallery/images/right.jpg" alt="Right" />
</p>
<!-- container for all the pictures -->
<div id="pictures">
<img src="http://webster.cs.washington.edu/cse190m/sections/9/photogallery/images/picture1_thumb.jpg" alt="photo" />
<img src="http://webster.cs.washington.edu/cse190m/sections/9/photogallery/images/picture2_thumb.jpg" alt="photo" />
<img src="http://webster.cs.washington.edu/cse190m/sections/9/photogallery/images/picture3_thumb.jpg" alt="photo" />
<img src="http://webster.cs.washington.edu/cse190m/sections/9/photogallery/images/picture4_thumb.jpg" alt="photo" />
<img src="http://webster.cs.washington.edu/cse190m/sections/9/photogallery/images/picture5_thumb.jpg" alt="photo" />
<img src="http://webster.cs.washington.edu/cse190m/sections/9/photogallery/images/picture6_thumb.jpg" alt="photo" />
<img src="http://webster.cs.washington.edu/cse190m/sections/9/photogallery/images/picture7_thumb.jpg" alt="photo" />
<img src="http://webster.cs.washington.edu/cse190m/sections/9/photogallery/images/picture8_thumb.jpg" alt="photo" />
<img src="http://webster.cs.washington.edu/cse190m/sections/9/photogallery/images/picture9_thumb.jpg" alt="photo" />
<img src="http://webster.cs.washington.edu/cse190m/sections/9/photogallery/images/picture10_thumb.jpg" alt="photo" />
<img src="http://webster.cs.washington.edu/cse190m/sections/9/photogallery/images/picture11_thumb.jpg" alt="photo" />
<img src="http://webster.cs.washington.edu/cse190m/sections/9/photogallery/images/picture12_thumb.jpg" alt="photo" />
<img src="http://webster.cs.washington.edu/cse190m/sections/9/photogallery/images/picture13_thumb.jpg" alt="photo" />
<img src="http://webster.cs.washington.edu/cse190m/sections/9/photogallery/images/picture14_thumb.jpg" alt="photo" />
<img src="http://webster.cs.washington.edu/cse190m/sections/9/photogallery/images/picture15_thumb.jpg" alt="photo" />
<img src="http://webster.cs.washington.edu/cse190m/sections/9/photogallery/images/picture16_thumb.jpg" alt="photo" />
<img src="http://webster.cs.washington.edu/cse190m/sections/9/photogallery/images/picture17_thumb.jpg" alt="photo" />
<img src="http://webster.cs.washington.edu/cse190m/sections/9/photogallery/images/picture18_thumb.jpg" alt="photo" />
<img src="http://webster.cs.washington.edu/cse190m/sections/9/photogallery/images/picture19_thumb.jpg" alt="photo" />
<img src="http://webster.cs.washington.edu/cse190m/sections/9/photogallery/images/picture20_thumb.jpg" alt="photo" />
<img src="http://webster.cs.washington.edu/cse190m/sections/9/photogallery/images/picture21_thumb.jpg" alt="photo" />
<img src="http://webster.cs.washington.edu/cse190m/sections/9/photogallery/images/picture22_thumb.jpg" alt="photo" />
<img src="http://webster.cs.washington.edu/cse190m/sections/9/photogallery/images/picture23_thumb.jpg" alt="photo" />
<img src="http://webster.cs.washington.edu/cse190m/sections/9/photogallery/images/picture24_thumb.jpg" alt="photo" />
<img src="http://webster.cs.washington.edu/cse190m/sections/9/photogallery/images/picture25_thumb.jpg" alt="photo" />
<img src="http://webster.cs.washington.edu/cse190m/sections/9/photogallery/images/picture26_thumb.jpg" alt="photo" />
<img src="http://webster.cs.washington.edu/cse190m/sections/9/photogallery/images/picture27_thumb.jpg" alt="photo" />
<img src="http://webster.cs.washington.edu/cse190m/sections/9/photogallery/images/picture28_thumb.jpg" alt="photo" />
<img src="http://webster.cs.washington.edu/cse190m/sections/9/photogallery/images/picture29_thumb.jpg" alt="photo" />
<img src="http://webster.cs.washington.edu/cse190m/sections/9/photogallery/images/picture30_thumb.jpg" alt="photo" />
</div>
<div id="w3c">
<a href="http://validator.w3.org/check/referer">
<img class="w3c" src="http://www.cs.washington.edu/education/courses/cse190m/09sp/images/w3c-xhtml.png" alt="Valid XHTML 1.1" /></a>
<a href="http://jigsaw.w3.org/css-validator/check/referer">
<img class="w3c" src="http://www.cs.washington.edu/education/courses/cse190m/09sp/images/w3c-css.png" alt="Valid CSS" /></a>
<a href="https://webster.cs.washington.edu/jslint?referer">
<img src="http://www.cs.washington.edu/education/courses/cse190m/09sp/images/jslint.png" alt="JavaScript Lint" /></a>
</div>
</body>
</html>
// CSE 190M, Spring 2009
// Section 8: Photo Gallery
// original code by TA Sylvia Tashev
document.observe("dom:loaded", function() {
var pictures = $$("#pictures img");
for (var i = 0; i < pictures.length; i++) {
pictures[i].observe("dblclick", pictureClick);
}
$("left").observe("click", goLeft);
$("right").observe("click", goRight);
// finish me
});
// called when any picture is clicked
function pictureClick() {
$("mainimage").src = this.src.replace("_thumb", "");
}
// called when Left arrow is clicked
function goLeft() {
// finish me
}
// called when Right arrow is clicked
function goRight() {
// finish me
}
Click the following image to run our sample solution: (solution JS code gallery.js
)
Right now, when you double-click an image, it shows in the large mainimage
area. But it's boring! Add Scriptaculous effects such as the following:
mainimage
image (the first one) should scale up to 200% of its normal size, and the mainimage
image should have a visual effect, such as shaking.
mainimage
image should shake as it changes. Also the old selected thumbnail should shrink back to its previous size. Note that if you double-click on the same thumbnail twice in a row, it shouldn't break.
Note that you can easily access neighbors of an element using Prototype's previous
and next
methods.
tag
and constraint
.
mainimage
area, the large image will update. (This is the same behavior as when the thumbnail is double-clicked. See the Scriptaculous Wiki pages about dragging and dropping.
mainimage
is updated, make it have two effects in a row, such as highlighting and then shaking.
References: Scriptaculous wiki, JSLint
problem by Sylvia Tashev
// CSE 190M, Spring 2012
// Section 9: Photo Gallery
// Sylvia Tashev
document.observe("dom:loaded", function() {
// set up basic event handlers
var pictures = $$("#pictures img");
for (var i = 0; i < pictures.length; i++) {
pictures[i].observe("dblclick", pictureClick);
}
$("left").observe("click", goLeft);
$("right").observe("click", goRight);
// set up initial pic and effects
setCurrentPic(pictures[0]);
// allows the user to rearrange the picture order
Sortable.create("pictures", {
tag: "img",
constraint: "horizontal"
});
// make all the thumbnails drag-and-droppable
for (var i = 0; i < pictures.length; i++) {
new Draggable(pictures[i], {revert: true});
}
Droppables.add("mainimage", {onDrop: setCurrentPic});
});
// sets the current large picture to be the one from the given thumbnail
function setCurrentPic(pic) {
var oldCurrentPic = $$(".currentpic")[0];
if (oldCurrentPic != pic) {
// emphasize new current pic
pic.addClassName("currentpic");
new Effect.Scale(pic, 200);
$("mainimage").src = pic.src.replace("_thumb", "");
$("mainimage").shake();
if (oldCurrentPic) {
// de-emphasize old current pic
oldCurrentPic.removeClassName("currentpic");
new Effect.Scale(oldCurrentPic, 50);
}
}
}
// called when any picture is clicked (or dragged into the main show area)
function pictureClick() {
setCurrentPic(this);
}
// called when Left arrow is clicked
function goLeft() {
var current = $$(".currentpic")[0];
var previous = current.previous();
if (previous) {
setCurrentPic(previous);
}
}
// called when Right arrow is clicked
function goRight() {
var current = $$(".currentpic")[0];
var next = current.next();
if (next) {
setCurrentPic(next);
}
}
<?php
header('Content-type: text/plain');
if (!isset($_GET['n'])) {
header('HTTP/1.1 400 Bad Request');
print "Please provide a parameter n.";
} else {
$factors = factorize($_GET["n"]);
print implode(" * ", $factors);
}
function factorize($num) {
$factors = array();
for ($factor = 2; $factor <= $num; $factor++) {
while ($num % $factor == 0) {
$num /= $factor;
array_push($factors, $factor);
}
}
return $factors;
}
?>
<?php
header("Content-type: text/plain");
$file_text = file_get_contents("addresses.txt");
if ($_SERVER['REQUEST_METHOD'] == "GET") {
$lines = explode("\n", $file_text);
if (isset($_GET["name"])) {
$name = $_REQUEST["name"];
foreach ($lines as $line) {
$info = explode(",", $line);
if ($info[0] == $name) {
print $info[1];
}
}
} else {
$names = array();
foreach ($lines as $line) {
$info = explode(",", $line);
array_push($names, $info[0]);
}
print(implode($names, ","));
}
} else {
$name = $_REQUEST["name"];
$address = $_REQUEST["address"];
$file_text = file_get_contents("addresses.txt");
$file_text .= "\n" . $name . "," . $address;
file_put_contents("addresses.txt", $file_text);
}
?>
document.observe('dom:loaded', function() {
createBrowsers();
Droppables.add('trashcan', {
accept: 'browser',
hoverclass: 'full',
onDrop: removeBrowser
});
});
function createBrowsers() {
for (var i = 0; i < 10; i++) {
var img = $(document.createElement('img'));
img.addClassName('browser');
img.src = 'http://webster.cs.washington.edu/cse190m/sections/8/trashcan/ie6.png';
positionRandomly(img);
new Draggable(img, {
revert: 'failure'
});
$('browsers').appendChild(img);
}
}
function positionRandomly(elem) {
var x = parseInt(Math.random() * 472);
var y = parseInt(Math.random() * 172);
elem.style.left = x + "px";
elem.style.top = y + "px";
}
function removeBrowser(elem) {
new Effect.Puff(elem, {
duration: .5,
afterFinish: function() {
elem.remove();
}
});
}
// CSE 190M, Spring 2009
// Section 8: Photo Gallery
// Sylvia Tashev
document.observe("dom:loaded", function() {
// set up basic event handlers
var pictures = $$("#pictures img");
for (var i = 0; i < pictures.length; i++) {
pictures[i].observe("dblclick", pictureClick);
}
$("left").observe("click", goLeft);
$("right").observe("click", goRight);
// set up initial pic and effects
setCurrentPic(pictures[0]);
// allows the user to rearrange the picture order
Sortable.create("pictures", {
tag: "img",
constraint: "horizontal"
});
// make all the thumbnails drag-and-droppable
for (var i = 0; i < pictures.length; i++) {
new Draggable(pictures[i], {revert: true});
}
Droppables.add("mainimage", {onDrop: setCurrentPic});
});
// sets the current large picture to be the one from the given thumbnail
function setCurrentPic(pic) {
var oldCurrentPic = $$(".currentpic")[0];
if (oldCurrentPic != pic) {
// emphasize new current pic
pic.addClassName("currentpic");
new Effect.Scale(pic, 200);
$("mainimage").src = pic.src.replace("_thumb", "");
$("mainimage").shake();
if (oldCurrentPic) {
// de-emphasize old current pic
oldCurrentPic.removeClassName("currentpic");
new Effect.Scale(oldCurrentPic, 50);
}
}
}
// called when any picture is clicked (or dragged into the main show area)
function pictureClick() {
setCurrentPic(this);
}
// called when Left arrow is clicked
function goLeft() {
var current = $$(".currentpic")[0];
var previous = current.previous();
if (previous) {
setCurrentPic(previous);
}
}
// called when Right arrow is clicked
function goRight() {
var current = $$(".currentpic")[0];
var next = current.next();
if (next) {
setCurrentPic(next);
}
}