Monthly Archives: June 2014

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!

Best Restaurants at Disney

My annual pilgrimage to Orlando, FL is quickly approaching where I will once again pay the national tax on having children (commonly referred to in the USA as “theme park admission”). With this in mind, I thought that I might divert from writing about technology for a brief moment and provide some brief guidance to the uninitiated as to where to find some good eats.

But first, a public safety tip:

Do not eat dinner and then immediately ride the Twilight Zone Tower of Terror.

I’ve been to Dis over 15 times and eaten at most of the establishments. I make a point of always experiencing someplace new rather than hitting the same places over and over and, generally speaking, I try to keep my expectations low. I work in Washington, DC which has evolved into quite a foodie-destination over the last decade. I’ve also done a ton of expense-account dining over the years and had some extraordinary meals over the years, so I’m rather difficult to impress. The key to Disney dining is to approach each meal with relatively low expectations.  If you’re looking for great european food, go to Europe because you’re not going to find it at Epcot. In my opinion, Disney has always been best when it sticks to the basics rather than trying to bring great, complex ethnic meals to its customers. Buffet-style arrangements tend to produce better and more consistent results than ordering off a menu.

So, without further ado, Here’s a list of restaurants where the meals exceeded my expectations:

  • Hoop-De-Do Musical Revue @ Fort Wilderness Campground (American BBQ)
  • Flame Tree BBQ @ Animal Kingdom (BBQ)
  • Whispering Canyon Cafe @ Wilderness Lodge (BBQ)
  • Artist Point @ Wilderness Lodge (Pacific Northwest)
  • Boma @ Animal Kingdom Lodge (African)
  • Citricos @ Grand Floridian (American / Mediterranean)
  • Yak & Yeti @ Animal Kingdom (Asian)
  • Cabana Bar and Beach Club @ Dolphin Hotel

A few notes:

  • I have not yet dined at Victoria and Alberts (Grand Floridian) which is generally considered to be the best restaurant in Orlando.
  • I had a great meal at O’hana many years ago, but recent reviews have not been kind.
  • My business associates tell me that we had a great dinner at Bongo’s, but I think that’s just the Mojito’s talking…
  • Had a great time at Chef Mickey’s for breakfast one year, but was disappointed the next. I think that it’s one of those places that you should experience once — but only once.
  • Aborted an attempt to dine at Liberty Tree Tavern after we arrived on-time for our reservation but weren’t seated for over 40 minutes and finally gave up in frustration.

This year we’ll be dropping by a few new places listed below. Somehow I doubt that the sushi from Splitsville is going to get on my list, but who can turn down raw fish served at a bowling-alley? What could possibly go wrong? I do have some high hopes for Liberty Tree Tavern, assuming that we don’t have another reservation melt-down….

  • Tony’s Town Square Restaurant (Magic Kingdom)
  • TomorrowLand Terrace Desert Party
  • Luau Cove
  • T-Rex
  • Rose & Crown Dining Room
  • Biergarten Restaurant
  • Splitsville Luxury Lanes
  • Tusker House (return visit)
  • Narcoossee’s
  • 1900 Park Fare
  • Liberty Tree Tavern

What are your favorites? Feel free to enter a comment in the space below!

 

Top Sixteen Must-Have Drupal Modules

Drupal bills itself as a “Content Management System”, but, in fact, it’s really a “Content Management Framework.” Now, “framework” is really techno-jargon for stating that a piece of software doesn’t really do much “out of the box.” But what makes Drupal so powerful and popular is this pluggable architecture and zealous commitment to open source and community. One of the keys to being a successful Drupal developer is understanding which plug-in modules might be applicable to your business use-case (there are ~6,500 (!) being actively maintained for Drupal 7.x) and how to use them efficiently.

I’ll be teaching Acquia’s Site Building with Drupal class tomorrow in Washington DC (also available online), and one of the questions that comes up almost immediately pertains to which modules we typically use in the sites that we deliver for customers. While most of the following modules are covered in great detail within the course, there are a few additional ones that we typically add to the Acquia Drupal distro that we felt were worth mentioning.

To prevent admins and developers from losing their minds:

  • Admin Menu
    The Admin Menu Module provides a more robust administration menu that uses CSS and JS to give you a drop-down menu hierarchy. Deploying this module makes Drupal’s administration UI a lot easier to work with, particularly as you start installing additional modules and create new content types and views. It also enables you to flush Drupal’s caches in a single click.
  • Module Filter
    The number of modules that you’ll install on your site is likely to be quite long, quickly making the default admin gui painful to work with. This module improves the admin system by adding a text filter field as well as instantiating a tab-based GUI.
  • Features 
    Features provides a UI and API for taking different site building components from modules with exportables and bundling them together in a single feature module. A feature module is like any other Drupal module except that it declares its components (e.g. views, contexts, CCK fields, etc.) in its .info file so that it can be checked, updated, or reverted programmatically. In addition, Features enables developers to check-in features into source control systems, like Git.

For login management:

  • LoginToboggan
    Enables users to login using either their username or their email address. Because users can memorize usernames or passwords, but not both.
  • Password Policy
    This module provides a way to specify a certain level of password complexity (aka. “password hardening”) for user passwords on a system by defining a password policy, thereby ensuring that most users will need to use the password recovery feature…a lot.
  • Masquerade
    This module enables administrators to masquerade as other users in order to test site permissions, roles-based security, and personalized contextual selectors. Never have to ask someone for their password again!

For a better content contributor data input experience:

  • Link
    Provides a standard content field for links. It’s called the world-wide-web for a reason, yo!
  • Linkit
    Provides an easy interface for internal and external linking with editors and fields by using an autocomplete field. Linkit links to nodes, users, managed files, terms and have basic support for all entities by default. It improves upon the Link ui significantly as users do not have to remember specific urls.
  • Date
    Want to add a graphical date field to a Content Type or perform complex date calculations? Of course you do!
  • WYSIWYG
    What’s the worst browser-based WYSIWYG editor? With the WYSIWYG module, you can try all of them out to figure out which one sucks less! This module enables you to swap in all the usual suspects, including FCKEditor, TinyMCE 3, YUI editor, Whizzywig, EpicEditor, and a host of others in place of the default textarea that ships with Drupal 7. Unfortunately, our current favorite, TinyMCE 4 isn’t supported…yet.

For Developers:

  • Views
    This module is an indispensable part of every site that we develop. It enables you to define filters to select content that meet specified criteria and then output those content nodes into a variety of different formats. Views are so important that the module has become a core component of Drupal 8.
  • Views Bulk Operations
    This module augments Views by allowing bulk operations to be executed on the displayed rows. It does so by showing a checkbox in front of each node, and adding a select box containing operations that can be applied. Drupal Core or Rules actions can be used. Typically this module is used to create custom admin interfaces for business users.
  • Devel
    A suite of modules that helps facilitate development and testing, including a dynamic lorum-ipsum content generator. When I get around to it, I’m going to add a Star Trek ipsum generator. Because I can. And because I’m a nerd.
  • Entity Reference (Requires Entity API and Chaos Tools)
    This module facilitates the creation of 1:1 or 1:many relationships between content objects. 
  • Rules
    Allows site administrators to define conditionally executed actions based on occurring events (known as reactive or ECA rules). This is typically used to implement custom workflows.
  • Context
    Allows you to manage contextual conditions and reactions for different portions of your site. You can think of each context as representing a “section” of your site. For each context, you can choose the conditions that trigger this context to be active and choose different aspects of Drupal that should react to this active context. This module can be used to implement contextual “breadcrumb” links.

So what are you waiting for? Go check ’em out! Use the Force, read the Source!

And if you have any other favorite modules, please drop us a note in the comments section below!