Reusing Sencha Touch views with Controller Helpers

Reusing Sencha Touch views with Controller Helpers

One of the most repetitive task in Sencha Touch Controllers (and Ext JS) is view rendering. You click a button, a view renders. A list item is selected, another view. And it goes on and on… So, after the Facebook Sliding Menu code, here’s another treat that I use everywhere: a controller helper to render and reuse views.

Ext.define('SenchaCon.controller.Base',{
    extend: 'Ext.app.Controller',
    
    render: function(xtype) {
        // try to get the view on the viewport, or create it
        var view = Ext.Viewport.child(xtype) || Ext.Viewport.add({ xtype: xtype });
        
        // if is inner, activate
        if (view.isInnerItem()) {
            Ext.Viewport.setActiveItem(view);
        }
        
        // if is floating, just show
        else {
            view.show();
        }
        
        // return the instance so the controller can
        // make further view updates
        return view;
    }
});

What is this?

I added comments on the code so you can easily understand it, but let me exemplify the concepts. Ideally you want to initialize your app with the minimum number of views and render additional ones on demand. A card layout or tabpanel full of heavy and hidden views right from the start is not very useful to accelerate your app. When I see this pattern my question is “Why render all of this upfront if users might not even check this module?”.

You want to render the bare minimum, so the user is presented with views as soon as possible, but you also don’t want to destroy and re-create views over and over – this is an expensive operation. You need to reuse them! Keep the DOM and just change the data on the second, third and other visualizations.

This is the trick for fast transitions on the SenchaCon ’13 app. When a list item is tapped, the new view is rendered and data is injected. The next time, it’s even faster because the view is already rendered. Here’s how you do that:

Ext.define('SenchaCon.controller.Session',{
    extend: 'SenchaCon.controller.Base',
    
    // ...

    /**
     * Matches /session/:id
     */
    routeSessionDetails: function(sessionId) {
        
        // render (or reuse) the view
        var sessionDetails = this.render('sessiondetails');
        
        // get some session info that was loaded from previous session list
        var session = Conference.getSession(sessionId);
        
        // quickly present the user with some data
        sessionDetails.setData(session.getData());
            
        // if details_loaded flag is not up on the model, we need more data    
        if (!session.get('details_loaded')) {
            
            // load all the additional data
            session.loadAdditionalInfo(function() {
                
                // raise the flag and inject more data into the view
                session.set('details_loaded', true);
                sessionDetails.setData(session.getData());
            });
        }
    },

As you can see from the code above, I’m rendering the session details and immediately presenting the user with some data, like the session title, location, time and date. While the user is digesting this, a background request is fetching additional info like speaker information. The render method takes care of the view rendering and reusing, my controller logic is much simplified, easy to write and easy to understand.

Not an exact science

But there’s one more thing: you need to weight the pros and cons. If you have a big tablet app, for example, it might be to heavy to keep a lot of DOM just for the sake of reutilization.

At this point you might be thinking I’m contradicting myself:

  • Render views on demand. When a view is rendered, keep it and reuse it. Don’t destroy and recreate all the time.
  • At the same time, keep an eye on DOM size. Too much DOM will hurt performance.

The bottom line is: there’s no exact formula. What we have is different strategies for small or big apps. For a big app I like to refer to a solution on Sencha KitchenSink

I’ve learned a lot from KitchenSink by checking the Main.js controller. On its essence is the same logic I’m describing – render and re-use views – but spiced up a little bit. There you’ll see an array of views, and a default limit of 20 views that is maintained on cache.

config: {
    viewCache: [],
    //...
},
    
createView: function (item) {
    var name = this.getViewName(item),
        cache = this.getViewCache(),
        ln = cache.length,
        limit = item.get('limit') || 20,
        view, i = 0, j, oldView;
    
    // try to find the view on the cache and return 
    for (; i < ln; i++) {
        if (cache[i].viewName === name) {
            return cache[i];
        }
    }

    // also, make sure the cache respect the limit
    if (ln >= limit) {
        for (i = 0, j = 0; i < ln; i++) {
            oldView = cache[i];
            if (!oldView.isPainted()) {
                oldView.destroy();
            } else {
                cache[j++] = oldView;
            }
        }
        cache.length = j;
    }
    
    // view not found, create and push to the cache
    view = Ext.create(name);
    view.viewName = name;
    cache.push(view);
    this.setViewCache(cache);

    return view;
},

Wrap up

Whether you use my simple helper or a more robust solution like the one from the KitchenSink, you’ll probably notice some big improvements on the way you write Controller code. They are typically a messy layer on any application; encapsulating rendering logic is always helpful.

One note if you’re also using Sencha Architect: you can’t change which class your controllers extend. In this case, you can code this little helper outside Architect and import it, injecting into controllers as a mixin or just override Ext.app.Controller. Your call.

I hope you find this little helper useful. Enjoy!