THE BLOG

News, tips and tricks from 2Gears

Mobile User-Experience: Sencha Touch LoadMask delayed

Posted · 20 Comments
HTML5 white grey 60px

The Sencha Touch HTML5 mobile web app framework is great for building cross-platform mobile applications. On current mobile browsers the performance can however be tricky. For our splog.me application we squeeze every bit of performance out of Sencha Touch. This post describes an easy way to make sure a LoadMask in Sencha Touch is shown before anything else happens, making the perceived user experience better.

Perceived speed

There are a number of tricks one can apply to the design of a Sencha Touch application to optimize the performance on mobile devices. Tommy Maintz’s writeups about Event Delegation and Optimizing Memory Usage should be compulsory starting points for anyone who is serious about building a more-than-trivial Sencha Touch based app.

One thing about the user experience that is often forgotten about is the perceived speed of an application. Users of applications, whether they are mobile of internet applications, don’t mind too much about waiting a second or two for an action to finish, as long as they know what is going on. Nobody likes staring at a blank screen like the one on the left, wondering if anything still is going on.

The load mask

One of the simplest ways of accomplishing this in Sencha Touch is of course by using a loading mask that shows a spinner and a simple waiting text. The mask should immediately show once the action is initiated and hidden once it finishes. For maximum performance we create a global loadMask that can easily be called through the app namespace.

myTapHandler: function() {
	MyApp.mask();
	Ext.dispatch({
		controller: 'controllerX',
		action: 'loadComplexPanelIntoCardLayout'
	});
}

And in our app.js:

mask: function() {
	this.views.viewport.loadMask.show();
}

The problem

The panel’s afterrender event is used to make a call to ‘MyApp.unmask()’ that removes the mask. This setup should show our mask before loading and hide it after the new panel is rendered right? Wrong.

Now this is the part where things start getting a bit annoying. We are calling the show method of the load mask before our controller is instructed to start loading a new panel, but the mask is only shown after the entire panel has finished rendering. In fact at first you won’t see the loadMask at all because it is only shown after the panel is rendered, but the panels afterrender event immediately hides it again. If you’re lucky you might just catch a quick glimpse of it. Spitting through the code you make sure that the entire loadMask code from Sencha Touch is finished before the panel starts loading, then what is the problem???

Sencha Touch is not the problem, the JavaScript engine from your mobile browser is. The engine creates the required elements in the DOM, but rendering of the new elements (the load mask) is deferred until it ‘has time’. It is still busy finishing the rest of your call, creating the new panel and inserting it’s required elements into the DOM as well. The rendering process is only started after that.

The solution

We found a quick and easy solution to this problem: we force the Javascript engine to finish the flow of our first call first, including rendering and starting the Controller action only after that. For this we created a custom dispatch method that defers the call to the controller by 1 microsecond. What this does is that by means of JavaScript’s setTimeout function, a new function call is scheduled immediately. It will however only begin processing once the process that is currently active has finished, allowing the process to complete the rendering process as well.

in app.js

dispatch: function(config) {
	new Ext.util.DelayedTask(function(){
		Ext.dispatch(config);
	}).delay(1);
}

We can then replace our original dispatch call by:

myTapHandler: function() {
	MyApp.mask();
	MyApp.dispatch({
		controller: 'controllerX',
		action: 'loadComplexPanelIntoCardLayout'
	});
}

And voila, the loadMask show nicely before the controller starts working with no (well ok, 1ms) delay.

Hope this helps others. Let me know what you think or if you’ve got an even simpler solution

20 Responses to "Mobile User-Experience: Sencha Touch LoadMask delayed"
  1. Carlos H says:

    Thank you so much this really saved my life, and my sanity

  2. LDT says:

    Hey Rob,
    How’s you doing?
    I’m working on some project and got stuck where I need to download a file from within the app. So, the app will be getting data from a main website through service calls and then create a link or anything like that with the URL of the file. Tapping that link will start download the file and saving it on device. Could you please help me on how to do that. I’m using ST 2.1.1 and Sencha command 3.0.

    And I’m getting an error: Uncaught TypeError: Cannot call method ‘detach’ of null.
    It occurs when I itemtap on list item to go to next screen (using ‘Ext.navigation.View’ to navigate) showing some records on next screen associated with item-tapped. Then I delete those records through service call and go back to previous screen(having list) with the help of following code:
    if(Ext.getCmp('mainListScreen'))
    {
    Ext.getCmp('mainListScreen').destroy();
    }
    Ext.Viewport.setActiveItem('mainListScreen');

    But then it shows me above error and I’m not able to go to next screen again(screen showing records) with itemtap.
    Thanks for your time, Rob. I really appreciate any help from your side.
    Thanks,
    LDT

    • LDT says:

      Forgot to mention that I’m using some components with “id”, so need to have destroy() function above in the code.
      Thanks again.

    • Rob Boerman says:

      Hi,

      I don’t really understand your code. First you are checking whether the mainListScreen component (by ID) exists, and if so destroy it. Then you are making the just destroyed component the active component in your viewport…. how can you make a component active that you just destroyed? I would also recommend not using Ext.getCmp too much as it creates a hardcoded dependency on components, especially when you are destroying those components on the fly.

  3. Markus says:

    Hi Rob!

    Thank you for your really fast answer!
    After plenty hours of madness it finally works. Here is my code, that finally works for me. May others succeed in less time…

    In my controller:

    showLoadingScreen: function(){
    Ext.Viewport.setMasked({
    xtype: 'loadmask',
    message: 'Loading...'
    });
    },

    onTapButton: function(view, index, target, record, event){
    //Show loading mask
    setTimeout(function(){this.showLoadingScreen();}.bind(this),1);
    // Do some magic
    setTimeout(function(){this.doFancyStuff(para,meter);}.bind(this),400);
    // Remove loading screen
    setTimeout(function(){Ext.Viewport.unmask();}.bind(this),400);
    },

    Your blog made this possible. It’s the only site I found facing this problem. May I marry you?

    • Rob Boerman says:

      Hi Markus,

      Sorry disappoint you, but I am already married and besides I don’t think the name “Markus” belongs to a cute girl :-)

      Things like this look simple but can drive you nuts. Good to hear I could be of help.
      Cheers,
      Rob

  4. Markus says:

    Hi Rob!
    I’m going insame with that issue. Your post was the first light at the end of the tunnel. But actually I didn’t get it working yet. I’m using Sencha 2.2.1 and the following code within my controller:


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

    new Ext.util.DelayedTask(function(){
    Ext.app.Application.dispatch({
    controller: this,
    action: 'showLoadingScreen'
    });
    }).delay(1);

    But I get the following error:
    Uncaught TypeError: Cannot call method 'apply' of undefined at http://cdn.sencha.com/touch/sencha-touch-2.2.0-rc/sencha-touch-all.js:18

    Please notice that the method Ext.dispatch() was replaced by Ext.app.Application.dispatch().
    I hope you can still help me even if the last post was quite a while ago.

    • Rob Boerman says:

      Hi Markus,

      In Sencha Touch 2.2 you should really not be calling controller methods directly but use the Controller to listen for certain interface events (see the Controller documentation). However, you could still be having the same issue (didn’t test it yet) when a controller method tries to create a mask and then does something timeconsuming. What you can do is let the Controller function mask the app, then call a second function with an immediate callback which should allow the browser to show the mask first:

      onTapButton: function() {
      this.getApp().mask(); // or something similar
      setTimeout(this.doSomethingHorriblyTimeConsuming.bind(this), 1);
      },
      doSomethingHorriblyTimeConsuming: function() {
      for (var i=0;i<999999999;i++) {foo();}
      this.getApp().unmask();
      }

  5. Klaus says:

    Hi Rob,

    thank you for this blog post! After one and a half day of trial and error in the sencha touch source code, this post did the trick! Thank you!

    Greetings,
    Klaus.

  6. Sajid says:

    You said, “The panel’s afterrender event is used to make a call to ‘MyApp.unmask()’ that removes the mask. ”

    Try using activate event instead of afterrender event, This worked for me, afterrender event is fired right after render is complete, which is fast; but activate event is fired when the panel is visually activated, try it, no delay will be required.

    • Rob Boerman says:

      Hi, removing the mask is not the problem here, showing it in time is. When you make a call to a mask.show() and then a Ext.dispatch(), the dispatch is handled completely before the mask actually appears. When adding the micro-delay in the dispatch JavaScript goes on handling pending tasks such as the show and schedules the dispatch behind that. That means the mask actually gets a chance rendering itself

      (sorry for the late response, have been swamped)

  7. bagusflyer says:

    I’m a sencha touch and javascript newbie. I’m totally confused about what you’re talking about. Is there a complete sample code of app.js?

    Thanks

    • Rob Boerman says:

      Hi, a complete sample of app.js would be useless because the rest has nothing to do with this problem.
      The problem I am solving is the following: when you create a load mask and then instruct a controller to execute an action you would think the load mask shows before the controller action is executed. This is however not the case. The above solution solves that. If you ever run into that problem, before pulling your hair out in frustration, try this :)

  8. Brett says:

    I am curious as to what your delay() function looks like.

    • Rob Boerman says:

      Hi Brett,
      the delay function is the standard delay function from Ext.Util.DelayedTask. Basically this is just a convenience wrapper around the standard setTimeout function.

      Cheers,
      Rob

Leave a Reply

Your email address will not be published. Required fields are marked *

17 + nine =

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>