Telerik blogs

<-- Read Part 2

We're ready to round out this application that we've created by adding additional views, some native device access, and build it into an installable package using Icenium cloud services.

First off, I've removed all of the "explicit" jokes from the database to keep everything on the up and up. They still exist as text that says "You aren't old enough to see this joke." for the sake of having a third category on our upcoming dashboard chart. However, let's change the Tabstrip at the bottom to have "Funny", "Nerdy" and "Dashboard" buttons.

Altered Tabstrip

<!-- the funny jokes -->
<div data-role="view" id="funny" data-layout="main" data-title="The Facts: Funny">
  <p>Funny Chuck Norris facts will go here.</p>
</div>

<!-- the nerdy jokes -->
<div data-role="view" id="nerdy" data-layout="main" data-model="window.app.nerdy.viewModel" 
  data-title="The Facts: Nerdy" data-init="window.app.nerdy.getRandomJoke">
  <!-- this header overrides the header defined in the layout -->
  <div data-role="header">
     <div data-role="navbar">
        <span data-role="view-title"></span>
        <button data-role="button" data-align="right" data-icon="refresh" data-bind="click: refresh"></button>
     </div>
  </div>
  <h3 data-bind="html: joke"></h3>
</div>

<!-- the dashboard -->
<div data-role="view" id="dashboard" data-layout="main" data-title="Dashboard">
  <p>Chuck Norris charts will go here.</p>
</div>

<!-- the common layout for all views -->
<div data-role="layout" data-id="main">
  <!-- the footer widget pins content to the top of the screen -->
  <div data-role="header">
     <!-- the navbar widget creates a title bar which can also hold buttons -->
     <div data-role="navbar">
        <!-- the view-title widget displays the value of whatever is in the 'data-title' attribute on the current view -->
        <span data-role="view-title"></span>
     </div>
  </div>
  <!-- the footer widget pins the content to the bottom of the screen -->
  <div data-role="footer">
     <!-- the tabstrip widget creates the familiar mobile tabstrip -->
     <div data-role="tabstrip">
        <!-- each tabstrip button is an anchor. It's href attribute points
            to the id of the view that should be shown when that items is tapped. -->
        <a href="funny" data-icon="delete">Funny</a>
        <a href="nerdy" data-icon="home">Nerdy</a>
        <a href="dashboard" data-icon="more">Dashboard</a>
     </div>
  </div>
</div>

<script src="scripts/app.js"></script>

Now the nerdy jokes are on the second tab, and the funny ones are first. The icons don't really make sense anymore, but we'll fix that shortly. Right now, lets add in the ability to retrieve a funny joke. Let's take care of the JavaScript part first by creating a "funny" model that we can use just like we did the "nerdy" one. Since we already have the function to retrieve a joke from the database, we can just alter it a bit so that we can pass in the category of joke we want to get back, and the view model that should be updated when the AJAX call completes.

Add A Funny Model

(function () {
   var api = "http://facts.azurewebsites.net/api/";

   // create a variable on the window to hold all of our custom code
   window.app = {};

   // get a random joke by category
   var getRandomJoke = function (category, viewModel) {
      $.get(api + "jokes?type=" + category, function (data) {
         // update the view model
         viewModel.set("joke", data.JokeText);
      });
   };

   // create a funny model to encapsulate all of the logic needed for the funny jokes view
   window.app.funny = (function () {

      // create a view model which we can bind to the HTML
      var viewModel = kendo.observable({
         joke: null,
         refresh: function () {
            getRandomJoke("funny", this)
         }
      });

      // anything that is returned out of this function will be avaialble
      // on window.app.funny
      return {
         getRandomJoke: function () {
            // call the random joke method passing in the category and viewmodel to be updated
            getRandomJoke("funny", viewModel);
         },
         viewModel: viewModel
      }

   }());

   // create a nerdyModel to encapsulate all of the logic needed for the nerdy jokes view
   window.app.nerdy = (function () {

      // create a view model which we can bind to the HTML
      var viewModel = kendo.observable({
         joke: null,
         refresh: function () {
            getRandomJoke("nerdy", this)
         }
      });

      // anything that is returned out of this function will be avaialble
      // on window.app.nerdy
      return {
         getRandomJoke: function () {
            // call the random joke method passing in the category and viewmodel to be updated
            getRandomJoke("nerdy", viewModel);
         },
         viewModel: viewModel
      }

   }());

   // create a new kendo ui mobile app using the whole page
   new kendo.mobile.Application(document.body, { transition: "slide" });

}());

Notice that I altered the getRandomJoke function that the models actually return so that I could pass in a category and a view model. Now the getRandomJoke actually calls a function which invokes the global getRandomJoke and passes in the right parameters. JavaScript is very functional in nature, so using a bunch of functions like this is quite common. There are some patterns to make this a bit less verbose (promises and deferreds in jQuery), but for the sake of simplicity, we'll leave it as is. I'm sure you already have some ideas about how you might simplify that code!

Now let's wire up the HTML for the funny view to the funny model in the JavaScript. It's nearly identical the the nerdy view.

Funny View

<!-- the funny jokes -->
<div data-role="view" id="funny" data-layout="main" data-model="app.funny.viewModel"
     data-title="The Facts: Funny" data-init="app.funny.getRandomJoke">
    <!-- this header overrides the header defined in the layout -->
    <div data-role="header">
        <div data-role="navbar">
            <span data-role="view-title"></span>
            <button data-role="button" data-align="right" data-icon="refresh" data-bind="click: refresh"></button>
        </div>
    </div>
    <h3 data-bind="html: joke"></h3>
</div>

Notice that I overrode the header on the funny view too? At this point, it probably makes more sense to refactor and add a custom header to the dashboard which is now the only view which doesn't need the refresh button.

Let's wire up that dashboard view. I'm going to use a Kendo UI pie chart to show the percentage of jokes in different categories. Since Kendo UI DataViz chart configuration is usually somewhat verbose, I'm going to select an element from the view and initialize it with what we call "imperative initialization". This is simply when you select an element with jQuery and call a function on it to transform it from a simple div into something like, a pie chart for instance.

Before we can wire up our dashboard view, we need to add an API endpoint which returns our data in a chartable format. For pie charts, we want the data as a percentage of the whole.

Add a new controller to the the-facts.data project called DashboardController.

Create a new class in the Repositories folder called DashboardRepository. It should have one method called Get, which returns the data formatted for our pie chart. Specifically, we need a category field and a value field. All of the values when added together should equal 100 since a pie chart shows each category as a part of the whole. This means we get a count of each joke and divide it by the total.

Dashboard Repository

public class DashboardRepository {

   readonly ChuckNorrisEntities _entities = new ChuckNorrisEntities();

   public object Get() {

      // not the most efficient query - this would be better as a stored procedure with a common table expression (CTE)
      decimal count = _entities.Jokes.Count();

      decimal nerdy = (from t in _entities.Categories
                where t.CategoryId == 0
                select t.Jokes.Count()).FirstOrDefault();

      decimal xplicit = (from t in _entities.Categories
                 where t.CategoryId == 1
                 select t.Jokes.Count()).FirstOrDefault();

      decimal funny = (from t in _entities.Categories
               where t.CategoryId == 2
               select t.Jokes.Count()).FirstOrDefault();

      return new List<object> {
         new { category = "nerdy", value = (nerdy / count) * 100 },
         new { category = "explicit", value = (xplicit / count) * 100 },
         new { category = "funny", value = (funny / count) * 100 }
      };

   }
}

Obviously, that's not the most efficient query. Hopefully you wouldn't use 4 different queries to get your result. You could make a strong argument for a stored procedure here. Also, notice that I returned a List<object>? You can do that without issue if you are working with JSON. However, the XML Serializer is not OK with this and will throw an error.

Now we just return this Get method result from the "DashboardController".

DashboardController.cs

public class DashboardController : ApiController
{
   readonly Repositories.DashboardRepository _repository = new Repositories.DashboardRepository();

   public object Get() {
      return _repository.Get();
   }
}

If you visit the /api/dashboard endpoint in Chrome, you will see an error since the XML Serializer doesn't like our generic object. However, if you use IE you will get the valid JSON file.

Using Kendo UI DataViz

Kendo UI includes a rich Data Vizualization framework that you get for FREE with your Icenium projects. It's a very comprehensive suite that offers all manner of standard charts that are interactive and touch friendly.

For our purposes, we'll create a pie chart which allows us to visualize the categories of jokes we have. Now that the API method exists to return the data, we just need to create the chart and configure it.

We already have a chart view in the HTML for the Icenium project. Go ahead and create a <div> inside of that view which will eventually become the chart.

Dashboard View

<!-- the dashboard -->
<div data-role="view" id="dashboard" data-layout="main" data-title="Dashboard">
  <div id="chart"></div>
</div>

Now we just need to initialize the chart when the the view loads. We already know that each view fires an init method the first time it is shown. We are handling that event in the two other views. Let's create the JavaScipt model object with the init view.

Dashboard Model

window.app.dashboard = (function () {

  var init = function () {
     $("#chart").kendoChart({
        title: {
           text: "Chuck Norris Fact Categories"
        },
        dataSource: {
           transport: {
              read: api + "dashboard"
           }
        },
        legend: {
           position: "top"
        },
        chartArea: {
           background: ""
        },
        seriesDefaults: {
           labels: {
              visible: false
           }
        },
        series: [{
           type: "pie",
           field: "value",
           categoryField: "category",
        }]
     });
  };

  return {
     init: init
  };

}());

Then we just call the init method using the data-init attribute on the Dashboard View...

Call init Method On Dashboard View

<!-- the dashboard -->
<div data-role="view" id="dashboard" data-layout="main" data-init="app.dashboard.init" data-title="Dashboard">
    <div id="chart"></div>
</div>

And with that, the Dashboard View is wired up and we have a pie chart!

You now have a complete working app! Lets start to tweak the appearance some. First off, I want to get some better icons.

Creating Custom Icons

Kendo UI Mobile ships with over 300 icons out of the box, however only 33 are exposed to be used with data-icon attributes. You can see a full list of all of the icons shipped in Kendo UI in the documentation. There are some better ones in there that we can use. To do that, we need to use a bit of custom CSS. Open up the styles/main.css file and remove all the content. Replace it with the following CSS...

Custom Icons CSS

/* custom icon definitions */

.km-funny:after,
.km-funny:before
{
    content: "\e093";
}

.km-nerdy:after,
.km-nerdy:before
{
    content: "\e094";
}

.km-dasboard:after,
.km-dashboard:before
{
    content: "\e04c";
}

Now we can use these new icons with the data-icon attribute, setting the value to the value we defined in the stylesheet.

Update Icons In Tabstrip

<!-- the tabstrip widget creates the familiar mobile tabstrip -->
<div data-role="tabstrip">
  <!-- each tabstrip button is an anchor. It's href attribute points
       to the id of the view that should be shown when that items is tapped. -->
  <a href="funny" data-icon="funny">Funny</a>
  <a href="nerdy" data-icon="nerdy">Nerdy</a>
  <a href="dashboard" data-icon="dashboard">Dashboard</a>
</div>

IMG: New Icons

You aren't limited to just these icons. This article explains how to add in your own custom icons or third party icon fonts like Font Awesome.

Beyond just controlling icons, we can alter the visual look of the application itself. Right now, this application adapts to the look and feel of the OS. You also have the option of using a flat skin, which is consistent across any device. To do that, specify the skin property in the application initialization code.

Use Flat Skin

// create a new kendo ui mobile app using the whole page
new kendo.mobile.Application(document.body, { transition: "slide", skin: "flat" });

Now the app looks the same across devices with a nice flat look and feel.

This app is just about ready to go. However, so far we haven't accessed ANY device features. The whole point of building hybrid apps is to build the UI in HTML and then be able to access device features using Cordova.

Cordova And Device Access

Icenium is built on the Cordova platform which provides the shell in which the app runs, as well as some device methods out-of-the-box. You can see a complete list of device specific methods on the Cordova site. Cordova also exposes some device level events that we can attach event listeners to. In our case, it would be great to know when a user goes offline so our app doesn't appear broken. Fortunately, Cordova provides "offline" and "online" events that we can specify handlers for. When the app goes offline, we can just show the user a view telling them that they need to get back online to use the app. Here is the HTML for a new view called "offline".

Offline View

<div data-role="view" id="offline" data-transition="overlay:up">
  <div data-role="header" class="offline">
      <h2>Offline</h2>
  </div>
  <h3>Chuck Norris doesn't need an internet connection, but you do.  Make sure you are online and then try again.</h3>
</div>

I've also added a little bit of CSS to this view to make it a bit more "error"-like. You can add this to your main.css file.

Offline CSS

.offline {
  background-color: red;
  color: white;
  padding: 1rem;
}

Now we just need to attach event listeners for the online and offline events that Cordova will throw when a connection is lost or aquired. In these events, we need to show the offline view. The device level events happen at the document level, so we can add an event listener JavaScript style.

Listening For Online / Offline State

// create a new kendo ui mobile app using the whole page
// store the application object in window.app.application
window.app.application = new kendo.mobile.Application(document.body, { transition: "slide", skin: "flat" });

document.addEventListener("offline", function () {
    window.app.application.navigate("#offline");
});

document.addEventListener("online", function () {
    window.app.application.navigate("#:back");
});

To navigate to a different Kendo UI Mobile view, you call the navigate method on the application object. I'm storing the application object in window.app.application. When the application comes back online, we just go back to whatever view we were at by navigating to #:back as per the Kendo UI Mobile documentation.

Now we just need to test this code to make sure I got it all right. Believe it or not, the Icenium Simulator will let you test your connection state. There is a button that is a wifi icon. If you click this button, you will get a panel that toggles out and lets you change the way the device reports it's connectivity. Here let, me show you what I mean...

Change the connection type to "None" and the offline view slides up. Change it back to any other type of valid connection, and the view goes away. Isn't that great! You don't have to do any crazy hacks to track your apps connectivity. Simply respond to the online and offline events. You can even determine the connection type and only allow the use of that sort of connection.

Refactoring The JavaScript To Keep It DRY

One of the fundamental concepts of good software development is Don't Repeat Yourself. Otherwise known as DRY. If you look at the app.js file, you will notice that we are almost repeating ourselves verbatim on the nerdy and funny models. They are nearly identical, except for the fact that the category is different. The fact that we are using JavaScript doesn't excuse us from the burden of writing good code.

If we were using C# or VB, we would likely make a base class and create two new objects of that type. We can do the same thing in JavaScript. It's a bit hairy since it generally requires use of the prototype. However, Kendo UI provides you with a class object which provides you with a simple way of creating a new class that you can then create instances of using the new keyword.

I think it's better if I show it to you and then we'll talk briefly about it.

Using kendo.Class To Create Base Objects

// create a base class for the nerdy and funny view
var jokeModel = kendo.Class.extend({
   // the init method is the constructor and is called when the object 
   // is created using the 'new' keyword
   init: function (category) {
      // store a reference to the base class object
      var that = this;

      // attach a category field to this instance and set it's 
      // value to the parameter passed into the init method
      that.category = category;

      // create a view model
      that.viewModel = kendo.observable({
         joke: null,
         refresh: function () {
            getRandomJoke(that.category, this);
         }
      });

      // expose a random joke method
      this.getRandomJoke = function () {
         getRandomJoke(that.category, that.viewModel);
      }
   }
});

// create a new funny model from the base jokeModel class
window.app.funny = new jokeModel("funny");

// create a new nerdy model from the base jokeModel class
window.app.nerdy = new jokeModel("nerdy");

The init function is the constructor. Anything that you specify using this inside the constructor gets created on the base object. That code makes me feel a whole lot better! Let's take a look at the code we have written in this series in it's entirety.

index.html

<!DOCTYPE html>
<html>
<head>
   <title>Kendo UI DataViz</title>
   <meta charset="UTF-8" />

   <link href="kendo/styles/kendo.dataviz.mobile.min.css" rel="stylesheet" />

   <!--Once the final theme is chosen the redundant css reference should removed-->
   <link href="kendo/styles/kendo.dataviz.flat.min.css" rel="stylesheet" />
   <link href="kendo/styles/kendo.dataviz.silver.min.css" rel="stylesheet" />

   <link href="styles/main.css" rel="stylesheet" />

   <script src="cordova.js"></script>
   <script src="kendo/js/jquery.min.js"></script>
   <script src="kendo/js/kendo.dataviz.mobile.min.js"></script>

</head>

<body>

   <!-- the funny jokes -->
   <div data-role="view" id="funny" data-layout="main" data-model="app.funny.viewModel"
       data-title="The Facts: Funny" data-init="app.funny.getRandomJoke">
      <!-- this header overrides the header defined in the layout -->
      <div data-role="header">
         <div data-role="navbar">
            <span data-role="view-title"></span>
            <button data-role="button" data-align="right" data-icon="refresh" data-bind="click: refresh"></button>
         </div>
      </div>
      <h3 data-bind="html: joke"></h3>
   </div>

   <!-- the nerdy jokes -->
   <div data-role="view" id="nerdy" data-layout="main" data-model="app.nerdy.viewModel" 
      data-title="The Facts: Nerdy" data-init="app.nerdy.getRandomJoke">
      <!-- this header overrides the header defined in the layout -->
      <div data-role="header">
         <div data-role="navbar">
            <span data-role="view-title"></span>
            <button data-role="button" data-align="right" data-icon="refresh" data-bind="click: refresh"></button>
         </div>
      </div>
      <h3 data-bind="html: joke"></h3>
   </div>

   <!-- the dashboard -->
   <div data-role="view" id="dashboard" data-layout="main" data-init="app.dashboard.init" data-title="Dashboard">
      <div id="chart"></div>
   </div>

   <!-- the common layout for all views -->
   <div data-role="layout" data-id="main">
      <!-- the footer widget pins content to the top of the screen -->
      <div data-role="header">
         <!-- the navbar widget creates a title bar which can also hold buttons -->
         <div data-role="navbar">
            <!-- the view-title widget displays the value of whatever is in the 'data-title' attribute on the current view -->
            <span data-role="view-title"></span>
         </div>
      </div>
      <!-- the footer widget pins the content to the bottom of the screen -->
      <div data-role="footer">
         <!-- the tabstrip widget creates the familiar mobile tabstrip -->
         <div data-role="tabstrip">
            <!-- each tabstrip button is an anchor. It's href attribute points
                to the id of the view that should be shown when that items is tapped. -->
            <a href="funny" data-icon="funny">Funny</a>
            <a href="nerdy" data-icon="nerdy">Nerdy</a>
            <a href="dashboard" data-icon="dashboard">Dashboard</a>
         </div>
      </div>
   </div>

   <div data-role="view" id="offline" data-transition="overlay:up">
      <div data-role="header" class="offline">
         <h2>Offline</h2>
      </div>
      <h3>Chuck Norris doesn't need an internet connection, but you do.  Make sure you are online and then try again.</h3>
   </div>

   <script src="scripts/app.js"></script>

</body>
</html>

app.js

(function () {
   var api = "http://facts.azurewebsites.net/api/";

   // create a variable on the window to hold all of our custom code
   window.app = {};

   // get a random joke by category
   var getRandomJoke = function (category, viewModel) {   
      $.get(api + "jokes?type=" + category, function (data) {
         // update the view model
         viewModel.set("joke", data.JokeText);
      });
   };

   // create a base class for the nerdy and funny view
   var jokeModel = kendo.Class.extend({
     // the init method is the constructor and is called when the object 
     // is created using the 'new' keyword
     init: function (category) {
        // store a reference to the base class object
        var that = this;

        // attach a category field to this instance and set it's 
        // value to the parameter passed into the init method
        that.category = category;

        // create a view model
        that.viewModel = kendo.observable({
           joke: null,
           refresh: function () {
              getRandomJoke(that.category, this);
           }
        });

        // expose a random joke method
        this.getRandomJoke = function () {
           getRandomJoke(that.category, that.viewModel);
        }
     }
  });

  // create a new funny model from the base jokeModel class
  window.app.funny = new jokeModel("funny");

  // create a new nerdy model from the base jokeModel class
  window.app.nerdy = new jokeModel("nerdy");

   window.app.dashboard = (function () {

      var init = function () {
         $("#chart").kendoChart({
            theme: "flat",
            title: {
               text: "Chuck Norris Fact Categories"
            },
            dataSource: {
               transport: {
                  read: api + "dashboard"
               }
            },
            legend: {
               position: "top"
            },
            chartArea: {
               background: ""
            },
            seriesDefaults: {
               labels: {
                  visible: false
               }
            },
            series: [{
               type: "pie",
               field: "value",
               categoryField: "category",
               startAngle: 150,

            }]
         });
      };

      return {
         init: init
      };

   }());

   // create a new kendo ui mobile app using the whole page
   window.app.application = new kendo.mobile.Application(document.body, { transition: "slide", skin: "flat" });

   document.addEventListener("offline", function () {
      window.app.application.navigate("#offline");
   });

   document.addEventListener("online", function () {
      window.app.application.navigate("#:back");
   });

}());

main.css

/* custom icon definitions */

.km-funny:after,
.km-funny:before
{
    content: "\e093";
}

.km-nerdy:after,
.km-nerdy:before
{
    content: "\e094";
}

.km-dashboard:after,
.km-dashboard:before
{
    content: "\e04d";
}

/* end custom icons */

.offline {
    background-color: red;
    color: white;
    padding: 1rem;
}

Building The App

We're ready to build the application and run it on the device. Before we do that, make sure that you have the API portion of this project located in a place where your device can access it. If you would like, you can use the Azure instance that I published which is http://thefacts.azurewebsites.net. Since we made the api a variable at the top of the app.js file, we can swap it out without issue.

Building For Android

Icenium makes building for Android a snap. Simply had up the Icenium menu in the Visual Studio toolbar and select "Build the-facts In Cloud". You will get a new window asking if you want to build for Android or iOS.

Select Android and immediately your application begins building in the Icenium cloud. There are some instructions for enabling "Unkown Sources" that you will want to follow on your Android phone. Once the build is finished, it will display a QR code.

All you have to do is scan this code with your phone and it will install the application. Might I suggest QR Droid from the Google Play store if you don't already have a QR Code scanner app installed. In fact, you can scan the QR code above with your phone, it will install my version of the app.

Building For iOS

Installing on iOS is a few degrees trickier. This is because of how Apple treats provisioning, certificates and authorized devices. Apple requires you to be a registered developer to deploy an app to ANY device - including your own. You can follow along with the Icenium documentation for getting your provisioning profile setup with Apple. It requires a few steps, but is really rather easy once you do it the first time.

 

When you have your provisioning profile setup in Icenium, you can select Build In Cloud and choose "iOS (Build With Provision)". If you created an "Ad Hoc" provisioning profile, Icenium will return you a QR code.

If you don't have an Ad Hoc profile, you will get a "Deploy Using iTunes" message, with a link to download the iOS IPA file.

You can also find the .ipa file for your project in your bin folder. The Android .apk will get dropped there as well if you built for Android.

Final Step: Profit

Well, we've created the next billion dollar app. The next step is for us to submit it to the various app stores and then wait for Facebook to call us with their best offer. While I'm waiting for that call, you can grab the full code for this project from the Icenium GitHub Repo. Remember that you need to have an Icenium account as well as the Visual Studio extension installed. The rest is up to you. Now that you have the full power of Visual Studio at your fingertips, you can build the mobile applications that your enterprise needs to be successful using the tools that you already know and love.


Burke Holland is the Director of Developer Relations at Telerik
About the Author

Burke Holland

Burke Holland is a web developer living in Nashville, TN and was the Director of Developer Relations at Progress. He enjoys working with and meeting developers who are building mobile apps with jQuery / HTML5 and loves to hack on social API's. Burke worked for Progress as a Developer Advocate focusing on Kendo UI.

Comments

Comments are disabled in preview mode.