Sencha touch is a Javascript framework based on the most popular MVC pattern. It lets you build a cross-platform mobile web application using Javascript, HTML5 and CSS that amazingly look like native mobile applications. I think it can be described and understood better with the help of a demo application. If you can’t wait to see a few of the Sencha’s stuff till we build our demo application you should click here.

Note: If you don’t have a smartphone with a WebKit browser in it, you can view Sencha demo in any WebKit browser preferably Safari.

This is a simple application backed by Rails where you can ask questions and those can be answered. For no specific reason, I name it ‘Tarot’. With Tarot we want to be able to ask a question, answer to it, show a list of question for a given tag and show a question with its answers.

Before we go further I would suggest you to clone the code from my github account. As a matter of fact, we like short and sweet blogs and I won’t be giving full application code in the blog. After you have cloned in the application directory, don’t forget to run:

bundle install
rake db:setup

Without doing much of the talk lets dive into building the Rails part first.

Use of Rails in this application is minimal. It simply serves a few ajax requests and sends back json response. Everything else you expect from a mobile application could be achieved with Sencha Touch.

AR Models

On the server side we are going to have two models Question and Answer.

As it is expected our question has_many answers. There is one thing to mention, I have used acts_as_taggable gem to tag the questions. Simple run all the migrations and run server.

Controllers on Server:

As we just want to do the minimal with our rails part of the application.

Keeping things simple I have setup the index and tags action to respond with the array of all tagged questions and list of all tags to create the tag cloud respectively in the JSON format, which we would finally be parsing at the client end with the help of Sencha.

The client expects JSON data in the following format:

{
	data: {
		   question: {
				 title: 'Title'
				 description: 'Description'
				 answer_count: 4
                                           }
	         }
}

By writing “render :json => {:sucess => true, :data => [question]}” we have made create action to respond accordingly.

def create
    question = Question.new(params[:questions].first.except(:answer_count))
    respond_to do |format|
      question.save
        format.json {
          render :json => {:sucess => true, :data => [question]}
        }
    end
 end 

It seems to be enough of rails and the time when we start giving our Tarot some touches of Sencha.
First thing first, you must have sencha library somewhere in your application where views have access to it and the most appropriate place I find is the public directory where all your .js and .css files go.
Fortunately you don’t have to download the library separately if you have already cloned the application from github. If not I would suggest you do it now.

Note: Sencha Touch comes with lot of other things bundled with it which we can always get rid of. The files of our interest are sencha-touch-debug.js and sencha-touch.css in the development mode and sencha-touch.js and sencha-touch.css in the production mode.

As mentioned earlier Sencha Touch is an MVC framework, its directory structure can be made to look somewhat like a rails application, though it is not mandatory. As it turns out I am a rails guy and love the rails way. Let’s check the public directory where all our Sencha code is placed. Sencha directory structure look like:

public/app    #main application folder
app/app.js    #Tarot boots up from here
app/util.js    # Extra utility function go inside this

#Try to get the significance of following from their names, its easy
app/models
models/questionModel.js
models/answerModel.js
models/tagModel.js

app/conrollers
controller/questionController.js

app/views
views/home.js
views/list.js
views/tarotViewport.js
views/new.js
app/resources
resources/terot.css

If you check application.html.erb, all the the .js files are included in it.

Tip: You should compress all your Javascript files when you run your application in production.

Sencha Digging Deeper:

The first step for most of the Sencha Application is to create a namespace by registering the application. Simply open up app.js file(the starting point of the application) and the check the code in there:

Ext.regApplication({
    name: 'tarot',
    launch: function() {
        this.views.viewport = new this.views.Viewport();
    }
});

The above represents a Sencha Application. Most Applications consist of at least the application’s name and a launch function.

Instantiating a new application automatically creates a global variable using the configured name property and sets up namespaces for views, stores, models and controllers within the app:

//this code is run internally automatically when creating the app (Senncha way of creating namespace)
Ext.ns('MyApp', 'MyApp.views', 'MyApp.stores', 'MyApp.models', 'MyApp.controllers');

The launch function usually creates the Application’s Viewport and runs any actions the Application needs to perform when it boots up. The launch function is only expected to be run once.

A few snapshots of the application viewport:

Home Image
Fig 1: Home
Fig 2: Create Question

Before we create our viewport or any other viewable component, lets create our models and avail the data to play by creating a store(remember sencha touch is MVC framework). A store simply fetches data from the remote server via proxies and keeps it stored in the local storage of the browser. You can get whole lot of information about the store object from the sencha documentation.

We have three model on the client part of Tarot and their corresponding stores. The minimum requirement for a model is its field object. It can also be stuffed with validations, associations, proxy( can be moved to the store).….

Our models/tagModel.js file look like:

tarot.models.Tag = new Ext.regModel('Tag', {
    fields: [
    {
        name: 'name',
        type: 'string'
    },

    {
        name: 'id',
        type: 'int'
    }],
    proxy: {
        type: 'ajax',
        url: 'tags',
        id  : 'taggings',
        reader: {
            type  : 'json',
            record: 'tag',
            root: 'data'
        }
    }
});

tarot.stores.Tags = new Ext.data.Store({
    autoLoad: true,
    model: "Tag",
    sorter: [{
        property : 'name',
        direction: 'ASC'
    }],
    getGroupString: function(instance) {
        return instance.get('name')[0];
    }
});

The official documentation of proxy says:

Proxies are used by Stores to handle the loading and saving of Model data. Usually developers will not need to create or interact with proxies directly.

proxy: {
        type: 'ajax',
        url: 'tags',
        id  : 'taggings',
        reader: {
            type  : 'json',
            record: 'tag',
            root: 'data'
        }
    }

tarot.stores.Tags uses the proxy which has been defined in the model to fetch the initial set of tags from the server. We have made our store to fetch the tags when the application first boots up by specifying

autoload: true

It sends a get(ajax) request to the pathname ‘tags’ wich inturn invokes the tags action within QuestionController and response with the requested data.

The ‘reader’ object specify the format of the record data it should expect from the server. By default it takes ‘records’ as the root value.

The store:

tarot.stores.Tags = new Ext.data.Store({
    autoLoad: true,
    model: "Tag",
    sorter: [{
        property : 'name',
        direction: 'ASC'
    }],
    getGroupString: function(instance) {
        return instance.get('name')[0];
    }
});

Having autoLoad set to true loads our store with the initial set of tags when the application boots up.
The similar can be achieved by calling tarot.stores.Tags.load() in the Application launch function.

‘getGroupString’ takes the instance of the model and should return the string by which the data is grouped in the store. We’d be using formed groups later in the application.

The other two models are written similarly, if you still get stuck, refer to the Sencha Touch documentation.

Finally we have reached the point where we can watch things moving on the screen. Back in the application launch function we create and object of Viewport. But what actually is a Viewport?

Note: Before going further you should keep the app/tarotViewport.js file open.

The basic building block(viewport) of a sencha app is a panel which contains subpanel and other components. Our viewport panel has a toolbar with back(initially hidden) and ‘askme’ buttons and the list of tags docked on the left. This consists of the navigation part of our application and makes it nicer and easier to navigate.

this.tagList = new Ext.List({
            id: 'tagindexlist',
            title: 'Tags',
            store: tarot.stores.Tags,
            itemTpl: '
<div>{name}</div>

',
            grouped: true,
            indexBar: true,
            emptyText: 'Tags Empty',
            ui: 'round',
            style: {
                minWidth: '250px',
                borderRight: '5px solid #4D80BC'
            },
            onItemDisclosure: function(record){
                Ext.dispatch({
                    controller: tarot.controllers.questions,
                    action: 'index',
                    animation: {
                        type: 'slide',
                        direction: 'left'
                    },
                    extras: record.data.name
                })
            }

        });

The store attribute above instructs list the load the data from. For every record in the store the itemTpl is rendered. Back in the tag store definition we grouped our store with the first character of every instance. Grouped: true takes the advantage of the same and shows the list items grouped together. ‘onItemDisclosure’ adds a disclosure icon to the list item and a listener to it which is fired when the icon is tapped and invokes the index function of our ‘tarot.controllers.questions’.

this.backButton = new Ext.Button({
            text: 'Back',
            ui: 'back',
            hidden: true,
            handler: this.onBackTap,
            scope: this
        });

The handler of a button specifies the function to call when the button is tapped. The button in our viewport is hidden for the first card. Once any other card is set as an activeItem the back button is shown. For the same purpose we have added a listener to the viewport:

listeners: {
        beforecardswitch : function( viewprt,newCard,oldCard,index){
            if(index == 0)
                viewprt.backButton.hide();
            else
                viewprt.backButton.show();
        }
    }

Our viewport has the tarot.views.home as its first item which would be set as active item(card) of the panel by default. The code can be in app/views/home.js.

tpl: new Ext.XTemplate('
<div>
<div class="question-blk">
<div class="ques-title">{title}</div>

<span class="answerCount">Answer

{answers_count}

</span></div>

'),

Our home defines a Data View view which renders the list of all question autoloaded by the the tarot.stores.questions store. A dataview if capable of displaying data with custom templates. Just like a list in our viewport it can also be bound to the store. Tpl Represents an HTML fragment template. The html inside is rendered for every record inside the store by looping through it.

It also adds a listener to the items rendered which on being tapped invokes the show function of the controller. The similar code has been written in the app/views/list.js which can be refered in our application by tarot.views.questionList (the magic of the namespaces).

tarot.views.answerList in the app/views/show.js file does extra efforts to render two more components. It docks the current question on the top and a form to give answers to that question at the bottom. tarot.views.questionNew simply has almost code and it is self explanatory.

Its time when we move to the ‘C’ part of our Sencha MVC.

Our controllers/questionController.js has defined a few function which can be invoked from our application using Ext.dispatch as we have previously been doing. The index function is called when the tag disclosure from the tag list is tapped and is responsible for loading tarot.stores.taggedQuestions and setting tarot.views.questionList to be the current activeItem which inturn renders the template for each question.

It merges options with some more attributes and calls the loader function from util.js file.

loader: function(options){
        var mask = tarot.util.actions.createLoadingMask();
        mask.show();
        options.store.data.clear();
        options.model.proxy.extraParams = {
            filter_param: options.extras
        }
        options.store.load(
        {
            scope   : this,
            callback: function(records, operation, success) {
                mask.hide();
                if(success){
                     tarot.views.viewport.setActiveItem(options.activeItem, options.animation);
                }
                else{
                    alert('Error while loading the questions');
                }
            }
        });
    }

The loader function does a number of things. The previous data from the store in cleared and sends a fresh request to the server by options.store.load() with an extra parameter to filter the records. In this case, it is going to be the tag name. The load() function accepts a parameter as an object which should define the callback function, that gets invoked when the response is received from the server. This function is passed the records that are fetched from the server, the operation object which avails more information about the current operation and a boolean success specifying whether the operation was successful. If the operation was a success we hide the mask which was shown at the very first. At last, it sets the activeItem of the viewport to be the tarot.views.questionList.

In the similar fashion tarot.views.answerList is loaded. One thing is to mention here is, every time the store is updated the corresponding views(DataView) are automatically updated to render the current state of the store. No extra work is to be done, isn’t it nice! Just in case you have not associated any store with the data view check out the update() function.

Creating a new question:

On tapping the save button from tarot.views.QuestionNew the create function is called from the tarot.controller.question. The create function takes the from values and creates a model object filling in the parameters pulled from the form. As we like to keep our code DRY, we use our saveRecord() function to create both the question and answers. We merge options in the create action with the required properties and pass it onto saveRecord().

create: function(options) {
        options.form = tarot.views.questionNew;
        options.activeitem = tarot.views.home;
        var params = options.form.getValues();
        options.record = Ext.ModelMgr.create(params, tarot.models.Question);
        options.store = tarot.stores.questions;
        this.saveRecord(options)
    },

if (errors.isValid()) {
            record.save({
                success: function(){
                    options.form.reset();
                    if(options.activeitem){
                        Ext.Msg.show({
                            title: 'Saved',
                            msg: 'Record has been saved',
                            buttons: Ext.MessageBox.OK,
                            fn: function() {
                                tarot.views.viewport.setActiveItem(options.activeitem, options.animation);
                            }
                        });
                    }
                    options.store.load();

                }
            });
            return true;
        } else{}

Our saveRecord() takes validations into and proceeds only if the record is valid else it shows up a message box with the validations errors. Again the magic of the proxy spreads here, which is hidden from the developer. When the save function is called the it sends the POST(Ajax) request to the ‘questions’ path with record fields in the parameters and extra parameters(if any). On success, the success callback is called. Few other callbacks are ‘failure’ and ‘callback’ (get called whether success or failure) .

Our answers to the questions are created the same way but no card of the viewport is switched and being on the same card tarot.views.DataView object is updated automatically.

I hope this gives you a nice overview of building a mobile web application with Sencha Touch. But this is not it! There is a lot more and can be used to build amazing cross-platform application efficiently.

Share this:

Privacy Preference Center