Building a mobile app with Cordova, Ionic and Hippo CMS 

Ivo Bronsveld

Ivo Bronsveld is Hippo Lead Pre-Sales Engineer. Often the guy to come up with great solutions to challenging problems to help new customers and partners see how using the Hippo CMS technology stack can make their life easier.

Code for this lab can be found on GitHub.

Introduction

As you might have already picked up by now, Hippo CMS has a slightly different approach when it comes to content management. Because in Hippo, CMS editors will not directly edit HTML pages, they edit the information behind the HTML page. This information is stored in the repository, in a structure defined by a content type. Now, when a visitor requests a certain page, the content (that is actually stored as structured data) will dynamically be transformed to layout (typically HTML) through the use of Hippo components and templates.

This separation of information (content) and the actual presentation has many benefits, but the most important one is the fact that it enables reuse of content across multiple channels. Using a completely different look and feel for the same piece of content is as simple as varying the components and templates that are used for a given URL (site).

A great example of this so-called "separation of concerns" is the multi channel setup in our online demo. Unfortunately, it does not necessarily show the power of structured content. Especially for people looking for a Content as a Service (CaaS) solution.

Working as a pre-sales consultant means you often need to come up with solutions on the spot, so when I was asked if we had a good example to demonstrate what we can do in a Content as a Service (CaaS) scenario I had to be creative. Luckily I had a couple of hours to prepare, so I decided to fire up my development environment and started coding on such an example by building a mobile app using Apache Cordova, Ionic Framework and our Hippo Go Green demo environment.

Building a mobile app with Cordova, Ionic and Hippo CMS

In this post I will explain how you can use the REST API of our Hippo Go Green demo environment in a native mobile environment using Cordova and Ionic.

Cordova: At Hippo, we use a lot of open source components from the Apache Software Foundation, so Cordova was a logical choice. It is a platform that can build native apps by just using HTML, CSS and JavaScript.

IonicIonic is very popular open source framework to build hybrid mobile apps. It uses a lot of the web technologies you are probably already familiar with (such as AngularJS and Sass), which makes development easier.

Creating the app

For the rest of the tutorial, it is important you have both Cordova and Ionic installed. If not, the links above provide instructions on how to download and setup the tools. Next, you will also want an IDE of some sorts. vi works, but IDE's do make your life easier. Oh, and as I am using Mac OS X, I will focus on building an iOS app. This tutorial can obviously also be applied to Android as well.

Now that that's out of the way, let's get started!

Step 1: Create the project

Creating an empty project is as simple as this:

$ ionic start hippogogreen-app tabs

This will create a new application using the Ionic tools. The great thing about Ionic is that it ships with cool templates out of the box. In this case we use the 'tabs' template.

Before we can test this, we need to add a platform to it so Cordova knows what platforms to support:

$ cd hippogogreen-app
$ ionic platform add ios

Now, build it to see the results of our effort.

$ ionic build ios
$ ionic emulate ios

//onehippo-prod.global.ssl.fastly.net/binaries/ninecolumn/content/gallery/connect/labs/mobile/cordova-ionic/sample-screen.png

It's as easy as that!

Adding some functionality to the app

As mentioned, I will use the REST API provided by the Hippo GoGreen demo environment. This API is implemented using the RESTful API support of Hippo CMS. In this post I will not describe how to actually build a RESTful API using Hippo CMS, I will just use the data that this API provides.

This specific API provides two methods:

  • Top Products Get all the top products through the REST API
  • Product detail Get the details of a product

In our app we will have to create support for these two features.

Step 1: Load the project

First, start up your IDE and import / load the project. The most important folder for now is the www folder. In this folder there is a template folder, which contains the HTML templates for our views and a js folder that contains all the actual javascript files.

Step 2: Adding the data access methods

Open the file services.js. In this file, there are a bunch of AngularJS factories defined to provide the data to the view. We will have to add our own one to the file. To do so, add the following code:

.factory('Products', function() {
    return {
        top: function() {
            // TODO: Implement a GET method for the top products
        },
        get: function(id) {
            // TODO: Implement a GET method for the specified product
        }
    }
})

We have not really added any logic just yet, we just created a factory that we can use to get the data. The data URL we will be using is this one: http://www.demo.onehippo.com/restapi/topproducts?_type=json&sortby=hippogogreen%3Arating&sortdir=descending&max=10

To get and process the data for the view to use, we will use the Angular $http provider. Replace the previous code with this:

.factory('Products', function($http) {
    return {
        top: function() {
            var url = 'http://www.demo.onehippo.com/restapi/topproducts?_type=json&sortby=hippogogreen%3Arating&sortdir=descending&max=10';
            return $http.get(url);
        },
        get: function(id) {
            // TODO: Implement a GET method for the specified product
        }
    }
})

The method returns a promise to the data (array). In other words, we don't return the data yet, we only return a 'promise' that we will (sometime, when the data actually gets returned from the external URL). In the next step we will handle this promise and make sure the view gets the right data to work with.

Step 3: Add a view

If you used the 'tabs' option when creating the app, you should have a bunch of templates already available. Add a new file to this folder and name it tab-products.html.

Paste the following HTML in the file:

<ion-view view-title="Top Products">
  <ion-content>
    <ion-list>
      <ion-item class="item-avatar" ng-repeat="product in products" type="item-text-wrap" href="#/tab/product/{{product.id}}">
          <img ng-src="{{product.smallThumbnail}}">
          <h2>{{product.localizedName}}</h2>
          <small>{{product.price| currency:"EUR "}}</small>
      </ion-item>
    </ion-list>
  </ion-content>
</ion-view>

The app itself does not know about this view yet, so we need to update the routing and tab bar in the bottom. To change the tab bar, open the tabs.html file and add the following snippet in the <ion-tabs> tag:

    <!-- Products Tab -->
    <ion-tab title="Products" icon="ion-navicon-round" href="#/tab/products">
        <ion-nav-view name="tab-products"></ion-nav-view>
    </ion-tab>

The URL in the href above does not really exist (at least not there). When we request that URL, the Angular ui router will determine the correct view to use. So, we need to tell ionic where to find our template and controller, which can be done in the app.js file in js folder. Open it and add the following lines:

  .state('tab.products', {
      url: '/products',
      views: {
          'tab-products': {
              templateUrl: 'templates/tab-products.html',
              controller: 'ProductsCtrl'
          }
      }
  })

The controller (ProductsCtrl) is in charge of passing the data to the view. We have not added this one yet, so let's open the controllers.js file and add the following controller to the script:

.controller('ProductsCtrl', function($scope, products) {        
  $scope.products = products.data;
})

This is a very simple script. All it does is pass the data to the scope (and therefor the template). If you are familiar with Angular, you might realize that we return a promise in our factory, yet we do not resolve the data here. Instead, we are using a variable here called products. So the final piece of the puzzle is to make sure that we resolve the data. We could do this in the controller itself, but in this case we can also do this by using the resolve attribute of the ui router.

Replace the previous addition to the app.js file with this snippet:

  .state('tab.products', {
      url: '/products',
      views: {
          'tab-products': {
              templateUrl: 'templates/tab-products.html',
              controller: 'ProductsCtrl',
              resolve: {
                  products: function (Products) {
                      return Products.top();
                  }
              }
          }
      }
  })

This ensures that the router loads the data first, before loading the view. It will pass the resolved products variable to our controller, where we put the data in the view.

Step 4: Test the app

As before, we could build and run the app using the following commands:

$ ionic build ios
$ ionic emulate ios

However, Ionic also ships with a very cool way to test the app inside your browser. All you have to do is run the serve command and you've got a site running!

$ ionic serve

This will pick up any changes you make to the app without having to rebuild it every time. Just hit refresh.

//onehippo-prod.global.ssl.fastly.net/binaries/ninecolumn/content/gallery/connect/labs/mobile/cordova-ionic/result-screen.png

Adding the detail page

We currently have the overview page up and running. However, we also need to provide our customers with a way to see more product information. To do so, we will need to add a detail page to the app.

Step 1: Adding the routing

Adding the detail page should not be too hard by now. In order to access the details of a product we should actually just request the URL we get as part of the overview data. It provides us with a complete fully qualified URL in the 'productLink' field. So, all we need to do is actually request the contents using this URL.

We will implement the link between the overview and the detail page. Replace the <ion-item> line in tab-products.html with the line below.

<ion-item class="item-avatar" ng-repeat="product in products" type="item-text-wrap" href="#/tab/product/{{product.productLink}}">

This will make sure it will include the productLink as a parameter for the view to use. Next, let's setup the routing. Open the app.js file and add the following lines:

  .state('tab.product-detail', {
      url: '/product/{productUrl:.*}',
      views: {
          'tab-products': {
              templateUrl: 'templates/product-detail.html',
              controller: 'ProductCtrl',
              resolve: {
                  product: function (Products, $stateParams) {
                      var productUrl = $stateParams.productUrl;
                      return Products.get(productUrl);
                  }
              }
          }
      }
  })

In these lines we added the logic that will make sure the factory gets the right URL, as well as ensuring the product will be passed to the controller by using the 'resolve' mechanism.

Step 2: Adding the data access and controller

But, in order to get the details of a product, we will need to implement the get method of the Product factory in services.js like this:

        get: function(productUrl) {
            // Final url is productUrl + the _type=json argument
            var url = productUrl + "?_type=json";
            return $http.get(productUrl);
        }

It is pretty similar to the other method, but we will need to add the _type=json argument in order to ensure the correct output.

We are almost done, but the controller is still missing. So, add the following controller to the controllers.js file. Note that this is for a single Product - we already had the Product sCtrl (plural!).

.controller('ProductCtrl', function($scope, product) {
      $scope.product = product.data;
    })

Step 3: Add the detail page

Last but not least, add the detail page to the templates folder. Create a new HTML file called product-detail.html.

<ion-view view-title="{{product.localizedName}}">
    <ion-content class="padding">
        <div class="list card">
            <div class="item item-divider">
                {{product.localizedName}}
            </div>
            <div class="item item-image">
                <ion-slide-box on-slide-changed="slideHasChanged($index)">
                    <ion-slide ng-repeat="image in product.images">
                        <img ng-src="{{image}}">
                    </ion-slide>
                </ion-slide-box>
            </div>
            <div class="item item-text-wrap" ng-bind-html="product.summary">
            </div>
            <div class="item item-text-wrap" ng-bind-html="product.description">
            </div>
            <div class="item">
                <b>{{product.price| currency:"EUR "}}</b>
            </div>
        </div>
        <button class="button button-block button-balanced">
            <i class="icon ion-ios7-cart"></i> Order...
        </button>
    </ion-content>
</ion-view>

You can see the power of a framework like Ionic. To add a slider for the product images, all we have to do is add the <ion-slide-box> to the view. Ionic has many more of these powerful components that you can use to build pretty much any type of mobile application.

Step 4: a final test

If you used the serve command before, you can just simply refresh the page and see the results in your browser. It should look something like this:

//onehippo-prod.global.ssl.fastly.net/binaries/ninecolumn/content/gallery/connect/labs/mobile/cordova-ionic/result-screen2.png

Finally, let's build it and see if it works in the emulator as well.

$ ionic build ios
$ ionic emulate ios

//onehippo-prod.global.ssl.fastly.net/binaries/ninecolumn/content/gallery/connect/labs/mobile/cordova-ionic/result-screen3.png

As we can see, success!

Conclusion

As you can see building a mobile app that looks great and works like an actual mobile app should work is actually quite easy. Combining the powerful content management capabilities of Hippo CMS with a modern framework like Ionic allows you to create very powerful and innovative CaaS solutions.

Feel free to let me know what you think!