Sencha Inc. | HTML5 Apps

Idiomatic Layouts with Sencha Touch II

Published Aug 01, 2011 | James Pearce | Tutorial | Medium
Last Updated Aug 10, 2011

This Tutorial is most relevant to Sencha Touch, 1.x.

Sencha Touch allows you to create applications that work on both mobile phone and tablet devices, and use layouts that cater for different screen sizes. But as well as the differences in display between different types of devices, users also have certain expectations about apps' user-interface conventions.

In this two-part article, we show how, with a single code-base, we can create an app which responds to these conventions, and which, through the use of the Sencha Touch 'application profiles' mechanism, delivers familiar user interfaces to both phone and tablet users.

This is part two of the tutorial. Head back to part one if you missed it!

Theming

In part one, we got the application working, more or less. But now, let's get this thing looking nicer, with a little SASS. We'll be working in the theming directory. There, create a config.rb file just to show Compass where the files it needs are, and that we want our CSS compressed:

# Get the directory that this configuration file exists in
dir = File.dirname(__FILE__)
 
# Load the sencha-touch framework automatically.
load File.join(dir, '..', 'lib', 'touch', 'resources', 'themes')
 
# Compass configurations
sass_path    = dir
css_path     = dir
environment  = :production
output_style = :compressed

The SASS file, mondrian.scss, can start off as just a vanilla list of Sencha Touch mixins:

@import 'sencha-touch/default/all';
 
@include sencha-panel;
@include sencha-buttons;
@include sencha-toolbar;
@include sencha-list;
@include sencha-layout;
@include sencha-sheet;
@include sencha-msgbox;

To create the CSS file from this, make sure you have recent versions of SASS and Compass, and, on the command line in the theming directory, run:

compass compile mondrian.scss

All being well, you'll see Compass generate a simple new mondrian.css file accordingly.

But it will still look like the standard Sencha Touch theme, so let's customize this. Open up mondrian.scss again and set a few global style variables at the top:

$base-color: #eee;
$active-color: #ccc;
$include-default-icons: false;

The first of these sets the app's color scheme to a very light gray, and also uses gray for list selections. The third variable, $include-default-icons turns off the inclusion of the default set of base64-encoded icons. We're only using two (neither of which was in that default set, hence the white boxes), and so after the standard includes we explicitly add in 'list' and 'info', as per our button definitions above.

@include pictos-iconmask('list');
@include pictos-iconmask('info');

The actual source graphics are found down inside the resources/themes/images/default/pictos directory of the SDK. Compile with Compass as before, and immediately, an improvement. We now at least have icons:

But while we are here, let's tweak a few other things. Firstly, let's lighten up the look of the toolbar and these two buttons:

.x-toolbar {
  border-color: #aaa !important;
  .x-toolbar-title {
    font-weight: normal;
    color: #777;
    text-shadow: 0 1px 0 #fff;
  }
}
.x-button-normal {
  border: none !important;
  background: none !important;
  img {
    background: #777 !important;
  }
}

Then, we need to make sure that the docked menu on the left (in the landscape tablet profile) has a separating border from the main page:

.x-docked-left {
  border-right: 1px solid #aaa !important;
  background: #000 !important;
}

To make Mondrian feel at home, we'll give the pages a slight canvas background. (The canvas.png file needs to be placed in the images directory of the theming directory so we can base64-encode it - you'll find this file in the GitHub repo).

.page {
  background: #333;
 .x-html {
    background: #eee inline-image('canvas.png', 'image/png') !important;
  }
}

Finally, let's center the splash screen photograph and give it a some jauntiness, with a little rotation, a framed edge, and a drop shadow - that we can ensure will fit the screen, whatever its orientation.

.photo {
  display: block;
  margin: auto;
  max-height: 80%;
  padding: 8px;
  background: #fff;
  border: 1px solid #ccc;
  -webkit-box-shadow: 3px 3px 5px #ccc;
  -webkit-transform: rotate(3deg)!important;
}

That was the GitHub repo's 5_theming branch. Time to make the app actually work.

Interactions

Let's add the event listeners for the menu and the buttons, so that the user can interact with the application. In our launch function, add the following final pieces of code:

// menu button toggles (floating) menu
menuButton.addListener('tap', function () {
    menu.showBy(this);
});

This first interaction is very simple. When the user taps the menu button, the menu should appear. The showBy method ensures the floating menu appears on the screen as close as possible, with an anchoring arrow, to the button.

Clicking away from the menu (or on the button itself) will make it hide again. Remember that the button will only be present for profiles which have a floating menu anyway.

Then, we need to make sure that clicking the menu list itself has an effect:

// menu list (slides and) updates page with new content
menuList.addListener('selectionchange', function (model, records) {
    if (records[0]) {
        viewport.setActiveItem(page, {type: 'slide',direction: 'left'});
        page.update(records[0].data);
        if (app.getProfile()=='portraitPhone') {
            backButton.show();
        }
    }
});

We've already specified that the list can only have one item selected at any given time (apart from when the app starts up, when it has no selection), so we simply detect the change of selection, and set the viewport's active card to be the detail page. Then we update the page with the data of the selected record. If we're in the portrait phone profile, we should show the back button so that users can slide back to the menu.

There are a couple of things to note here. First, we explicitly check that a record has been selected, even though we forced allowDeselect: false on the list's configuration. This is because, even in that case, this event fires twice: once on the old item's deselection, and then once for the new item. We only want the second firing, when the selected records array has an entry.

Second, note that we are mandating a sliding transition to the page card. However, this has no effect when the viewport only has one card (as it does for three of the four profiles), and only does something when there are two or more. This gives us the sliding effect we want in the portrait phone profile.

Lastly, we add the two other buttons' interactions:

// back button slides back to (card) menu
backButton.addListener('tap', function () {
    viewport.setActiveItem(menu, {type: 'slide', direction: 'right'});
    this.hide();
});

The back button - which remember, is only present for the portrait phone profile too - slides the viewport back to the menu and hides itself. (It will be re-shown when the menuList's selectionchange event fires again to take us to another page.)

The information button does nothing more than display the Wikipedia attribution in a modal Ext.Msg.alert box.

// info button provides attribution
infoButton.addListener('tap', function () {
    Ext.Msg.alert('',
        'Information made available under ' +
        '<a href="http://creativecommons.org/licenses/by-sa/3.0/">CC BY-SA</a> ' +
        'from <a href="http://en.wikipedia.org/wiki/Piet_Mondrian">Wikipedia</a>.'
    );
});

Final tweaks

The app at this point (as found in in the GitHub repo's 6_interactions branch) should work very well. Try it with different devices and orientations and get a feel for how it's worked out, relative to our original intentions.

However, if you're a rigorous tester, you might be able to catch it out on a couple of things. Firstly, if you start it in the portrait phone profile, the back button will be present at the top of the initial list. As well as when the user slides back from a page, we need to explicitly hide the back button in its own profile switcher:

backButton.setProfile = function (profile) {
    if (profile=='portraitPhone' && viewport.showingPage) {
        this.show();
    } else {
        this.hide();
    }
};

Here, we're looking for a flag, called showingPage, which we hope is set on the viewport. This isn't a Sencha Touch flag: it's something we need to set ourselves whenever the portrait phone profile slides back and forwards between menu and page. Set it to false when the apps starts up, and then toggle it in the menuList and backButton interactions:

var viewport = this.viewport = new Ext.Panel({
    ...
    showingPage: false,
    ...
}
 
menuList.addListener('selectionchange', function (model, records) {
    ...
    viewport.showingPage = true;
    ...
}
 
backButton.addListener('tap', function () {
    ...
    viewport.showingPage = false;
    ...
}

Next, you might notice that if you are viewing a page on a phone in landscape orientation, and then flip it to portrait, that the view changes back to the menu list, thanks to the this.setActiveItem(this.menu); code in the viewport's setProfile function.

It's more likely that the user would expect see the same content page, just reflowed, so we need to make sure that the setActiveItem method references the page card instead. However, this need not be the case if the user is still looking at the splash screen.

To cater for these conditions, simply add another flag to the viewport, called showingSplash, set to true when the app starts up, and which is set to false as soon as a page is ever viewed:

var viewport = this.viewport = new Ext.Panel({
    ...
    showingSplash: true
    ...
}
 
menuList.addListener('selectionchange', function (model, records) {
    ...
    viewport.showingSplash = false;
    ...
}

When a rotation to portrait happens, we then merely check that this is no longer true, and set the active card to be the page, rather than the menu.

viewport.setProfile = function (profile) {
    if (profile=='portraitPhone') {
        this.setActiveItem(this.menu);
        if (!this.showingSplash) {
            this.setActiveItem(this.page);
        }
        ...

It's worth noting that we've left in the this.setActiveItem(this.menu) line unconditionally, since we still need it to add the (previously floating) menu card back into the viewport upon rotation, even though it won't end up active.

Wrap up

So that's it. Our app is now functional, suited to both phones and tables, and able to react to changes in orientation - while presenting idiomatic user interface layouts from a single code base.

The complete source code is on the master branch of the GitHub repo, and the application itself can be tried out here. For convenience, index.html in the master branch links to a CDN version of Sencha Touch, so you'll need to be online to run it. The other branches assume you have the SDK locally.

Hopefully, this tutorial has given you an insight into smart ways in which user interfaces can cater for various different runtime conditions, and you'll be able to try applying the same principles to your own Sencha Touch applications.

Share this post:
Leave a reply

Written by James Pearce
James Pearce heads developer relations at Sencha. He is a technologist, writer, developer and practitioner, who has been working with the mobile web for over a decade. Previously he was the CTO at dotMobi and has a background in mobile startups, telecoms infrastructure and management consultancy. James is the creator of tinySrc, the WordPress Mobile Pack, WhitherApps, modernizr-server and confess.js, and has written books on mobile web development for both Wiley and Wrox.
Follow James on Twitter

27 Comments

Michael Eggers

3 years ago

Awesome article—useful information presented very clearly.  I’d seen the profile feature while trolling the docs, but hadn’t yet had time to figure out how it works.  You saved me some research!

I’m wondering about the template defined within the “page” object, as shown in Part 1.  It seems to just be an empty H2 tag.  Is this a typo, or does the template system have some really cool defaults that I should try to figure out?

James Pearce Sencha Employee

3 years ago

@Michael,

Thanks for the feedback. Our CMS has unhelpfully stripped out the {...} parts of the Sencha Touch’s templates. We’re just updating the (CMS) template to put them back. Give it a few minutes.

Of course the correct code is in the GitHub repo.

A.T.McClain

3 years ago

Thanks for the great article - much appreciated!  I also hadn’t really tuned into application profiles before this.

My one gripe (sorry) is that the transitions are all unexpectedly choppy on my first-gen iPad.  In particular, when switching from portrait to landscape then selecting the longest “Later Work” chapter, I can end up with a gray square artifact hovering over the text for a very noticeable amount of time. 

I’m sure the current generation iPad performs better, but it makes me worry about rearranging more complex layouts such as lists or forms (or running on slower platforms like BlackBerry).  Hopefully the device and browser performance can continue to evolve to get closer to completely fluid transitions available to native apps.

Fernando Madruga

3 years ago

Like A.T.McClain, I found the demo to be VERY unresponsive, taking up to 7 or 8 secs sometimes to switch to another item. I’m using an iPad 1 3G and using iOS 5 beta 4: have you tested the demo app on iOS 5 beta?

If this performance issue is solved (I’ll give it another try when iOS 5 goes live) then this article will surelly save me a lot of time, so thanks for the awesome two part article!

Fernando Madruga

3 years ago

Update: I’ve rebooted my iPad and the slow performance is still there. I noticed however that it appears less responsive when in landscape view or switching from portrait to landscape; in portrait mode feels more acceptable, but still slow, and switching from landscape to portrait also seems to take half as much as going in the other direction… Hope this extra info helps tracking down the problem.

Dawesi

3 years ago

Great article, was looking for this article when I noticed profiles in another example.

I’ve been developing apps and web apps for Android tablets and profiles are great for that. I think as tablets get the grunt they deserve (aka tablets coming out as we speak with common sense specs) we’ll see better specs than the ipad (aka ereader) type specs people have come to expect. Then rendering times will be sweet. (as it was with mobile and pc’s)

Thank goodness Android is taking 17% market share per quarter (q4 2010 and Q1 2011 IDC =34%) which is pretty good considering Android had 17% market share before they even released a tablet OS!

Looking forward to more Android action with Sencha Touch!

Well done.

Eric Guffens

3 years ago

Very useful articles but for people who are interested, it is impossible to find it with the Search engine in the Learning Center ( have tried with ‘Sencha Touch’, ‘Layouts’, ‘James Pearce’ keywords).
Best regards,
Eric.

James Pearce Sencha Employee

3 years ago

@Eric, we released this article as an ‘exclusive’ as part of our newsletter that went out last week. It will soon be generally available and then listed and searchable. Thanks!

Fernando Madruga

3 years ago

As I said b4, this is an excellent article. However, one thing I noticed, trying to get back to it and not having bookmarked it b4 (also, I didn’t remember HOW I came to the article in the first place): this excellent article has probably the worst title in history!!! I googled my heart out trying to get back here and it was only when I stumbled on the newsletter mail that I, seemingly hesitantly, clicked on an article named “Idiomatic Layouts”: c’mon! “Idiomatic”? I think of multi-language apps, *not* multi-layout or universal apps or orientation-aware apps or a thousand other variations I tried!! No wonder I couldn’t get here by googling!!

Again, excellent article but the title could have been more “searchable” and less “stylistic”, IMHO…

Rafael Oropeza

3 years ago

Hi

I’m using your excellent tutorial for a liitle proof of concept called FAM, located at http://enquis.dyndns.org/tablet


I’m wondering how to put a raw HTML code snippet instead when we invoke page.update(‘head.jpg’);

Question: is it possible to use any valid HTML tag inside page.update function? I mean, I’m trying to insert a video from a remote provider called Brightcove and it recommends to use something like this (I’m copying my own modification to your code

  var initialContent =
  ‘[removed][removed]’
  + ‘<object id=“myExperience1112998483001” class=“BrightcoveExperience”>’
  + ’  ‘
  + ’  ‘
  + ’  ‘
  + ’  ‘
  + ’  ‘
  + ’  ‘
  + ’  ‘
  + ’  ‘
  + ’  ‘
  + ‘</object>’
  + ‘[removed]brightcove.createExperiences();[removed]’
  + ‘resources/img/am.nino.png’;

  page.update(initialContent);

It should shows a video, but unfortunatelly it doesn’t work in Sencha Touch.

Any hint/suggestion?

Thx

cool

3 years ago

Ummm… Am I the only one who can’t figure out how to download Sencha Touch 2? All i see is ver 1.1.0

James Pearce Sencha Employee

3 years ago

@cool - the ‘II’ at the end of the title refers to the fact that this is part two of the article, not Sencha Touch v2 grin

Mike S

3 years ago

How do I change the color of the Toolbar ? Menu ?
Also when viewing on an IPAD the menu that comes on the left, can it have an icon next to it?

Steve O

3 years ago

Is it possible to mention, how to get compass up and running? I totally fail to install YARD, which is a dependency somehow and all i can read on the internetz is: “gem install yard” - but it’s simply not working, as it cannot be found neither locally nor in any repository i added or i provide manually.
I’d love to go on with sencha, but have to mess around with that ruby stuff right now. :(

Besides that, thank you very much for these examples! smile

Steve O

3 years ago

looks like the gems version in APT was outdated. i then fetched it from https://rubygems.org/pages/download and installed manually, now it works out of the box.

JM

3 years ago

This is a great tutorial.  Thank you.  I just have one question.  How can I put an image in each of the pages, as in an image in the Introduction page, in the Cubism page, etc.?  I am new to Sencha Touch, and would greatly appreciate your help.  Thanks.

col

3 years ago

great tutorial, thanks heaps.
well structured and nice and easy to follow, well more or less, since sencha seems a little bit wild and out of control with these endless possibilities wink

whats the easiest way to feed the menu and content with “normal” ext.components as for example a googlemap implementation?
in other words, not to define any store pages with fixed data structure.

any feedback is highly appreciated - best regards

Seth

3 years ago

I’ve noticed that if you set the width of “menu” to anything other than 150, it resets to 150 when showing back in tablet portrait mode.

I tried adding a “this.setWidth(other_width)” to the menu.SetProfile line for landscape tablet, but that causes the menu to not appear on first render.

Any insights into how to set it to larger width for sidebar rendering, but still a smaller size for menu rendering?

Mariusz

3 years ago

how can I make info button show info in panel, like data picked from sidebar?

Minh Ha

3 years ago

Thanks for the great tutorial.
There seems to be a bug though. Switching back and forth between landscapephone and portraitphone when viewing a page results in the menu shown instead of the page from the second time (so the first time going from landscape to portrait is ok).
Simple fix:
menu.setProfile = function (profile) {
        if (profile==“landscapePhone” || profile==“portraitTablet”) {
          this.hide();
          if (this.rendered) {
              this.el.appendTo(document.body);
          }
          this.setFloating(true);
          this.setSize(150, 200);
        } else {
          this.setFloating(false);
          // if showing page then hide menu
  if (viewport.showingPage) {
  this.hide();
  } else {
  this.show();
  }
        }
      };

Jeff Wooden

3 years ago

I noticed another small bug: When you select a menu item in the portraitPhone profile and decide to hit the back button, you cannot re-select the menu choice you were previously on because it is still currently selected and highlighted in the list. The fix for this is easy by adding an additional line of code to the backButton tap listener:

backButton.addListener(‘tap’, function () {
        viewport.setActiveItem(menu, {type: ‘slide’, direction: ‘right’});
        viewport.showingPage = false;
        this.hide();
    menuList.deselect(menuList.getSelectedRecords(), true);
});

Guido serio

3 years ago

Hi, great TUT!
However in iPad 2 the transition don’t work.
The content just load in the view replacing the previous one With a blink.
This happens alzo on the link of the finished app in part one.
Any Clues?
Thanks in advance
Guido

Ken Corey

3 years ago

Great Tutorial!  Thanks.  It’s very helpful to understand basic flow of an app in Sencha.

I did notice a few problems:
1) On my iOS5 iPad 2 the profile calls seems to get ‘landscapeTablet’, even if the app is started in portraitTablet mode.  When rotated into landscape, there’s no setProfiles called as it the iPad thinks it’s already in landscapeTablet.  Only when the iPad is then turned back to portrait does Sencha understand that it is, in fact, in portrait.

This is odd, as I tested it out on Nexus One, ipod (4.3.4), iPad 2 (5.0), and the desktop (Chrome latest under windows 7).

2) the line with “$base-color: #eee; ” in the mondrian.scss file seems to have no effect. My theme is still dark blue on all platforms.  Not sure how to check the version of sencha, but I just downloaded it a week ago, so we’re not talking old.

3) On my Nexus One with Android, the double buffering on animations is truly awful.  Basically, the animation indicating a move from one state to another is shown immediately, then the original state is shown for 1 second or so, then the new state is shown.  Looks very broken (even if it is working under the covers).

4) On the iPod the response to orientation changes is slow.  I turn the phone, you see the interface start to change (not changing width appropriately yet), and then it pauses for 2/3 of a second or so, and then finally snaps into place.  On the iPad 2, the change from landscape to portrait seems instantaneous.  The change from portrait to landscape seems to take over 1 second.

Are these known issues?  Is someone working on them?

-Ken

Fernando Madruga

3 years ago

Considering that Sencha Touch 2 is already in Developper Preview, I’d say that most of these bugs will not be fixed for v1. Check out http://docs.sencha.com/touch/2-0/#!/guide/whats_new for more info on ST 2. I haven’t tried yet to run this demo w/ it but just cutting a lot of the text from the data with v1.1, say, keeping 1 or 2 paragraphs worth of info, made it run much snappier, so if the rendering in v2 is as good as they say, maybe it’s finally useable…

Raeid Saqur

2 years ago

Thanks James!

Rach

2 years ago

Very helpful, thanks so much. Found in testing that starting up the app in upside-down portrait orientation on the iPad to show as ‘landscapeTablet’ as someone previously mentioned.

What worked for me was to replace Ext.orientation with Ext.getOrientation() and viola.

Andrew Wong

2 years ago

Currently using this tutorial to create an ebook for our http://www.ryetaga.com

I’m trying to modify the code to add extra pages, but the portrait slideout menu does not update with the extra pages.

All I have done is added extra sections in the data.js

===========
{id: 6, title: ‘Another One’, content:
        “Pieter Cornelis ‘Piet’ Mondriaan (after 1912, Mondrian); March 7, 1872 - February 1, 1944), was a Dutch painter.” +
        “<h1>Yet another chapter.</h1>”
      }
===========

Is there something else i have to add?

Leave a comment:

Commenting is not available in this channel entry.