Developing hybrid mobile applications with Onsen UI

Hybrid mobile applications have become trendy in the last years. Writing native applications could be hard for casual developers, those who don’t know too much about programming and just want to make a simple app (or not so simple) as quickly as possible. It requires learning different programming languages (and different ways to interact with each device) for every platform where you want to deploy your application, although it certainly has some advantages: best possible performance, full access to the device hardware, etc.

On the other hand, hybrid applications relies in a set of technologies that won’t change regardless the platform, the same as in web applications: HTML, CSS and Javascript. And since this set of technologies is so common on the Internet we will find unlimited resources and tutorials about how to implement this or that, anything we need. We could even use any common tool or framework for web development also for hybrid apps, such as jQuery or AngularJS, thus making the development process much simpler and faster. Although hybrid apps are usually not as efficient as native ones, the truth is that current smartphones are powerful enough to run most of the applications we could imagine without noticing any difference.

In this post we are going to develop an example hybrid application (Memo app) to show how to use Onsen UI, a framework to build quick and fancy User Interfaces. I am going to use Monaca IDE to develop this application (you can create a fully functional free account here) since it works pretty well with Onsen UI and its debugger is just awesome. You can check and fix your app from the IDE itself or from your phone (mobile debugger is available in Play Store and App Store for free) in a few seconds. Plus, for those who are not advanced mobile developers, Monaca will ease most of the process of importing plugins, debugging and building the application. If you prefer to use something different it’s totally fine, just remember to manually include the libraries that we are going to need.

Developing with Onsen UI and AngularJS

Onsen UI is a HTML5 framework to create modern and usable User Interfaces, so that we don’t need to fight too much with the appearance of our application. It keeps the UI development simple while we can focus in the functionality of the application itself. Onsen UI is meant to be used along with AngularJS, but it is also possible to use jQuery or any other framework. Here we will use AngularJS but will only describe the tricky parts in order to focus on Onsen UI.

The application we will make is a Memo App where we can store a list of tasks, classify them by category, modify and delete. To do this we are going to use the following Onsen UI components:

  • ons-navigator
  • ons-toolbar
  • ons-sliding-menu
  • ons-list
  • ons-carousel
  • ons-template
  • ons-popover

You can learn how these components work together, so you can, for example, include an sliding-menu only inside one specific page and not in the others, or make every element of a list into a carousel. Here you have the description of all Onsen UI components, as well as some quick styles that you may want to take a look at.

We will use Onsen UI 1.2.1 in this example. First, if you are using Monaca, create a Onsen UI minimum template project in the Dashboard and open it. AngularJS is already included in the default libraries, so there is no need to do anything else. If you don’t use Monaca just follow these instructions to install Cordova and Onsen UI and include the necessary libraries.

Before starting, let’s explain how do we use AngularJS in this application. In AngularJS we bind every page or main element of our application to a controller that will store the necessary variables and functions for the scope related to the bound page. For example, in order to list our current tasks in the main page we will need to store an array of tasks in the scope related to the main page, i.e. the main page controller. We can also have functions in this controller to modify our array of tasks, such as delete or complete functions. However, sometimes it would be better to split this functionality into different controllers because, for example, we address the involved functionality in different pages but still need to access to the same data (for instance, adding a new task).

Therefore, we will need a way to allow communication between our controllers. In the current approach we use a service to manage all the storage of the task itself and common functionality, so that our controllers will call this service any time they need to access or modify the task list. Every task and task list are objects defined in models/Memo.js, and each task object contains the needed information for the task itself, including name, category, description, creation date and whether it is completed or not. This is just an example and is not meant to be the best approach, so you can change the model as you want. The model is not related to Onsen UI so we won’t speak too much about it here, although you can have a look to the code on Github.

Enough details so far, let’s get down to work.

Coding a Memo Application

All the code of this application is available to download on Github. It is recommended to check it out if you have any doubt while reading this post. You may also want to play a little bit with the application to understand how it works before getting hands dirty:

https://frandiox.github.io/OnsenUI-Memo-App/

All of the style and design is easily customizable!

Approach and code

First element and welcoming page

In Onsen UI we should have only one main element that will decide the pattern of our application in order to manage several pages. We could use a sliding menu to change between pages, a tab bar and so on. We will use the most common one, the navigation pattern, what will allow us to have a parent-child relationship between pages (we have a stack of pages and we can navigate from one to another). Therefore, we write our ons-navigator as the main element in our index.html:

We declare the name of our navigator with var=”myNavigator”, so we can refer to it later on as a global variable. Inside the navigator we will display a welcoming page (with ons-page) where we show the title of the app with an ons-toolbar.

Tip #1 ↝ Notice that, even if we specify class=”center” for the title, the real style behavior is determined by the OS where the application is run. On iOS it will be displayed in the center, but on Android i’ts left-aligned.. To fix the title in the center we have specify fixed-style in the ons-toolbar

We could display a welcoming image as well, but let’s show a simple icon using ons-icon instead. Onsen UI allow to display icons from Ionicons and Fontawesome just like part of the font, so it will be displayed in any device. Our last element in this welcoming page will be a start button that will take us to the actual application. We use an ons-button for that and choose one of the possible modifiers to change its appearance. When the user clicks on this button, the navigator will change the current page to the next one. We use the method resetToPage( … ) (instead of pushPage( … )) just to remove all the previous pages from the stack and start from zero in the page we specify (slidingmenu.html). With this we will prevent to return to the welcoming page by pressing any “back” button.

Menu

Once we have our first element and our first page, let’s make the next pages that will be displayed after the welcoming page. The next one will be the main page where we will see all the tasks that our application stores. However, since we want to have a sliding menu in this page, we need to declare this element as the parent of the main page, what means that we will call this sliding menu first. To do so, let’s create a slidingmenu.html page containing our actual sliding menu. Onsen UI allows you to create a new HTML page inside the index.html itself by using templates, what is quite handy. It works exactly in the same way but, if our pages are small, we can have them all in the same place.

Our ons-sliding-menu will take the content of the menu itself from menu-page=”menu.html”, and this sliding menu will be displayed over main-page=”memo.html”. We set swipeable=”false” to avoid a conflict of sliding with our future carousel, but you can change this in anyway you need.

Let’s see now the content of our menu.html:

Static ons-list-item:

Dynamic ons-list-item (with AngularJS’ ng-repeat):

Once again we use an ons-template, so this page will be located in index.html. Inside the ons-toolbar we just set the title of the menu and include a button to close it. This menu will allow to choose a certain view of our tasks based on task’s category, and this category list will be accessed from ng-controller="categoryController". There are 3 static views over the tasks: show all of them, show only the completed tasks and show the ones without a specified category. Of course, this is just an example, you can change them or even delete them without problem.

After these static views we define a repeatable ons-list-item (by using AngularJS’ ng-repeat in the last ons-item-list in this code) that will iterate over our category list and display every category in the menu. Since we also want to display the number of tasks that each category has, we need to have that information in our categoryController as well. For instance, we have a counter variable called countAll that stores the total number of tasks in the memo app. Since this variable is stored in a service (memoService) and can be modified by several controllers (for example, when a task is created or deleted), we need to set a listener to this variable in categoryController, so that AngularJS will update its value automatically to the view everytime it is modified:

Changes in the view will be held in categoryController‘s method setViewRefresh(...).

Main page

So far so good, let’s look at the content of the main page, memo.html, where the actual task list will be displayed:

Lists of carousel tasks:

For every task, level 0 of the carousel with 3 different actions (buttons):

For every task, level 1 of the carousel with the basic information:

Since this is a big and important page, it is preferable to set it in its own file instead of using ons-template inside index.html. In general, we will only include there relatively small things like the sliding menu and the popover. Either way, once again we use an ons-toolbar to set the title and a link to a new page (additems.html, explained later), but also a button to open the sliding menu.

Tip #2 ↝ Here we have a little trick to make the sliding menu swipeable only when it’s open and not when it’s closed, so it doesn’t conflict with the carousels. We prepare two events to change the behavior of the sliding menu like this:

Besides, we will show a message in case there is no task displayed with the current view by using ng-hide. And finally, the bulk of this page, the dynamic ons-list where each item is an ons-carousel. We use ng-repeat to iterate over the task list that memoController provides, and each of these items will be defined as a ons-carousel of two levels where the level 1 will be a summary of the task and the level 0 (to go from 1 to 0 we have to swipe to the right) will contain buttons to perform actions over the selected item.

Tip #3 ↝ The buttons in the level 0 of a carousel are displayed vertically and horizontally centered no matter how many there are.

This is a CSS trick:

We can delete it by just triggering a function in our memoController that removes this item from the task list, modify it in a similar way, or mark it as completed. For the complete action, in addition to trigger the corresponding function behind the scenes (to check a task as completed in our objects), we want to make it fancy and restore the corresponding carousel to its level 1 (the task summary) and show an icon to know that it is now completed:

Tip #4 ↝ In order to restore a carousel to its original position we need to dynamically set a distinct name for every carousel in our list, so we can refer to it later on and change its level. 
var="{{'carousel.id' + $index}}" does exactly the trick, naming every new item as carousel.idX, where X is the item’s index in the ons-list. Notice that if we name these items in a static way, i.e. var=”myCarousel”, every item in ng-repeat will overwrite the previous one, so in the end only the last one will be accessible by that name. In order to call the function setActiveCarouselItemIndex(1) over the clicked item we need to use bracket notation instead of dot notation: carousel['id'+$index].setActiveCarouselItemIndex(1);

Show details & Modify task

Let’s address now to the show-details/modify task action. We have a new page for it, itemdetails.html, with its own controller:

This new page is called from the main page by myNavigator.pushPage( … ), what basically adds a new page to the stack in our ons-navigator. Later on we can come back to the previous page in the stack by using myNavigator.popPage() or an ons-back-button.

In this page we display all the content of a specified task inside HTML inputs, so in addition to see the content we can modify it. It is necessary to bind every input to an ng-model, so we can access to the new values from the controller. For instance, the name of a task is bound to ng-model=”item_name”, so we can access it with $scope.item_name from detailsController.

Add new task and popOver

Finally, the page to add a new task to the list is called additem.html, and is the simplest one. Exactly like the previous page, we bind the HTML inputs to ng-model‘s, and in our addItemController we just add the new task to the task list in memoService after checking that all the inputs are valid. In the case that, for example, the name input is empty, we want to alert the user about it and avoid creating a new task. To do that we create an ons-popover in our index.html:

Quite simple. And now we need to display it from our controllers (addItemController and detailsController, when saving a modification):

This last line will show the popover right on the HTML element '#item-name', we just need to control when to display it (only when the input is empty).

Final notes

Since this application is just an example of Onsen UI and AngularJS it does not support persistent data with window.localStorage, Web SQL, IndexedDB, or anything else, though it is possible to implement it. Instead, we add some example data when the app starts, so we can test it easily.

Conclusion

We have reviewed so far how to build a not-so-trivial hybrid mobile application with a quite fancy User Interface using Onsen UI as well as several tips that could become handy in some situations. AngularJS is not compulsory but recommended since it works very well with OnsenUI and makes things a lot easier. As mentioned before, all of this code is on Github, so you can change it and try any new feature that comes to your mind.

And that’s pretty much it, hybrid mobile applications are certainly worth a try, especially because tools like Onsen UI keep everything simple. If any doubt comes up, don’t hesitate to ask anything in the comments or on Stack Overflow under the tag onsen-ui. More tutorials will be coming soon!


Originally published at onsen.io on February 23, 2015.

One clap, two clap, three clap, forty?

By clapping more or less, you can signal to us which stories really stand out.