Write your own Pinterest like Image Gallery using jQuery Wookmark and Symfony2

This article provides a quick tutorial on how to write your own Pinterest like Image Gallery using the jQuery Wookmark Plugin and Symfony2.

Assuming you already created a Symfony2 Bundle and image files are stored on disk, below are the steps to follow for writing a new Controller and Twig template.

WookmarkController (/Controller/WookmarkController.php)

NOTE: Requires the Finder component for listing image files, and the PHP GD extension.

// Namespace and bundle specific code goes here

// Make use of Symfony2 FileSystem functionality.
use Symfony\Component\Filesystem\Filesystem;
use Symfony\Component\Filesystem\Exception\IOException;

// Make use of the Symfony2 Finder
use Symfony\Component\Finder\Finder;

// Use Request object
use Symfony\Component\HttpFoundation\Request;

/**
 * Provides Wookmark UI and JSON list.
 * Requires the Finder component: http://symfony.com/doc/2.0/components/using_components.html
 */
class WookmarkController extends Controller {
	// Image directory
	const IMAGE_DIRECTORY = "/path/to/images/on/disk";
	// File name pattern: http://api.symfony.com/2.0/Symfony/Component/Finder/Finder.html#files()
	const FILENAME_PATTERN = "*.jpg";
	// Default page size
	const PAGE_SIZE = 20;

	/**
	 * Generates the user interface.
	 */
	public function indexAction() {
		// Render template
		return $this->render(
			'BridgemanContemporaryBundle:Wookmark:index.html.twig'
		);
	}

	/**
	 * Returns a list of images, and their properties.
	 * NOTE: Requires php-gd, for fetching image dimenstions!
	 */
	public function listAction( Request $request ) {
		// Prepare finder
		$finder = new Finder();

		// Load image file names, from the configured directory...
		$finder->in( self::IMAGE_DIRECTORY )
			// ...and the specified pattern
			->name( self::FILENAME_PATTERN )
			// ...sorted by name
			->sortByName()
			// ...non-recursive
			->depth( '== 0' );

		// Prepare paging
		$start = ( $request->get( 'page' ) - 1 ) * self::PAGE_SIZE;
		$end = $start + self::PAGE_SIZE;

		// Prepare the array of images
		$images = array();
		$item = 0; // Paging index
		foreach ( $finder as $file ) {
			// Verify page
			if ( $item >= $start && $item < $end ) {
				// Get image dimensions
				list( $width, $height ) = @getimagesize( $file->getRealpath() );

				// Push each image
				$images[] = array(
					// File properties
					"file" => $file->getFilename()
					,"size" => $file->getSize()
					,"last_change_time" => $file->getCTime()
					// Image properties
					,"width" => $width
					,"height" => $height
				);
			}

			// Exit loop, if already parsed all images for the current page
			if ( $item == $end ) {
				break;
			}

			$item++;
		}

		// Render template
		return $this->render(
			'BridgemanContemporaryBundle:Wookmark:list.html.twig'
			,array(
				// Image array
				"images" => $images
			)
		);
	}
} 

List action template (/Resources/views/Wookmark/list.html.twig)

{{ images|json_encode|raw }}

Index action template (/Resources/views/Wookmark/index.html.twig)

<!DOCTYPE html>
<html>
	<head>
		<title>Wookmark Image Galley</title>
		<!-- Load jQuery -->
		<script src="//ajax.googleapis.com/ajax/libs/jquery/1.6.2/jquery.min.js"></script>
		<!-- Load Wookmark -->
		<script type="text/javascript" src="/wookmark/jquery.wookmark.js"></script>
		<!-- Load Wookmark sample style -->
		<link href="/wookmark/example/css/style.css" type="text/css" rel="stylesheet" />
		<!-- Add some extra page style -->
		<style>
			/* Body style */
			body {
				margin: 0px;
				padding: 0px;
				background-color: black;
			}
			/* Image container style */
			#container {
				width: 900px;
				margin-left: auto;
				margin-right: auto;
			}
			/* Image box style */
			#container img {
				cursor: auto;
			}
		</style>
	</head>
	<body>
		<!-- Image Container -->
		<div id="container">
				<div id="main" role="main">
					<ul id="tiles"></ul>
				</div>
		</div>
		<!-- User Interface code -->
		<script>
			// From Wookmark Examples
			var handler = null;
			var page = 1;
			var isLoading = false;
			var apiURL = '/wookmark/list';
			var imageURL = 'IMAGE_URL';
			var imageWidth = 200;

			// Prepare layout options.
			var options = {
				autoResize: true, // This will auto-update the layout when the browser window is resized.
				offset: 20, // Optional, the distance between grid items
				container: $( '#container' ), // Optional, used for some extra CSS styling
				itemWidth: 210 // Optional, the width of a grid item
			};

			/**
			* When scrolled all the way to the bottom, add more tiles.
			*/
			function onScroll(event) {
				// Only check when we're not still waiting for data.
				if(!isLoading) {
					// Check if we're within 100 pixels of the bottom edge of the broser window.
					var closeToBottom = ($(window).scrollTop() + $(window).height() > $(document).height() - 100);
					if(closeToBottom) {
						loadData();
					}
				}
			};

			/**
			* Refreshes the layout.
			*/
			function applyLayout() {
				// Clear our previous layout handler.
				if(handler) handler.wookmarkClear();

				// Create a new layout handler.
				handler = $('#tiles li');
				handler.wookmark(options);
			};

			/**
			* Loads data from the API.
			*/
			function loadData() {
				isLoading = true;

				var data = {
					page: page
				};

				$.ajax({
					url: apiURL,
					dataType: 'json',
					data: data, // Page parameter to make sure we load new data
					success: onLoadData
				});
			};

			/**
			* Receives data from the API, creates HTML for images and updates the layout
			*/
			function onLoadData(data) {
				isLoading = false;

				// Increment page index for future calls.
				page++;

				// Create HTML for the images.
				var html = '';
				var i=0, length=data.length, image;
				var lastModifyDate;
				for(; i<length; i++) {
					image = data[i];
					lastModifyDate = new Date( image.last_change_time * 1000 );
					html += '<li>';

					// Image tag
					html += '<img src="' + imageURL + image.file + '" width="' + imageWidth + '" height="' + Math.round( image.height / image.width * imageWidth ) + '">';

					// Properties
					html += '<p>Filename: ' + image.file + '<br/>\
						Size (MB): ' + ( image.size / 1024 * 1024 ).toFixed( 2 ) + '<br/>\
						Last modify date: ' + lastModifyDate.getHours() + ':' + lastModifyDate.getMinutes() + ':' + lastModifyDate.getSeconds() + ' ' + lastModifyDate.getDate() + '/' + lastModifyDate.getMonth() + '/' + lastModifyDate.getFullYear() + '</p>';

					html += '</li>';
				}

				// Add image HTML to the page.
				$('#tiles').append(html);

				// Apply layout.
				applyLayout();
			};

			$(document).ready(new function() {
				// Capture scroll event.
				$(document).bind('scroll', onScroll);

				// Load first data from the API.
				loadData();
			});
		</script>
	</body>
</html>

And finally, register your controller in your routing.yml (or .xml) file

bundle_wookmark:
        pattern:  /wookmark/
        defaults: { _controller:Bundle:Wookmark:index }
bundle_wookmark_list:
        pattern:  /wookmark/list
        defaults: { _controller:Bundle:Wookmark:list }

Configuration options

The following options may be configured in the controller:

IMAGE_DIRECTORY – path to image directory, on disk

FILENAME_PATTERN – file pattern

PAGE_SIZE – items per page

And Javascript code:

apiURL – API URL path, as configured in your bundle routing

imageURL – URL path to public image folder

imageWidth – default image width

References:

http://symfony.com/doc/2.0/components/using_components.html

http://api.symfony.com/2.0/Symfony/Component/Finder/Finder.html#files()

http://www.wookmark.com/jquery-plugin

Leave a Reply

Your email address will not be published. Required fields are marked *