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:
- A running web server. I used a Rails server running on localhost:3000
- A WebKit based browser like chrome or safari.
- 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,
Awesome tutorial.
Greetings from Brazil.
Thanks Felipe.
Great tutorial dude, sencha rocks!!! Regards from Mexico.
Thanks .
Many thanks, Swapnil. Visiting from Korea.
Nice article Swapnil, very concise.
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!
Great……
Greetings from India.
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!
That is a great tip especially to those fresh to the blogosphere.
Short but very precise info… Thank you for sharing this one.
A must read article!
I quite like looking through an article that can make men and women think.
Also, thank you for allowing me to comment!
I am regular reader, how are you everybody? This piece of writing posted at this web
site is truly nice.
Hello to every single one, it’s in fact a fastidious for me to pay a visit this website, it includes priceless Information.
You should take part in a contest for one of the highest quality sites online.
I’m going to recommend this blog!
My brother suggested I might like this website.
He was totally right. This post truly made my day. You can
not imagine just how much time I had spent for this
info! Thanks!
Hi there, just wanted to mention, I enjoyed this post. It was practical.
Keep on posting!
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!
If you want to increase your experience simply keep visiting this web page and be updated with the
newest information posted here.
Exceptional post however , I was wondering if you could
write a litte more on this topic? I’d be very thankful if you could elaborate a little bit more. Thanks!
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.
Hello! Do you know if they make any plugins to help 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. Kudos!
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.
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.
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!
Hey There. I ffound your blog using msn. This is an extremely well written article.
I’ll make sure to bookmark it and come back to read more of your useful
information. Thanks for the post. I will definitely return.
If some one wants expert view on the topic of blogging
after that i suggest him/her to payy a quick visit this weblog, Keep up tthe god work.
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!!
magnificent points altogether, you just won a new reader.
What might you suggest about your put up that you made some
days ago? Any certain?
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.