Create Image Gallery Using Sencha Touch

August 20th, 2011 by aditia rahman / 16 Comments  

     

Today post I want share some of my thought creating simple image gallery using sencha touch. I created this example to learn more about sencha touch MVC (Model View Controller) application structure, basically I created this example after watching the sencha con video about Structuring Your Sencha Touch Application, so the code in this example is almost like from that video.

Sencha Touch Gallery Mockup

The image above is the mockup design of this example that will created, there are three main screen first for list all album folder, second for viewing image inside the album folder, and the last for viewing full image.

Demo | Download
Application Structure

Using unix system you can use sencha touch command to generate the mvc application structure. Just go to jsbuilder folder and type a command, for example

./sencha.sh generate app Gallery ../gallery

If you’re running windows you can use virtual system software and install a linux on it, I install virtual box and ubuntu so I can try sencha touch command, and share the generated files using virtual box shared folder and guest addition.

sencha touch file structure

In lib folder there are other generated library like jasmine, JSBuilder, sencha-jasmine which are not used in this example and I don’t know yet what are these for. In index.html sencha touch file and style not configured properly so you have to put by yourself.

sencha touch index setup

I put sencha touch file and style on lib/touch and for the images on public/img/{album_name} with thumbs folder in each album folder.

Configure Application

First we configure defalult controller to be called, open app.js in app/ folder, and set default url to ‘Album/index’

Gallery = new Ext.Application({
    name: "Gallery",
    defaultUrl: 'Album/index',
    launch: function() {
        this.viewport = new Gallery.Viewport();
    }
});

And leave the Viewport.js in blank, which mean for this state not rendering anything inside the panel.

Gallery.Viewport = Ext.extend(Ext.Panel, {
    id        : 'viewport',
    layout    : 'card',
    fullscreen: true
});

Create a controller app/controllers/Album.js, with 3 three empty method and below we will implement the method one by one starting from model, view and controller.

Ext.regController("Album", {
    // index controller - first screen (album list)
    index: function() {
    },
    // viewList controller - second screen (thumbnail list)
    viewList: function() {
    },
    // viewCarousel controller - third screen (image view)
    viewCarousel: function() {
    }
});

Actually there are a sencha command to generating model and controller but I choose to create manually. And to make it easier I assume to use mobile screen resolution in 320 x 640 pixels.

First Screen (Album List)

Model: Create album model in app/models/Album.js

Ext.regModel("Album", {
    fields: [
        {name: "name", type: "string"},
        {name: "path", type: "string"},
        {name: "last_modified", type: "string"}
    ],

    proxy: {
        type: 'ajax',
        url: 'get-album.php',
        reader: {
            root: 'albums',
            type: 'json'
        }
    }
});

Create a php file to get all the image folder, in get-file.php

<?php
$path = 'public/img';

if ($handle = opendir($path)) {
    $dir = array();
    while (false !== ($file = readdir($handle))) {
        if ($file != '.' & $file != '..') {
            $album_path = $path . '/' . $file;
            $dir[] = array(
                'name' => preg_replace('/-/', ' ', $file),
                'path' => $album_path,
                'last_modified' => date("F d Y H:i:s", filemtime($album_path))
            );
        }
    }
    closedir($handle);
}

echo json_encode(array('albums' => $dir));
?>

View: Create album list view in app/views/AlbumListPanel.js

Gallery.views.AlbumListPanel = Ext.extend(Ext.Panel, {
    layout: 'fit',
    initComponent: function() {
        this.store = new Ext.data.Store({
            autoLoad: true,
            model: 'Album'
        });

        this.dockedItems = [{
            xtype: 'toolbar',
            dock: 'top',
            title: 'Album List'
        }];

        this.list = new Ext.List({
            itemTpl: '{name}',
            store: this.store
        });

        this.items = [this.list];

        Gallery.views.AlbumListPanel.superclass.initComponent.apply(this, arguments);
    }
});

Ext.reg('gallery-albumlistpanel', Gallery.views.AlbumListPanel);

Controller: Create index function inside the controller, this controller render the album list with select event that pass the list, record (which contain selected album folder data) and swipe direction to the viewList function.

index: function() {
    if (!this.listPanel) {
        this.listPanel = this.render({
            xtype: 'gallery-albumlistpanel',
            listeners: {
                list: {
                    select: function(list, record) {
                        this.viewList(list, record, 'left');
                    },
                    scope: this
                },
                activate : function(listPanel) {
                    listPanel.list.getSelectionModel().deselectAll();
                }
            }
        });

        Gallery.viewport.setActiveItem(this.listPanel);
    } else {
        Gallery.viewport.setActiveItem(this.listPanel, {
            type: 'slide',
            direction: 'right'
        });
    }
},
Second Screen (Thumbnail List)

Model: Create image models in app/models/Image.js, the url in proxy not specified cause it will load dynamically on the controller.

Ext.regModel("Image", {
    fields: [
        {name: "name", type: "string"},
        {name: "size", type: "string"},
        {name: "date", type: "string"},
        {name: "path", type: "string"},
        {name: "thumb", type: "string"},
    ],
    proxy: {
        type: 'ajax',
        reader: {
            root: 'files',
            type: 'json'
        }
    }
});

Create php to get all images in one specific folder in get-file.php

<?php
$dir = $_GET['path'] . "/";

$dh = opendir($dir);
$files = array();
while (($file = readdir($dh)) !== false) {
    if ($file != '.' AND $file != '..' ) {
        if (filetype($dir . $file) == 'file') {
            $files[] = array(
                'name' => $file,
                'size' => filesize($dir . $file). ' bytes',
                'date' => date("F d Y H:i:s", filemtime($dir . $file)),
                'path' => $dir . $file,
                'thumb' => $dir . 'thumbs/thumb_' . $file
            );
        }
    }
}
closedir($dh);

echo(json_encode(array('files' => $files)));
?>

View: Create thumbnail view in app/views/gallery/AlbumViewPanel.js, you can see on below code I inserted the image url as a <div> background image, cause when using <img> tag directly it seems the problem appear when swiping an item, and I applied to the third screen (viewCarousel controller) as well.

Gallery.views.AlbumViewPanel = Ext.extend(Ext.Panel, {
    layout: 'fit',
    html: '&lt;div style=&quot;width:320px;height:480px;background:#000;&quot;&gt;&lt;/div&gt;',
    initComponent: function() {
        this.store = new Ext.data.Store({
            autoLoad: true,
            model: 'Image'
        });

        this.dockedItems = [{
            xtype: 'toolbar',
            dock: 'top',
            itemId: 'albumToolbar',
            items: [{
                itemId: 'homeButton',
                ui: 'back',
                text: 'Back'
            }]
        }];

        this.xtpl = new Ext.XTemplate(
            '&lt;div style=&quot;padding:10px 5px 5px 5px;&quot;&gt;',
            '&lt;tpl for=&quot;.&quot;&gt;',
                '&lt;div class=&quot;node&quot; style=&quot;background:url({thumb});&quot;&gt;',
                '&lt;/div&gt;',
            '&lt;/tpl&gt;',
            '&lt;/div&gt;'
        );

        this.dataView = new Ext.DataView({
            store: this.store,
            tpl: this.xtpl,
            itemSelector: 'div.node'
        });

        this.items = [this.dataView];

        Gallery.views.AlbumViewPanel.superclass.initComponent.apply(this, arguments);
    }
});

Ext.reg('gallery-albumviewpanel', Gallery.views.AlbumViewPanel);

Controller : Create viewList function inside the controller, in this function there are two event in image dataview, first is itemtap, to switch to the third screen and viewing full image, second is item swipe to changing the current background image. In itemtap the total image and tapped image passed to the viewCarousel function.

viewList: function(list, record, slide) {
    var albumView = this.render({
        xtype: 'gallery-albumviewpanel',
        listeners: {
            deactivate: function(albumView) {
                albumView.destroy();
            },
            dataView: {
                itemtap: function(list, index) {
                    var imgdata = albumView.dataView.store.data.items;
                    this.viewCarousel(list, index, imgdata, record);
                },
                itemswipe: function(list, index) {
                    var imgdata = albumView.dataView.store.data.items;
                    albumView.update(
                        '&lt;div style=&quot;background:url(' +
                            imgdata[index].data.path + ') no-repeat;'+
                            'width:320px;height:640px;&quot;&gt;&lt;/div&gt;'
                    );
                },
                scope: this
            }
        }
    });
    albumView.dataView.store.proxy.url = 'get-file.php?path=' + record.data.path;

    albumView.query('#homeButton')[0].on({
        tap: this.index,
        scope: this
    });

    albumView.query('#albumToolbar')[0].setTitle(record.data.name);

    Gallery.viewport.setActiveItem(albumView, {
        type: 'slide',
        direction: slide
    });
}
Third Screen (Image View)

This function not using model, cause we have already the image information passed from the viewList function so we do not need to load any data anymore.

View: Create view in app/views/gallery/AlbumCarousel.js

Gallery.views.AlbumCarouselPanel = Ext.extend(Ext.Panel, {
    layout: 'fit',
    initComponent: function() {
        this.dockedItems = [{
            xtype: 'toolbar',
            dock: 'top',
            items: [{
                    itemId: 'backButton',
                    ui: 'back',
                    text: 'Back',
                    iconMask: true
                }, {
                    itemId: 'homeButton',
                    iconCls: 'home',
                    iconMask: true
                }
            ]
        }];

        this.carousel = new Ext.Carousel({
            indicator: false,
            defaults: {
                scroll: 'vertical'
            }
        });

        this.items = [this.carousel];

        Gallery.views.AlbumCarouselPanel.superclass.initComponent.apply(this, arguments);
    }
});

Ext.reg('gallery-albumcarouselpanel', Gallery.views.AlbumCarouselPanel);

Controller: Create viewCarousel function inside the controller, this controller using carousel, it will created the layout inside the carousel as many as the images inside the folder, so we can swipe between image in the same folder.

viewCarousel: function(list, index, imgdata, album) {
    var albumCarousel = this.render({
        xtype: 'gallery-albumcarouselpanel',
        listeners: {
            deactivate: function() {
                albumCarousel.destroy();
            }
        }
    });

    for(i=0; i&lt;imgdata.length; i++) {
        albumCarousel.carousel.add({
            html: '&lt;div class=&quot;img&quot; style=&quot;background: url('+imgdata[i].data.path+');&quot;&gt;&lt;/div&gt;'
        });
    }

    albumCarousel.query('#homeButton')[0].on({
        tap: this.index,
        scope: this
    });

    albumCarousel.query('#backButton')[0].on({
        tap: function() {
            this.viewList(list, album, 'right');
        },
        scope: this
    })

    Gallery.viewport.setActiveItem(albumCarousel, 'slide');
    albumCarousel.carousel.setActiveItem(index);
}

And here are the example screen tested on google chrome in 320 x 640 pixels, I’m getting lazy to test on android emulator cause it run very slow on my laptop, and I change the $base-color to #0033cc and $base-gradient to ‘glossy’ using SASS.

Sencha Gallery Capture

DemoDownload
        submit to reddit Delicious

16 Comments Leave a Comment Subscribe RSS

  • ben says:

    Thanks a lot for this. I am trying to learn about the viewport concept and events in sencha.
    hope more to come from you
    regards,
    Ben

  • Jackal says:

    I downloaded the files and is not working on ios simulator.
    Any idea why, have you tested this yet or not?

  • Megha says:

    Sir.. can you please tell me how to run it in ios simulator

  • Simon Luca says:

    Thank you very much for this tutorial. It will really help me when I finish my iPad carousel app. I would like to share it with you when it is finished :-)

    Si

  • Simon Lucas says:

    Just wanted to say again thank you for sharing…you are very kind and also very clever to code this.

    One small error I noticed, the first time you mention this line:

    ‘Create a php file to get all the image folder, in get-file.php’

    I think it should say:

    Create a php file to get all the image folder, in get-album.php

    …and then the next one is correct. I think you refer to get-file twice.

    Thanks again,

    Si

  • Malky says:

    Hi, I just discovered this and wow! I was waiting to see the new version of sencha to see if they added filters to the nested list but no.

    I reckon I will have to go down this route, although I have 3 levels of lists and one details panel at the end.

    If I tried this is it possible to load the data into the store and add filters to the store and the lists would only show filtered data?

    For example if I have a leaf in the last list, can I use filters on the table assuming they are stored as rows straight from the mysql db as json?

    I hope I’ve put this correctly?

    Any comment is welcome as I was relying on a filter as I said.

    Thanks,

    Malky

  • Fabio says:

    Only work in webkit browsers?

  • jane says:

    example is not working – data is not loading.

    Returns an error:
    Ext.data.JsonReader.getResponseData: Unable to parse JSON returned by Server.

    Has anyone worked out how to fix it so that it runs?

  • Rajiv says:

    Do you have a version of this app without the php files? Perhaps you can hard-code the JSON array to be returned by the server? It will help those who don’t have a PHP server to use this sample. We can add the AJAX calls ourselves if we can get the sample working.

  • Nag says:

    Thanks for your help. This works great.

    This only works for webkit browsers.

    I tried to change it so it can work on mobile but running into an issue.

    Can you please help

    Thanks, Nag

  • Girish says:

    I am getting the following type of error when i run it on the android 2.2 device

    Uncaught Ext.data.JsonReader.getResponseData: Unable to parse JSON returned by Server. at file:///android_asset/www/lib/touch/sencha-touch.js:6

  • Leafy Leaf says:

    stop buying GMO foods from the supermarkets… stop taking vaccines and flu shots…. the American and European Government are SICK EVIL WAR CRIMINALS who inject toxic chemicals and poisons into our food supply

  • Liz says:

    Also getting the same error:

    Uncaught Ext.data.JsonReader.getResponseData: Unable to parse JSON returned by Server. at file:///android_asset/www/lib/touch/sencha-touch.js:6

    would love to figure out how to fix it but i’m way too new to this stuff

  • Thanks for sharing great article

  • Girish says:

    Thanks.
    super coding structure and very well working it article
    you have share article it very god i liked your coding structure

Leave a Comment