How Well Sencha Touch MVC Application Works With Rails

For sometime, we have been toying around with different frameworks to build cross-platform mobile applications. Our latest research was with Sencha Touch. This post is a tutorial to get kick-started.

Installation

To run Sencha Touch application you need:

  1. A running web server. I used a Rails server running on localhost:3000
  2. A WebKit based browser like chrome or safari.
  3. Sencha Touch SDK.

After downloading the SDK, extract it to the public/javascripts directory of your web application. We typically need only  “sencha-touch.js” and “resources/css/sencha-touch.css”. You may remove the remaining files.

Configuring the User Agent

I used Safari to test my application. To make it work, I needed to change its User agent to Safari iOS 4.1 iPhone. To change the User agent in Safari, you can go to Menu -> Develop -> User Agent and set the one you need.

In Chrome, refer how to change the user agent of browser.

Rembember Sencha Touch uses HTML5.  In case you require to support requests from a web-application as well as mobile devices (as is in most cases), in the rails application we need some modifications:

  • A method to check that the request is coming from a web browser or a mobile. To do so, modify your application controller as described at Railscasts episode 199. Here is a snippet:
# application_controller.rb
class ApplicationController < ActionController::Base

helper :all
 protect_from_forgery
def mobile_device?
 user_agent = request.user_agent
 user_agent =~ /Mobile|webOS/
end
 helper_method :mobile_device?
end

Here we are checking that if user agent of request contains ‘Mobile’ or not, as

  • Safari on Windows has user agent as “Mozilla/5.0 (Windows; U; Windows NT 6.0; en-us) AppleWebKit/533.20.25 (KHTML, like Gecko) Version/5.0.4 Safari/533.20.27”.
  • Mozilla Firefox on Ubuntu has user agent as “Mozilla/5.0 (X11; Linux i686; rv:7.0.1) Gecko/20100101 Firefox/7.0.1”.
  • Safari installed on Ubuntu on Wine with user agent as Safari iOS4.1 iPhone has user agent as “Mozilla/5.0 (iPhone; U; CPU iPhone OS 4_1 like Mac OS X; en-us) AppleWebKit/532.9 (KHTML, like Gecko) Version/4.0.5 Mobile/8B117 Safari/6531.22.7″
  • Android Mobile phone has user agent as “Mozilla/5.0 (Linux; U; Android 2.3.4; en-gb; GT-I9100G Build/GINGERBREAD) AppleWebKit/533.1 (KHTML, like Gecko) Version/4.0 Mobile Safari/533.1″.
  • Update the application layout. (app/views/layouts/application.html.erb)
<!DOCTYPE html>
<html>
<head>
<title>Senchamvc</title>
<%= csrf_meta_tag %>
<% if mobile_device? %>
<%= stylesheet_link_tag 'sencha-touch' %>
<%= javascript_include_tag 'sencha-touch-1.1.1/sencha-touch' %>
</head>
<body>
</body>
</html>
<% else %>
<%= stylesheet_link_tag :all %>
<%= javascript_include_tag :defaults %>
</head>
<body>
<%= yield %>
</body>
</html>
<% end %>

You can notice that for mobile devices we are not adding ‘yeild’ in body of layout as we have added for web application’s code, this is because all view rendering is going to be  done by the included javascripts.

Now lets see how to write code for our application using the Sencha Touch SDK.

We are building a very simple application. There are 2 models:

  • Business – This is any corporate, company or registered profit making entity.
  • Organization – This is any non-profit organization like NGOs, NPOs etc.

The larger product maps businesses with organizations they support. For the sake of our demo, we are simply using these 2 models to list information. This will help us understand Sencha Touch SDK.

First,create a directory named “app” under public/javascripts. In that create a directory structure like this:


Under the public/javascripts/app directory, create directories named models, controllers and views. These are exactly similar to a standard MVC Rails application.
So application_layout will look like this,

<!DOCTYPE html>
<html>
<head>
<title>Senchamvc</title>
<%= csrf_meta_tag %>
<% if mobile_device? %>
<%= stylesheet_link_tag 'sencha-touch' %>
<%= javascript_include_tag 'sencha-touch-1.1.1/sencha-touch' %>
<%= javascript_include_tag 'app/app' %>
<%= javascript_include_tag 'app/routes' %>
<%= javascript_include_tag 'app/views/Viewport' %>
<%= javascript_include_tag 'app/views/TabBarMvc' %>
<%= javascript_include_tag 'app/models/business' %>
<%= javascript_include_tag 'app/models/organization' %>
<%= javascript_include_tag 'app/controllers/business' %>
<%= javascript_include_tag 'app/controllers/organization' %>
<%= javascript_include_tag 'app/views/business/index' %>
<%= javascript_include_tag 'app/views/business/show' %>
<%= javascript_include_tag 'app/views/organization/index' %>
</head>
<body></body>
</html>
<% else %>
<%= stylesheet_link_tag :all %>
<%= javascript_include_tag :defaults %>
</head>
<body>
<%= yield %>
</body>
</html>
<% end %>

Sencha application is initialized from app.js. My application initialization starts with listing of businesses so my public/javascripts/app/app.js looks like this:

new Ext.Application({
name: 'senchamvc',
defaultUrl: 'business/index',
launch: function() {
this.views.viewport = new senchamvc.views.Viewport();
}
});

Now lets see what exactly we are doing here,

  • Ext is the global namespace for the whole Sencha Touch framework. Every class, function and configuration for the whole framework exists under this single global variable.
  • Ext.Application represents the entire application.
  • name: ‘senchamvc’ creates a global variable called ‘senchamvc’ – all of your Application’s classes (such as its Models, Views and Controllers) will reside under this single namespace.
  • defaultUrl: When the app is first loaded, this url will be redirected to.
  • when all of your JavaScript are loaded, your application’s launch function is called, at which time you can run the code that starts your app. The launch function is only expected to be run once.
  • Viewport extends from Ext.Container, it has as layout ( defaults layouts is Ext.Layout.Card). This means you can add items to it at any time, from anywhere in your code. Viewport for an application is defined in app/views/Viewport.js

Routing the requests

We routes the request to specific action of controller using app/routes.js

Ext.Router.draw(function(map) {
map.connect(':controller/:action');
});

The Router is used to map url to controller/action pairs. Every Ext.application can set up Routes using the default Ext.Router instance. So request to url  “business/index” will be dispatched to index action of controller registered as “business”.

The Model

The Business model can be defined as shown below in the app/models/business.js file

senchamvc.models.Business = new Ext.regModel('Business', {
fields: [
{name: 'id' , type: 'int'},
{name: 'name', type: 'string'},
{name: 'address', type: 'string'}
]
});

Here,

  • Ext.regModel:  Registers a model definition i.e creates a new Model class from the specified config object
  • fields : specify the attributes of a model.

To increase performance, we can sync data from remote Rails server to a local data store at time of application initialization. For this I keep 2 data stores in my application, one is remote data store and another is local data store. So, I add this to my business.js model

senchamvc.stores.remoteBusinesses = new Ext.data.Store({
id: 'remoteBusinesses',
model: 'Business',
proxy: {
type: 'ajax',
url: 'http://localhost:3000/businesses.json',
reader: {
type: 'json',
root: 'businesses',
record: 'business'
},
writer: {
type: 'json',
record: 'business'
}
}
});

senchamvc.stores.localBusinesses = new Ext.data.Store({
id: 'localBusinesses',
model: 'Business',
proxy: {
type: 'localstorage',
id: 'businessses'
}
});

Here we are defining the data stores with their proxies. The Store class contains the client side cache of model objects. Stores load data via a Proxy.

Now lets see various attributes of Store,

  • id: is the id we are assigning to data Store.
  • model: specify the model for which we are defining the Store.
  • proxy: Proxies are used by stores to handle the loading and saving of Model data.
  • proxy type: json: Configuration is automatically turned into an Ext.data.AjaxProxy instance, with the url we specified being passed into AjaxProxy’s constructor. So when we call load() on store makes a request to the url we configured.
  • proxy type: localstorage: The LocalStorageProxy uses the new HTML5 localStorage API to save Model data locally on the client browser.
  • reader in this case is JSON Reader is used by a Proxy to read a server response that is sent back in JSON format.
  •  the business data is nested an additional level inside the “businesses” as each “business” item has additional data surrounding it, so we specify “record” i.e within the JSON response that the record data itself can be found at and “root” i.e the name of the property which contains the Array of row objects.

Every time we sync data from the server, it is necessary to clear local data store before syncing it with remote data store. This is to avoid  data inconsistency. We can use far more superior techniques in production systems to sync only deltas. Here I am clearing this, so that we can study the concept of listeners! Update the business.js model

senchamvc.stores.remoteBusinesses.addListener('load', function () {
var store = Ext.getStore('localBusinesses');
store.getProxy().clear();
store.data.clear();
store.sync();
this.each(function (record) {
var business = senchamvc.stores.localBusinesses.add(record.data)[0];
});
senchamvc.stores.localBusinesses.sync();
});

The Controller

Moving onto controllers, we ensure that when we initialize the Business controller, we load both the data stores. The app/controllers/business.js looks like this:

Ext.regController('business',{
init: function(options) {
senchamvc.stores.remoteBusinesses.load();
senchamvc.stores.localBusinesses.load();
}
});

Here Ext.regController: Creates a new Controller class .

To show list page of businesses, in index method of business controller set the active item in Viewport as business index view.

index: function(options) {
senchamvc.stores.localBusinesses.load();
if (! this.indexView)
{
this.indexView = this.render({
xtype: 'BusinessIndex',
});
}
senchamvc.views.viewport.setActiveItem(this.indexView, options.animation);
},

You can notice here that we are rendering “BusinessIndex” instead of just index, this is because we are registering the views also, so if we want to register more than 1 index views of different controllers, it will lead to confusion so we are registering index view of business as BusinessIndex. While rendering we are mentioning xtype because every component has a specific xtype, which is its Ext-specific type name.

The View

In the view we can define title of page, store from which you want to show the data, action to be handled on tap event etc. Our Business index (app/views/business/index.js) is as follows,

senchamvc.views.BusinessIndex = Ext.extend(Ext.Panel, {
dockedItems: [{
xtype: 'toolbar',
title: 'Businesses',
items: []
}],
layout: {
type: 'vbox',
align: 'center',
pack: 'center'
},
items: [{
xtype: 'list',
id: 'businessesindex',
scroll: 'vertical',
width: Ext.Element.getViewportWidth()*0.9,
store: senchamvc.stores.localBusinesses,
style: {
background: '#ffffff'
},
itemTpl: new Ext.XTemplate(
 '<tpl for=".">',
       '<div>',
'{name}',
'</div>',
'<div>',
'{address}',
'</div>',
'</tpl>'
),
onItemDisclosure: function(record){
},
onItemTap: function(item) {
record = this.getRecord(item);
Ext.dispatch({
controller: 'business',
action: 'show',
id: record.getId(),
animation: {type: 'slide', direction: 'left'}
});
}
}],
initComponent: function() {
senchamvc.stores.localBusinesses.load();
senchamvc.views.BusinessIndex.superclass.initComponent.apply(this, arguments);
}
});
Ext.reg('BusinessIndex', senchamvc.views.BusinessIndex);

Now lets see what exactly we are doing in the view definition,

  • Panel is a container which is building block for user interfaces.
  • dockedItems: A component or series of components to be added as items to panel, items can be docked to either the top, right, left or bottom of a panel. This is typically used for things like toolbars or tab bars.
  • layout should be defined for child items to be correctly sized and positioned, if not then the default layout manager will be used which does nothing but render child components sequentially into the Container (no sizing or positioning will be performed in this situation).
  • items can be single or array of child Components to be added in Container.
  • within items we can specify the properties such as width, scrolling style etc. for Container.
  • One thing you will notice here that is we are defining 2 events, onItemDisclosure and onItemTap. This is because onItemDisclosure display a disclosure icon on each list item. This won’t bind a listener to the tap event. By setting dipatch configuration to an action, it will automatically add a tap event listeners to the disclosure button [not to  entire row]. So as to add listener to entire row we are defining listener for onItemTap event.
  • At the end we are registering this view as BusinessIndex so from a controller we can render this view using the name with which it is registered.

So my application is ready to go with data stored locally.

You can clone the complete example from github.

For more information you can refer to sencha touch application with ruby on rails.

References :

For adding Tabbar to mobile application I refered to sencha-touch-tabbar-in-a-mvc-application.

To generate an installable, such as for android *.apk, of your code please refer to phonegap documentation.

Also an excellent example for MVC application in sencha with phonegap.

At the end I was able to develop this application which shows list of businesses and organizations,


29 thoughts on “How Well Sencha Touch MVC Application Works With Rails

  1. Woah! I’m really digging the template/theme of this website. It’s simple, yet effective.
    A lot of times it’s very hard to get that “perfect balance” between superb usability and visual appearance. I must say that you’ve done a
    excellent job with this. Additionally, the blog loads super
    quick for me on Firefox. Exceptional Blog!

  2. You’re so cool! I do not suppose I have read a single thing like that before. So good to discover another person with a few genuine thoughts on this issue. Seriously.. many thanks for starting this up. This web site is one thing that is needed on the internet, someone with some originality!

  3. Hi there! Do you know if they make any plugins to assist with SEO?
    I’m trying to get my blog to rank for some targeted keywords but I’m not seeing very
    good success. If you know of any please share. Many thanks!

  4. The statistic shows a ratio of 1:100 for men: women diagnosed cases of breast cancer.
    Ideas such as Recognition Day, Sports Day, Information Day,
    and Decoration Day provide inspiring ways that
    organizers can arrange activities around a daily theme.

    With these elements in place, your awareness pin will be a big success.

  5. Normally I don’t read article on blogs, however I would like
    to say that this write-up very compelled me to try and do
    it! Your writing taste has been surprised me. Thanks, quite great post.

  6. May I just say what a relief to uncover somebody that truly knows what
    they’re talking about on the net. You definitely realize
    how to bring an issue to light and make it important. More people must check this out and understand this side of
    the story. It’s surprising you aren’t more popular because you most certainly possess the gift.

  7. you are really a good webmaster. The website loding speed is amazing.
    It seems that you are doing any unique trick.
    Also, The contents aare masterwork. you have performed a fantastic
    activity onn this subject!

  8. A fascinating discussion is definitely worth comment. I do think that you ought to publish
    more about this issue, it may not be a taboo subject but usually people don’t talk about these subjects.
    To the next! All the best!!

  9. I’m not sure why but this weblog is loading incredibly slow for
    me. Is anyone else having this problem or is it a problem on my end?

    I’ll check back later and see if the problem still exists.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.