Intro

After digging around in the UI5 examples and code, I was unable to find a mobile application that supported swipe-to-navigate pages, and I needed some for a project.

Swipe to navigate means, you use your finger to navigate between pages by swiping left or right: left will take you back, right will take you forward. Turns out, it wasn’t very hard to build. Here’s a description what I did. Please be nice, this is my first blog and I hardly consider myself a writer. Also if this stuff comes with the framework, feel free to laugh at me in the comments. I find it difficult to navigate the UI5 documentation 😛

Have a peek at the GitHub repository, it contains all the code here so you can try it for yourself. It’s not a bad base to start with either if you want to build an app. I’m packaging this up with PhoneGap when I’m done.

To give you on idea, here’s a visual of what we’re making:

What’s needed

We need four things:

  • Swipe events, so the app can detect when someone is putting their fingers on the screen and moving them in a particular direction
  • Slide animations. The slide animation that comes with UI5 out-of-the-box is only right-to-left, so we need one going in the other direction.
  • A way to identify the next or previous page
  • Combining everything by attaching the swipe event and animation to an app.to call.

And of course we need an application to build this into. I’m using grunt, bower and a Component to bootstrap an application. My abbreviated project’s code structure looks like this:


root
|-- package.json
|-- bower.json
|+- src
|   |-- Component.js
|   |-- index.html
|   |+- controller
|   |   |-- App.controller.js
|   |+- model
|   |+- views
|   |   |-- App.view.xml
|   |   |-- Page1.view.xml
|   |   |-- Page2.view.xml
|   |   |-- Page3.view.xml

The code that matters for this is all in Component.js, App.controller.js and App.view.xml.

For full code, please refer to the Git repository (above).

Swipe events

Conveniently, UI5 provides those for us, so: score. The events swipe-left and swipe-right are perfect for this. On non-mobile devices they’re also triggered by holding down the mouse button and dragging so that’s a bonus.

Swipe Animations

The page animations in UI5 are declared in sap.m.NavContainer (API document). We could create a fully custom one, but we can also realize that swiping from left-to-right is identical to right-to-left —- only in reverse!

This means, lucky us, that we can use the default slide animation and flip it. Here’s the code. You’ll notice it has a to and a back component.

Component.js (github)


init: function() {
    //attach animations
    this._setUpSwipeAnimations();
    //other things
    //call parent method
    UIComponent.prototype.init.call(this, arguments);
},
_setUpSwipeAnimations: function() {
    var slide = NavContainer.transitions.slide;
    //technically, swiping from left to right just means
    //reversing the "to/back functions" of the existing slide animation
    NavContainer.transitions["slide-left"] = slide;
    NavContainer.transitions["slide-right"] = {
        to: slide.back,
        back: slide.to
    };
}

This lives in Component.js. The code takes the default slide animation, and re-attaches it to the NavContainer, renamed as slide-left. It is then added again as slide-right, only the to and back parts are switched. That is all!

By adding this code in the init portion of the Component, and before the call the parent init method, it’s ready for action by the time the app itself is boostrapped.

Identifying pages

I like to keep things dynamic. Thankfully, by keeping the pages` class name and ID similar, this can be achieved with relatively little code:

Page1.view.xml (github)


<Page backgroundDesign="Solid" class="swipe-page">

By adding the same classname to all pages, the events can be attached to just the HTML bits that we like. In this case, all my pages have the class swipe-page attached to them.

The last thing is adding the pages to the app in the right order in App view:

App.view.xml (github)


<mvc:View
    xmlns:mvc="sap.ui.core.mvc"
    xmlns:core="sap.ui.core"
    xmlns="sap.m"
    controllerName="holcim.swipedemo.controller.App"
    displayBlock="true"
    resourceBundleName="holcim.swipedemo.i18n.i18n"
    resourceBundleAlias="i18n"
    id="swipedemo-view">
    <Shell>
        <App id="swipedemo-app">
          <mvc:XMLView id="swipe-page1" viewName="holcim.swipedemo.view.Page1" />
          <mvc:XMLView id="swipe-page2" viewName="holcim.swipedemo.view.Page2" />
          <mvc:XMLView id="swipe-page3" viewName="holcim.swipedemo.view.Page3" />
        </App>
    </Shell>
</mvc:View>

Observe the multitude of ID’s here: the main view is called swipedemo-view, the app is called swipedemo-app and all the pages have numbered ID’s: swipe-page1 to .. however many pages you have. This is to make retrieving all parts of the application easier.

This way we can determine the next page and the previous page.

Putting it together

The best place to link everything together is in the initialization phase of the App controller. This way the animations are ready to be used, and the code belonging to swiping the app is actually inside the correct controller.

App.controller.js (github)


return Controller.extend("holcim.swipedemo.controller.App", {
    onInit: function() {
        this._setupTransitions();
    },
    _setupTransitions: function() {
        $('body').on('swipeleft', '.swipe-page', function(e) {
            this._navigate(e.currentTarget.parentNode.id, 'left');
        }.bind(this));
        $('body').on('swiperight', '.swipe-page', function(e) {
            this._navigate(e.currentTarget.parentNode.id, 'right');
        }.bind(this));
    },
    _navigate: function(id, direction) {
        var newId, match, add;
        match = id.match(/.*swipe-page([0-9]{1,}$)/);
        add = (direction === 'left') ? 1 : -1
        if (match && match.length > 1) {
            newId = this.createId('swipe-page' + (Number(match[1]) + add));
            this.byId('swipedemo-app').to(newId, 'slide-' + direction);
        }
    }
});

Let’s break that down. The first interesting block of code is this one:


$('body').on('swipeleft', '.swipe-page', function(e) {
    this._navigate(e.currentTarget.parentNode.id, 'left');
}.bind(this));

This attached a handler to the body of the page for the swipeleft event, limited to class swipe-page. Without the class addition, this triggers on elements overlaying the page itself as well like header elements, lists, tables, forms and anything else you might have on there. By filtering on this class, the event’s currentTarget always contains a page. Observe that the ID was actually located in the view that the page is in, hence the currenTarget.parentNode.

The same code is attached to the swiperight event.

Second code block is the function that does the navigating:


_navigate: function(id, direction) {
    var newId, match, add;
    match = id.match(/.*swipe-page([0-9]{1,}$)/);
    add = (direction === 'left') ? 1 : -1
    if (match && match.length > 1) {
        newId = this.createId('swipe-page' + (Number(match[1]) + add));
        this.byId('swipedemo-app').to(newId, 'slide-' + direction);
    }
}

This takes the current target’s ID, which is something like __xmlview0–swipe-page1. To get the right page number, all we need is to get the number at the end of the string, here collected in regex array match, and either add 1 or subtract 1, depending on the direction of the swipe, and create an ID using the appropriately named method.

Thankfully, it doesn’t matter if the page you’re trying to navigate to doesn’t exist. UI5 will not throw an error, although you might see a warning in the console.

That’s it, really. Once again, see the gif at the top for a quick visual, or check the git repo for the details.

Cheers

To report this post you need to login first.

8 Comments

You must be Logged on to comment or reply to a post.

    1. Jorg Thuijls Post author

      Good question. First off, I never realized Carousel could be used in such a way.

      Having said that, the base of my solution is `sap.m.App`, which offers more flexibility in terms of application. I could have used SplitApp as well. Also you’re not limited to swiping left and right, if there’s ever a use case for it… this works up/down too. 

      For most purposes the Carousel seems to work, though. I’ll try it sometime.

      (0) 

Leave a Reply