Skip to content

Modus-Logo-Long-BlackCreated with Sketch.

  • Services
  • Work
  • Blog
  • Resources

    OUR RESOURCES

    Innovation Podcast

    Explore transformative innovation with industry leaders.

    Guides & Playbooks

    Implement leading digital innovation with our strategic guides.

    Practical guide to building an effective AI strategy
  • Who we are

    Our story

    Learn about our values, vision, and commitment to client success.

    Open Source

    Discover how we contribute to and benefit from the global open source ecosystem.

    Careers

    Join our dynamic team and shape the future of digital transformation.

    How we built our unique culture
  • Let's talk
  • EN
  • FR

Orientation Aware Apps in Sencha Touch Part 2 of 2

Published on July 4, 2014
Last Updated on April 8, 2021
Application Development

In part one of this series, Tim Eagan introduced us to the challenge of developing apps which can support the changing of device orientation. While the techniques outlined in part one will account for the majority of our requirements, it’s important to understand our options when things get complicated. In this last part of this series, we’ll walk through a scripting approach to handling orientation change in your Sencha Touch apps.

Complexity happens quickly

As Sencha Touch developers, we know how much of a challenge it is to properly handle orientation change in our apps. However with any challenge comes great opportunity and orientation change is no different. Armed with the knowledge that we have multiple screen resolutions to account for coupled with two different orientations, the user experience has a chance for dramatic customization. For example take the idea of developing a list/detail type view to a mini-tablet. In a card layout this will look fine in portrait but in landscape it might be a little vertically ‘short’. A better way would be to present this type of view as an hbox in landscape.

Can profiles help?

Up to this point we’ve talked about delivering custom experiences based on screen resolution (orientation change). While this sounds like it fits well with Sencha Touch profiles, you’re right, but it’s important to understand some key differences.

Sencha Touch profiles are used to deliver custom code to a group of devices or even specific device. The choice of what to load can be based on anything but typically it would be device type (phone, tablet, mini-tablet). With profiles, this decision has to be made at launch. This makes profiles a static solution when we’re trying to solve the dynamic problem of orientation change.

Dynamically loading new experiences

While Sencha profiles aren’t able to help us much with orientation change, we still have some techniques at our disposal. Let’s implement the list/detail example mentioned earlier.

We essentially want to swap out a card layout for an hbox layout. Knowing that styles can’t handle this and dynamically assigning new layouts at runtime is not supported, we need to come up with something else.

Let’s start by reviewing the code we’ll use to represent the portrait and landscape experiences. The portrait experience will be a card layout using an Ext.navigation.View http://docs.sencha.com/touch/2.3.1/#!/api/Ext.navigation.View container as shown in Figure 1 below.

Ext.define('MyApp.view.portrait.MasterDetail', {
    extend : 'Ext.navigation.View',
    xtype  : 'portraitmasterdetail',

    requires : [
        'Ext.dataview.List'
    ],

    config : {
        items  : [
            {
                xtype   : 'list',
                title   : 'Master',
                itemId  : 'master',
                itemTpl : '{title}'
            }
        ],

        store  : undefined
    },

    initialize : function() {
        var me = this,
            master;

        me.callParent();

        me.down('#master').on({
            select : me.onMasterSelect,
            scope  : me
        });
    },

    onMasterSelect : function(list, record) {
        this.push({
            xtype            : 'component',
            itemId           : 'detail',
            title            : 'Detail',
            styleHtmlContent : true,
            data             : record.getData(),
            tpl              : ''.concat(
                '<h1>{title}</h1>',
                '{detail}'
            )
        });
    },

    applyStore : function(value) {
        var me     = this,
            master = me.down('#master');

        master &amp;&amp; master.setStore(value);

        return value;
    }
});

Figure 1 – Portrait view

This view has some features baked in such as supporting the configuration of a store on the navigation view itself. When the store is defined on this container it will be applied to the list component which renders as the initial view. When a list item is tapped, the detail view is pushed on top of the card deck.

The landscape experience will be a container with an HBox http://docs.sencha.com/touch/2.3.1/#!/api/Ext.layout.HBox layout as shown in Figure 2 below.

Ext.define('MyApp.view.landscape.MasterDetail', {
    extend : 'Ext.Container',
    xtype  : 'landscapemasterdetail',

    config : {
        layout : 'hbox',
        cls    : 'landscape-master-detail',
        items  : [
            {
                xtype   : 'list',
                flex    : 1,
                itemId  : 'master',
                store   : 'mystore',
                itemTpl : '{title}'
            },

            {
                xtype            : 'component',
                itemId           : 'detail',
                styleHtmlContent : true,
                flex             : 2,
                tpl              : ''.concat(
                    '<h1>{title}</h1>',
                    '{detail}'
                )
            }
        ],

        store : undefined
    },

    initialize : function() {
        var me = this,
            master;

        me.callParent();

        me.down('#master').on({
            select : me.onMasterSelect,
            scope  : me
        });
    },

    onMasterSelect : function(list, record) {
        this.down('#detail').setData(record.getData());
    },

    applyStore : function(value) {
        var me     = this,
            master = me.down('#master');

        master &amp;&amp; master.setStore(value);

        return value;
    }
});

Figure 2 – Landscape view

Notice some traits the portrait and landscape code have in common. First, the master and detail sub-views have an itemId http://docs.sencha.com/touch/2.3.1/#!/api/Ext.Component-cfg-itemId of #master and #detail. Also each of the main containers support the definition of a store. These are two important traits will be key to leveraging our orientation container.

To complete the story, the code we’ll use to define the store is shown in Figure 3 below.

Ext.define('MyApp.store.MyStore', {
    extend : 'Ext.data.Store',

    config : {
        storeId : 'mystore',
        data    : [
            {
                id     : 1,
                title  : 'Item 1',
                detail : 'Item 1 details'
            },

            {
                id     : 2,
                title  : 'Item 2',
                detail : 'Item 2 details'
            },

            {
                id     : 3,
                title  : 'Item 3',
                detail : 'Item 3 details'
            }
        ]
    }
});

Figure 3 – The store used for both the portrait and landscape views

The store http://docs.sencha.com/touch/2.3.1/#!/api/Ext.data.Store is a simple in-memory store of data that is registered with the storeId of ‘mystore.’

Creating the orientation container

The custom container we’ll use to manage the swapping of views to support portrait and landscape orientations is simpler than you may think. The source code for the orientation container is shown in Figure 4 below.

Ext.define('MyApp.view.OrientationContainer', {
    extend : 'Ext.Container',
    xtype  : 'orientationcontainer',

    config : {
        layout : 'card',

        portrait  : undefined,
        landscape : undefined
    },

    initialize : function() {
        var me = this;

        Ext.Viewport.on({
            orientationchange : me.initView,
            scope             : me
        });

        me.initView();

        me.callParent();
    },

    initView : function() {
        var me          = this,
            orientation = Ext.Viewport.getOrientation(),
            viewConfig  = orientation === 'portrait' ? me.getPortrait() : me.getLandscape(),
            oldView     = me.getActiveItem(),
            newView;

        // add the new view to the container
        newView = me.add(viewConfig);

        // remove the old view
        me.remove(oldView);
    }
});

Figure 4 – Orientation container

The orientation container has two custom properties, ‘portrait’ and ‘landscape’ which are used to contain the views specific to their orientation. When this container is initialized, the orientationchange http://docs.sencha.com/touch/2.3.1/#!/api/Ext.Viewport-event-orientationchange event is handled by the ‘initView’ method. This method drives the whole process as it will add the new view based on the currently detected orientation, then remove the old view.

To implement this container we could use the the configuration shown in Figure 5 below.

{
    xtype     : 'orientationcontainer',
    title     : 'List',
    defaults  : {
        store : 'mystore'
    },
    portrait  : {
        xtype : 'portraitmasterdetail'
    },
    landscape : {
        xtype    : 'landscapemasterdetail'
    }
}

Figure 5 – Implementing the orientation container

The orientation container takes advantage of a key feature of Sencha Touch containers; the defaults http://docs.sencha.com/touch/2.3.1/#!/api/Ext.Container-cfg-defaults config option. This is used to apply the same store to the children of this container. So as views are swapped out based on orientation, they’re sure to use the same store.

This is great, but what about state?

Our orientation container is working just fine but we have a problem. As portrait and landscape views are rendered, we lose the state of the view. Each time the orientation is changed, the old view is destroyed and the new view is re-created.

To fix this, we’ll modify the orientation container as shown in figure 6 below.

Ext.define('MyApp.view.OrientationContainer', {
    extend : 'Ext.Container',
    xtype  : 'orientationcontainer',

    config : {
        layout : 'card',

        portrait  : undefined,
        landscape : undefined
    },

    initialize : function() {
        var me = this;

        Ext.Viewport.on({
            orientationchange : me.initView,
            scope             : me
        });

        me.initView();

        me.callParent();
    },

    initView : function() {
        var me          = this,
            orientation = Ext.Viewport.getOrientation(),
            viewConfig  = orientation === 'portrait' ? me.getPortrait() : me.getLandscape(),
            oldView     = me.getActiveItem(),
            newView;

        // add the new view to the container
        newView = me.add(viewConfig);

        // run the optional callback
        viewConfig.callback &amp;&amp; viewConfig.callback.call((viewConfig.scope || newView), newView, oldView);

        // remove the old view
        me.remove(oldView);
    }
});

Figure 6 – Adding support for a callback to the orientation container

We’ve added just one more line to the orientation container which adds support for running an optional callback function for a given view. This callback supports the assignment of an optional scope. If no scope is defined, the callback will execute under the scope of the new view.

We can use this to our advantage with our master/detail example, shown below in Figure 7.

{
    xtype     : 'orientationcontainer',
    title     : 'List',
    defaults  : {
        store : 'mystore'
    },
    portrait  : {
        xtype : 'portraitmasterdetail'
    },
    landscape : {
        xtype    : 'landscapemasterdetail',
        callback : function(newView, oldView) {
            var oldDetail = oldView &amp;&amp; oldView.down('#detail'),
                newList   = newView.down('#master'),
                selected;

            if(oldDetail) {
                selected = newList.getStore().findExact('id', oldDetail.getData().id);
            }
            else {
                selected = newList.getStore().first();
            }

            newList.select(selected);
        }
    }
}

Figure 7 – Implementing a callback

In the case where a detail view is selected in portrait view, when switched to landscape view, we want that selection to be preserved. Our callback does just that by first detecting whether the old view was showing its detail view at the time of orientation change. If so, then the landscape view will pre-select the entry and automatically show its version of the detail. Otherwise, the first selection is shown.

Moving forward

While this served as a valid solution to handling complex orientation change requirements, it’s not the only technique. More than likely the solutions you apply to your orientation change requirements will be diverse and should be evaluated on a case-by-case basis.

Posted in Application Development
Share this

Brice Mason

Brice Mason is a speaker and published author with over 10 years of experience building software. A solutions based problem solver, Brice has exposure to working on government contracts, as well as experience in the private sector with large and small businesses.
Follow

Related Posts

  • Orientation Aware Apps Sencha Touch
    Orientation Aware Apps in Sencha Touch Part 1 of 2

    Introduction Mobile application development has always had its own unique set of challenges. The effort…

  • Orientation Aware Apps Sencha Touch
    Orientation Aware Apps in Sencha Touch Part 1 of 2

    Introduction Mobile application development has always had its own unique set of challenges. The effort…

Want more insights to fuel your innovation efforts?

Sign up to receive our monthly newsletter and exclusive content about digital transformation and product development.

What we do

Our services
AI and data
Product development
Design and UX
IT modernization
Platform and MLOps
Developer experience
Security

Our partners
Atlassian
AWS
GitHub
Other partners

Who we are

Our story
Careers
Open source

Our work

Our case studies

Our resources

Blog
Innovation podcast
Guides & playbooks

Connect with us

Get monthly insights on AI adoption

© 2025 Modus Create, LLC

Privacy PolicySitemap
Scroll To Top
  • Services
  • Work
  • Blog
  • Resources
    • Innovation Podcast
    • Guides & Playbooks
  • Who we are
    • Our story
    • Careers
  • Let’s talk
  • EN
  • FR