Telerik blogs

We recently announced that Kendo UI Mobile widgets have been decoupled from the Kendo UI Mobile framework so that they can be used anywhere and with other JavaScript frameworks. Kendo UI Mobile as originally intended to be used specifically for building hybrid mobile (PhoneGap) applications. In fact, it was one of the very first frameworks designed specifically with the hybrid application in mind. We've had over two years to tweak, refine and perfect Kendo UI Mobile. Given the announcement about the decoupling, you might be wondering - when should I use the mobile application framework, and when should I just use the mobile widgets?

Great question!

A good rule of thumb is that if you are building a hybrid (Cordova/PhoneGap) application, you want to use the Mobile Application Framework. If you are doing mobile web development, you will want to use the decoupled mobile widgets with a CSS and JavaScript framework of your choice (i.e. The Kendo UI SPA framework). Even though both mobile web and hybrid apps are built with HTML, JavaScript and CSS, there are some very important distinctions between the two.

Hybrid apps have no server component

The development is done entirely in the front end. There are no URLs to post back to; no ASP.NET MVC or PHP to query your database with. Hybrid apps expect that you will be providing data via a service which is probably written in .NET, PHP or what have you, but is running somewhere else. The application itself just wants to be able to make AJAX calls to this service to get data.

Hybrid apps ARE native

At least in-so-much as the user knows or expects. How you built this installed app is irrelevant to them. How it works is paramount. Users have an expectations for installed apps. They expect very obvious and loud transitions; sweeping animations and responsiveness. The Kendo UI Mobile framework is specifically designed to deliver this type of experience.

Web apps should behave like web apps

The same features that users expect from mobile apps become incredibly obnoxious on the web. Those loud sweeping animations are awkward and gaudy in a web site and mostly serve to detract from it's utility. Having a slide animation from page to page on a mobile web site is the programmatic equivalent of Moonwalking through the mall.

 author: Adam Kliczek / Wikipedia, licence: CC-BY-SA-3.0

There is no need for those transitions on the web. User's have a completely different set of expectations. Just walk normally man!

Structuring Mobile Applications

Web applications can be structured with any framework - be it Kendo UI SPA, Backbone, Angular or another. Applications that use the Kendo UI Mobile framework are different in that they cannot be used with other frameworks since the mobile application framework controls everything in the application from top to bottom in order to guarantee that "native" experience.

Without a MV* framework to structure your Kendo UI Mobile hybrid applications, you may be left wondering how you should structure your application so that it scales and is maintainable. Today we'll look at some best practices for structuring a Kendo UI Mobile hybrid application.

We'll be creating a Todo List application with a sliding drawer of categories. You can run this application on iOS using the free AppBuilder Companion App. Just scan the QR code below.

Or you can just clone the project on GitHub. I've also embedded the application here, but remember that this NOT a web application. This is a hybrid application.

 

Creating The Base Project

Since we are building a hybrid application, I'm going to be using AppBuilder and the new CLI to create a new empty project.

> appbuilder create kendo-mobile-todo --template blank

This creates a new "blank" hybrid application in-so-much as it doesn't contain any libraries for you to work with for building your application. It does contain some boilerplate code that we want to get rid of, so delete the css and js directories.

Install Dependencies

In order to build this application, we need to install the required dependencies - not the least of which is Kendo UI Mobile.

I use Bower to get RequireJS, the RequireJS Text plugin and jQuery Tiny PubSub so I don't have to go out and download these files manually.

> bower install requirejs
> bower install requirejs-text
> bower install jquery-tiny-pubsub

Open the index.html file and delete everything. Replace it with the following markup.

<!DOCTYPE html>
<html>
    <head>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
        <meta name="format-detection" content="telephone=no" />
        <meta name="viewport" content="user-scalable=no, initial-scale=1, maximum-scale=1, minimum-scale=1, width=device-width, height=device-height, target-densitydpi=device-dpi" />
        <title>Todos</title>
        <link rel="stylesheet" href="kendo/styles/kendo.mobile.all.min.css">
        <link rel="stylesheet" href="styles/main.css">
    </head>
    <body>

        <script src="cordova.js"></script>
        <script src="bower_components/jquery/jquery.min.js"></script>
        <script src="bower_components/jquery-tiny-pubsub/dist/ba-tiny-pubsub.js"></script>
        <script src="kendo/js/kendo.mobile.min.js"></script>
        <script src="bower_components/requirejs/require.js" data-main="app/main.js"></script>

    </body>
</html>

You will see that Require is the last script file being loaded and it's data-main is set to app/main.js. Create a folder called app at the top level and then within it add the main.js file. Put the following code in that file:

// configure the path to the text plugin since it is not in the same directory
// as require.js
require.config({
  paths: {
    'text': '../bower_components/requirejs-text/text'
  }
});

define([
  'app'
], function (app) {

  // if we are running on device, listen for cordova deviceready event
  if (kendo.mobileOs) {
    document.addEventListener('deviceready', function () {

      // initialize application
      app.init();

      // hide the native spash screen
      navigator.splashscreen.hide();
    }, false);
  }
  else {
    // we are running on the web (prolly debug) so just show the app
    app.init();
  }

});

The main file is listening for that all important deviceready event that is fired by Cordova and it then initializes the application and removes the splash screen. BTW - if you don't remove the splash screen manually when your application is ready, Cordova will do it for you after five seconds. Five seconds is an absolute eternity on a mobile device. Make sure to manually hide the splash screen.

Now add an app.js file to the app directory. Place the following code in the app.js file:

define([
  'views/todos/todos',
  'views/categories/categories',
  'views/newTodo/newTodo',
  'views/newCategory/newCategory'
], function () {

  // create a global container object
  var APP = window.APP = window.APP || {};

  var init = function () {

    // initialize the application
    APP.instance = new kendo.mobile.Application(document.body, { skin: 'flat' });

  };

  return {
    init: init
  };

});

The app file brings in all of the views which are separated out into "modules" if you will. Each view has a .js file and an html file associated with it. Let's start with the "todos" view.

Create the following directory structure under the app folder.

  • app
    • views
      • todos
      • categories
      • newTodo
      • newCategory

Kendo UI Mobile will require some items to be accessible from the global namespace. Each "view" - be it a regular Kendo UI Mobile View, Drawer, ModalView, PopOver or other visual container needs to be able to access it's events and model from the window object. To automate this, we create a custom class which handles this on behalf of our views.

Create a new file in the views folder called view.js. Place the following code inside:

define([], function () {

  var APP = window.APP = window.APP || {};

  var View = kendo.Class.extend({
    init: function (name, template, model, events) {

      // append the template to the DOM
      this.html = $(template).appendTo(document.body);

      // expose the model and events off the global scope
      APP[name] = { model: model || {}, events: events || {} };
    }
  });

  return View;

});

We use Kendo's nifty ability to create classes with an init constructor and then pass in the name that will be exposed off the global APP object along with the HTML template, the model and an object holding any events we want to be able to access from the HTML. Now that the plumbing is laid, lets write the Todos module.

Create a new file under the app/views/todos folder called todos.js and place the following code within:

define([
  'views/view',
  'text!views/todos/todos.html'
], function (View, html) {

  var view, navbar, category;

  var todos = new kendo.data.DataSource({
    data: [
      { title: 'Talk to corporate', category: 'Work' },
      { title: 'Promote synergy', category: 'Work' },
      { title: 'Eat a bagel', category: 'Personal' },
      { title: 'Eat some chicken strips', category: 'Personal' }
    ]
  });

  var model = kendo.observable({
    todos: todos
  });

  var events = {
    init: function (e) {
      // store a reference to the navbar component in this view
      navbar = e.view.header.find('.km-navbar').data('kendoMobileNavBar');
    },
    afterShow: function (e) {
      // pull the current category off the parameters object
      category = e.view.params.category || 'Work';

      // filter the data source against the current category
      todos.filter({ field: 'category', operator: 'eq', value: category });

      // update the navbar title
      navbar.title(category);
    }
  };

  // create a new view
  view = new View('todos', html, model, events);

  // subscribe to the /newTodo/add message
  $.subscribe('/newTodo/add', function (e, text) {
    todos.add({ title: text, category: category });
  });

});

In this file a collection of todos is defined. In this example it's static, but you would most likely wire your datasource up to a remote service. The model object is pretty simple since it just exposes our todos collection. The events object is interesting since it uses a very important strategy that will be critical to your Kendo UI Mobile development. The $.subscribe method there at the end is part of our tiny PubSub library and that function will be called whenever the /newTodo/add event is fired. We'll get to that when we build the newTodo view.

The Kendo UI Mobile Application object is very tightly coupled with the idea of declarative initialization. That means that you can't actually get a reference to things like the view itself or many of it's components as they do not exist until the application object processes them. When is that? It's technically when the init event is fired by the application object. However, there is an easier and more localized way to get the references you need and that's by using an init event in the view. When the view init method fires, you can get references to all of the Kendo UI Mobile widgets that it contains. In this example, I am storing a reference to the NavBar widget so that I can set it's title in the afterShow method.

Add a new file to the app/views/todos folder called todos.html. Add the following markup to that file:

<div data-role="view" id="todos" data-model="APP.todos.model" data-init="APP.todos.events.init" data-after-show="APP.todos.events.afterShow">
  <header data-role="header">
    <div data-role="navbar">
      <a data-role="button" data-rel="drawer" href="#categories" data-icon="drawer-button" data-align="left"></a>
      <span data-role="view-title"></span>
      <a data-role="button" data-rel="modalview" href="#newTodo" data-align="right" data-icon="compose"></a>
    </div>
  </header>
  <ul data-role="listview" data-bind="source: todos" data-template="todos-template"></ul>
</div>

<script type="text/x-kendo-template" id="todos-template">
  #: title #
</script>

This file is loaded by the RequireJS text plugin when text!views/todos/todos.html is placed in the define method at the top of the todos.js file. You can examine the markup to see where events are being bound and what items within the view are bound to the object declared at the view level in data-model.

Let's add in the Drawer component which will contain the list of categories that we want to filter on. Add a new file to the app/views/categories folder called categories.js. Place the following code inside:

define([
  'views/view',
  'text!views/categories/categories.html'
], function (View, html) {

  var categories = new kendo.data.DataSource({
    data: [
      { name: 'Work' },
      { name: 'Personal' },
      { name: 'Other' }
    ]
  });

  var model = {
    categories: categories
  };

  // create a new view
  var view = new View('categories', html, model);

  // subscribe to the add event from newCategory
  $.subscribe('/newCategory/add', function (e, text) {
    categories.add({ name: text });
  });

});

There are no new surprises here, so add the corresponding template file in app/views/categories called categories.html. Give it the following markup:

<div data-role="drawer" id="categories" style="width: 270px" data-model="APP.categories.model">
  <div class="km-group-title">Categories</div>
  <ul data-role="listview" data-bind="source: categories" data-template="categories-template"></ul>
  <a data-role="button" data-icon="compose" class="full" data-rel="modalview" href="#newCategory">New</a>
</div>

<script type="text/x-kendo-template" id="categories-template">
  <a href="todos?category=#: name #">#: name #</a>
</script>

Notice from the markup that this is not a Kendo UI Mobile View, but rather a Drawer. As I mentioned before, all of these layout containers derive from the Kendo UI Mobile view so they all have events and will take a model that you can bind to. That means we can work will all of these container objects in much the same way.

So that we can add new todo items to the todo list, create a new file in app/views/ called newTodo.js which will be the backing code for a Kendo UI Mobile ModalView in the same folder called newTodo.html

define([
  'views/view',
  'text!views/newTodo/newTodo.html'
], function (View, html) {

  var view, modalView;

  var model = kendo.observable({
    text: null,
    add: function (e) {
      $.publish('/newTodo/add', [ this.get('text') ]);
      modalView.close();
    },
    close: function (e) {
      modalView.close();
    }
  });

  var events = {
    init: function (e) {
      modalView = e.sender;
    }
  };

  view = new View('newTodo', html, model, events);

});
<div data-role="modalview" id="newTodo" data-model="APP.newTodo.model" data-init="APP.newTodo.events.init" style="display: none" data-width="80%">
  <div data-role="header">
    <div data-role="navbar">
      <span data-role="view-title">New Todo</span>
      <a data-role="button" data-bind="click: close" data-align="right" data-icon="close"></a>
    </div>
  </div>
  <ul data-role="listview" data-style="inset">
    <li>
        Todo:
        <textarea name="newTodo" id="newTodo" data-bind="value: text"></textarea>
    </li>
  </ul>
  <a data-bind="click: add" data-role="button" class="full">Add</a>
</div>

Note that the ModalView has a set width of 80%. If you don't set the width with data-width, the Modal will show up at the bottom of the screen on the mobile device and you will have to scroll down to see it. For a list of other Kendo UI Mobile Tips, check out Jeff Valore's excellent article on the subject.

By now, you are hopefully getting used to the pattern we are following here and this is starting to all look very familiar. Lastly you just need to add a folder called app/views/newCategory with newCategory.js and ____newCategory.html__ files.

define([
  'views/view',
  'text!views/newCategory/newCategory.html'
], function (View, html) {

  var view, modalView;

  var model = kendo.observable({
    text: null,
    close: function (e) {
      modalView.close();
    },
    add: function (e) {
      $.publish('/newCategory/add', [ this.get('text') ]);
      modalView.close();
    }
  });

  var events = {
    init: function (e) {
      modalView = e.sender;
    }
  };

  return new View('newCategory', html, model, events);

});
<div data-role="modalview" id="newCategory" data-model="APP.newCategory.model" data-init="APP.newCategory.events.init" data-width="80%">
  <div data-role="header">
    <div data-role="navbar">
      <span data-role="view-title">New Category</span>
      <a data-role="button" data-bind="click: close" data-align="right" data-icon="close"></a>
    </div>
  </div>
  <ul data-role="listview" data-style="inset">
    <li>
      Name: 
      <input type="text" data-bind="value: text" >
    </li>
  </ul>
  <a data-bind="click: add" data-role="button" class="full">Add</a>
</div>

About $.publish and $.subscribe

The last item we need to touch on are the $.publish and $.subscribe methods. When you separate your code into modules like we have done here, the modules will need to communicate with each other. In this example, the newTodo and newCategory views need to be able to send the input from the user back to the todo and category views so the items can be added to the DataSource in those views. It's tempting to expose a method on those views - something like addTodo on the todo.js file and then put it in the define block on the newTodo module. This creates a tight coupling between those two modules and also forces you to have inconsistent API's between your different modules. In order to avoid this, we use the PubSub pattern which allows us to publish a message from any module and then respond to that message from any other module while passing data with the message.

PubSub is really a simple concept. You can expand on it with very powerful libraries such as Postal, but I find that for most of my needs, the basic PubSub model works just fine. In this application, I'm using the jQuery Tiny PubSub library from Ben Alman. Here is how it works: in the todod.js file we subscribe to a /newTodo/add message. That's just a string. We could have called it anything, but we typically format messages like URLs because URLs are really quite nice to work with.

// subscribe to the /newTodo/add message
$.subscribe('/newTodo/add', function (e, text) {
  todos.add({ title: text, category: category });
});

The first parameter is always the jQuery event, and any parameters after that are objects sent along with the message. The /newTodo/add message is published from the newTodo.js file on the click event in the model.

$.publish('/newTodo/add', [ this.get('text') ]);

Style It

Lastly lets add in a some simple styles to add a few extra icons and tweak our buttons and inputs just a little bit so that they take up all of the available space in the sidebar and modals. Add a new stylesheet to your app and put the following styles inside. I've called mine main.css and put it in the styles folder at the application root.

.full {
      display: block;
      margin: .6em .8em;
      font-size: 1.2em;
      text-align: center;
}

textarea {
  resize: none;
}

/* Custom Icons */

.km-close:after,
.km-close:before
{
    content: "\e038";
}

.km-drawer-button:after,
.km-drawer-button:before
{ 
  content: "\E077"; 
}

Since I'm using AppBuilder, I can package all of this up now and test it in the companion application.

> appbuilder build ios --companion

I can also just deploy it straight to my device if it's connected.

> appbuilder deploy ios

Modularity For Maintainability

That's the name of the game when it comes to structuring your Kendo UI Mobile hybrid applications. Keep your functionality as modular as possible. To recap:

  • Use RequireJS to break code up into modules
  • Any view container (view, modalview, popover, drawer) should be it's own view
  • Use RequireJS Text plugin to load HTML dynamically allowing you to place the HTML files with their corresponding JavaScript
  • Use the container events (init, show, ect) to get references to widgets that you might need to manipulate
  • Use a simple PubSub pattern to keep your modules loosely coupled

The guidelines will help keep you on the right path to a mobile application that now only looks amazing thanks to Kendo UI, but is also structured so that you can grow it and maintain it for a long time to come.


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.