David Walsh Blog

Create a Download Package Using MooTools Moousture

Zohaib Sibt-e-Hassan recently released a great mouse gestures library for MooTools called Moousture. Moousture allows you to trigger functionality by moving your mouse in specified custom patterns. Too illustrate Moousture’s value, I’ve created an image download builder using Mooustures and PHP.

The XHTML


<div id="messager">Waiting for you to select images...</div>
<img src="preload-images/1.jpg" alt="Image 1" width="100" class="moouse" />
<img src="preload-images/2.jpg" alt="Image 2" width="100" class="moouse" />
<img src="preload-images/3.jpg" alt="Image 3" width="100" class="moouse" />
<img src="preload-images/4.jpg" alt="Image 4" width="100" class="moouse" />
<img src="preload-images/5.jpg" alt="Image 5" width="100" class="moouse" />
<img src="preload-images/6.jpg" alt="Image 6" width="100" class="moouse" />
<img src="preload-images/7.jpg" alt="Image 7" width="100" class="moouse" />
<img src="preload-images/8.jpg" alt="Image 8" width="100" class="moouse" />
<div style="clear:both;"></div>
<input type="submit" name="download" id="download" class="button" value="Download Package" />

We provide the images, a messenger DIV which lest the user know when an image has been added and removed, and a submit button to generate the file.

The CSS


#messager	{ padding:10px; border:2px solid #fc0; background:#fffea1; margin-bottom:10px; }
.unselected	{ border:2px solid #ccc; background:#fff; }
.selected	{ border:2px solid #3b5998; background:lightblue; }
.moouse		{ width:100px; float:left; margin:0 20px 0 0; padding:5px; }

Nothing special, except that the selected class should make it very apparent to the user that the image has been selected.

The MooTools JavaScript


/* dom ready */
window.addEvent('domready',function() {
	/* add to each image */
	$$('img.moouse').each(function(img) {
		/* add unselected class */
		img.addClass('unselected');
		/* create the gesture */
		var gest = new Moousture.ReducedLevenMatcher();
		/* set the "add" and "remove" gestures */
		gest.addGesture([4,5,6,7,0,1,2,3], function(error) {
			/* good gesture! */
			if(error < 3) {
				img.addClass('selected');
				document.id('messager').set('text','Image ' + img.get('alt') + ' added.');
			}
			/* bad! */
			else {
				//document.id('messager').set('text','Sorry - we couldn\'t detect that you wanted: ' + img.get('alt') + '.');
			}
		}); //add
		/* set the "add" and "remove" gestures */
		gest.addGesture([4,3,2,1,0,7,6,5], function(error) {
			/* good gesture! */
			if(error < 3) {
				if(img.hasClass('selected')) {
					img.removeClass('selected');
					document.id('messager').set('text','Image ' + img.get('alt') + ' removed.');
				}
			}
			/* bad! */
			else {
				//document.id('messager').set('text','Sorry - we couldn\'t detect that you wanted: ' + img.get('alt') + '.');
			}
		}); //add
		/* add the probe to the image */
		var probe = new Moousture.MouseProbe(img);
		/* create the recorder */
		recorder = new Moousture.Recorder({maxSteps: 20, minSteps: 8, matcher: gest});
		/* monitor the image */
		monitor = new Moousture.Monitor(30, 2);
		/* start monitoring */
		monitor.start(probe, recorder);
	});
	
	/* submission */
	document.id('download').addEvent('click',function() {
		/* get the selected images */
		var selected = $$('img.selected');
		if(selected.length) {
			var qstring = '?images=';
			/* for each image */
			selected.each(function(img) {
				/* add to qstring */
				qstring += img.get('src') + ';';
			});
			/* force download */
			window.location = 'moousture.php' + qstring;
		}
	});
	
});

When the DOM is ready, we set up two gesture patterns for each image — one to select an image, one to unselect the image. When the user has selected the images they want and click the submit button, Moo redirects them to a PHP address with the images in the $_GET[‘image’] variable.

Note that the “error” threshold I’ve set is 3. If a user attempts a gesture on an image, an error number is automatically calculated. This error number represents how different the user’s gesture was than the given gesture listener.

The PHP


/* if we got images */
if($_GET['images']) {
	/* split the querystring */
	$images = explode(';',$_GET['images']);
	$valid_files = array();
	/* for every image */
	foreach($images as $image) {
		/* if the image exists */
		if(file_exists($image)) {
			/* add it to our good image list */
			$valid_files[] = $image;
		}
	}
	/* if we have good images */
	if(count($valid_files)) {
		/* files */
		$destination_folder = 'image-packages';
		$filename = time().'.zip';
		$path = $destination_folder.'/'.$filename;
		/* create zip! */
		$zip = new ZipArchive();
		if($zip->open($path,ZIPARCHIVE::CREATE) !== true) {
			return false;
		}
		/* add the files */
		foreach($valid_files as $file) {
			$zip->addFile($file,$file);
		}
		/* close the zip */
		$zip->close();
		/* force download zip */
		if(ini_get('zlib.output_compression')) { ini_set('zlib.output_compression', 'Off');	}
		header('Pragma: public');
		header('Expires: 0');
		header('Cache-Control: must-revalidate, post-check=0, pre-check=0');
		header('Last-Modified: '.gmdate('D, d M Y H:i:s', filemtime($path)).' GMT');
		header('Cache-Control: private',false);
		header('Content-Type: application/zip');
		header('Content-Disposition: attachment; filename="package.zip"');
		header('Content-Transfer-Encoding: binary');
		header('Content-Length: '.filesize($path));
		header('Connection: close');
		readfile($path);
		exit();
	}
	die();
}

We receive the image paths via $_GET[‘images’]. Once we validate that each image exists, we build the ZIP file and force-download it to the user.

This solution isn’t great when there are many images available; a POST method (via form submission) would be best for that situation. What are your thoughts on Moousture though? Any suggestions for the creator?