Enterprise Web Development (2014)

Part II. Enterprise Considerations

Chapter 12. Sencha Touch

The Sencha Touch framework is a little brother of Ext JS. They both have the same creator, Sencha, and they both are built on the same set of core classes. But Sencha Touch is created for developing mobile web applications, whereas Ext JS is for desktop web applications.

Enterprise IT managers need to be aware of another important difference: Ext JS offers free licenses only for open source projects, but Sencha Touch licenses are free unless you decide to purchase this framework bundled with developer tools.

This chapter is structured similarly to Chapter 11, which describes jQuery Mobile—minimum theory followed by the code. A fundamental difference, though, is that whereas Chapter 11 has almost no JavaScript, this chapter has almost no HTML.

We’ll try to minimize repeating the information you can find in Sencha Touch Learning Center and extensive product documentation, which has multiple well-written Guides on various topics. This chapter begins with a brief overview of the features of Sencha Touch followed by a code review of yet another version of the Save The Child application. In this chapter, we are going to use Sencha Touch 2.3.1, which is the latest version at the time of this writing. It supports iOS, Android, BlackBerry, and Windows Phone.

NOTE

If you haven’t read Chapter 4 on Ext JS, please do it now. Both of these frameworks are built on the same foundation, and we assume that you are familiar with such concepts as MVC architecture and xtype, SASS, and other terms that are explained in that chapter. For the most part, Ext JS and Sencha Touch non-UI classes are compatible, but there are some differences that might prevent you from attaining 100 percent code reuse between these frameworks (for example, see the section Stores and Models). Future releases of Sencha should come up with some standard solutions to remove the differences in class systems of both frameworks.

Introducing Sencha Touch

Let’s begin by downloading Sencha Touch. If you want to get a free commercial license, just specify your email address; you’ll receive the download link in the email. The Sencha Touch framework comes as a ZIP file, which you can unzip in any directory. Later, you’ll copy the framework’s code either into your project directory or in the document root of your web server.

WARNING

A commercial license of Sencha Touch doesn’t include charts (you need to get either Sencha Complete or Sencha Touch Bundle for chart support). Therefore, we’ll use the General Public License (GPL) of Sencha Touch for the open source Save The Child project, and our users will see the little watermark, “Powered by Sencha Touch GPLv3,” as shown in Figure 12-1.

The GPL watermark

Figure 12-1. The GPL watermark

After downloading Sencha Touch, unzip it into the directory /Library/touch-2.3.1. The code-generation process copies this framework into our application directory.

Performing Code Generation and Distribution

If you haven’t downloaded and installed the Sencha CMD tool, do it now as described in Generating Applications with the Sencha CMD Tool. This time we’ll use Sencha CMD to generate a mobile version of Hello World. After opening a terminal or command window, enter the following command, specifying the absolute path to your Ext JS SDK directory and to the output folder, where the generated project should reside:

sencha -sdk /Library/touch-2.3.1 generate app HelloWorld /Users/yfain11/hellotouch

After the code generation is complete, you’ll see the folder hello with the structure shown in Figure 12-2. It follows the Model-View-Controller (MVC) pattern discussed in Chapter 4.

A CMD-generated project

Figure 12-2. A CMD-generated project

To test your newly generated application, make sure that the directory hellotouch is deployed on a web server (simply opening index.html in a web browser won’t work). You can either install any web server or just follow the instructions in Developing Save The Child with Ext JS inChapter 4. In the same chapter, you can find the command to start the Jetty web server embedded in the Sencha CMD tool.

Here, we are going to use the internal web server that comes with the WebStorm IDE. It runs on port 63342, and if your project’s name is helloworld, the URL to test it is http://localhost:63342/helloworld.

NOTE

To debug your code inside WebStorm, choose Run→Edit Configurations, click the plus sign in the upper-left corner, and then in the JavaScript Debug→Remote panel, enter the URL http://localhost:63342, followed by the name of your project (for example, ssctouch) and name your new debug configuration. After that, you’ll be able to debug your code in your Chrome web browser (it will ask you to install the JetBrains IDE Support extension on the first run).

TIP

Mac OS X users can install the small application Anvil, which can easily serve static content of any directory as a web server with a URL that ends with .dev.

Figure 12-3 shows how the generated Hello World application will look in a Chrome browser. It’ll consist of two pages controlled by the buttons in the footer toolbar.

image

Figure 12-3. Running CMD-generated Hello World

Microloader and configurations

The main application entry is the JavaScript file app.js. But if in Ext JS, this file was directly referenced in index.html, Sencha Touch applications generated by the CMD tool use a separate microloader script, which starts with loading the file app.json that contains the names of the resources needed for your application, including app.js. The only script included in the generated index.html is this one:

<script id="microloader" type="text/javascript"

       src="touch/microloader/development.js"></script>

This script uses one of the scripts located in the microloader folder, which gets the object names to be loaded from the configuration file app.json. This file contains a JSON object with various attributes such as js, css, resources, and others. So if your application needs to load the scriptssencha-touch.js and app.js, they should be located in the js array. Example 12-1 illustrates what the js attribute of app.json contains after the initial code generation by Sencha CMD.

Example 12-1. The js attribute of app.json

"js": [

    {

        "path": "touch/sencha-touch.js",

        "x-bootstrap": true

    },

    {

        "path": "app.js",

        "bundle": true,

        "update": "delta"

    }

]

Eventually, if you need to load additional JavaScript code, CSS files, or other resources, add them to the appropriate attribute in the file app.json.

Introducing a separate configuration file and additional microloader script might seem like an unnecessary complication, but it’s not. On the contrary, it gives you the flexibility of maintaining a clean separation between development, testing, and production environments. You can find three loader scripts in the folder touch/microloaderdevelopment.jsproduction.js, and testing.js. Each of them can load a different configuration file.

TIP

Our sample application includes sample video files. Don’t forget to include the resources/media folder in the resources section of app.json.

If you open the source code of the production loader, you’ll see that it uses an application cache to save files locally on the device (see Application Cache API for a refresher), so the user can start the application even without having an Internet connection.

The production microloader of Sencha Touch offers a smarter solution for minimizing unnecessary loading of cached JavaScript and CSS files than the HTML5 application cache. The standard HTML5 mechanism doesn’t know which resources have changed and reloads all cacheable files. CMD-generated production builds for Sencha Touch keep track of changes and create deltas, so the mobile device will download only those resources that have been actually changed. To create a production build, open a terminal or a command window, change to your application directory, and run the following command:

sencha app build production

See “Deploying Your Application” for more details on Sencha CMD builds. When we start building our Save The Child application, you’ll see how to prompt the user that the application code has been updated. Refer to the online documentation on using Sencha CMD with Sencha Touch for details.

CODE DISTRIBUTION AND MODULARIZATION

The ability of Sencha Touch to monitor modified pieces of code helps with deployment; just change SomeFile.js on the server and it will be automatically downloaded and saved on the user’s mobile device. This can have an effect on the application modularization decisions you make.

Reducing the startup latency and implementing lazy loading of certain parts of the application are the main reasons for modularizing web applications. The other reason for modularization is an ability to redeploy certain portions of the code versus the entire application if the code modifications are limited in scope.

So, should we load the entire code base from local storage (it’s a lot faster than getting the code from remote servers) or still use loaders to bring up the portion of the code (a.k.a. modules) on an as-needed basis? There is no standard answer to this question—every application is different.

If your application is not too large and the mobile device has enough memory, loading the entire code of the application from local storage can lower the need for modularization. For larger applications, consider the Workspaces feature of Sencha CMD, with which you can create some common code to be shared by several scripts.

The code of Hello World

Similar to Ext JS, the starting point of the Hello World application is the app.js script, which is shown in Example 12-2.

Example 12-2. The app.js file of the Sencha Touch version of Save The Child

Ext.Loader.setPath({

    'Ext': 'touch/src',        1

    'HelloWorld': 'app'

});

Ext.application({

    name: 'HelloWorld',

    requires: [

        'Ext.MessageBox'

    ],

    views: [

        'Main'

    ],

    icon: {

        '57': 'resources/icons/Icon.png',

        '72': 'resources/icons/Icon~ipad.png',

        '114': 'resources/icons/Icon@2x.png',

        '144': 'resources/icons/Icon~ipad@2x.png'

    },

    isIconPrecomposed: true,

    startupImage: {

        '320x460': 'resources/startup/320x460.jpg',

        '640x920': 'resources/startup/640x920.png',

        '768x1004': 'resources/startup/768x1004.png',

        '748x1024': 'resources/startup/748x1024.png',

        '1536x2008': 'resources/startup/1536x2008.png',

        '1496x2048': 'resources/startup/1496x2048.png'

    },

    launch: function() {

        // Destroy the #appLoadingIndicator element

        Ext.fly('appLoadingIndicator').destroy();

        // Initialize the main view

        Ext.Viewport.add(Ext.create('HelloWorld.view.Main'));

    },

    onUpdated: function() {              2

        Ext.Msg.confirm(

            "Application Update",

            "This application has just successfully

             been updated to the latest version. Reload now?",

            function(buttonId) {

                if (buttonId === 'yes') {

                    window.location.reload();

                }

            }

        );

    }

});

1

This code instructs the loader that any class that starts with Ext can be found in the directory touch/src or its subdirectories. The classes with names that begin with HelloWorld are under the app directory.

2

This is an interception of the event that’s triggered if the code on the server is updated. The user is warned that the new version of the application has been downloaded. You can see more on this in the comments to app.js in the section Using Sencha Touch for Save The Child.

The code of the generated main view of this application (Main.js) is shown next. It extends the class Ext.tab.Panel so that each page of the application is one tab in this panel. Figure 12-4 is a snapshot of a collapsed version of Main.js taken from the WebStorm IDE from JetBrains, which is our IDE of choice in this chapter.

image

Figure 12-4. Collapsed version of Main.js from Hello World

As you can see from this figure, the items[] array includes two objects, Welcome and Get Started, and each of them represents a tab (screen) on the panel. Example 12-3 shows the code of the Welcome and Get Started screens.

Example 12-3. Code of the Welcome and Get Started screens

Ext.define('HelloWorld.view.Main', {

  extend: 'Ext.tab.Panel',

  xtype: 'main',

  requires: [

      'Ext.TitleBar',

      'Ext.Video'

  ],

  config: {

    tabBarPosition: 'bottom',          1

    items: [

        {                              2

            title: 'Welcome',

            iconCls: 'home',

            styleHtmlContent: true,

            scrollable: true,

            items: {

                docked: 'top',

                xtype: 'titlebar',

                title: 'Welcome to Sencha Touch 2'

            },

            html: [

                "You've just generated a new Sencha Touch 2 project."

                "What you're looking at right now is the ",

                "contents of <a target='_blank' href=\"app/view/Main.js\">"

                "app/view/Main.js</a> - edit that file ",

                "and refresh to change what's rendered here."

            ].join("")

        },

        {                               3

            title: 'Get Started',

            iconCls: 'action',

            items: [

                {

                    docked: 'top',

                    xtype: 'titlebar',

                    title: 'Getting Started'

                },

                {

                    xtype: 'video',

                    url: 'http://av.vimeo.com/64284/137/87347327.mp4?token=

                    1330978144_f9b698fea38cd408d52a2

                    393240c896c',

                    posterUrl:

                          'http://b.vimeocdn.com/ts/261/062/261062119_640.jpg'

                }

            ]

        }

      ]

  }

});

1

The tab bar has to be located at the bottom of the screen.

2

The first tab is a Welcome screen.

3

The second tab is the Getting Started screen. It has xtype: video, which means it’s ready for playing video located at the specified url.

This application has no controllers, models, or stores. But it does include the default theme from the SASS stylesheet resources/sass/app.scss, which was compiled by the Sencha CMD generation process into the file resources/css/app.css.

Constructing the UI

Sencha Touch has UI components specifically designed for mobile devices. These components include lists, forms, toolbars, buttons, charts, audio, video, carousels, and more. The quickest way to become familiar with them is by browsing the Kitchen Sink website, where you can find examples of how UI components look and see the source code.

Containers

In general, the process of implementing a mobile application with Sencha Touch consists of selecting appropriate containers and arranging navigation among them. Each screen that a user sees is a container. Often, it will include a toolbar docked at the top or bottom of the container.

Containers can be nested; they are needed for better grouping of UI components on the screen. The lightest container is Ext.Container. It inherits all the functionality from its ancestor Ext.Component, plus it can contain other components. When you review the code of the Save The Child application, note that the main view SSC.view.Main from Main.js extends Ext.Container. The hierarchy of Sencha Touch containers is shown in Figure 12-5.

Sencha Touch containers hierarchy

Figure 12-5. Sencha Touch containers hierarchy

The FieldSet is also a pretty light container; it simply adds a title to a group of fields that belong together. You’ll see several code samples in this chapter with xtype: 'fieldset' (for example, Login or Donate screens).

If your containers display forms with such inputs as text field, text area, password, and numbers, the virtual keyboard will automatically show up, occupying half of the user’s screen. On some platforms, virtual keyboards adapt to the type of input field—for example, if the field has xtype: 'emailfield', the keyboard will be modified for easier input of emails. Figure 12-6 is a snapshot taken from the Donate screen of the Save The Child application as the user taps inside the Email field. Note the key with the “at” sign (@) on the main keyboard, which wouldn’t be shown for nonemail inputs.

The iPhone virtual keyboard for entering emails

Figure 12-6. The iPhone virtual keyboard for entering emails

If the field is for entering a URL (xtype: 'urlfield'), expect to see a virtual keyboard with a button labeled .com. If the input field has xtype: 'numberfield', the user might see a numeric keyboard when the focus is in this field.

TIP

If you need to detect the environment on the user’s mobile device, use Ext.os. to detect the operating system, Ext.browser to detect the browser, and Ext.feature to detect supported features.

Layouts

Besides grouping components, containers allow you to assign a Layout to control its children arrangements. In desktop applications, physical screens are larger, and often you can place multiple containers on the same screen at the same time. In the mobile world, you don’t have that luxury, and typically you’ll be showing just one container at a time. Not all layouts are practical to use on smaller screens, which is why not all Ext JS layouts are supported in Sencha Touch.

Figure 12-10, shown later in this chapter, illustrates the main container that shows either the tabpanel or loginform. The tabpanel is a container with a special layout that shows only one of its child containers at a time (for example, About or Donate). You can see all these components in action at savesickchild.org—just run the Sencha Touch version of our Save The Child application and view the sources.

By default, a container’s layout is auto, which instructs the rendering engine to use the entire width of the container, but use just enough height to display the children. This behavior is similar to the vbox layout (vertical box), in which all components are added to the container vertically, one below another. Accordingly, the hbox arranges all components horizontally, one next to the other.

TIP

If you want to control how much vertical or horizontal screen space is given to each component, use the flex property as described in Setting proportional layouts by using the flex property.

The fit layout fills the entire container’s space with its child element. If you have more than one child element in the container, the first one will fill the entire space and the other one will be ignored.

The card layout can accommodate multiple children while displaying only one at a time. The container’s method setActiveItem() allows you to programmatically select the “card” to be on top of the deck. With a card layout, all containers are preloaded to the device, but if you want to create new containers at runtime, you can use the method setActiveItem(), passing a config object that describes the new container.

You can find examples of card and fit layouts in the code of Main.js of the Save The Child application. Figure 12-11 shows the card layout, but if you expand the tabpanel container, each tab has the fit layout.

The classes TabPanel and Carousel represent two implementations of containers that use the card layout.

Events

Events can be initiated either by the browser or by the user. Working with Events covers general rules of dealing with events in the Ext JS framework. Many system events are dispatched during UI component rendering. The online documentation lists every event that can be dispatched on Sencha classes. Look for the Events section on the top toolbar in the online documentation. Figure 12-7 is a snapshot from online documentation for the class Ext.Container, which has 32 events.

image

Figure 12-7. Events in the Sencha online documentation

Sencha Touch knows how to handle various mobile-specific events. Check out the documentation for the class Ext.dom.Element: you’ll find such events as touchstart, touchend, tap, doubletap, swipe, pinch, longpress, rotate, and others.

You can add event listeners by using techniques. One of them is defining the listeners config property during object instantiation. This property is declared in the Ext.Container object and makes it possible for you to define more than one listener at a time. You should use it while calling the Ext.create() method:

Ext.create('Ext.button.Button', {

   listeners: {

     tap: function() { // handle event here }

   }

}

If you need to handle an event only once, you can use the option single: true, which will automatically remove the listener after the first handling of the event. For example:

listeners: {

  tap: function() { // handle event here },

  single: true

}

TIP

Read the comments to the code of SSC.view.CampaignsMap in Chapter 4 about the right place for declaring listeners.

You can also define event handlers by using yet another config property, control from Ext.Container. Example 12-4 is a code fragment from the Login controller of the Save The Child application. It shows how to assign the tap event handler functions showLoginView() andcancelLogin() for the Login and Cancel buttons.

Example 12-4. Registering tap event handlers

Ext.define('SSC.controller.Login', {

    extend: 'Ext.app.Controller',

    config: {

        control: {

            loginButton: {

                tap: 'showLoginView'

            },

            cancelButton: {

                tap: 'cancelLogin'

            }

        }

    },

    showLoginView: function () {

      // code of this function is removed for brevity

    },

    cancelLogin: function () {

      // code of this function is removed for brevity

    }

});

NOTE

With the proliferation of touch screens, Sencha has introduced the tap gesture, which is semantically equivalent to the click event.

Read more about the role of controllers in event handling in the section Controller. Online documentation includes the Event Guide, which describes the process of handling events in detail.

TIP

If you want to fire custom events, use the method fireEvent(), providing the name of your event. The procedure for defining the listeners for custom events remains the same.

NOTE

Bring Your Own Device (BYOD) is becoming more and more popular in enterprises. Sencha offers a product called Sencha Space, which is a secure and managed environment for deploying enterprise HTML5 applications that can be run on a variety of devices that employees bring to the workplace. Sencha Space promises a clear separation between work-related applications and personal data. It uses a secure database and secure file API and facilitates app-to-app communication. For more details, visit the Sencha Space web page.

Using Sencha Touch for Save The Child

The Sencha Touch version of the Save The Child application is based on the mockup presented in Prototyping the Mobile Version with some minor changes. This time, the home page of the application will be a slightly different version of the About page shown in Figure 12-8.

Building the Application

The materials presented in this chapter were tested with the Sencha Touch 2.3.1 framework, which was current at the time of this writing, and you can use the source code of the Save The Child application that comes with the book. It’s packaged with Sencha 2.3.1. We’ve also deployed this application at link:http://savesickchild.org:8080/ssc-touch-prod.

If you need to use a newer version of Sencha Touch, just download and unzip it to the directory of your choice (in our case, we use /Library/touch-2.3.1). Download the book code and remove the content of the touch directory from Lesson12/ssc-mobile. After that, cd to this directory and copy a newer version of Sencha Touch there. For example, on Mac OS we did it as follows:

cd ssc-mobile

cp -r /Library/touch-2.3.1/ touch

Then, run the Sencha CMD (version 4 or above) command to make a production build of the application and start the embedded web server:

sencha app build

sencha web start

Finally, open this application at http://localhost:1841 in one of the emulators or just on your desktop browser. You’ll see the starting page that looks like Figure 12-8.

The Starting/About page

Figure 12-8. The Starting/About page

We’ll review the code of this application next.

The Application Object

The code of the app.js in the Save The Child project is shown in Example 12-5 (we removed the default startup images and icons for brevity). For the most part, it has the same structure as the Ext JS applications.

Example 12-5. The app.js file of Save The Child

Ext.application({

    name: 'SSC',

    requires: [

        'Ext.MessageBox'

    ],

    views: [

        'About',

        'CampaignsMap',

        'DonateForm',

        'DonorsChart',

        'LoginForm',

        'LoginToolbar',

        'Main',

        'Media',

        'Share',

        'ShareTile'

    ],

    stores: [

        'Campaigns',

        'Countries',

        'Donors',

        'States',

        'Videos'

    ],

    controllers: [

        'Login'

    ],

    launch: function() {

        // Destroy the #appLoadingIndicator element

        Ext.fly('appLoadingIndicator').destroy();

        // Initialize the main view

        Ext.Viewport.add(Ext.create('SSC.view.Main'));

    },

    onUpdated: function() {

        Ext.Msg.confirm(

            "Application Update",

            "This application has just successfully been updated to the latest "

            "version. Reload now?",

            function(buttonId) {

                if (buttonId === 'yes') {

                    window.location.reload();

                }

            }

        );

    }

});

NOTE

Compare this application object with that of Ext JS, shown in Best Practice: MVC. They are similar.

The application loads all the dependencies listed in app.js and instantiates models and stores. The views that require data from the store will either mention the store name (for example, store: 'Videos') or will use the get method from the class StoreMgr (for example,Ext.StoreMgr.get('Campaigns');). After this is done, the launch function is called—and this is where the main view is created.

In this version of the Save The Child application, we have only one controller, Login, that doesn’t use any stores, but the mechanism of pointing controllers to the appropriate store instances is the same as for views. The application instantiates all controllers automatically. Accordingly, all controllers live in the context of the Application object.

We don’t use explicitly defined models here. All the data is hardcoded in the stores in the data attributes.

You’ll see the code of the views a bit later, but we want to draw your attention to the onUpdated() event handler. In the earlier section Microloader and configurations, we mentioned that production builds of Sencha Touch applications watch the locally cached JavaScript and CSS files listed in the JS and CSS sections of the configuration file app.json and compare them with their peers on the server. They also watch all the files listed in the appCache section of app.json. If any of these files change, the onUpdated event handler is invoked. For illustration purposes, we decided to intercept this event. Figure 12-9 shows how the update prompt looks on iPhone 5.

The code on the server has changed

Figure 12-9. The code on the server has changed

At this point, the user can either choose to work with the previous version of the application or reload the new one.

Our index.html file includes one more script (besides the microloader script) that support the Google Maps API:

<script type="text/javascript"

        src="http://maps.google.com/maps/api/js?sensor=true"></script>

TIP

If you want your program documentation to look as good as Sencha’s, use the JSDuck tool.

The Main View

The code of the UI landing page of this application is located in the views folder in the file Main.js. First, take a look at the screenshot from WebStorm in Figure 12-10; note that it shows only two objects on the top level: the container and a login form.

Main.js in a collapsed form

Figure 12-10. Main.js in a collapsed form

The card layout means that the user will see either the content of that container or the login form—one at a time. Let’s open the container. It has an array of children, which are our application pages. Figure 12-11 shows the titles of the children.

TabPanel’s children in a collapsed form

Figure 12-11. TabPanel’s children in a collapsed form

The entire code of Main.js is shown in Example 12-6.

Example 12-6. The complete version of Main.js

Ext.define('SSC.view.Main', {

 extend: 'Ext.Container',

 xtype: 'mainview',                             1

 requires: [

     'Ext.tab.Panel',

     'Ext.Map',

     'Ext.Img'

 ],

 config: {

    layout: 'card',

    items: [

     {

      xtype: 'tabpanel',                         2

              tabBarPosition: 'bottom',

             items: [

               {

                     title: 'About',

                     iconCls: 'info',      3

                     layout: 'fit',        4

                     items: [

                         {xtype: 'aboutview'

                         }

                     ]

                },

               {

                     title: 'Donate',

                     iconCls: 'love',

                     layout: 'fit',

                     items: [

                         {xtype: 'logintoolbar',   5

                          title: 'Donate'

                         },

                         {xtype: 'donateform'

                         }

                     ]

                },

               {

                     title: 'Stats',

                     iconCls: 'pie',

                     layout: 'fit',

                     items: [

                         {xtype: 'logintoolbar',

                          title: 'Stats'

                         },

                         {xtype: 'donorschart'

                         }

                     ]

                 },

               {

                    title: 'Events',

                    iconCls: 'pin',

                    layout: 'fit',

                    items: [

                        {xtype: 'logintoolbar',

                         title: 'Events'

                        },

                        {xtype: 'campaignsmap'

                        }

                    ]

                },

               {

                    title: 'Media',

                    iconCls: 'media',

                    layout: 'fit',

                    items: [

                        {xtype: 'mediaview'

                        }

                    ]

                },

               {

                    title: 'Share',

                    iconCls: 'share',

                    layout: 'fit',

                    items: [

                        {xtype: 'logintoolbar',

                            title: 'Share'

                        },

                        {xtype: 'shareview'

                        }

                    ]

                }

             ]

     },

     {xtype: 'loginform',

         showAnimation: {

             type: 'slide',

             direction: 'up',

             duration: 200

         }

     }

    ]

 }

});

1

We’ve assigned the xtype: 'mainview' to the main view so that the Login controller can refer to it.

2

Note that the tabpanel doesn’t explicitly specify any layout; it uses card by default.

3

Each tab has a corresponding button on the toolbar. It shows the text from the title attribute and the icon specified in the class iconCls.

4

Each view has the fit layout, which forces the content to expand to fill the layout’s container.

5

Each view has a Login button on the toolbar. It’s implemented in LoginToolbar.js, shown later in this chapter.

Sencha Touch can render icons by using icon fonts from the Pictos library located in the folder resources/sass/stylesheets/fonts. We’ve used icon fonts in the jQuery Mobile version of our application, and in this version we’ll also use fonts, which consume much less memory than images.Example 12-7 presents the content of our app.scss file, which includes several font icons used in the Save The Child application.

Example 12-7. The application styles are located in app.scss

@import 'sencha-touch/default';

@import 'sencha-touch/default/all';

@include icon-font('IcoMoon', inline-font-files('icomoon/icomoon.woff', woff,

'icomoon/icomoon.ttf', truetype,'icomoon/icomoon.svg', svg));

@include icon('info',  '!', 'IcoMoon');

@include icon('love',  '"', 'IcoMoon');

@include icon('pie',   '#', 'IcoMoon');

@include icon('pin',   '$', 'IcoMoon');

@include icon('media', '%', 'IcoMoon');

@include icon('share', '&', 'IcoMoon');

.child-img {

  border: 1px solid #999;

}

// Reduce size of the icons to fit 6 buttons in the tabbaradd Share tab

.x-tabbar.x-docked-bottom .x-tab {

  min-width: 2.8em;

  .x-button-icon:before {

    font-size: 1.4em;

  }

}

// Share icons

.icon-twitter.icon-facebook.icon-google-plus.icon-camera {

  font-family: 'icomoon';

  speak: none;

  font-style: normal;

  font-weight: normal;

  font-variant: normal;

  text-transform: none;

  line-height: 1;

  -webkit-font-smoothing: antialiased;

}

.icon-twitter:before {

  content: "\27";

}

.icon-facebook:before {

  content: "\28";

}

.icon-google-plus:before {

  content: "\29";

}

.icon-camera:before {

  content: "\2a";

}

// Share tiles

.share-tile {

  top: 25%;

  width: 100%;

  position: absolute;

  text-align: center;

  border-width: 0 1px 1px 0;

  p:nth-child(1) {

    font-size:4em;

  }

  p:nth-child(2) {

    margin-top: 1.5em;

    font-size: 0.9em;

  }

}

$sharetile-border: #666 solid;

.sharetile-twitter {

  border: $sharetile-border;

  border-width: 0 1px 1px 0;

}

.sharetile-facebook {

  border: $sharetile-border;

  border-width: 0 0 1px;

}

.sharetile-gplus {

  border: $sharetile-border;

  border-width: 0 1px 0 0;

}

// Media

.x-videos {

  .x-list-item > .x-innerhtml {

    font-weight: bold;

    line-height: 18px;

    min-height: 88px;

    > span {

      display: block;

      font-size: 14px;

      font-weight: normal;

    }

  }

  .preview {

    float: left;

    height: 64px;

    width: 64px;

    margin-right: 10px;

    background-size: cover;

    background-position: center center;

    background: #eee;

    @include border-radius(3px);

    -webkit-box-shadow: inset 0 0 2px rgba(0,0,0,.6);

  }

  .x-item-pressed,

  .x-item-selected {

    border-top-color: #D1D1D1 !important;

  }

}

The first two lines of app.scss import the icons from the default theme. We’ve added several more. Note that we had to reduce the size of the icons to fit six buttons in the application’s toolbar. All the @include statements use the SASS mixin icon().

If you need more icons, use the IcoMoon application. Pick an icon there and click the Font button to generate a custom font (see Figure 12-12). Download and copy the generated fonts into your resources/sass/stylesheets/fonts directory and add them to app.scss by using the @include icon-font directive. The downloaded ZIP file will contain the fonts as well as the index.html file that will show you the class name and the code of the generated font icon(s).

Generating Twitter icon font with IcoMoon

Figure 12-12. Generating Twitter icon font with IcoMoon

When you compile the SASS with compass (or build the application by using Sencha CMD), the SASS styles are converted into a standard CSS file, resources/css/app.css.

Controller

Now let’s review the code of the Login page controller, which reacts to the user’s actions performed in the view LoginForm. The name of the controller’s file is Login.js. It’s located in the folder controller, and Example 12-8 presents the code.

Example 12-8. The Login controller

Ext.define('SSC.controller.Login', {

    extend: 'Ext.app.Controller',

    config: {

        refs: {

            mainView: 'mainview',                 1

            loginForm: 'loginform',               2

            loginButton: 'button[action=login]',  3

            cancelButton: 'loginform button[action=cancel]'

        },

        control: {                                 4

            loginButton: {

                tap: 'showLoginView'

            },

            cancelButton: {

                tap: 'cancelLogin'

            }

        }

    },

    showLoginView: function () {

        this.getMainView().setActiveItem(1);  5

    },

    cancelLogin: function () {

        this.getMainView().setActiveItem(0);  6

    }

});

1

Including mainView: 'mainview' in the refs attribute forces Sencha Touch to generate the getter function getMainView(), providing access to the main view if need be.

2

This controller uses components from the LoginForm view (its code comes a bit later).

3

The loginButton is the one that has action=login. The cancelButton is the one that’s located inside the loginform and has action=cancel.

4

Defining the event handlers for tap events for the buttons Login and Cancel from the LoginForm view.

5

The main view has two children (see Figure 12-10). When the user taps the Login button, show the second child: setActiveItem(1).

6

When the user clicks the Cancel button, show the main container: the first child of the main view, setActiveItem(0).

TIP

Controllers are automatically instantiated by the Application object. If you want a controller’s code to be executed even before the application launch function is called, put it in the init function. If you want code to be executed right after the application is launched, put it in the controller’s launch function.

For illustration purposes, we’ll show you a shorter (but not necessarily better) version of Login.js. The preceding code defines a reference to the login form and button selectors in the refs section. Sencha Touch will find the references and generate the getter for these buttons. But in this particular example, we are using these buttons only to assign them the event handlers. Hence, we can make the refs section slimmer and use the selectors right inside the control section, as shown in Example 12-9.

Example 12-9. Making the ref section slimmer in Login controller

Ext.define('SSC.controller.Login', {

    extend: 'Ext.app.Controller',

    config: {

        refs: {

            mainView: 'mainview',

        },

        control: {

            'button[action=login]': {

                tap: 'showLoginView'

            },

            'loginform button[action=cancel]': {

                tap: 'cancelLogin'

            }

        }

    },

    showLoginView: function () {

        this.getMainView().setActiveItem(1);

    },

    cancelLogin: function () {

        this.getMainView().setActiveItem(0);

    }

});

This version of Login.js is shorter, but the first one is more generic. In both versions, the button selectors are the shortcuts for the ComponentQuery class, which is a singleton that is used to search for components.

With the Model-View-Controller (MVC) pattern, the event-processing logic is often located in controller classes. By using refs and ComponentQuery selectors, you can reach event-generating objects located in different classes. For example, if the user taps a button in a view, the controller’s code includes the tap event handler, where it triggers an event on a store class to initiate the data retrieval.

But if the control config is defined not in the controller, but in a component, the scope where ComponentQuery operates is limited to the component itself. You’ll see an example of using the control config inside DonateForm.js, later in this chapter.

Other Views in Save The Child

Let’s do a brief code review of the other Save The Child views.

LoginForm

Figure 12-13 is a snapshot of the Login view taken from an iPhone 5, which was the only mobile device on which we’ve tested this application.

The Login form view

Figure 12-13. The Login form view

Example 12-10 shows the code of the LoginForm view; it’s self-explanatory. The ui: 'decline' is the Ext.Button style that causes the Cancel button to have a red background.

Example 12-10. The LoginForm view

Ext.define('SSC.view.LoginForm', {

  extend: 'Ext.form.Panel',

  xtype: 'loginform',

  requires: [

      'Ext.field.Password'

  ],

  config: {

    items: [

        {   xtype: 'toolbar',

            title: 'Login',

            items: [

                {   xtype: 'button',

                    text: 'Cancel',

                    ui: 'decline',

                    action: 'cancel'

                }

            ]

        },

        {  xtype: 'fieldset',

            title: 'Please enter your credentials',

            defaults: {

                labelWidth: '35%'

            },

            items: [

                {   xtype: 'textfield',

                    label: 'Username'

                },

                {   xtype: 'passwordfield',

                    label: 'Password'

                }

            ]

        },

        {  xtype: 'button',

            text: 'Login',

            ui: 'confirm',

            margin: '0 10'

        }

    ]

  }

});

NOTE

One of the reviewers of this book reported that the text fields from this Login form do not display on his Android Nexus 4 smartphone. This can happen, and it illustrates why real-world applications should be tested on a variety of mobile devices. If you run into a similar situation while developing your application with Sencha Touch, use platform-specific themes, which are automatically loaded based on the detected user’s platform (see the platformConfig object). Sencha Touch offers a number of out-of-the-box schemes and theme switching capabilities.

The Login form displays when the user clicks the Login button that is displayed on each other page in the toolbar. For example, Figure 12-14 shows the top portion of the Donate view.

The Login toolbar

Figure 12-14. The Login toolbar

The Login button is added as xtype: 'logintoolbar' to the top of each view in Main.js. It’s implemented in LoginToolbar.js, shown in Example 12-11.

Example 12-11. The LoginToolbar.js

Ext.define('SSC.view.LoginToolbar', {

  extend: 'Ext.Toolbar',

  xtype: 'logintoolbar',

  config: {

      title: 'Save The Child',

      docked: 'top',                1

      items: [

          {

              xtype: 'spacer'       2

          },

          {

              xtype: 'button',

              action: 'login',

              text: 'Login'

          }

      ]

  }

});

1

The Login toolbar has to be located at the top of the screen.

2

Adding the Ext.Spacer component to occupy all the space before the Login button. By default, the spacer has a flex value of 1, which means it takes all the space in this situation. You can read more about it in Setting proportional layouts by using the flex property.

TIP

If you add the Save The Child application as an icon to the home screen on iOS devices, the browser’s address bar will not be displayed.

DonateForm

We want to make the Donate view look like the mockup that our web designer, Jerry, supplied for us (see Figure 11-10). With jQuery Mobile, it’s simple: the HTML container <fieldset data-role="controlgroup" data-type="horizontal" id="radio-container"> with a bunch of <input type="radio"> rendered the horizontal button bar shown in Figure 11-25Example 12-12 shows the fragment from the initial Sencha Touch version of DonateForm.js.

Example 12-12. The fragment of the initial version of DonateForm.js

 config: {

  title: 'DonateForm',

  items: [

      { xtype: 'fieldset',

          title: 'Please select donation amount',

          defaults: {

              name: 'amount',

              xtype: 'radiofield'

          },

          items: [

              { label: '$10',

                value: 10

              },

              { label: '$20',

                 value: 20

              },

              { label: '$50',

                value: 50

              },

              { label: '$100',

                  value: 100

              }

          ]

      },

      { xtype: 'fieldset',

        title: '... or enter other amount',

          items: [

              { xtype: 'numberfield',

                label: 'Amount',

                name: 'amount'

              }

          ]

      }

It’s also a fieldset with several radio buttons, xtype: 'radiofield'. But the result is not what we expected. These four radio buttons occupy half of the screen, which looks like Figure 12-15.

Rendering of xtype radio field

Figure 12-15. Rendering of xtype radio field

After doing some research, we discovered that Sencha Touch has a UI component called Ext.SegmentedButton with which you can create a horizontal bar with toggle buttons, which is exactly what is needed from the rendering perspective. The resulting Donate screen is shown inFigure 12-16.

Donation form with SegmentedButton

Figure 12-16. Donation form with SegmentedButton

This looks nice, but as opposed to a regular HTML form with inputs, the SegmentedButton is not an HTML <input> field and its value won’t be automatically submitted to the server. This requires a little bit of a manual coding, which will be explained as a part of the DonateForm code review that follows (we’ve split it into two fragments for better readability). Example 12-13 shows the first part.

Example 12-13. The final version of DonateForm.js, part 1

Ext.define('SSC.view.DonateForm', {

 extend: 'Ext.form.Panel',

 xtype: 'donateform',

 requires: [

     'Ext.form.FieldSet',

     'Ext.field.Select',

     'Ext.field.Number',

     'Ext.field.Radio',

     'Ext.field.Email',

     'Ext.field.Hidden',

     'Ext.SegmentedButton',

     'Ext.Label'

 ],

 config: {

     title: 'DonateForm',

     control: {                              1

         'segmentedbutton': {

             toggle: 'onAmountButtonChange'

         },

         'numberfield[name=amount]': {

             change: 'onAmountFieldChange'

         }

     },

     items: [

         { xtype: 'label',

            cls: 'x-form-fieldset-title',    2

            html: 'Please select donation amount:'

         },

         { xtype: 'segmentedbutton',          3

           margin: '0 10',

           defaults: {

               flex: 1

           },

           items: [

                 { text: '$10',

                   data: {

                     value: 10                4

                   }

                 },

                 { text: '$20',

                   data: {

                     value: 20

                    }

                 },

                 { text: '$50',

                   data: {

                     value: 50

                   }

                 },

                 { text: '$100',

                   data: {

                     value: 100

                   }

                 }

             ]

         },

         { xtype: 'hiddenfield',             5

           name: 'amount'

         },

1

Define event listeners for the segmentedbutton and the field for entering another amount. When the control section is used not in a controller, but in a component, it’s scoped to the object in which it was defined. Hence the ComponentQuery will be looking for segmentedbuttonand numberfield[name=amount] only within the DonateForm instance. If these event handlers were defined in the controller, the scope would be global.

2

Borrow the class that Sencha Touch uses for all fieldset containers, so our title looks the same.

3

The segmentedbutton is defined here. By default, its config property is allowToggle=true, which allows only one button to be pressed at a time.

4

The segmentedbutton has no property to store the value of each button. But any sublcass of Ext.Component has the property data. We are extending the data property to store the button’s value. It will be available in the event handler in button.getData().value.

5

Because the buttons in the segmentedbutton are not input fields, we define a hidden field to remember the currently selected amount.

Example 12-14 presents the second half of SSC.view.DonateForm.

Example 12-14. The final version of DonateForm.js, part 2

         { xtype: 'fieldset',

           title: '... or enter other amount',

             items: [

                 { xtype: 'numberfield',     1

                   label: 'Amount',

                   name: 'amount'

                 }

             ]

         },

         {

           xtype: 'fieldset',

           title: 'Donor information',

           items: [

             { name: 'fullName',

               xtype: 'textfield',

               label: 'Full name'

             },

             { name: 'email',

               xtype: 'emailfield',

               label: 'Email'

              }

           ]

         },

         {

             xtype: 'fieldset',

             title: 'Location',

             items: [

                 {  name: 'address',

                    xtype: 'textfield',

                    label: 'Address'

                 },

                 {  name: 'city',

                    xtype: 'textfield',

                    label: 'City'

                 },

                 {  name: 'zip',

                    xtype: 'textfield',

                    label: 'Zip'

                 },

                 {  name: 'state',

                    xtype: 'selectfield',

                    autoSelect: false,

                    label: 'State',

                    store: 'States',

                    valueField: 'id',

                    displayField: 'name'

                 },

                 {  name: 'country',

                    xtype: 'selectfield',

                    autoSelect: false,

                    label: 'Country',

                    store: 'Countries',

                    valueField: 'id',

                    displayField: 'name'

                 }

             ]

         },

         {

             xtype: 'button',

             text: 'Donate',

             ui: 'confirm',

             margin: '0 10 20'

         }

     ]

 },

 onAmountButtonChange: function (segButton,

                                button, isPressed) { 2

  if (isPressed) {                                   3

      this.clearAmountField();

      this.updateHiddenAmountField(button.getData().value);

      button.setUi('confirm');                       4

  }

  else {

      button.setUi('normal');

  }

 },

 onAmountFieldChange: function () {         5

   this.depressAmountButtons();

   this.clearHiddenAmountField();

 },

 clearAmountField: function () {

   var amountField = this.down('numberfield[name=amount]');

   amountField.suspendEvents();            6

   amountField.setValue(null);

   amountField.resumeEvents(true);         7

 },

 updateHiddenAmountField: function (value) {

   this.down('hiddenfield[name=amount]').setValue(value);

 },

 depressAmountButtons: function () {

   this.down('segmentedbutton').setPressedButtons([]);

 },

 clearHiddenAmountField: function () {

   this.updateHiddenAmountField(null);

 }

});

1

This numberfield stores the other amount, if entered. Note that it has the same name amount as the hidden field. The methods clearAmountField() and clearHiddenAmountField() ensure that only one of the amounts has a value.

2

When the toggle event is fired, it comes with an object that contains a reference to the button that was toggled, and whether the button becomes pressed as the result of this event.

3

The toggle event is dispatched twice: once for the button that is pressed, and again for the button that was pressed before. If the button is clicked (isPressed=true), clean the previously selected amount and store a new one in the hidden field.

4

Change the style of the button to make it visibly highlighted. We use the predefined confirm style (see the Kitchen Sink application for other button styles).

5

When the other amount field loses focus, this event handler is invoked. The code cleans up the hidden field and removes the pressed state from all buttons.

6

Temporarily suspend dispatching events while setting the value of the amount numberfield to null. Otherwise, setting to null would cause unnecessary dispatching of the change event.

7

Resume event dispatching. The true argument is for discarding all the queued events.

Previous versions of the Save The Child application illustrated how to submit the Donate form to the server for further processing. The Sencha Touch version of this application doesn’t include this code. If you’d like to experiment with this, just create a new controller class that extendsExt.app.Controller and define an event handler for the Donate Now button (see the Login controller as an example).

On the tap event, invoke donateform.submit(), specifying the URL of the server that knows how to process this form. You can find details on submitting and populating forms in the online documentation for Ext.form.Panel—the ancestor of the “DonateForm”.

TIP

If you want to use Ajax-based form submission, use submit(). Otherwise, use the method standardSubmit(), which performs a standard HTML form submission.

Charts

The charting support is just great in Sencha Touch (and similar to Ext JS). It’s JavaScript based, and the charts are live and can get the data from the stores and model. Figure 12-17 shows how the chart looks on an iPhone when the user selects the Stats page.

Donor’s statistics chart

Figure 12-17. Donor’s statistics chart

The code that supports the UI part of the chart is located in the view DonorsChart that’s shown in Example 12-15. It uses the classes located in the Sencha Touch framework in the folder src/chart.

Example 12-15. The view DonorsChart.js

Ext.define('SSC.view.DonorsChart', {

    extend: 'Ext.chart.PolarChart',         1

    xtype: 'donorschart',

    requires: [

        'Ext.chart.series.Pie',

        'Ext.chart.interactions.Rotate'     2

    ],

    config: {

        store: 'Donors',                    3

        animate: true,

        interactions: ['rotate'],

        legend: {                           4

            inline: false,

            docked: 'left',

            position: 'bottom'

        },

        series: [

            {

                type: 'pie',

                donut: 20,

                xField: 'donors',

                labelField: 'location',

                showInLegend: true,

                colors: ["#115fa6", "#94ae0a", "#a61120", "#ff8809",

                 "#ffd13e", "#a61187", "#24ad9a", "#7c7474", "#a66111"]

            }

        ]

    }

});

1

Create a chart that uses polar coordinates.

2

The Rotate class allows the user to rotate (with a finger) a polar chart around its central point.

3

The data shown on the chart comes from the store named Donors, which is shown in the section Stores and Models.

4

The legend is a bar at the bottom of the screen. The user can horizontally scroll it with a finger.

Media

The Media page of our application displays the list of available videos. When the user taps one of them, a new page opens on which the user must tap the Play button. We use the Ext.dataview.List component to display video titles from the Videos store.

The Media view extends Ext.NavigationView, which is a container with the card layout that also allows pushing a new view into this container. We use it to create a view for the selected video from the list. The code of the Media view is shown in Example 12-16.

Example 12-16. The view Media.js

Ext.define('SSC.view.Media', {

  extend: 'Ext.NavigationView',

  xtype: 'mediaview',

  requires: [

      'Ext.Video'                       1

  ],

  config: {

      control: {

          'list': {

              itemtap: 'showVideo'      2

          }

      },

      useTitleForBackButtonText: true,  3

      navigationBar: {

          items: [

              {   xtype: 'button',

                  action: 'login',

                  text: 'Login',

                  align: 'right'

              }

          ]

      },

      items: [

          {   title: 'Media',

              xtype: 'list',

              store: 'Videos',

              cls: 'x-videos',

              variableHeights: true,

              itemTpl: [                         4

                  '<div class="preview"

                  style="background-image:url(resources/media/{thumbnail});">

                  </div>',

                  '{title}',

                  '<span>{description}</span>'

              ]

          }

      ]

  },

  showVideo: function (view, index, target, model) {

      this.push(Ext.create('Ext.Video', {        5

          title: model.get('title'),

          url: 'resources/media/' + model.get('url'),

          posterUrl: 'resources/media/' + model.get('thumbnail')

      }));

  }

});

1

Sencha Touch offers Ext.Video a wrapper for the HTML5 <video> tag. In Chapter 4, we used the HTML5 tag <video> directly.

2

Define the event listener for the itemtap event, which fires whenever the list item is tapped.

3

When the video player’s view is pushed to the Media page, we want its Back button to display the previous view’s title, which is Media. It’s a config property in NavigationView.

4

The list with descriptions of videos is populated from the store Videos by using the list’s config property itemTpl. This is an HTML template for rendering each item. We decided to use the <div> showing the content of the store’s properties title, description with a background image from the property thumbnail, and the video located at the specified url. The source code of the store Videos is included in the section Stores and Models.

5

Create a video player and push it into NavigationView. When the itemtap event is fired, it passes several values to the function handler. We just use the model that corresponds to the tapped list item. For all available config properties, refer to the Ext.Video documentation.

NOTE

A template [Ext.Template] represents an HTML fragment. The values in square braces are passed to the template from the outside. In the preceding example, the values are coming from the store Videos. The class Ext.XTemlate offers advanced templating—for example, auto-filling HTML with the data from an array, which is used here.

Maps

Integration with Google Maps is a pretty straightforward task in Sencha Touch, which comes with Ext.Map, a wrapper class for the Google Maps API. Our view CampainsMap is a subclass of Ext.Map. Note that we’ve imported the Google Maps API in the file index.html as follows:

<script type="text/javascript"

        src="http://maps.google.com/maps/api/js?sensor=true"></script>

Figure 12-18 shows the iPhone’s screen when the Events button is tapped.

The Events page

Figure 12-18. The Events page

Of course, some additional styling is needed before offering this view in a production environment, but the CampaignsMap.js that supports this screen (see Example 12-17) is only 90 lines of code!

Example 12-17. The view CampaignsMap.js

Ext.define('SSC.view.CampaignsMap', {

  extend: 'Ext.Map',

  xtype: 'campaignsmap',

  config: {                                   1

      listeners: {

          maprender: function () {            2

            if (navigator && navigator.onLine) {

                try {

                    this.initMap();

                    this.addCampaignsOnTheMap(this.getMap());

                } catch (e) {

                    this.displayGoogleMapError();

                }

            } else {

                this.displayGoogleMapError();

            }

          }

      }

  },

  initMap: function () {

      // latitude = 39.8097343 longitude = -98.55561990000001

      // Lebanon, KS 66952, USA Geographic center

      // of the contiguous United States

      // the center point of the map

      var latMapCenter = 39.8097343,

          lonMapCenter = -98.55561990000001;

      var mapOptions = {

          zoom     : 3,

          center   : new google.maps.LatLng(latMapCenter, lonMapCenter),

          mapTypeId: google.maps.MapTypeId.ROADMAP,

          mapTypeControlOptions: {

              style   : google.maps.MapTypeControlStyle.DROPDOWN_MENU,

              position: google.maps.ControlPosition.TOP_RIGHT

          }

      };

      this.setMapOptions(mapOptions);

  },

  addCampaignsOnTheMap: function (map) {

      var marker,

          infowindow = new google.maps.InfoWindow(),

          geocoder   = new google.maps.Geocoder(),

          campaigns  = Ext.StoreMgr.get('Campaigns');

      campaigns.each(function (campaign) {

          var title       = campaign.get('title'),

              location    = campaign.get('location'),

              description = campaign.get('description');

          geocoder.geocode({

              address: location,

              country: 'USA'

          }, function(results, status) {

              if (status == google.maps.GeocoderStatus.OK) {

                 // getting coordinates

                 var lat = results[0].geometry.location.lat(),

                     lon = results[0].geometry.location.lng();

                 // create marker

                 marker = new google.maps.Marker({

                     position: new google.maps.LatLng(lat, lon),

                     map     : map,

                     title   : location

                 });

                 // adding click event to the marker to show info-bubble

                 // with data from json

                 google.maps.event.addListener(marker, 'click', (function(marker)

                     {

                     return function () {

                         var content = Ext.String.format(

                             '<p class="infowindow"><b>{0}</b><br/>{1}

                             <br/><i>{2}</i></p>',

                             title, description, location);

                         infowindow.setContent(content);

                         infowindow.open(map, marker);

                     };

                 })(marker));

              } else {

                 console.error('Error getting location data for address: ' +

                                                                location);

              }

          });

      });

  },

  displayGoogleMapError: function () {

    console.log("Sorry, Google Map service isn't available");

  }

});

1

We use just the listeners config here, but Ext.Map has 60 of them. For example, if we wanted the mobile device to identify its current location and put it in the center of the map, we’d add useCurrentLocation: true.

2

This event is fired when the map is initially rendered. We are reusing the same code as in previous chapters for initializing the map (showing the central point of the United States) and adding the campaign information. The code of the store Campaigns is shown in the section Stores and Models.

Sencha Touch is a framework for mobile devices, which can be on the move. Ext.util.Geolocation is a handy class for applications that require knowing the current position of the mobile device. When your program instantiates Geolocation, it starts tracking the location of the device by firing the locationupdate event periodically (you can turn auto updates off). Example 12-18 shows how to get the current latitude of the mobile device.

Example 12-18. Getting the current latitude of the device

var geo = Ext.create('Ext.util.Geolocation', {

  listeners: {

    locationupdate: function(geo) {

       console.log('New latitude: ' + geo.getLatitude());

    }

  }

});

geo.updateLocation();  // start the location updates

Stores and Models

In the Sencha Touch version of the Save The Child application, all the data is hard-coded. All store classes are located in the store directory (see Figure 12-11), and each of them has the data property. Example 12-19 presents the code of Videos.js.

Example 12-19. The store Video.js

Ext.define('SSC.store.Videos', {

  extend: 'Ext.data.Store',

  config: {

      fields: [

          { name: 'title',       type: 'string' },

          { name: 'description', type: 'string' },

          { name: 'url',         type: 'string' },

          { name: 'thumbnail',   type: 'string' }

      ],

      data: [

          { title: 'The title of a video-clip 1', description: 'Short video

          description 1', url: 'intro.mp4', thumbnail: 'intro.jpg' },

          { title: 'The title of a video-clip 2', description: 'Short video

          description 2', url: 'intro.mp4', thumbnail: 'intro.jpg' },

          { title: 'The title of a video-clip 3', description: 'Short video

          description 3', url: 'intro.mp4', thumbnail: 'intro.jpg' }

      ]

  }

});

WARNING

There is a compatibility issue between Ext JS and Sencha Touch 2 stores and models. For example, in the preceding code, fields and data are wrapped inside the config object, whereas in the Ext JS store they are not. Until Sencha offers a generic solution to resolve these compatibility issues, you have to come up with your own if you want to reuse the same stores.

The code of the Donors store supports the charts on the Stats page. It’s self-explanatory, as you can see in Example 12-20.

Example 12-20. The store Donors.js

Ext.define('SSC.store.Donors', {

  extend: 'Ext.data.Store',

  config: {

      fields: [

          { name: 'donors',   type: 'int' },

          { name: 'location', type: 'string' }

      ],

      data: [

          { donors: 48, location: 'Chicago, IL' },

          { donors: 60, location: 'New York, NY' },

          { donors: 90, location: 'Dallas, TX' },

          { donors: 22, location: 'Miami, FL' },

          { donors: 14, location: 'Fargo, ND' },

          { donors: 44, location: 'Long Beach, NY' },

          { donors: 24, location: 'Lynbrook, NY' }

      ]

  }

});

The Campaigns store is used to display the markers on the map, where charity campaigns are active. Tapping the marker will show the description of the selected campaign, as shown in Figure 12-18 (we tapped the Chicago marker). Example 12-21 presents the code of the storeCampaigns.js.

Example 12-21. The store Campaigns.js

Ext.define('SSC.store.Campaigns', {

    extend: 'Ext.data.Store',

    config: {

        fields: [

            { name: 'title',       type: 'string' },

            { name: 'description', type: 'string' },

            { name: 'location',    type: 'string' }

        ],

        data: [

            {

                title: 'Mothers of Asthmatics',

                description: 'Mothers of Asthmatics - nationwide Asthma network',

                location: 'Chicago, IL'

            },

            {

                title: 'Lawyers for Children',

                description: 'Lawyers offering free services for the children',

                location: 'New York, NY'

            },

            {

                title: 'Sed tincidunt magna',

                description: 'Donec ac ligula sit amet libero vehicula laoreet',

                location: 'Dallas, TX'

            },

            {

                title: 'Friends of Blind Kids',

                description: 'Semi-annual charity events for blind kids',

                location: 'Miami, FL'

            },

            {

                title: 'Place Called Home',

                description: 'Adoption of the children',

                location: 'Fargo, ND'

            }

        ]

    }

});

Working with Landscape Mode

Handling landscape mode with Sencha Touch is done differently depending on how you deploy your application. If you decide to package this app as a native one, landscape mode will be supported. Sencha CMD will generate the file packager.json, which will include a section dealing with orientation:

    "orientations": [

        "portrait",

        "landscapeLeft",

        "landscapeRight",

        "portraitUpsideDown"

    ]

If you’re not planning to package your app as a native one, you’ll need to do some manual coding by processing the orientationchange event. For example:

Ext.Viewport.on('orientationchange', function() {

   // write the code to handle the landscape code here

});

This concludes the review of the Sencha Touch version of our sample application, which consists of six nice-looking screens. The amount of manual coding to achieve this is minimal. In the real world, you’d need to add business logic to this application, which comes down to inserting the JavaScript code into well-structured layers. The code to communicate with the server goes to the stores, the data is placed in the models, the UI remains in the views, and the main glue of your application is controllers. Sencha Touch does a good job for us, wouldn’t you agree?

Comparing jQuery Mobile and Sencha Touch

In Chapter 11 and this chapter, you’ve learned about two different ways of developing a mobile application. So, what’s better, jQuery Mobile or Sencha Touch? There is no correct answer to this question, and you will have to make a decision on your own. But here’s a quick summary of pros and cons for each library or framework.

Use jQuery Mobile if the following are true:

§  You are afraid of being locked into any one vendor. The effort to replace jQuery Mobile in your application with another framework (if you decide to do so) is a magnitude lower than switching from Sencha Touch to something else.

§  You need your application to work on most mobile platforms.

§  You prefer declarative UI and hate debugging JavaScript.

Use Sencha Touch if the following are true:

§  You like to have a rich library of precreated UIs.

§  Your application needs smooth animation. Sencha Touch performs automatic throttling based on the actual frames-per-second supported on the device.

§  Splitting the application code into cleanly defined architectural layers (model-view-controller-service) is important.

§  You believe that using code generators adds value to your project.

§  You want to be able to customize and extend components to fit your application’s needs perfectly. Yes, you’ll be writing JavaScript, but it still may be simpler than trying to figure out the enhancements done to an HTML component by jQuery Mobile under the hood.

§  You want to minimize the effort required to package your application as a native one.

§  You want your application to look as close to the native ones as possible.

§  You prefer to use software that is covered by the commercial support offered by a vendor.

While considering support options, do not just assume that paid support translates into better quality. This is not to say that Sencha won’t offer you quality support, but in many cases, having a large community of developers will lead to a faster solution to a problem than dealing with one assigned support engineer. Having said this, we’d like you to know that the Sencha forum has about half a million registered users who are actively discussing problems and offering solutions to one another.

Even if you are a developer’s manager, you don’t have to make the framework choice on your own. Bring your team into a conference room, order pizza, and listen to what your team members have to say about these two frameworks, or any other, being considered. We have offered you information about two of many frameworks, but the final call is yours.