Category Archives: Sencha Touch 2

Sencha Touch 2.3: Downloading and Saving Large Binary Files to Local Devices with PhoneGap/Cordova

Note: This article pertains to PhoneGap/Cordova 3.5

Sencha Touch 2.3 has added a new Cordova/PhoneGap abstraction layer to the Ext.Device class. Unfortunately the documentation/guides don’t seem to have an example of downloading and saving a binary file to the device’s filesystem.

Since mobile connectivity can be unreliable and usually produces high latency, being able to cache large documents on the device is critical for developing high-performance apps.

Before you can get started, you’ll need to install phonegap:

  1. Install Java JRE
  2. Install Node.JS
  3. Install PhoneGap by typing the following at a command prompt:
    npm install -g phonegap

Next, you’ll need to use Sencha Command 4.x+ to create a phonegap project. Simply change directories to the root of your application’s folder and issue the following command:

sencha phonegap init [AppId]

Where [AppId] is your application’s id (e.g. com.figleaf.myapp)

This will create a /phonegap folder off your project root.

Change directories to the phonegap subdirectory and install common phonegap plugins to enable the Sencha Touch Ext.Device classes to detect device type, enable file system access, and get real-time network information:

  • phonegap plugin add org.apache.cordova.device
  • phonegap plugin add org.apache.cordova.file
  • phonegap plugin add org.apache.cordova.network-information

Now you can get started with adding the download feature!

Request access to the local filesystem by using the code below, which I typically place in the application’s launch function, caching the result in the application object.

Ext.device.FileSystem.requestFileSystem({
    
    type: PERSISTENT,
    size: 50 * 1024 * 1024, // 50mb -- gonna store video
    success: function(fs) {
        MyApp.app.Fs = fs;
    },
    failure: function() {
        Ext.Msg.alert("FileSystem Error","Could not access the local file system<br>You will not be able to save notifications for offline use.");
    }
});

The following controller function illustrates how to download a file with a progress indicator and save it locally on the device. Note that the local file URL is returned to the callback function and would typically be written to a data store.

Also, note that the file had to be written out in chunks, otherwise the fileWrite() method would gpf the app somewhere north of a 10MB file size.

saveFile: function(url,recordId,prompt,callback) {

 var me = this;

 // create progress indicator
 var progIndicator = Ext.create('Ext.ProgressIndicator', {
    loadingText: prompt + " {percent}%",
    modal: true
 });

 // create unique filename
 var fileName = url.split('/');
 fileName = fileName[fileName.length - 1];
 fileName = "notification-" + recordId + '-' + fileName;

 // let's get the party started!
 Ext.Ajax.request({

    url: url,
    timeout: 180000,
    useDefaultXhrHeader: false,
    method: 'GET',
    xhr2: true,
    progress: progIndicator,
    responseType: 'blob',
    success: function(response) {
        
        Ext.defer(
           function(p) {
               p.destroy();
           },
           250,
           this,
           [progIndicator]
       );

       // define file system entry
       var fe = new Ext.device.filesystem.FileEntry("/" + fileName, MyApp.app.Fs);

       fe.getEntry({
               
                file: fileName,
                options: {create: true},
                success: function(entry) {

                    console.log('entry',entry);

                    fullPath = entry.nativeURL;
                    console.log(fullPath);

                     Ext.Viewport.setMasked({xtype:'loadmask'});
                    
                     entry.createWriter(function(fileWriter) {

                         // write data in blocks so as not to
                         // gpf iOS

                         var written = 0;
                         var BLOCK_SIZE = 1*1024*1024;
                         var filesize = response.responseBytes.size;
                                           
                         fileWriter.onwrite = function(evt) {
                             if (written < filesize) {
                               fileWriter.doWrite();
                             } else {
                               Ext.Viewport.setMasked(false);
                               if (callback) { 
                                  callback.call(me,fullPath);
                               }  
                             }
                         };
                         
                         fileWriter.doWrite = function() {
                             
                             var sz = Math.min(BLOCK_SIZE, filesize - written);
                             var sub = response.responseBytes.slice(written, written+sz);
                             console.log('writing bytes ' + written + " to " + written+sz);
                             written += sz;  
                             fileWriter.write(sub);         
                         };
                         
                         fileWriter.doWrite();
                                  

                    });

                },
                
                failure: function(error) {              
                    Ext.Msg.alert("Transaction Failed (02)", error.message);
                }
        });

    },
    failure: function(error) {
       progIndicator.destroy();
       Ext.Msg.alert("Transaction Failed (03)", error.message);
    }

});

Note that while you’ll be able to test this code on device simulators, the Ext.device.filesystem.FileEntry.getEntry() method will fail if the app is run through desktop Chrome.

An alternative approach that we used for Android and Cordova 3.5 involved calling the fileTransfer API’s download method. To install the file transfer plugin, invoke the following command:

cordova plugin add org.apache.cordova.file-transfer

After installing the plugin, you can access the fileTransfer.download() method as illustrated by the following snippet:

function saveFile(url,recordId,prompt,callback) {

 var me = this;

 Ext.Viewport.setMasked({xtype:'loadmask'});

 var fileName = url.split('/');
 fileName = fileName[fileName.length - 1];
 fileName = "notification-" + recordId + '-' + fileName;


 var newFilePath = MyApp.app.Fs.fs.root.fullPath + fileName;

 MyApp.app.Fs.fs.root.getFile(
    "dummy.html", 
    {create: true, exclusive: false}, 
    function success(fe) {
         var sPath = fe.toURL().replace("dummy.html","");
         var fileTransfer = new FileTransfer();  
         fe.remove();
         fileTransfer.download(
           url,
           sPath + fileName,
           function(theFile) {
             Ext.Viewport.setMasked(false);
             if (callback) {
                callback.call(me,theFile.toURI());
             }
           },
           function(error) {
             Ext.Viewport.setMasked(false);
             Ext.Msg.alert('Failed',JSON.stringify(error));
           }
         );
    }
 );
}

Happy coding!

Using Routes with Sencha Touch Navigation Views

Note: This post pertains to Sencha Touch 2.3.1

Using routes in Sencha Touch enables you to support the Android “back” button as well as allow for “deep linking” to a deeply nested view.

The only problem with using routes is that there’s not a lot of documentation or simple examples that describe integrating them with a Sencha Touch Navigation View, which is often used as the primary mechanism for navigating within small mobile phone apps.

Supporting routes is a three-step process:

1) Override the Ext.History class
It’s not just your imagination — it does appear as though the cards were stacked against you from the beginning. There’s actually a bit of a bug in the Ext.History class that prevents the history from dequeuing properly. To fix it, drop in this override:

Ext.define('MyApp.overrides.History', {
	override: 'Ext.app.History',

	back: function() {

        var actions = this.getActions(),
            previousAction = actions[actions.length - 2];

        if (previousAction) {
            
            actions.pop(); // pop current view

            // Added by Steve Drucker
            // need to pop previous view, because it will get reinstantiated on next line
            actions.pop(); 

            previousAction.getController().getApplication().redirectTo(previousAction.getUrl());
        }
        else {
            actions[actions.length - 1].getController().getApplication().redirectTo('');
        }
    }
});

2) Override the Default Navigation View Back Button

Ext.define('MyApp.controller.Main', {
    extend: 'Ext.app.Controller',

    requires: [
        'Ext.app.Route'
    ],

    config: {
        routes: {
            '#login': 'onLogin',
            '#forgotpassword': 'onForgotPassword',
            '': 'onHome',
            '#notificationList': 'onNotificationList'
        },

        refs: {
            main: 'main',
            appBackButton: 'main button[ui=back]' // target acquired
        },

        control: {
            "main": {
                show: 'onNavigationviewShow'
            },
            "appBackButton": {
                tap: function(button, e) {
                  var appHistory = this.getApplication().getHistory();

                  // fire previous route
                  appHistory.back();

                  // prevent the default navigation view
                  // back button behavior from firing
                  return false;

                }
            }
        }
    }
});

3) Pop Back to Previous Views in your Route Handlers
Since you disabled the default “pop” action in step 2, you’ll need to deal with this in your route handlers by following the pattern illustrated in the following snippet:

onNotificationSelect: function(id) {
 
 var record = Ext.getStore('Notifications').getById(id);
 
 // if view does not exist, instantiate it
 if (Ext.ComponentQuery.query('notificationdetail').length == 0) {

    this.getMain().push({
        xtype: 'notificationdetail',
        title: record.get('headline'),
        record: record
    });

 } else {

    // we're popping back to the view
    // from a "back" button push
    this.getMain().pop('notificationdetail');
 }
}

Happy coding!

9 Chrome Plugins to help you debug your web apps and GSD

I’m personally obsessed with GSD (getting stuff done) and find the following (free) developer tools to be invaluable in this regard. Check ’em out, and if you have any other suggestions, please list them in the comments!

POSTMAN

Postman is a free Google Chrome App that enables you to easily test your REST API’s.

Image

Features include:

  • Ability to create requests quickly
  • Simulate HTTP requests with file upload support
  • Auto-Formatting of JSON and XML responses
  • Basic an OAuth Helpers
  • Share collections between developers
  • Create unit tests by adding “Jetpacks” ($9.95 US)

CLEAR CACHE

Clear Cache is a free Google Chrome App that enables you to reliably clear your browser cache at the click of a button.

Image

APACHE RIPPLE

Apache Ripple™ is a web based mobile environment simulator designed to enable rapid development of mobile web applications for various web application frameworks, such as Apache Cordova™ and BlackBerry® WebWorks™. It can be paired with current web based mobile development workflows to decrease time spent developing and testing on real devices and/or simulators.

Image

It is free software, licensed under the Apache License, Version 2.0.

Features include:

  • Disabling cross-domain security restrictions
  • Accelerometer simulation
  • Geolocation simulation

JSON EDITOR ONLINE

JSON Editor is a Chrome plugin for viewing, editing, and formatting JSON. It shows your data in an editable treeview and in a code editor.

JSONEditor

Features include:

  • View and edit JSON side by side in treeview and a code editor.
  • Edit, add, move, remove, and duplicate fields and values.
  • Change type of values.
  • Sort arrays and objects.
  • Colorized values, color depends of the value type.
  • Search & highlight text in the treeview.
  • Undo and redo all actions.
  • Load and save files and urls.
  • Format, compact, and inspect JSON in the code editor.

REGEXP TESTER

This free Chrome plugin enables you to quickly and easily verify the validity of your javascript regular expressions.

regexp

ICOMOON

Icomoon enables you to rapidly select and encode icons as a custom font. This is particularly useful  if you’re developing custom themes for Sencha Touch and Ext JS 4.21+ applications.

icomoon

SPEEDTRACER

Speed Tracer by Google is a tool to help you identify and fix performance problems in your web applications. It visualizes metrics that are taken from low level instrumentation points inside of the browser and analyzes them as your application runs.

speedtracer

If you’re obsessed with Javascript RIA application performance, check it out!

CHROMEVOX

If you want to verify that your Javascript-based RIA (Ext JS 4) is accessible for the vision impaired, check out this free Chrome plugin.

APP INSPECTOR FOR SENCHA

App Inspector for Sencha™ is an extension for Google Chrome DevTools which makes debugging Sencha Ext JS and Touch applications easier than ever before!

App Inspector for Sencha™ helps you to inspect your component tree, data stores, events, and layouts. It provides similar functionality to Illuminations for Developers.

appinspector

About Ext JS 4, CORS, and IIS 6

CORS (Cross-Origin Resource Sharing) enables you to run your app from a domain other than where your web services are being hosted. This has a number of advantages, the least of which is that you can get away from using the kludgey JSON-P proxy and rely on AJAX/REST for all of your get/post server transactions while maintaining the flexibility of launching your app from anywhere (including localhost).

Configuring CORS on IIS 7.5 required setting the following http headers:

Image

 

We set identical headers for IIS 6, but for some wacky reason that we have yet to discover, we actually had to remove the Access-Control-Allow-Origin entry for it to work. (I think that our .NET webservices might have been sending that along automatically, and duplicating any headers will cause CORS to fail.

About Sencha Touch 2.x, PhoneGap/Cordova, and the iOS 7 Status Bar

arch1Well, the zany think-tank at Apple sure does seem to enjoy making our lives as cross-device mobile app developers exciting.  And by ‘exciting,’ I mean ‘Oh God, Oh God, WE’RE BLOWING OUR DEADLINE!!!’

Their latest attempt to ‘Think Different’ resulted in changing the behavior of the iOS status bar. In prior versions of iOS, your phonegap-based app ran directly underneath it. However, in iOS 7, your phonegap-based app actually runs ON TOP OF IT as illustrated by the screenshot at right.

So… that, uh.. sucks.

To resolve this issue, I came up with the following hacks:

1) In your index.html file, add the following code to your deviceready handler:

document.addEventListener("deviceready", function() {
  if (parseFloat(window.device.version) === 7.0) {
    console.log('iOS7 is the official OS of SATAN!');
    document.body.style.marginTop = "20px";
  }
  // other phonegap related stuff
});

Next, you’ll need to modify the launch() function in your Sencha Touch app.js file to reduce the size of the viewport by 20 pixels:

Ext.application({
 // bunch of code omitted for brevity...
 name: 'ArchSTL',
 isNative: true,
 launch: function() {
  if (Ext.os.is.iOS) {
    if (Ext.os.version.major >= 7) {
       if (ArchSTL.app.isNative) // manual flag to denote "native" mode
        Ext.Viewport.setHeight(Ext.Viewport.getWindowHeight() - 20);
       }
    }
  }
  // other stuff
 }
});

Ultimately this solves the problem, and you can even set the background color of the status bar by tweaking the body background color CSS style in your index.html file. In this case, we set it to the client’s complementary app color to try and reinforce their branding standards.

Here’s the final result:

archbalt2

But hey, if it was easy, everyone would be doing it…right?

Icon and Splash Screen Reference for Mobile Web/PhoneGap/Cordova Apps

If you’re a mobile web/app developer like myself, you’ve probably had to suffer with getting dirty looks from your art department as you have to go back to them repeatedly for assets to support four different mobile operating systems (iOS, Android, Blackberry, Windows Mobile 8).  In order to avoid these uncomfortable conversations, I bring you a list of all required art assets that you might need in order to get your app published and make your customers happy.

iOS Icons

  • 1024 x 1024, 72 dpi jpg/png for iTunes Connect
  • 57 x 57 for iPhone, iPod Touch
  • 114 x 114 for Retina displays
  • 72  x 72 for iPad non-retina
  • 120 x 120 for iOS 7 Phones
  • 144 x 144 for IPad w/Retina display
  • 152 x 152 for iPad w/Retina display on iOS 7

iOS Spotlight Icons

  • 29 x 29
  • 58 x 58
  • 80 x 80

iOS Settings Icons

  • 29 x 29 (iPhone non-retina)
  • 58 x 58 (iPhone Retina)

iOS Launch Images

  • 320 x 460
  • 320 x 480
  • 640 x 920
  • 640 x 960
  • 640 x 1096
  • 640 x 1136
  • 768 x 1004
  • 748 x 1024
  • 1536 x 2008
  • 1496 x 2048

iOS Startup Images (Native App)

  • 320 x 480 (3.5″ non-retina)
  • 640 x 960  (3.5″ Retina)
  • 640 x 1136 (4 inch iPhone)

Google Play Store Artwork

  • 512 x 512 32-bit PNG icon
  • 1024 x 500 png feature graphic
  • 180 x 120 promo graphic
  • Promo video on youtube

Android Splash Screens

  • xlarge (xhdpi): at least 960 x 720
  • large (hdpi): at least 640 x 480
  • medium (mdpi): at least 470 x 320
  • small (ldpi): at least 426 x 320

Highly recommended to use a 9-patch image

Android Icons

  • 72 x 72
  • 36 x 36
  • 48 x 48
  • 96 x 96

Blackberry 10.x Splash Screen

  • 768×1280 (Z10)
  • 720×720 (Q10)

Blackberry 10.x Icon

  • 114 x 114

Blackberry World

  • 480 x 480 – Store Icon
  • 1920×1186 – Product Featured Image
  • Screenshots – can’t be more than 1280 x 1280

I’ll be updating this with more details later this week…

Using Sencha.io Src in your Touch Apps

before

Sencha.io Src is a nifty little anonymous cloud service available from Sencha Inc. that will automatically scale images for a mobile devices. This is particularly handy if you’re trying to dynamically adapt markup that had been produced for desktop web to a mobile device.

Recently we developed a Sencha-based app for the Archdiocese of St. Louis. One of the functions of this app was to display news articles from http://stlouisreview.com/. Content is delivered to the device from a JSON data feed, however as illustrated below, none of the images were sized appropriately for mobile devices – resulting in a blown-out layout.

Our solution was to simply modify our Sencha Touch container’s display template to dynamically redirect the header image to Sencha io.src as illustrated below. Note that the project was developed using Sencha Architect, thereby necessitating use of an override:

Ext.define('ArchSTL.view.override.news.Detail', {
    override: 'ArchSTL.view.news.Detail',
    config: {
      tpl: Ext.create('Ext.XTemplate',
       '{[this.senchifyUrl(values.description)]}', {

        senchifyUrl: function(str) {

            // match src="url"
            var urls = str.match(/src=(.+?[\.jpg|\.gif|\.png]')/);

            // get width of viewport
            var width = Ext.Viewport.element.getWidth() - 50;
         
            if (urls != null) {
              var url = urls[0].slice(5,-1);
              str = str.replace(url,"http://src.sencha.io/" + width + "/" + url);
            }
      
            return str;
        }
      })
    }
});

after
This change to the template worked great, as illustrated by the final result:

You can learn more about Sencha io.src here:
http://docs.sencha.io/current/index.html#!/guide/src

Check it out!