API Development

Dynamically Style Your App Based on Device Orientation

Alloy has some really interesting ways of styling your app. Not only can you create styles based on IDs and classes, but it’s also possible to add rules to that. Most people already use this by specifying platform specific style rules like this:

"Label[platform=ios]": {
width: Ti.UI.SIZE,
height: Ti.UI.SIZE,
color: "#000"
}

However, what a lot of people don’t know is that these rules can be based on custom variables.

I got the question recently about how to set up style rules based on device orientation. Of course, this can be solved using these style rules, but it wouldn’t update it when the page was already loaded. Then of course I remembered you can configure backbone events, which would be perfect for the job.

So how do we do it?

Define Globals

First, we’ll need to define the globals we want to use. For the tss rules to work we would need to store a boolean, but for clarity I’ve chosen to store two different booleans: one for portrait and one for landscape. This, I’ve put in alloy.js

Alloy.Globals.isLandscape = false;
Alloy.Globals.isPortrait = true;

As you can see, we’re guessing the phone is in portrait mode, which is most likely the case, but might not be. So, we’ll need to validate this.

if ([Ti.UI.LANDSCAPE_LEFT, Ti.UI.LANDSCAPE_RIGHT].indexOf(Ti.Gesture.orientation) > -1){
    Alloy.Globals.isLandscape = true;
    Alloy.Globals.isPortrait = false;
  }

So now, in case the phone is in landscape, we override the booleans to match this situation.

Monitor Orientation Change

Now, we have to monitor orientation change using Ti.Gesture

Ti.Gesture.addEventListener('orientationchange', (e) => {
});

One thing you’ll notice is I’m using ES6 here, which is fully supported starting in Titanium SDK 7.1.0.GA which was recently released.

Next, we’ll have to determine which orientation the device has changed to, and we need to store it in the already defined globals. That’s how you end up with the following code:

Ti.Gesture.addEventListener('orientationchange', (e) => {
  if ([Ti.UI.LANDSCAPE_LEFT, Ti.UI.LANDSCAPE_RIGHT].indexOf(e.orientation) > -1) {
    Alloy.Globals.isLandscape = true;
    Alloy.Globals.isPortrait = false;
  } else {
    Alloy.Globals.isLandscape = false;
    Alloy.Globals.isPortrait = true;
  }
});

Dynamic Styling

With this all set up, we can now style every page based on the orientation of the device using tss rules. For every element you want to style differently in Landscape and Portrait, you’ll need to define the styling separately in your tss by doing the following:

".DynamicLabel[if=Alloy.Globals.isPortrait]": {
".DynamicLabel[if=Alloy.Globals.isLandscape]": {

Here you already see why I found it important to define 2 globals instead of one – it gives clarity in the styling files.

As long as the booleans are always updated correctly according to what was shown above, only one of the 2 rules should be applied, never both.

But now we’re facing one important problem, the UI doesn’t update when the page is already opened, which brings us to the next topic.

Using Alloy’s Class Methods

Alloy has some very powerful methods built in. Classes is one of them, and those are not lost when compiling the app. You can still add and remove classes as described in our documentation.

When orientation changes, you’ll need to re-apply the class, in our example DynamicLabel, to the element you want to have changed dynamically. To do this, you could first remove it, and then re-add it. This is the way to go when you’ve assigned the element multiple classes. However, if the only class added to the element is the class you want to re-apply, you can just reset the element altogether using the following:

$.resetClass($.DynamicLabel, 'DynamicLabel');

ResetClass removes all classes, and then adds the class(es) you’ve provided, in our case DynamicLabel. By doing so, all rules that are provided in the tss are re-applied. We defined the class twice, both with a different boolean as rule. So when you re-apply the class, we’re automatically using the right one.

Listening for Events

So now the last part, how do you know, in a window, the orientation changed? For this we’re going to use backbone events. In alloy.js, we’ll be adding the events first.

Alloy.Globals.events = _.clone(Backbone.Events);

Then, in the monitor for events section, we’re going to add an event trigger like this:

Ti.Gesture.addEventListener('orientationchange', (e) => {
  if ([Ti.UI.LANDSCAPE_LEFT, Ti.UI.LANDSCAPE_RIGHT].indexOf(e.orientation) > -1) {
    Alloy.Globals.isLandscape = true;
    Alloy.Globals.isPortrait = false;
  } else {
    Alloy.Globals.isLandscape = false;
    Alloy.Globals.isPortrait = true;
  }
  Alloy.Globals.events.trigger('orientationchange');
});

You can see we’ve added one extra line to fire the event. This is done after the booleans were updated.

Then, in our controller, we’re going to add this snippet:

Alloy.Globals.events.on('orientationchange', () => {
  $.resetClass($.DynamicLabel, 'DynamicLabel');
});

This will, as explained earlier, re-apply the class to the specified element. The above is actually all the logic needed anywhere in the app where you want. So what does our xml look like? Nothing special actually, just this:

<Label id="DynamicLabel" class="DynamicLabel" />

So, how does it look like? Have a look:

The entire sample app is also added to GitHub of course, check out the full source of the app, and run the sample locally, right here.