This section is about using Scriptaculous for visual effects, and creating PHP web services to supply data to Ajax requests.

  1. Prime Factors
  2. Prime Factors 2
  3. Address Book
  4. Address Book 2
  5. Garbage Collector
  6. Photo Gallery (long)

1. Prime Factors

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

Solution

factors.php

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

2. Prime Factors 2

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

Solution

factors.php

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

3. Address Book

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

Solution

addressbook.php

<?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);
}
?>

4. Address Book 4

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

Solution

addressbook.php

<?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);
}
?>

5. Garbage Collector

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):

<!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>

trashcan.js (skeleton of JS code):

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:

problem by Morgan Doocy

Solution

trashcan.js

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();
      }
   });
}

6. Photo Gallery (long)

Today we will write an animated photo gallery using Scriptaculous. Start with this skeleton:

gallery.html (complete HTML/CSS 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>

gallery.js (skeleton of JS code):

// 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:

  1. When the page first loads up, the thumbnail of the current 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.
  2. When the user double-clicks a thumbnail to show a new image, the same effects as just described should occur. The new thumbnail should grow to 200% size, and the 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.
  3. When the previous/next, left/right arrows are clicked, the corresponding previous or next image should be placed into the large area. It should be as though the user double-clicked on the previous or next thumbnail, complete with the same effects.

    Note that you can easily access neighbors of an element using Prototype's previous and next methods.

  4. Make the list of images so that its order can be rearranged by dragging the images left and right. See the Scriptaculous wiki page for ideas and options such as tag and constraint.
  5. Make it so that when the user drags a thumbnail image to the 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.
  6. If you finish the previous effects, add other bling to the page. For example, when the images are rearranged by dragging them to sort them, make effects occur on the affected thumbnails. Or add sequences of effects that occur on the same element; for example, when the large 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

Solution

gallery.js

// 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);
    }
}

Solutions

1. Prime Factors

factors.php

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

2. Address Book

addressbook.php

<?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);
}
?>

3. Garbage Collector

trashcan.js

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();
      }
   });
}

4. Photo Gallery

gallery.js

// 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);
    }
}
Valid HTML Valid CSS