Category Archives: mobile

Image Caching

17 May 2016

I’ve been putting together a demo the past couple of days for a sales pitch that our sales guys will be going on in a couple of weeks. The demo will be wrapped in Cordova and shown on an iPad Pro and will have lots of full-screen (or at minimum near-full-screen) background images. As you might imagine, those images can be quite large in file size. I’ve compressed them to a reasonable amount but fearing that my view rendering might be less than perfect I’ve decided to write a caching mechanism for them.
_cache
First I’ve set up the following variables; one that lists all the images that I want to cache, the next is a counter that will be incremented with each image load.

// add all images to be cached in this array
var _imagesToCache = ['home_bg.jpg','interstate_bg.jpg'];

// incremented by onload events. when this equals (_imagesToChache.length -1) we know all large assets have been loaded
var _cachedImages = 0; 

Here’s the class I wrote that will do the heavy lifting:

/**
 * This will cache an image
 * @private
 * @param {string} path the path to the image to be cached
 */
function CacheImage(){
   var img = new Image();
   img.onload = function(){
       _cachedImages++;
   };
   this.cache = function(path){
      img.src = 'css/images/' + path;
   };
}

Next I iterate through the image array and start the caching process:

/**
 * loads large assets, (i.e., images) in an attempt to cache them and speed up view render time
 * @method
 * @type {Function}
 * @name airstream.model.loadLargeAssets
 */
ns.loadLargeAssets = function(){
   var i, len = _imagesToCache.length;
   for (i=0;i

When the app clears the DOM Ready and Device Ready events I then start caching my templates and images. I poll the status of both with a setTimeout within the following function:

/**
 * Recursive function that checks for the successful caching of templates and images.
 * @method
 * @type {Function}
 * @name airstream.model.loadTemplates()
 * @param {}
 * @return {} Returns nothing
 */
ns.checkForCompleteLoad = function(){
   // this is for my template caching, not related to image caching stuff
   var loaded = true;
   for (var p in _templates){
      if (!_templates[p]){
         loaded = false;
      }
   }
   // This next line deals with looking to see if the images have been loaded 
   if (!loaded || _cachedImages !== _imagesToCache.length){ // check the image cache counter
      window.setTimeout(airstream.model.checkForCompleteLoad,100); // try again in 100ms
   } else {
      console.log('Images Cached: ' + _cachedImages);
      airstream.view.renderHomeView();
   }
};

In reviewing this I could easily remove the polling and instead do the "complete load check" in each template or image-caching instance where once they have completed their tasks they check the counters vs the length of the template and image arrays. At that point they could decided if they were the last ones to load and then fire the rendering of the initial view if true.

Anyway, the above does the job for now. I'll find time to refine things when there's time to be had.

Angular and Scrolling Content

15 Oct 2015

I’m currently enrolled at Thinkful.com in the Angular course. I’m well over half-way through and have been applying Angular to my work projects with great success. One thing that I needed to do was to have scrolling views in my hybrid apps while having other areas of the app remain static. My future goals involve using Ionic – which handles scrolling easily enough, and I suspect, in the exact same way I’ve done it here – but I’m a logical step-by-step kind of guy so for now I’m content with learning Angular and rolling my own UI. That being the case I thought to search for the “Angular” way of getting views to scroll and tried a few of the custom directives available on the Internet.

The easiest to set up was ng-scrollable which technically worked but lacked the finesse of the vaunted iScroll. I also tried angular-iscroll but without much success. I reverted to the usually reliable iScroll but Angular didn’t want to play nicely with it. I then reverted to ng-scrollable thinking I could revisit it and proceeded with my other layout duties only to find that ng-scrollable injected too much crap into the DOM which broke a previously working flex-box-based layout. Attempts to rectify the layout proved to be a waste of time. So, I removed ng-scrollable and instead pursued a native / pure css solution.

The Technique

So lets get back to what I wanted – native-like smooth scrolling with inertia. The answer can be found via CSS by creating a class with the following properties (your wrapping container must have a set width / height, and a setting of “overflow:hidden” on the wrapper will kill scrolling):

.scrollable {
    overflow-x: hidden; // Control how to clip the container's content.
    overflow-y: scroll;
    -ms-overflow-style: -ms-autohiding-scrollbar; // configure overflow scrolling behavior for IE 10
    -webkit-overflow-scrolling: touch; // The magic happens here
}

Each of the above styles are configured as follows:

  • -webkit-overflow-scrolling
    • auto – Non-inertia scrolling. Scrolling stops as soon as your finger no longer touches the screen.
    • touch – Use inertia-based scrolling, the faster you flick the scrollable content the faster it scrolls but continues to a deaccelration scroll whne your finger stops touching the screen. I find this to be the desired behavior as its closest to native on Mobile.
  • overflow-x, overflow-y – These properties specify how to treat overflowing content via one of the following values:

    • visible – Indicates the content is not clipped, it may be rendered outside the content box.
    • hidden – Indicates the content is clipped and no scrollbars are provided.
    • scroll – Indicates the content is clipped and desktop browsers use scrollbars whether or not any content is clipped. For hybrid application development the scrolling behavior mimics native scrolling where scroll bars don’t appear unless you interact with the scrollable content. On Android at least the scrollbar overlaps content so you should add padding to account for it. On desktop the scrollbars use up additional space within the scrolling wrapper.
    • auto – Depends on the user agent – meaning that you should expect the native behavor for the overflowing content.

I’m not too concerned with IE in my hybrid app development but here are the configuration options none-the-less:

  • -ms-overflow-style – Sets the scrolling behavior for overflowing elements in IE (copied from MS).

    • auto – Indicates the element inherits its -ms-overflow-style from its parent element
    • none – Indicates the element does not display scrollbars or panning indicators, even when its content overflows. Unlike overflow: hidden, elements with -ms-overflow-style: none can still be scrolled via touch panning, keyboard, or mouse wheel.
    • scrollbar – Indicates the element displays a classic scrollbar-type control when its content overflows.
    • -ms-autohiding-scrollbar – Indicates the element displays auto-hiding scrollbars during mouse interactions and panning indicators during touch and keyboard interactions.

Notes

If using this on Android / PhoneGap be sure to install the Crosswalk plugin so that you don’t have to worry about compatibility with older webkits.

Also, it is worth noting that sometimes it wont work unless you apply the webkit-specific styling directly to the element like so:

<div id="comeContent" style="-webkit-ovrflow-scrolling: touch;">...

Also worth noting is that if the content of the scrolling element changes that you may need to force the content heights to be recalculate on iOS by inserting a psuedo-element using a simple calc() function to determine its new height:

#comeContent:before {
  content:'';
  width: 1px;
  float: left; // remove from the document flow
  height: calc(100% + 1px); // force the recalculation of the container's height
  margin-left: -1px; // remove from view
}

Other methods of forcing a screen redraw may work as well.

Eliminating Touch Event Lag via CSS

12 Mar 2014

It’s common knowledge that ontouchstart on mobile doesn’t fire immediately because a 300ms pause is implemented in WebKit to determine if the touchstart is the beginning of a double-tap. For that reason, web app developers use the ontouchend event instead because it doesn’t have any built-in delays.

Some people will try to code around the issue by synthesizing a touchend event when touchstart is detected thereby skirting the inherent delay. FastClick is an example of this sort of work-around.

If you find that you need to make a “click-based” application feel more responsive you could try something like the above – or you could just try some CSS. Its possible to eliminate the standard 300ms delay entirely without any JavaScript:

.yourButtonClass, a {
    -ms-touch-action: none; // Deprecated as of IE 11, Microsoft recommends the the standard below
     touch-action: none; // webkit
}

Related to this topic is this dated article at Quirks Mode which I found to of interest (keep in mind its from 2010): The touch action.

Accessing External Storage in Android & PhoneGap 3.3

02 Jan 2014

While playing with PhoneGap’s filesystem api I noticed that window.requestFileSystem() would only give me a path to the on-device file storage area (sdcard0) but not allow me to gain access to the external sd card (extSdCard).

[edit 11/29/2014] Please review this article: Browsing Filesystems in PhoneGap as it has details about the setup used to accomplish the subject of this article.

The following illustrates the above:

window.requestFileSystem(LocalFileSystem.PERSISTENT, 0, fsSuccess, fsFail);

function fsSuccess(fs){
  console.log(fs);
  // the above prints this to the console:
  // {"name":"persistent",
  //  "root:{"isFile":false,
  //     "isDirectory":true,
  //     "name":"sdcard0",
  //     "fullPath":"file:///storage/sdcard0",
  //     "filesystem":null
  //  }
  // }
}

As you can see simply getting the filesystem returns the location at /storage/sdcard0 which is a couple of levels below the root. You would think that its not a big deal because you could use the getParent() method to go higher up the directory chain. In practice however getParent() on file:///storage/sdcard0 returns the same location.

What you can do then is change the value of the fileSystem’s “fullPath” property to the desired path and thus get the location one directory level above sdcard0 without using getParent(). This one edit can be used with a directoryReader() to reveal the external sd card.

window.requestFileSystem(LocalFileSystem.PERSISTENT, 0, fsSuccess, fail);

function fsSuccess(fs){
  if (fs.root.fullPath === 'file:///storage/sdcard0'){
    fs.root.fullPath = 'file:///storage'; // change the path
  }
  // create directory reader
  var directoryReader = fs.root.createReader()
  // get a list of all entries in the directory
  directoryReader.readEntries(dirSuccess,fail);
}

function dirSuccess(entries){
  console.log(entries);
  // will print something like the following to the console
  // [{"isFile":false,"isDirectory":true,"name":"extSdCard",
  //    "fullPath":"file:///storage/extSdCard","filesystem":null},
  //  {"isFile":false,"isDirectory":true,"name":"sdcard0",
  //    "fullPath":"file:///storage/sdcard0","filesystem":null}
  // ]
}

And that’s all there is to it – from there you have an entry for the internal storage area and and the external sdcard storage to do with as your app requires.

By modifying the fullPath you can also get all the way to file:/// if you so desired though at that point you’d need to be careful what you enable your users to do.

As a side note I have noticed that the path to file://storage is reported as having a trailing slash contrary to other paths.

Simple Android Back Buttons in Sencha Architect 2 & Phonegap

15 Oct 2013

As you may know Android has a back button – present as a software back button or in older devices as a capacitive button on the device itself. The question is how to hook into it and get your views to change in Sencha Touch. Sure, Sencha walks you through Routes and such, but all I want is something simple, and this technique is just that, simple and easy to understand.

This approach uses the browser’s history object and updates it with a hash comprised of the current panel’s id. As you navigate about your app the hash is updated as desired. When the user taps Android’s back button the history object’s back() method is fired. Hash changes don’t cause a page reload so your app doesn’t reload either. After firing the back() method we wait a few milliseconds and then fire our own function to update the view based on the current hash.

This works great for an app that is comprised of a single container whose children are the panels that you want to view. More complex structures would require that you get into Sencha Touch’s Routing mechanism (and to be honest, you *should* be using routes).

One Level of Navigation within a single container

Lets review a scenario that is conducive to implementing simple back button functionality – an app built with the following structure:

back_history_structure_1

As you can see this is a very simple app – a single container with one level of children.

To begin lets add 2 custom methods to our application. Start Architect, and click on the “launch” node within the Project inspector and paste the following into the code view:

Ext.define('MyApp.appHistory', {
    statics: {
        goBack: function(){
            if (location.hash.length != 0){
                var hash = location.hash.substring(1);
	            Ext.getCmp('initialView').setActiveItem(Ext.getCmp(hash));
            } else {
                MyApp.Utilities.addHashToUrl();                                      
            }
        },
        addHashToUrl: function(){
            var id = Ext.getCmp('initialView').getActiveItem().id;
            var loc = location.href;
            var hash = location.hash.substring(1);
            var root = null;

            if (loc.indexOf('#') != -1){
                root = loc.split('#');
                location.href = root[0] + '#' + id;
            } else if (id != hash){
                location.href = loc + '#' + id;
            }
        }
    }
});


Ext.define('MyApp.MyView', {
    extends: 'Ext.panel.Panel',
    requires: ['MyApp.appHistory'],
    initComponent: function() {
        MyApp.appHistory.goBack();
    }
});

Ext.define('MyApp.MyView', {
    extends: 'Ext.panel.Panel',
    requires: ['MyApp.appHistory'],
    initComponent: function() {
        MyApp.appHistory.addHashToUrl();
    }
});

What we’ve done here is add an “appHistory” object to our “MyApp” app (“MyApp” is the default namespace that Architect gives your app) and exposed two methods:

  • MyApp.appHistory.goBack() – this handles the back functionality for the app.
  • MyApp.appHistory.addHashToUrl() – this updates the location hash.

Finally we need to hook into PhoneGap’s “backbutton” event. We do so by adding an event listener within our index.html. You’ll notice the typical “deviceready” event listener wrapped by the document’s “load” listener which ensures that our code runs only when the DOM has been loaded and the device is ready:

window.addEventListener('load',function(){
	document.addEventListener('deviceready',function(e){
		// setup the back button
		document.addEventListener('backbutton', function(e){
			history.back() // go back in the browser's history
			setTimeout(function(){
				MyApp.appHistory.goBack(); // update the view against the current hash
			},100);
			return false;
		}, false);
	});
});

Looking at the above we can see that when the “backbutton” event fires we go back in the browser history then we wait a short bit of time to ensure that the location has been updated before following with the call to navigate back within the app.

The last thing to do is to update the hash from within your Sencha application. I’ve placed the ” MyApp.Utilities.addHashToUrl();” method call within my controller’s onButtonTap event which is sufficient for this example.

This is a good starting point – you’ll of course need to modify per your specific needs, have fun!