Sencha Touch MVC Application – Part 2

In my previous post, I shared presentation on Sencha Touch MVC. That’s all the “talk”, it is time to see it in action. This short tutorial will walk through the implementation of Sencha Touch MVC application. I revamped my old (and long forgotten & messy) USGS apps as example. Before I dive into the code, I will share how to structure the apps.

Structuring Your Apps

Firstly, create the folder structure to support the MVC application. The figure below shows the basic structure required and the codes are available here. I personally think this is a good practice and can be use as coding standard and guideline for Sencha Touch apps. In addition to that, you may want to standardize the file & object naming convention for the controllers, views and data models.

  • m.usgs – the root folder for the application
    • app – folder for your sencha touch code, containing app.js and folders for controllers, models and views.
    • lib – folder for sencha library and other plug-in. (in my example, I moved the sencha library out for sharing with other apps)
    • resources – folder for web resources such as css & images.
    • index.html – the main page to launch the apps.
Sencha Touch - Structuring Your Apps for MVC
Sencha Touch - Structuring Your Apps for MVC

Coding

Step 1: Instantiating the Application

Create app/app.js and add the basic launcher for an Ext.Application instance. In the example it will be namespaced as “usgs”.

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

Create a main Viewport view which will house the pages required for displaying USGS Earthquake data. Here we place references to the card instances in the “usgs.views” namespace, so that we can refer to them explicitly in our page flow.

usgs.views.Viewport = Ext.extend(Ext.Panel, {
    fullscreen: true,
    layout: 'card',
    cardSwitchAnimation: 'slide',
    initComponent: function() {
        //put instances of cards into app.views namespace
        Ext.apply(usgs.views, {
        	usgsMenu: new usgs.views.UsgsMenu()
        	,usgsList: new usgs.views.UsgsList()
        	,usgsMap: new usgs.views.UsgsMap()
        });
        //put instances of cards into viewport
        Ext.apply(this, {
            items: [
                usgs.views.usgsMenu
                ,usgs.views.usgsList
                ,usgs.views.usgsMap
            ]
        });
        usgs.views.Viewport.superclass.initComponent.apply(this, arguments);
    },
    layoutOrientation : function(orientation, w, h) {
        usgs.views.Viewport.superclass.layoutOrientation.call(this, orientation, w, h);
    }
});

Step 2: Modelling Data

Like any MVC-based application the next step is to prepare for the data model. In the models folder, create a definition of the USGS model. The data structure match the content structure is USGS website. In order to demonstrate the use of JSON, I used YQL to convert the RSS data from USGS.

Ext.regModel("usgs.models.UsgsData", {
    fields: [
      {name: 'id', 		type: 'int'},
      {name: 'title', 		type: 'string'},
      {name: 'description', 	type: 'string'},
      {name: 'link', 		type: 'string'},
      {name: 'pubDate', 	type: 'date'},
      {name: 'lat', 		type: 'string'},
      {name: 'long', 		type: 'string'}
    ]
});

Create a simple data store to retrieve the data. For cross domain ajax, set the proxy type to ‘scripttag’. It also handles the callback. 🙂

usgs.stores.usgsData = new Ext.data.Store({
    model: 'usgs.models.UsgsData',
    proxy: {
    	type: 'scripttag',
    	url: 'http://query.yahooapis.com/v1/public/yql',
    	extraParams: {
    		format: 'json'
    	},
	  	reader: {
	  		root: 'query.results.item'
	  	}
  	}
});

Step 3: Creating the Views

The data is ready, now we start to create the view for the USGS data. It allows us to test the data store as well. In this example, I have 3 views:

  1. USGS Menu – this view contains list of data we can retrieve from USGS
  2. USGS List – based on the selected menu, this view shows the list of USGS earthquake data.
  3. USGS Map – this view shows the map where the earthquake occurred.

Here I will just show one of the view. Ignore the call to the controller (Ext.dispatch(…)) for now, wire the view when the controller is ready.

usgs.views.UsgsList = Ext.extend(Ext.Panel, {
    dockedItems: [{
        xtype: 'toolbar',
        title: 'USGS',
        dock: 'top',
        items: [{
        	xtype: 'button',
          text: 'Back',
          ui: 'back',
          handler: function() {
            Ext.dispatch({
                controller: usgs.controllers.usgsController,
                action: 'backToIndex'
            });
          },
          scope: this
        }]
    }],
    items: [{
        xtype: 'list',
        emptyText   : 'No data available.',
        store: usgs.stores.usgsData,
        itemTpl: '{title}',
        onItemDisclosure: function (record) {
            Ext.dispatch({
                controller: usgs.controllers.usgsController,
                action: 'showMap',
                data: record.data
            });
        },
        grouped: false,
        scroll: 'vertical',
        fullscreen: true
    }],
    initComponent: function() {
        usgs.views.UsgsList.superclass.initComponent.apply(this, arguments);
    }
});

Step 4: Building the Controller

We reached the last step and this is the point where all the action and view wired together. If you look back at the view, the handler dispatches specific action to the controller. The actions are defined in the methods below and the controller will then manage the page flow by activating the card. Switching page is done by calling the setActiveItem() and it is applicable because my Viewport is using card layout.

usgs.controllers.usgsController = new Ext.Controller({

    index: function(options) {
        usgs.views.viewport.setActiveItem(
            usgs.views.usgsMenu, options.animation
        );
    },

    showMap: function(options) {
        var data = options.data;
    	usgs.views.usgsMap.addMap(data);
        usgs.views.viewport.setActiveItem(
           usgs.views.usgsMap, options.animation
        );
    },

    showUsgsList: function(options) {
        var id = parseInt(options.id);
    	usgs.stores.usgsData.getProxy().extraParams.q = usgs.query[id].q;
    	usgs.stores.usgsData.read();
        usgs.views.viewport.setActiveItem(
            usgs.views.usgsList, options.animation
        );
    },

    backToIndex: function(options) {
        usgs.views.viewport.setActiveItem(
            usgs.views.usgsMenu, options.animation
        );
    },

    backToUsgsList: function(options) {
        usgs.views.viewport.setActiveItem(
            usgs.views.usgsList, options.animation
        );
    }
});

If you are not using card layout then the controller will need to render the UI component. In the sample code below the view is rendered using the Ext.Controller render() and listener is added to catch the event bubble from the view.

list: function() {
    this.listView = this.render({
        xtype: 'myMainPanel',
        listeners: {
            scope : this,
            filter: this.onFilter,
            selectionchange: this.onSelected
        }
    }, Ext.getBody()).down('.dataList');
}

The screenshots below are the result from the above tutorial.

Implementing the structure and MVC into Sencha Touch definitely helps to simplify my design. I can easily implement the page flow and not end up with spaghetti code. It also helps to separate the view, data and controller and make the code more maintainable.

Hope it is useful and happy hacking!

60 thoughts on “Sencha Touch MVC Application – Part 2

  1. Nice post….I’m currently reading up on the Sencha Touch FW to develop a web app using MVC…so your article just came in handy 🙂 keep up the good work!

  2. Very nice article. May I ask why you removed the map in your addMap function, after checking whether the first component on the page was a box/map? (see UsgsMap.js source)

    I had to do the same thing to get my MVC app working– otherwise, whenever the controller switched from the map panel to another, the map ‘remained’ onscreen and obscured my other panel. So your code helped me in this regard, but I’m not sure why it’s needed? A bug in Sencha, or is there some other reason?

    • Hi Larry,
      Thanks.

      I removed the map to manage the memory usage. The panel maintain each item in a collection. Each time add(map) is called, the map object will be appended to the collection. Initially I just call the doLayout() after add(), the screen get refreshed but I realize the collection keep growing. That’s not good, so I decided to remove first b4 adding.

      Checking the box/map may not be the best way. In this case my panel only show a single map, so I can safely remove the first item in the collection. Alternatively, you can get the map & map id after calling add() and track it.

      Hope that explain why I remove the map.

      Cheers.

  3. Could you help me please?
    I am looking for way to pass variable to View and then use it.
    I am calling View from controller like this (it’s an action):

    show: function(options) {
    var userdata = options.userdata;
    this.view = this.render({
    xtype: ‘cmTeamuser’
    });
    }

    So how to pass userdata to cmTeamuser view?

    • Not sure what is the view implementation look like. But I can think of 3 ways, both are used in my sample code.
      1. to have a method in the view that allow you to set the value from the controller, e.g.

      showMap: function(options) {
      var data = options.data;
      usgs.views.usgsMap.addMap(data);
      usgs.views.viewport.setActiveItem(
      usgs.views.usgsMap, options.animation
      );
      },

      2. if the item in the view allows the use of ‘store’, you can load the store before calling the view. e.g.

      showUsgsList: function(options) {
      var id = parseInt(options.id);
      usgs.stores.usgsData.getProxy().extraParams.q = usgs.query[id].q;
      usgs.stores.usgsData.read();
      usgs.views.viewport.setActiveItem(
      usgs.views.usgsList, options.animation
      );
      },

      3. if the view is a form, you can pass the model object to the form. For this to work the data field must match the form fields name e.g.

      formView.load(userdata);

      Hopefully that helps.

      • Oh. Thank you. And sorry – View in my implementation is:

        Ext.views.ViewName = Ext.extend(Ext.Panel, {});

        I follow architecture as in Kiva example.

      • ic.. i checked out the sample. i believe the data is passed by calling view.setLoan(options.instance), where options.instance is the loan record. similar to the option (1) in my previous reply.

  4. Following your setup…i have a form in one of the views. For one of the fields i want to get the current date…

    I have the following function for getting that date…

    function getDate(){
    var currentTime = new Date();
    var month = addZero(currentTime.getMonth() + 1);
    var day = addZero(currentTime.getDate());
    var year = currentTime.getFullYear();
    return(day + “/” + month + “/” + year);
    }

    where should i put this function so it can be used by many views and how would i call it for my form??

    Something like this??

    xtype: ‘textfield’,
    name: ‘date’,
    label: ‘Date’,
    value: getDate()

    • There is no fix rule on this, my suggestion is to separate .js file e.g. util.js. Include the file the same as app.js.
      if you prefer textfield over datefield, you can extend the textfield to create your own date. Similarly, include it in the same way as app.js

      just a quick sample.
      Ext.form.MyDateTextField = Ext.extend(Ext.form.TextField, {
      initComponent : function(){
      Ext.form.MyDateTextField.superclass.initComponent.call(this);
      this.setValue(Ext.util.Format.date(new Date(), 'd/m/Y'));
      }
      });
      Ext.reg('mydatefield', Ext.form.MyDateTextField);

      use it in this way
      {
      xtype: 'mydatefield',
      fieldLabel: 'Date',
      name: 'date'
      }

      hope that helps.

  5. Awesome thanks so much! That works. But now i am on to bigger things 🙂

    Sorry for this being off topic of your post. But I want to put a list in one of my views. I cannot get this to work and was wondering if you have any insight. Any help would be greatly appreciated Here is my view:

    Toolbar.views.Contact = Ext.extend(Ext.List,{
    fullscreen: true,
    iconCls: ‘home’,
    layout: {
    type: ‘vbox’,
    align: ‘center’,
    pack: ‘center’
    },
    items:[{
    xtype: “list”,
    store: ListDemo.ListStore
    }],

    initComponent: function() {
    Toolbar.views.Contact.superclass.initComponent.apply(this, arguments);
    }
    });

    And here is my date store

    Ext.regModel(‘Contact’, {
    fields: [‘firstName’, ‘lastName’]
    });

    ListDemo.ListStore = new Ext.data.Store({
    model: ‘Contact’,

    data: [
    { firstName: “Domino”, lastName: “Derval” },
    { firstName: “Elektra”, lastName: “King” },
    { firstName: “Fiona”, lastName: “Volpe” }
    ]
    });

    • just got time look at it. The Toolbar.views.Contact extend Ext.List, so you do not need to define items property anymore and you must define item template also, e.g.
      Toolbar.views.Contact = Ext.extend(Ext.List,{
      fullscreen: true,
      iconCls: 'home',
      layout: {
      type: 'vbox',
      align: 'center',
      pack: 'center'
      },
      store: ListDemo.ListStore,
      itemTpl: '

      {firstName} {lastName}

      ',

      initComponent: function() {
      Toolbar.views.Contact.superclass.initComponent.apply(this, arguments);
      }
      });

      noticed that you used namespace, you will need to register them first
      Ext.ns('ListDemo', 'Toolbar.views');

      That work for me, hope it helps.

  6. No that doesn’t work. It does not seem to like the way i have my store set up.

    Does this store look right??

    Ext.ns(‘ListDemo’, ‘Toolbar.views’);

    Ext.regModel(‘Contact’, {
    fields: [‘firstName’, ‘lastName’]
    });

    ListDemo.ListStore = new Ext.data.Store({
    model: ‘Contact’,
    data: [
    { firstName: “Domino”, lastName: “Derval” },
    { firstName: “Elektra”, lastName: “King” },
    { firstName: “Fiona”, lastName: “Volpe” }
    ]
    });

  7. oh never mind. I got it. If i combine the section i posted above with my View, it works.

    Thanks for the help.

  8. Hi, wowi,
    I really loved your work . I got what I was looking for. I am going to start one sencha touch application at my office, but I am not sure how to start as this is my first experience with sencha touch, could you please post or send me email how to do set up for sencha application and how to test. I am planning on android.

    thanks in adv,
    kprk

  9. Thanks for this! This has been the most useful example I’ve found while trying to learn Sencha Touch. Please do some more!

  10. Thanks a lot for this awesome example! i’m working in a finder aplication with this concept. Just one tip, in UsgsMap class, at the end, when adding the listener for the map bubble marker, you should use ‘mousedown’ instead of ‘click’ event, touch devices like iPad/iPhone doesn’t recognize the click event.

    Regards!

  11. Thank you very much for this excellent mvc structure.
    Please I need to know how to add a permanent “tabPanel” using MVC, please help

    • hi,
      did a quick modification on code but din have time to do the write up. the changes are:
      1. the viewport is now base on the tabpanel
      2. add a new tab view for the usgs and then add it into the viewport
      3. the controller is intended for the usgs tab only, so the code is modifiy to switch view withtin the tab.

      i uploaded the sample and available @ http://bit.ly/mNRQZ5. Hope that help

  12. Thanks for a very useful tutorial. It would have been helpful in the text if you could indicate the file names into which the above sample code should be placed. I ended up looking at the Git Hub sources to figure out what files and which code was in it.

    Now that it is working trying to understand how it works….

    • hi… What browser r u using? Sencha Touch is primarily based on WebKit, so it can support only WebKit-based browsers.
      I have tried in iPhone and got my friend to try on default android browser.

      • I’m using the default browser I guess, I haven’t installed any other browser.

        I have tried the MVC sample from Sencha as well as my own project. Both projects works fine on the iPhone but not on Android.

        If I try to run the Contact sample from Sencha in Chrome it works if i comment out the statement
        if (!device || !this.launched) {return;}

        It turns out that the device variable is null but I can’t find any documentation regarding this variable.

      • My sample work on Chrome (desktop) as well. Not sure whether or not different version of the Android has different browser. I heard the phone manufacturer can modify the default browser, but I cannot confirm.

        Let’s see what sencha will respond to your question.

  13. Hi Wowi, could you please how to use webkit, I have been to website . do I need to install nightly build ? and how am I going to place my code ? could you please the guideline on this ? I know this could be basic qn but I am new bie and will start my app soo n 🙂

    kprk..

    • hi… Some of the browser like Safari or Chrome uses webkit engine. Thus the webkit specific can run on those browser and you don’t have to install it separately. e.g. if you are using webkit specific css attribute, you can just test it on Safari or Chrome.

  14. Hi it’s me again,
    I like the way you code withdout reg, but in the tabs example there is an problem when content overflowed and vertical slider

    • thanks for the feedback. btw, what are you displaying on each of the tabs? may be I can simulate and see if I can fix my sample.

    • hi, r u referring to the network round trip from having too many js? if yes, one way to merge them as part of the build process…

  15. Thanks for doing this. This is really helpful. I was working with your example code modified for a TabPanel. I’d like to skip the first Menu and load the list when the app launches and then tap to load map. Is that possible using this structure?

    Thanks

  16. Excellent tutorial. Will someone explain the argument called “argument” in line 20 of the Viewport file? What type is it? Where does it come from? It makes very little sense to me.

  17. This tutorial is great stuff! It really helped me get a better understanding of sencha and mvc and I thank you for that.

    I’ve been recently trying to employ BDD on my app that’s inspired by this tutorial (using Jasmine which the Sencha team seems to like) and I found that the framework doesn’t get along with objects with dot notation in their names. (i.e. – new usgs.views.UsgsMenu() will always return an error, but new UsgsMenuView() works fine)

    What’s your experience with using testing frameworks with Ext/Sencha?

  18. Thank you.
    This is a better example than Sencha has on their tutoriol which does not explain the MVC approach or only does a superficial job.
    Great work.

  19. Hi – thanks – this is really interesting – one question… in the index.html file, why have you put the script files inside the body tag, and also, why are they inside divs?

  20. Any chance of seeing the USGS app updated for Sencha Touch 2.0? I noticed they are handling a few things differently in 2.0.

    Your examples have been the best resource I’ve found for learning!

  21. […] #split {}#single {}#splitalign {margin-left: auto; margin-right: auto;}#singlealign {margin-left: auto; margin-right: auto;}#splittitlebox {text-align: center;}#singletitlebox {text-align: center;}.linkboxtext {line-height: 1.4em;}.linkboxcontainer {padding: 7px 7px 7px 7px;background-color:#eeeeee;border-color:#000000;border-width:0px; border-style:solid;}.linkboxdisplay {padding: 7px 7px 7px 7px;}.linkboxdisplay td {text-align: center;}.linkboxdisplay a:link {text-decoration: none;}.linkboxdisplay a:hover {text-decoration: underline;} function opensingledropdown() { document.getElementById('singletablelinks').style.display = ''; document.getElementById('singlemouse').style.display = 'none'; } function closesingledropdown() { document.getElementById('singletablelinks').style.display = 'none'; document.getElementById('singlemouse').style.display = ''; } Create An HTML/CSS Mobile Web App Using Sencha TouchI liked this: Create An HTML/CSS Mobile Web App Using Sencha TouchSencha Touch Charts Sparks a Revolution in Data Interaction and Visualization on the WebSencha Touch MVC Application – Part 2 […]

  22. While building installable file using phone gap I am getting error in app.js at this.views.viewport = new this.views.Viewport();

    as ReferenceError: Can’t find variable: app at file ://android_asset/www/app/app.js:9
    my code in app.js is

    Ext.regApplication({
    name: ‘c4g’,
    launch: function() {
    this.launched = true;
    this.mainLaunch();
    },
    mainLaunch: function() {
    if (!device || !this.launched) {return;}
    this.views.viewport = new app.views.Viewport();
    }
    });

    • it should be this.views.viewport = new this.views.Viewport();
      because your app is registered as ‘c4g’, subsequently you have to refer to ‘c4g’ instead of ‘app’.

  23. i am beginner of sencha I didnt know how to start as this is my first experience with sencha touch, could you please post or send me email how to do set up for sencha application and how to test. I am planning on android.

  24. This design is incredible! You obviously know how to keep a reader amused.
    Between your wit and your videos, I was almost moved to start my own
    blog (well, almost…HaHa!) Wonderful job. I really loved what
    you had to say, and more than that, how you presented it.
    Too cool!

  25. Simply want to say your article is as astounding.
    The clearness to your publish is just spectacular and that i can suppose you’re knowledgeable in this subject. Fine together with your permission let me to grab your feed to keep updated with drawing close post. Thanks one million and please continue the enjoyable work.

  26. What i do not realize is in reality how you are not
    actually much more neatly-liked than you may be now.
    You’re very intelligent. You already know therefore significantly relating to this subject, produced me personally believe it from a lot of various angles. Its like women and men aren’t interested except it’s one thing to accomplish with Lady gaga! Your individual stuffs excellent. Always deal with it up!

Leave a reply to kprk Cancel reply