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 SymfonyComponentFilesystemFilesystem;
use SymfonyComponentFilesystemExceptionIOException;

// Make use of the Symfony2 Finder
use SymfonyComponentFinderFinder;

// Use Request object
use SymfonyComponentHttpFoundationRequest;

/**
 * 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)


        
                Wookmark Image Galley
                
                
                
                
                
                
                
                
                        /* 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;
                        }
                
        
        
                
                
                                
                                        
                                    
                    
                    
                    
                            // 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